В своих микроконтроллерных поделках я постоянно использую кнопки. Поэтому написал
простенький драйвер для работы с ними. В текущей версии драйвер заточен под четыре кнопки, но его довольно легко переделать. В этой статье я опишу содержимое файлов драйвера, а в следующей разберу какой-нибудь практический пример.
Схема
Схема подключения кнопок традиционная – один контакт кнопки соединен с выводом микроконтроллера, другой с нулем питания. Подтягивающие резисторы внутренние. Кнопки подключены к обычным выводам микроконтроллера, не обладающим функциями внешних прерываний.
Заголовочный файл
Заголовочный файл содержит символические имена регистров и выводов порта, к которому подключены кнопки. Коды которые будут записываться в буфер при нажатии кнопок. Прототипы функций.
void BUT_Init(void); Настраивает выводы к которым подключены кнопки на вход и включает подтягивающие резисторы. Вызывается обычно в начале main`a.
void BUT_Debrief(void); Выполняет однократный опрос кнопок. В функции реализована антидребезговая защита – чтобы номер кнопки был занесен в буфер, она должна быть нажата в течении заданного числа циклов опроса. Номер кнопки заносится в буфер однократно, даже если кнопка долго удерживается. Эта функция вызывается в прерывании таймера.
unsigned char BUT_GetKey(void); Возвращает содержимое кнопочного буфера, при этом буфер очищается. Вызывается из основного цикла программы.
void BUT_SetKey(unsigned char key); Записывает в буфер значение. Используется для имитации нажатия кнопки.
Сишный файл
Си файл содержит реализацию всех функций. Из них интерес, пожалуй, представляет только функция опроса.
Обычная тактовая кнопка может находиться в двух состояния – нажатом и отпущенном (третье состояние – сломанное, нас сейчас не интересует). В идеальном случае кнопка переходит из одного состояния в другое мгновенно, однако в реальности при нажатии ее контакты многократно замыкаются и размыкаются, прежде чем окажутся в установившемся замкнутом состоянии. Это явление называется дребезгом контактов, и оно присуще всем механическим переключателям.
Микроконтроллер работает на частоте в несколько мегагерц, и пока происходит дребезг, может опросить кнопку несколько раз. Если не принять каких-то мер, то дребезг контактов будет “воспринят” микроконтроллером как многократное нажатие. Для некоторых приложений это не критично, ну, например, если кнопка управляет включением лампы фонаря. Но в большинстве случаев это явление нежелательное.
Дребезг контактов можно устранить аппаратно, с помощью пассивного НЧ фильтра. Но гораздо проще побороть дребезг, сделав программу микроконтроллера невосприимчивой к нему. Вариант подобного алгоритма как раз и реализован в функции BUT_Debrief()
#define THRESHOLD 20
volatile unsigned char pressedKey = 0;
unsigned char comp = 0;
void BUT_Debrief(void)
{
unsigned char key;
//последовательный опрос выводов мк
if (BitIsClear(PIN_BUTTON, ENTER))
key = KEY_ENTER;
else if (BitIsClear(PIN_BUTTON, CANCEL))
key = KEY_CANCEL;
else if (BitIsClear(PIN_BUTTON, UP))
key = KEY_UP;
else if (BitIsClear(PIN_BUTTON, DOWN))
key = KEY_DOWN;
else {
key = KEY_NULL;
}
//если во временной переменной что-то есть
if (key) {
//и если кнопка удерживается долго
//записать ее номер в буфер
if (comp == THRESHOLD) {
comp = THRESHOLD + 10;
pressedKey = key;
return;
}
else if (comp < (THRESHOLD+5)) comp++;
}
else comp=0;
}
В начале функции выполняется последовательный опрос выводов. Если какая-нибудь из кнопка нажата, во временную переменную key заносится соответствующий код. Затем проверяется значение переменной comp, чтобы выяснить как долго кнопка находится в нажатом состоянии. Пока comp не достигнет некоего порогового значения THRESHOLD, она будет инкрементироваться. Как только значение comp станет равным THRESHOLD, временная переменная key будет скопирована в кнопочный буфер pressedKey. При этом comp получит новое значение, чтобы при следующих опросах кнопок микроконтроллер не инкрементировал эту переменную, и не производил запись в буфер. Таким образом, как бы долго пользователь не удерживал кнопку, микроконтроллер зафиксирует факт ее нажатия только один раз.
Довольно часто кнопки используются для установки каких-нибудь линейно меняющихся величин. В этом случае удобно использовать такой алгоритм - короткое нажатие на кнопку медленно изменяет величину, длительное нажатие быстро. Подправим часть функции опроса и получим простой вариант реализации этого алгоритма.
#define THRESHOLD2 300
...
if (key)
{
if (comp > THRESHOLD2)
{
comp = THRESHOLD2 - 40;
pressedKey = key;
return;
}
else comp++;
if (comp == THRESHOLD)
{
pressedKey = key;
return;
}
}
else comp=0;
...
Теперь, если кнопка удерживается больше 300 циклов опроса, микроконтроллер снова и снова будет записывать в буфер ее код.
На этом пока все, но тема кнопок не закрыта. Продолжение следует...
Comments
включение кнопки без нажатия .
Когда положил плату ( на проводах ) в
корпус все прекратилось .
Какими могут быть основные причины .
использовал internal pull-up (P) resistor ( из CodeVisionAVR ) .
Code:
#include "buttons.h"
#define MASK_BUTTONS (1<<DOWN)|(1<<CANCEL)|(1<<UP)|(1<<ENTER)
#define ClearBit(reg, bit) (reg) &= (~(1<<(bit)))
#define SetBit(reg, bit) (reg) |= (1<<(bit))
#define BitIsClear(reg, bit) ((reg & (1<<(bit))) == 0)
#define BitIsSet(reg, bit) ((reg & (1<<(bit))) != 0)
volatile unsigned char pressedKey = 0;
unsigned char comp = 0;
//_______________________________________
void BUT_Init(void)
{
DDRX_BUTTON &= ~(MASK_BUTTONS);
PORT_BUTTON |= MASK_BUTTONS;
}
//_______________________________________
void BUT_Debrief(void)
{
unsigned char key;
if (BitIsClear(PIN_BUTTON, ENTER))
key = KEY_ENTER;
else if (BitIsClear(PIN_BUTTON, CANCEL))
key = KEY_CANCEL;
else if (BitIsClear(PIN_BUTTON, UP))
key = KEY_UP;
else if (BitIsClear(PIN_BUTTON, DOWN))
key = KEY_DOWN;
else {
key = KEY_NULL;
}
if (key) {
if (comp == THRESHOLD) {
comp = THRESHOLD+10;
pressedKey = key;
return;
}
else if (comp < (THRESHOLD+5)) comp++;
}
else comp=0;
}
//__________________________
unsigned char BUT_GetKey(void)
{
unsigned char key = pressedKey;
pressedKey = KEY_NULL;
return key;
}
//____________________________
void BUT_SetKey(unsigned char key)
{
pressedKey = key;
}
int main(void)
{
void BUT_Init();
DDRA=255;
PORTA = 128;
while(1){
BUT_Debrief();
if (BUT_GetKey()== 4) PORTA = 255;
if (BUT_GetKey()== 3) PORTA = 0;
}
return 0;
}
Компилятор воспринимает это как объявление прототипа функции, а не ее вызов.
Code:
int main(void)
{
BUT_Init();
DDRA=255;
PORTA = 128;
while(1){
BUT_Debrief();
if (BUT_GetKey()== 2)
PORTA = 134;
else if (BUT_GetKey()== 1)
PORTA = 113;
else if (BUT_GetKey()== 3)
PORTA = 213;
else if (BUT_GetKey()== 4)
PORTA = 132;
}
return 0;
}
BUT_Init();
DDRA=255;
PORTA = 128;
while(1){
BUT_Debrief();
i=BUT_GetKey();
if (i== 2)
PORTA = 134;
else if (i== 1)
PORTA = 113;
else if (i== 3)
PORTA = 213;
else if (i== 4)
PORTA = 132;
}
return 0;
искупил свои грехи, этот код работает!
Quote:
А значит эту функцию нужно вызывать однократно!!
А как бы ее к WinAVR адаптировать?
Code:
#include <ioavr.h>
на заголовочный файл используемый в WINAVR
Code:
#include <ioavr.h>
на заголовочный файл используемый в CVAVR.
вопрос что значат 1;3:4?откуда они взяты
[
if (i== 2)
PORTA = 134;
else if (i== 1)
]
может 2 это 0b00000010
а 1 это 0b00000001
"В этой статье я опишу содержимое файлов драйвера, а в следующей разберу какой-нибудь практический пример. "
Если можно, то примеры для AVR STUDIO
Зарание СПАСИБО
Code:
#include <io.h>
#include <delay.h>
#include "indicator.h"
#include "buttons.h"
unsigned char counterProg = 0;
unsigned char pass[4];
unsigned char temp[4];
unsigned char i;
unsigned char kol = 1;
void main(void)
{
pass[1]=3;
pass[2]=5;
pass[3]=2;
temp[1]=0;
temp[2]=0;
temp[3]=0;
#asm("sei")
IND_Init();
BUT_Init();
TIMSK = (1<<TOIE0);
TCCR0B = (0<<CS02)|(0<<CS01)|(1<<CS00);
TCNT0 = 0xb2;
while(1){
BUT_Debrief();
i=BUT_GetKey();
if (i==2)
{
counterProg++;
}
else if (i==3)
{
counterProg--;
}
else if (i==1)
{
temp[kol]=counterProg;
kol++;
}
if ((temp[1]==pass[1]) && (temp[2]==pass[2]) && (temp[3]==pass[3]))
{
counterProg=10;
kol=1;
}
if (counterProg == 100)
{
counterProg = 0;
}
if (counterProg >= 100)
{
counterProg = 99;
}
IND_Conv(counterProg);
}
}
//прерывания таймера Т0 – вывод на индикатор
#pragma vector = TIM0_OVF
__interrupt void Tim0_Ovf(void)
{
TCNT0 = 0xb2;
IND_Update();
}
После ввода трех чисел на циферблате должно загореться 10 но ничего подобного не происходит.
Все было в массивах, и код был аккуратнее.
Но из-за того что ничего не работает, начал изменять всё подряд... :(
Помоги пожалуйста, в чём проблема?
При использовании пошаговой отладки в AVR Studio 4.19 работает, Code:
#define BitIsClear(reg, bit) (reg & (1<<(bit)))
, а при работе в Proteus 7 работает вот этот Code:#define BitIsClear(reg, bit) ((reg & (1<<(bit))) == 0)
Объясните, пожалуйста, почему?
Code:
#define BitIsClear(reg, bit) (!(reg & (1<<(bit))))
...
if (BitIsClear(PIND, 0)){
...
}
Заменил ioavr.h на io.h
Не реагирует на кнопки.
Работает.
Неправильно использовал библиотеку.
Не увидел что буфер обнуляется.
Присвоил переменной значения буфера и все стало работать нормально.
Попробовал добавить длительное нажатие в библиотеку... теперь не срабатывает короткое нажатие. Увеличивал THRESHOLD2 и THRESHOLD.
Посоветуйте как правильно изменить библиотеку чтобы работало и короткое и длительное нажатие?
В форму напишу обязательно.
Вышел из ситуации пока-что так:
использовал кнопки вверх и вниз для ввода числа, а копку отменадля умножения на 10 (т.е. сдвига влево. Получаетсявведе нное число сдвигается на местодесятков, вводишь значение. Снова "отмена" теперьу тебя уже сотни и десятки, вводишь единицы)
if(PINB.4==0)
{
delay_ms(150); // антидребезг кнопки
if(PINB.4==0) // повторная проверка
{
while(PINB.4==0 );
код программы;
}
}
Нужен код нажатия двух кнопок одновременно. С этим не получается.
RSS feed for comments to this post