Работа с кнопками программа обработки нажатия для микроконтроллера ATmega8

В этой заметке пойдет речь о считывании информации с помощью клавиш. Выводить введенную информацию будем на алфавитно-символьный ЖКИ. Предлагаю начать с самого простого. Подсоединим одну клавишу, без фиксатора, нормально разомкнутую и рассмотрим некоторые тонкости работы.

В этой заметке пойдет речь о считывании информации с помощью клавиш. Выводить введенную информацию будем на алфавитно-символьный ЖКИ. Предлагаю начать с самого простого. Подсоединим одну клавишу, без фиксатора, нормально разомкнутую и рассмотрим некоторые тонкости работы.


Испытуемая клавиша А2 при нажатии закорачивает вывод PB0 на «землю». Вся остальная обвязка стандартна.
Первой тонкостью работы является дребезг контактов, неизбежный при использовании не специализированных кнопок. Его можно убрать с помощью таймера LM555, но это извращение, если мы собрались использовать микроконтроллер.

Второй тонкостью является вопрос:

«когда считать кнопку нажатой?»

При появлении логического нуля на выводе порта микроконтроллера на протяжении 1мс? Но при залипании клавиши может возникнуть неприятная ситуация.
Третьим будет вопрос,

«с какой периодичностью проверять клавиши?»

