Учебный курс. Опрос кнопок

26/11/2009 - 21:00
    В своих микроконтроллерных поделках я постоянно использую кнопки. Поэтому написал простенький драйвер для работы с ними. В текущей версии драйвер заточен под четыре кнопки, но его довольно легко переделать. В этой статье я опишу содержимое файлов драйвера, а в следующей разберу какой-нибудь практический пример.

Схема

   Схема подключения кнопок традиционная – один контакт кнопки соединен с выводом микроконтроллера, другой с нулем питания. Подтягивающие резисторы внутренние. Кнопки подключены к обычным выводам микроконтроллера, не обладающим функциями внешних прерываний.

Заголовочный файл

   Заголовочный файл содержит символические имена регистров и выводов порта, к которому подключены кнопки. Коды которые будут записываться в буфер при нажатии кнопок. Прототипы функций.

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   

# alexandershahbazov 2009-12-08 20:47
У меня иногда случалось самопроизвольное
включение кнопки без нажатия .
Когда положил плату ( на проводах ) в
корпус все прекратилось .
Какими могут быть основные причины .
# Pashgan 2009-12-08 23:15
наводки наверное... конденсаторы по входам пробовали ставить?
# alexandershahbazov 2009-12-08 23:38
конденсаторы по входам не ставил , а
использовал internal pull-up (P) resistor ( из CodeVisionAVR ) .
# alexandershahbazov 2009-12-08 23:54
Забыл поздравить ЦСКА с выходом в 1/8 !!!
# Guest 2010-01-04 19:19
Ставь обычные резюки кОм в 10, наводки исчезнут. Долго не мог побороть - внутренние часто не тянут ни фига
# SergeyB 2011-02-24 16:24
Не работает программа по нажатию кнопки зажигающая светодиоды. Использовал Atmega16, buttons.h я поправил. Светодииоды висят на порту А. После инициализации я приравниваю портА=128 чтобы проверить, работу порта, один диод горит как надо. Но по нажатию кнопки ничего не происходит. В чем моя ошибка?

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;
}

# Pashgan 2011-02-24 22:46
void убери перед BUT_Init()
Компилятор воспринимает это как объявление прототипа функции, а не ее вызов.
# SergeyB 2011-02-25 08:36
Спасибо большое за ответ, я все исправил, но когда я пытаюсь зажечь разные светодиоды , по нажатию кнопок, у меня работает лишь та кнопка , которая после if (все кнопки на это место пробовал ставить - все работали, а остальные 3 нет) , которая после else if не работает.
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;
}
# SergeyB 2011-02-26 08:32
Code: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;

искупил свои грехи, этот код работает!
# Pashgan 2011-02-27 09:37
Читаем описание:
Quote:
unsigned char BUT_GetKey(void);
Возвращает содержимое кнопочного буфера, при этом буфер очищается.