Так как реакция человека на внешние раздражители редко когда превышает 0,03с. То условимся считывать состояние клавиатуры 100 раз в секунду. Клавишу будем считать нажатой, тогда, когда на порту микроконтроллера в течении более чем 0,01с будет присутствовать низкий уровень, после чего клавиша будет отжата (на порту микроконтроллера появится единица). В связи с этими рассуждениями напишем программу, которая будет обрабатывать нажатие одной клавиши, имитируя включение чего-то, на ЖКИ соответственно будем писать «On», «Off».

  1. #include <avr/io.h>//Библиотека ввода/вывода
  2. #include <avr/interrupt.h>//Библиотека прерываний
  3.  
  4. #define RS 2 //Вывод ЖКИ RS подключаем на вывод PD2 МК
  5. #define E 3 //Вывод ЖКИ E подключаем на вывод PD3 МК
  6.  
  7. #define TIME
  8. //Временная константа для ЖКИ
  9. //Тактовая частота 4Mhz
  10.  
  11. unsigned char status=0;
  12. //Переменная статуса
  13.  
  14. //Программа задержки
  15. void pause (unsigned int a)
  16. {
  17. unsigned int i; //декларация переменной для программы задержки
  18. for (i=a;i>0;i--); //Основной цикл программы задержки
  19. }
  20.  
  21. //Передача команды в ЖКИ
  22. void lcd_com (unsigned char lcd) //Программа записи команд в ЖКИ
  23. {
  24. unsigned char temp;
  25.  
  26. temp=(lcd&~(1<<RS))|(1<<E); //RS=0 передаем команду в ЖКИ
  27. PORTD=temp; //Выводим на portD старшую тетраду команды, сигналы RS, E
  28. asm("nop"); //Небольшая задержка в 1 такт МК, для стабилизации
  29. PORTD=temp&~(1<<E); //Запись команды в ЖКИ
  30.  
  31. temp=((lcd*16)&~(1<<RS))|(1<<E); //RS=0 передаем команду в ЖКИ
  32. PORTD=temp; //Выводим на portD младшую тетраду команды, сигналы RS,
  33. asm("nop"); //Небольшая задержка в 1 такт МК, для стабилизации
  34. PORTD=temp&~(1<<E); //Сигнал записи команды
  35.  
  36. pause(10*TIME); //Пауза для выполнения команды
  37. }
  38.  
  39. //Програма записи данных в ЖКИ
  40. void lcd_dat (unsigned char lcd)
  41. {
  42. unsigned char temp;
  43.  
  44. temp=(lcd|(1<<RS))|(1<<E); //RS=1 - ввод данных в ЖКИ
  45. PORTD=temp; //Выводим на portD старшую тетраду данных, сигналы RS, E
  46. asm("nop"); //Небольшая задержка в 1 такт МК, для стабилизации
  47. PORTD=temp&~(1<<E); //Запись данных в ЖКИ
  48.  
  49. temp=((lcd*16)|(1<<RS))|(1<<E); //RS=1 – это данные
  50. PORTD=temp; //Выводим на portD младшую тетраду данных, сигналы RS, E
  51. asm("nop"); //Небольшая задержка в 1 такт МК, для стабилизации
  52. PORTD=temp&~(1<<E); //Сигнал записи данных
  53.  
  54. pause(TIME); //Пауза для вывода данных
  55. }
  56.  
  57. //Программа инициализации ЖКИ
  58. void lcd_init (void)
  59. {
  60. lcd_com(0x2c); //Формат данных 4-проводный интерфейс, 5x8 размер символа
  61. pause(100*TIME); //Пауза на выполнение команды
  62. lcd_com(0x0c); //Показать изображение, курсор не показывать
  63. pause(100*TIME); //Пауза на выполнение команды
  64. lcd_com(0x01); //Очистить DDRAM и установить курсор на 0x00
  65. pause (100*TIME); //Пауза на выполнение команды
  66. }
  67.  
  68. //Программа инициализации таймера 0
  69. void init_timer (void)
  70. {
  71. TIMSK=(1<<TOIE0); //Разрешить прерывания по переполнению таймера 0
  72. TCCR0=(1<<CS00)|(1<<CS01)|(0<<CS02); //Делитель =/64
  73. }
  74.  
  75. //Программа вывода статуса на ЖКИ
  76. void write_status(unsigned char status)
  77. {
  78. if (status == 0) //Если статус "Выключено"
  79. {
  80. lcd_com(0x80); //Команда очистки ЖКИ
  81. lcd_dat('O'); //Выводим на ЖКИ "O"
  82. lcd_dat('f'); //Выводим на ЖКИ "f"
  83. lcd_dat('f'); //Выводим на ЖКИ "f"
  84. }
  85. else
  86. {
  87. lcd_com(0x80); //Команда очистки ЖКИ
  88. lcd_dat('O'); //Выводим на ЖКИ "O"
  89. lcd_dat('n'); //Выводим на ЖКИ "n"
  90. lcd_dat(' '); //Выводим на ЖКИ " "
  91. }
  92. }
  93.  
  94. //Обработчик прерывания по таймеру 0
  95. ISR (TIMER0_OVF_vect)
  96. {
  97. if ((PINB&0x01)==0x00)
  98. {
  99. //Если низкий уровень на PB0 есть
  100. pause(1000); //Пауза на 0.01с
  101. if ((PINB&0x01)==0x00) //Если кнопка нажата
  102. {
  103. while ((PINB&0x01)==0x00); //Ждем отжатия клавиши
  104. if (status==0) //Если кнопка отжата
  105. status=1; //Меняем статус
  106. else status=0; //В другом случаи статус = 0
  107. }
  108. }
  109. TCNT0=0x00; //Обнуление счетчика таймера 0
  110. TIFR=0x00; //Обнуление флага переполнения
  111. return;
  112. }
  113.  
  114. //Основная программа
  115. int main(void)
  116. {
  117. DDRD=0xfc; //Конфигурация порта D на выход
  118. PORTD=0x00; //Вывод на порт D 0x00
  119. DDRB=0x00; //Конфигурация порта B на вход
  120. PORTB=0x01; //Включаем подтягивающий резистор бит 0 порт B
  121.  
  122. pause(1000); //Пауза на включение ЖКИ
  123. lcd_init(); //Инициализация ЖКИ
  124. init_timer(); //Инициализация таймер 0
  125.  
  126. sei(); //Глобальное разрешение прерываний
  127.  
  128. while(1) //Вечный цикл
  129. {
  130. cli(); //Запрещаем прерывания на время вывода статуса
  131. write_status(status); //Выводим на ЖКИ статус
  132. sei(); //Разрешить прерывания
  133. pause(1000); //Пауза
  134. }
  135. return 1;
  136. }

Как видно из кода, используется прерывания по переполнению нулевого таймера. Разберемся немного с прерываниями. Прерывание микроконтроллера – это реакция, быстрая реакция микроконтроллера на какое-то внешнее или внутреннее событие все зависимости от выполняемого в данный момент кода. Под реакцией подразумевается выполнения некой функции, обрабатывающей прерывания. События, вызывающее прерывания могут быть самыми разнообразными, например переполнение таймера, прием байта по USART, смена уровня на выводе INT0. В прерывания и есть особая фишка микроконтроллеров семейства AVR, можно реализовать «псевдомультизадачность». Для разрешения прерываний есть 2 бита. Для глобального разрешения прерывания бит I в регистре SREG должен равняется единице, а для запрета прерываний вообще – нулю (функция sei() разрешает прерывания; cli(); - запрещает). Далее для разрешения конкретного прерывания требуется установка бита, отвечающего за прерывание от данного события. Например, для прерывания по переполнению таймера нужно активировать бит TOIE0 в регистре TIMSK, а для прерывания по окончанию приема байта USART, бит RXCIE в регистре USCRB в ATmega8. Конкретные имена битов и регистров нужно смотреть в даташите микроконтроллера. Функция обработки прерывания описывается в виде:

  1. ISR(прерывание_vect)
  2. {
  3. выполняемый код
  4. }

Где прерывание_vect – вектор обрабатываемого прерывания. Также берется с даташита. Также в начало программы нужно подключить модуль avr/interrupt.h в котором записаны адреса векторов прерываний и прочие константы.
Теперь разберемся с нулевым таймером. Таймер это 8-ми битный регистр TCNT0, значение которого инкрементируется (декрементируется), при некотором количестве тактовых импульсов микроконтроллера. При переполнении регистр обнуляется и может генерировать прерывание, если бит TOIE0 в регистре TIMSK установлен. Количество импульсов для инкремента таймера – делитель. Он выставляется с помощью битов CS00, CS01, CS02 (Значение смотреть в даташите). Грубо говоря – это счетчик времени (в некоторых других контроллерах – внешних импульсов). В нашей программе прерывание от переполнения таймера генерируется каждые (тактовая частота)/(256*делитель) секунду. Тактовая частота у нас 4МГц, а делитель /64. Мы будем иметь 0,000256 секунды, или примерно 4 тысячи раз в секунду.

Это на порядок чаще, чем мы условились выше, но на обработку прерывания тоже нужно время. Итого будем иметь около 200-300 прерываний в секунду. В функцию обработки прерывания запихнем алгоритм считывания нажатия клавиши: сперва ждем 0,01с, если на PB0 нулевой потенциал, ждем отжима клавиши, меняем состояние и выходим. В теле программы мы инициализируем порты, разрешаем прерывания и в вечном цикле выводим текущее состоянии, оглашенное глобальной переменной (1=«On», 0=«Off») на ЖКИ.
Аналогичную программу можно было написать без использования прерываний. Просто в вечном цикле считывая нажатие клавиши, а после выводя считанное значение на экран. Но при этом тратиться все машинное время процессора, что не есть хорошо. Все более-менее сложные проекты на AVR не обходятся без прерываний.
фото устройства

Скачать Проект в AVR Studio 4

Также можно посмотреть видео:

А каким образом эту программу

А каким образом эту программу можно переписать под atmega168? а то проблемка в прерываниях

Какая именно проблемка? Там с

Какая именно проблемка? Там с малой степенью вероятности может быть с регистрами. И все. А так программа так же будет выглядеть. Уже собирал в железе устройство? работает?

устройство собрал. на 8меге

устройство собрал. на 8меге работает. на 168 меге не хочет. Выводит только Офф и все, на реакции кнопки не реагирует. АВР Студио материлась на это:

  1. void init_timer (void)
  2. {
  3. TIMSK=(1<<TOIE0); //Разрешить прерывания по переполнению таймера 0
  4. TCCR0=(1<<CS00)|(1<<CS01)|(0<<CS02); //Делитель =/64
  5. }
  6. я изменил на такой
  7. void init_timer (void)
  8. {
  9. TIMSK0=(1<<TOIE0); //Разрешить прерывания по переполнению таймера 0
  10. TCCR0A=(1<<CS00)|(1<<CS01)|(0<<CS02); //Делитель =/64
  11. } // добалив к TIMSK и TCCR0 - 0 и А соответственно.
  12. также ругнулся Студио на это
  13. TCNT0=0x00; //Обнуление счетчика таймера 0
  14. TIFR=0x00; //Обнуление флага переполнения

а именно на ТИФР и я просто добавил 0 к нему. Вообщем прошивка скомпилировалась, но прибор показывает ОФФ и все.

Скажите а в какой программе

Скажите а в какой программе Вы рисуете все эти принципиальные схемы?
Очень похоже по внешнему иду на Diptrace...

Да, правильно это программа

Да, правильно это программа Dip Trace.

прерывание

нехорошо использовать паузы и бесконечные циклы в прерываниях

мм, цикл while

мм, цикл

  1. while ((PINB&0x01)==0x00); //Ждем отжатия клавиши

вполне конечен.
А что паузы нехорошо использовать - согласен. Прерывания должны быть максимально короткими. Но как тогда обойти дребезг контактов??

нужно еще раз проговорить,