А значит эту функцию нужно вызывать однократно!!
# anwi 2011-04-07 05:29
Очень полезная библиотечка!
А как бы ее к WinAVR адаптировать?
# Pashgan 2011-11-11 09:28
Достаточно заменить ioavr.h в строке
Code:#include <ioavr.h>
на заголовочный файл используемый в WINAVR
# wellcom 2011-06-09 08:26
Функция void BUT_Debrief(voi d) написано, что вызывается в прерывании таймера. А из других мест (прерываний) ее можно вызывать?
# Pashgan 2011-11-11 09:25
Да. Идея в том, чтобы запускать эту функцию циклично.. Проще всего это реализовать с помощью прерываний таймеров.
# vlad145 2011-09-04 16:41
А как её перевести в CVAVR?
# Pashgan 2011-11-11 09:28
Достаточно заменить ioavr.h в строке
Code:#include <ioavr.h>
на заголовочный файл используемый в CVAVR.
# Viktor2011 2011-11-22 20:32
cпасибо за курс, хотелось бы побольше коментариев.Спа сибо и SergeyB
вопрос что значат 1;3:4?откуда они взяты
[
if (i== 2)
PORTA = 134;
else if (i== 1)
]
может 2 это 0b00000010
а 1 это 0b00000001
# Viktor2011 2011-12-11 16:25
Pashgan помоги разобраться с кнопками,опиши или подскажи где можно найти тем кто учит AVR сам, только с интернета.Хорош о бы с полными примерами
"В этой статье я опишу содержимое файлов драйвера, а в следующей разберу какой-нибудь практический пример. "
Если можно, то примеры для AVR STUDIO
Зарание СПАСИБО
# SevitOFF 2013-04-10 14:15
Всем привет! Pashgan помоги с написанием программы пожалуйста! Хочу сделать кодовый замок с UART в будущем, но пока застрял кажется на элементарном.

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 но ничего подобного не происходит.
Все было в массивах, и код был аккуратнее.
Но из-за того что ничего не работает, начал изменять всё подряд... :(
Помоги пожалуйста, в чём проблема?
# Pashgan 2013-04-10 19:13
Скинь свой проект целиком в эту ветку форума, я посмотрю. chipenable.ru/index.php/forum/materialy-sajta-chipenable/3245-vetka-dlya-vremennogo-khraneniya-proektov.html
# klnd 2014-01-28 00:52
Здравствуйте!
При использовании пошаговой отладки в AVR Studio 4.19 работает, Code:#define BitIsClear(reg, bit) (reg & (1<<(bit))), а при работе в Proteus 7 работает вот этот Code: #define BitIsClear(reg, bit) ((reg & (1<<(bit))) == 0)
Объясните, пожалуйста, почему?
# _Артём_ 2014-01-28 06:52
Потому что на протезах далеко не уйдёшь...
# Pashgan 2014-01-28 11:44
В первом макросе не хватает оператора отрицания !. Если его добавить, они будут выполняться одинаково.

Code:
#define BitIsClear(reg, bit) (!(reg & (1<<(bit))))
...
if (BitIsClear(PIND, 0)){
...
}
# Максим 2014-02-02 12:03
Не работает в CVAVR.
Заменил ioavr.h на io.h
Не реагирует на кнопки.
# Pashgan 2014-02-02 17:47
У тебя где-то в проекте ошибка. Этот код я неоднократно использовал. Например, здесь - http://chipenable.ru/index.php/programming-avr/item/75-chasy-na-mikrokontrollere-avr.html и здесь - http://chipenable.ru/index.php/programming-avr/item/50-organizatsiya-programm-sobytiynaya-sistema-prakticheskiy-primer.html. Там он немного изменен, чтобы события в буфере сохранять, но суть та же.
# Максим 2014-02-07 14:50
Спасибо.
Работает.
Неправильно использовал библиотеку.
Не увидел что буфер обнуляется.
Присвоил переменной значения буфера и все стало работать нормально.
Попробовал добавить длительное нажатие в библиотеку... теперь не срабатывает короткое нажатие. Увеличивал THRESHOLD2 и THRESHOLD.
Посоветуйте как правильно изменить библиотеку чтобы работало и короткое и длительное нажатие?
# Pashgan 2014-02-07 17:44
Надо поискать. У меня где-то готовый вариант валяется. Напиши мне через форму обратной связи, чтобы я не забыл.
# Максим 2014-02-08 06:32
Спасибо за помощь и за полезные библиотки.
В форму напишу обязательно.
Вышел из ситуации пока-что так:
использовал кнопки вверх и вниз для ввода числа, а копку отменадля умножения на 10 (т.е. сдвига влево. Получаетсявведе нное число сдвигается на местодесятков, вводишь значение. Снова "отмена" теперьу тебя уже сотни и десятки, вводишь единицы)
# SIG 2015-02-05 15:40
Нужна ваша помощь. Пишу программу в которой разные функции будут включаться малым количеством кнопок. Работа на отжатие одной кнопки такая:

if(PINB.4==0)
{
delay_ms(150); // антидребезг кнопки
if(PINB.4==0) // повторная проверка
{
while(PINB.4==0 );
код программы;
}
}
Нужен код нажатия двух кнопок одновременно. С этим не получается.

У вас недостаточно прав для комментирования.