нужно еще раз проговорить, почему нехорошо использовать бесконечные циклы в прерываниях и почему прерывания должны быть максимально короткими?

Прерывания вклиниваются в

Прерывания вклиниваются в работу основной программы и требуют некоторого времени на выполнение. Тем временем основная программа может быть занята чем-то критичным к задержкам, например передача какого-то пакета. При этом прерывание может нарушить работу основной программы и привести к сбою или неправильно переданному пакету. Потому стараются сделать прерывание как можно короче. Вечные цикли не способствуют быстрому выполнению прерывания, да и расчитать время их выполнение на порядок сложней, чем "конечных".

Опрос кнопок

Я например использую опрос кнопок каждых 250ms по прерыванию таймера, и так обходжу дребезг контактов.

У меня также опрос происходит

У меня также опрос происходит по прерыванию от таймера, плюс еще дребезг контактов учитывается, без него как-то корявенько работает.

кнопки

подскажите, как увизать в одном проэкте кнопки с фиксацией(от двух и более)с кнопками без фиксатора (от двух и более)...

Какая стоит задача? Если

Какая стоит задача? Если строить алгоритм так что в основном цикле программы все время крутиться подпрограмма проверки нажатия то это один вариант реализации. Второй вариант сделать опрос порта с кнопками по срабатыванию таймера то есть подпрограмму обработки нажатия кнопки запускать по срабатыванию таймера. И третий вариант это прерывания от внешнего источника int0, int1...

кнопки

K примеру; на LCD идёт секундный счет только при нажатие, а при отпускание останавливается; кнопка S1 на увеличение счёта, a S2 на уменьшение (при нажатие обоих-идёт счёт в ту сторону, кнопка которой была нажата первой), а так-же S3 и S4 при нажатие и после отпускание идёт счёт; S3 на увеличение, S4 на уменьшение и счёт останавливаеться толька при нажатие S5.
Конечно-же было бы интересно сравнить все три варианта, но хотя бы один на ваш взгляд наилучьший...........

Cчитывание кнопок в данной

Cчитывание кнопок в данной ситуации лучше всего сделать по срабатыванию внешних прерываний INT0, INT1 (если кнопок много - тогда возьми контроллер с PCINT, например mega88 или используй такую вот схему подключения кнопок):
схема подключения
(Вывод PD2 - INT0, при нажатии любой кнопки генерируется внешнее прерывание)
В данное ситуации такое решение аргументирую тем, что:
1)На считывание клавы, когда не было нажатия, не будет тратиться время, которое сложно рассчитать.
2)Остаются свободны таймеры, которыми можно измерять равные промежутки времени с большой точностью (главное кварц с частотой "поудобнее" взять).
3)Если нужно, можно будет юзать режимы пониженного энергопотребления, которые в некоторых ситуациях выручают.

посмотреть бы наглядно как

посмотреть бы наглядно как это выглядит в готовом проекте...

Может сперва про "Матричная

Может сперва посмотреть "Матричная клавиатура 4х4 динамическое считывание данных с портов"?
Если покажется слишком тяжелым - то лучше начинать с малого, сделать всего одну кнопку и светодиод для индикации, если получиться - кнопку + ЖКИ, а потом уже много кнопок и ЖКИ.

По поводу значений!

Для особо-одоренных подскажи где взять значения я не смог найти на той стр??? где они вообще находятся?

Вопрос по схеме?

Где взять значения коденсаторов и резисторов которые использовались в данной схеме???
С1-С7 и R1-R3???

Каждый раз просто лень

Каждый раз просто лень наверное указывать номиналы, программа в которой схемы рисуем не дает возможность указывать номиналы на схеме, попробуем вместо нумерации писать номиналы. А пока вот список номиналов:

C1 - до 28 пФ }должны быть одинаковыми
C2 - до 28 пФ }
C3 - 200 мкФ(электролит)
C4 - 0,1 мкФ(керамика)
C5 - 200 мкФ(электролит)
C6 - 0,1 мкФ(керамика)
C7 - 0,1 мкФ(керамика)

R1 - 1 кОм
R2 - 470 Ом
R3 - 16 Ом

VR1 - 1 кОм подстроечный

Немного уточню, С5 ставлю в 2

Немного уточню, С5 ставлю в 2 раза меньше, чем С3. При отключении питания может пойти обратный ток через стабилизатор l7805 и спалить его. А подстроечный VR1 - можно и на 10кОм ставить, не критично.
След. раз номиналы намалюю под схемой ;)