Avr как работать с клавиатурой микроконтроллер

Обновлено: 18.05.2024

В прошлой своей заметке я описывал считывание одной клавиши с помощью микроконтроллера ATmega8 и вывод на ЖКИ. Написанную выше программу можно легко модифицировать на большее количество кнопок. Следует подсоединить клавиши к свободным выводам и периодически их всех опрашивать.

Но при большом количестве клавиш не хватает ножек. Можно перейти на более «многоногие» контроллеры, но они, как правило, стоят больших денег и в некоторых случаях приходиться заново переписывать проект под них. Рассмотрим решение проблемы нехватки ножек при считывании с клавиатуры, так называемое динамическое считывание. Например, мы имеем 16 клавиш, и хотим считать с них информацию. Хитро соединим их в клавиатурную матрицу, как показано на рисунке ниже:

Со схемы видно, что для считывания 16-ти клавиш нам потребуется 8 выводов. По сравнению с обычным подключением (один пин – одна кнопка) мы выиграем в 16/8=2 раза. Алгоритм считывания будет таков: конфигурируем выводы PB0-PB3 как выхода и подадим на них нулевой уровень, а выводы PC0-PC3, выставим как входа и будем смотреть в какой колонке находиться нажатая клавиша. Далее поменяем все местами. Порты PC0-PC3 будут выходами с нулевым уровнем, а на портах PB0-PB3 будет считывать строку, в которой находиться нажатая клавиша. Зная строку и колонку можно однозначно вычислить клавишу, которая была нажата. Каждой клавише сопоставим код, который будет храниться в 2х мерном массиве 4х4 (строка нажатой клавиши – первый индекс массива, колонка - второй):


То есть, нажав клавишу во 2-й строке и 4-й колонке, микроконтроллер поймет, что была нажата клавиша «9» (массив будет отличаться, в зависимости от того, как подсоединить клавиатуру к микроконтроллеру). Далее все это будем выводить на экран ЖКИ. В схеме так же присутствуют ограничивающие резисторы R4-R11 номиналом 2 кОм, которые на «всякий пожарный случай» не дают пройти большому току через порт микроконтроллера. Клавиатуру проще всего изготовить из макетной платы и отдельных кнопок, соединив их короткими перемычками, она верой и правдой послужит в дальнейших опытах. Вид сверху:

И снизу:

Теперь перейдем к написанию программы:

Программа очень похожа на считывание одной клавиши, за исключением глобально объявленного массива key_code и функции обработки прерываний, которая проста, хорошо прокомментирована и словесно описывалась выше. Единственное, критично делать небольшую задержку, перед считыванием с порта, иначе всякие паразитные емкости и индуктивности могут внести помеху, и на экране будет отображаться ерунда. Если у вас после сборки не работают некоторые клавиши – следует проверить порты микроконтроллера на целостность. Сделать это просто скомпилировав простенькую программу проверки портов:

фото устройства

И подсоединить светодиод к каждой ножке порта, если он замигает – все в порядке, если нет – порт дефектный. Внешний вид макета можно увидеть на фото:

Видео для демонстрации приведено ниже:

Но на этом не стоит ограничиваться. Зачастую введенные данные нужно обрабатывать, также следует предусмотреть возможность сброса введенного или удаление последнего символа. Реализуем макет, где с клавиатуры будет вводиться шестизначное целое число, при нажатии на клавишу «С» на экране ЖКИ должен мигать курсор, число его миганий равно числу, введенному с клавиатуры. Также клавиша «F» будет означать очистку экрана, а клавиша «E» - последнего введенного символа. Перейдем к написанию программы:

Номиналы резисторов:
R4-R11 - 2кОм,
R3 - 17 Ом,
R1, R2 - 1 кОм.

Теперь программа выглядит посерьезней. Добавился глобальный массив row в котором содержаться нажатые цифры и переменная row_count – их количество. Функция void write_row(void) выводит на ЖКИ содержимое row, а void get_number(void) – конвертирует массив цифр в целочисленную переменную number. Также появилась функция проверки, является ли x цифрой – is_digit(x). Во всем остальном код хорошо прокомментирован, и разобраться в нем не составит труда. Исходные коды программ, в виде проектов под AVR Studio
скачиваем здесь.
Видео работы:

вход-выход

>> DDRC=0x00; //конфигурируем порт C как вход
>> PORTC=0x0F; //выводим на 4 младших бита порта C лог. 1

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

>> DDRC=0x00; //конфигурируем

>> DDRC=0x00; //конфигурируем порт C как вход
>> PORTC=0x0F; //выводим на 4 младших бита порта C лог. 1

В этом случае PC0-PC3 - входа с подключенным подтягивающим резистором.
Читать мануал на стр. 52, таблица 12-1

количество нажатых кнопок,а именно вывод этого количества на жки

как вывести на жки количество нажатых(или зажатых)кнопок,например я зажал 7 кнопок,и мне нужно чтобы на жки вывелось 7,я зажал 16 и на жки вывелось 16. помогите пожайлуста,очень нужно!

В этой статье мы рассмотрим подключение клавишной панели 4х4 (16 кнопок) к микроконтроллеру ATmega32 (семейство AVR). Клавишная панель (клавиатура, кейпад) является одним из важнейших устройств ввода в электронных схемах, она предоставляет возможности самого простого ввода различных команд и инструкций в электронные схемы.

Подключение клавишной панели к ATmega32: внешний вид

Необходимые компоненты

Аппаратное обеспечение

  1. Микроконтроллер ATmega32 (купить на AliExpress).
  2. Программатор AVR-ISP (купить на AliExpress), USBASP (купить на AliExpress) или другой подобный.
  3. JHD_162ALCD (ЖК дисплей 16x2) (купить на AliExpress).
  4. Клавишная панель (клавиатура) 4х4 (купить на AliExpress).
  5. Конденсатор 100 мкФ (купить на AliExpress).
  6. Конденсатор 100 нФ (купить на AliExpress).
  7. Резистор 10 кОм (8 шт.) (купить на AliExpress).
  8. Источник питания с напряжением 5 Вольт.

Программное обеспечение

  1. Atmel Studio версии 6.1 (или выше).
  2. Progisp или flash magic (необязательно).

Работа схемы

Схема устройства приведена на следующем рисунке.

В представленной схеме PORTB микроконтроллера ATmega32 соединен с портом данным жидкокристаллического (ЖК) дисплея. При этом следует помнить о том, что необходимо деактивировать JTAG интерфейс микроконтроллера на порту PORTC при помощи изменения фьюзов (fuse bytes) если мы хотим использовать PORTC как обычный порт ввода/вывода. В ЖК дисплее (если мы не хотим использовать черный цвет) можно задействовать только 14 его контактов: 8 контактов для передачи данных (7-14 или D0-D7), 2 контакта для подачи питания (1&2 или VSS&VDD или gnd&+5v), 3-й контакт для управления контарстностью, 3 контакта для управления (RS&RW&E).

В представленной схеме мы использовали только 2 контакта управления ЖК дисплея для лучшего понимания работы схемы. Бит контраста и READ/WRITE используются нечасто, поэтому они могут быть замкнуты на землю. Это обеспечивает ЖК дисплею максимальную контрастность и переводит его в режим чтения. Теперь нам всего лишь нужно контролировать контакты ENABLE и RS чтобы передавать на ЖК дисплей символы и данные. Также на нашем сайте вы можете прочитать более подробную статью о подключении ЖК дисплея к микроконтроллеру ATmega32.

В схеме необходимо сделать следующие соединения с ЖК дисплеем:
PIN1 или VSS - земля
PIN2 или VDD или VCC - +5v питание
PIN3 или VEE - земля (обеспечивает максимальный контраст ЖК дисплею)
PIN4 или RS (Register Selection) – контакт PD6 микроконтроллера
PIN5 или RW (Read/Write) - земля (переводит ЖК дисплей в режим чтения что упрощает взаимодействие с ним для начинающих)
PIN6 или E (Enable) - контакт PD5 микроконтроллера
PIN7 или D0 - контакт PB0 микроконтроллера
PIN8 или D1 - контакт PB1 микроконтроллера
PIN9 или D2 - контакт PB2 микроконтроллера
PIN10 или D3 - контакт PB3 микроконтроллера
PIN11 или D4 - контакт PB4 микроконтроллера
PIN12 или D5 - контакт PB5 микроконтроллера
PIN13 или D6 - контакт PB6 микроконтроллера
PIN14 или D7 - контакт PB7 микроконтроллера

Принципы работы клавишной панели

Клавишная панель, по сути, представляет собой мультиплексор ("уплотнитель") клавиш. Кнопки на ней соединены в мультиплексированной форме чтобы уменьшить количество используемых контактов.

Если рассматривать случай без мультиплексирования (уплотнения), то для работы клавишной панели 4х4 (16 кнопок) нам бы понадобилось 16 контактов, но это не очень хорошо с точки зрения управления подобной системой. Уменьшение числа используемых контактов достигается соединением кнопок клавишной панели в мультиплексированной (объединенной) форме.

К примеру, мы имеем 16 кнопок и мы хотим присоединить к ним контроллер чтобы получить клавишную панель, пусть эти кнопки упорядочены следующим образом, показанным на рисунке.

Объединим эти кнопки по столбцам как показано на следующем рисунке.

Как показано на рисунке, немаркированные концы всех четырех кнопок в столбце объединены в одно соединение, таким образом для 16 кнопок мы имеем 4 столбца.

Вместо объединения по столбцам можно использовать и объединение по строкам как показано на следующем рисунке.

Если мы используем объединение одновременно и по строкам, и по столбцам, то мы получим схему, представленную на следующем рисунке.

Здесь мы имеем 16 кнопок в мультиплексированной форме чтобы уменьшить количество контактов клавишной панели. Если бы мы использовали вариант без мультиплексирования, то нам бы понадобилось для соединения с 16 кнопками использовать 16 контактов микроконтроллера, здесь же нам будет достаточно всего лишь 8 контактов.

Внешний вид подобной клавишной панели со шлейфом показан на следующем рисунке.

Внешний вид клавишной панели со шлейфом

Таким образом, рассматриваемая нами клавишная панель содержит четыре строки и четыре столбца, поэтому для идентификации нажатой кнопки мы будем использовать метод перекрестных связей. Сначала соединим все строки или все столбцы к общему источнику постоянного напряжения (vcc), получим что если, к примеру, что если все строки подсоединены к общему источнику постоянного напряжения, мы столбцы можем подсоединить ко входам микроконтроллера. Теперь если кнопка 1 клавишной панели нажата, то мы получим следующую картинку.

Постоянный ток в этом случае будет протекать по клавишной панели как показано на следующем рисунке.

Таким образом, при нажатой кнопке 1 мы будем иметь на выходе C1 высокое напряжение. В этот момент мы собираемся переключить контакты, на которые подается постоянное напряжение, и контакты, соединенные со входами микроконтроллера, то есть как бы поменяем роль столбцов и строк. Эта ситуация показана на следующем рисунке.

Теперь мы на выводе R1 имеем высокое напряжение.
Таким образом, мы имеем высокое напряжение на C1 в первом случае и высокое напряжение на R1 во втором случае, поэтому мы можем однозначно идентифицировать позицию в рассматриваемой нами матрице соединений и определить что была нажата кнопка 1.

Если будет нажата кнопка 2, то мы будем иметь сигнал высокого уровня на выходе C1 в первом случае и сигнал высокого уровня на выходе R2 во втором случае. Сопоставляя эти факта (то есть наличие сигнала высокого уровня на C1 и R2), мы однозначно можем определить что была нажата кнопка 2.

Как видно из представленной схемы устройства, у нас 8 выходов (контактов) клавишной панели соединены с 8 входами (контактами) микроконтроллера. Вначале мы подадим высокое напряжение на контакты, соответствующие строкам клавишной панели, в это время столбцы клавишной панели будут рассматриваться как выходы. Во время нажатия кнопки соответствующий контакт в столбце будет иметь высокий логический уровень (напряжения) и на соответствующем ему входе микроконтроллера также будет высокий уровень напряжения, поэтому мы однозначно сможем определить тот факт, что была нажата какая-нибудь кнопка на панели. В этот момент мы можем переключить источник постоянного напряжения со строк на столбцы и в результате по изменению напряжению в соответствующей строке (а номер столбца, соответствующий нажатой кнопке, у нас уже есть) однозначно определить какая именно кнопка клавишной панели была нажата.

Номер нажатой кнопки высвечивается на экране ЖК дисплея. Далее мы рассмотрим программу, реализующую обработку нажатий кнопок клавишной панели.

Исходный код программы на языке C (Си) с пояснениями

Программа для рассматриваемой схемы представлена следующим фрагментом кода на языке C (Си). Комментарии к коду программу поясняют принцип работы отдельных команд.

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

Теперь следующий шаг, изучение схемы. Во время изучения схемы микроконтроллер представляем черным ящиком, который может либо дрыгать ножками, либо читать информацию с ножек. Смотрим на особенности схемы, на одну ножку приходится 4 кнопки по горизонтали (строки), аналогично 4 кнопки приходится на одну ножку по вертикали (столбцы). Кнопки в пределах одной строки/столбца объединены общим проводом. Получается матрица 4х4.

lesson20-1

Теперь нужно понять принцип работы более детально. Ножки, подключенные к общему проводу строк, настраиваются как вход, те что подключены к столбцам настроены как выход. Когда активен только первый столбец, то мы однозначно знаем, что нажаты могут быть только кнопки с 1 по 4. Далее, переключаемся на второй столбец, сканируем кнопки с 5 по 8 и т.д. Остается лишь читать состояния входов. Диоды нужны, чтобы защитить входы микроконтроллера, если нажато несколько кнопок одновременно. Этой информации должно быть достаточно для того, чтобы написать собственную прошивку. Написать свою прошивку это интересно и главное, ты знаешь как она работает. Поэтому я стараюсь все прошивки писать с нуля. Перейдем к программной части.

Можно использовать Таймер1, но он имеет множество полезных функций и может пригодиться для других вещей, поэтому задействуем таймер2. С какой периодичностью производить опрос? С такой, чтобы не пропустить самые быстрые нажатия. Для надежности, решил взять 20 раз в секунду, т.е. прерывание должно происходить раз в 50мс. Таймер2 не очень годится для длинных отсчетов, потому что максимальное число, которое можно записать в регистр сравнения 0xFF = 255. При минимальной частоте таймера 7813Гц, один тик будет длиться 1/7813=0,000128сек, т.е. максимальный промежуток между прерываниями 0,000128*255=32,6мс. В данном случае не принципиально 50 или 32, поэтому этот результат меня устроил.

За одно прерывание нужно опросить 4 столбца, для этого сделаем цикл for, переменная i хранит номер опрашиваемого столбца.

int i=0; // Timer 2 output compare interrupt service routine interrupt [TIM2_COMP] void timer2_comp_isr(void) < for(i=0; i<4; i++) < >>

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

int i=0; char portState[4]= ; // Timer 2 output compare interrupt service routine interrupt [TIM2_COMP] void timer2_comp_isr(void) < for(i=0; i<4; i++) < PORTD=portState[i]; >>

Неплохо еще вывести на экран какой нибудь символ. Для этого можно создать двухмерный массив, выбор символа, будет зависеть от номера столбца и строки. Символы до 9 соответствуют номерам кнопок, на остальные задействованы буквы. Символ выводится в момент сканирования строки, что очень удобно, так как мы знаем номер столбца и строки.

Примерно в такой последовательности я рассуждал, когда писал данную прошивку. Дальше должна следовать еще отладка на железе. Но в пределах данной статьи ограничусь симулятором.

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

pixpix

Update: Зачем нужны диоды? Допустим некий момент времени, когда опрашиваем первый столбец, соответственно на остальных столбцах логическая единица. Если диодов нет и нажать кнопку 1 и 5, то будет короткое замыкание.

47 комментариев: Урок 20. Опрос матричной клавиатуры.

void main(void)
PORTB=0x00;
DDRB=0xFF;

конечно не одно и тоже 🙂

У меня вопрос?.Порты установленные D установленые в логическую 1?

При одновременном нажатии на несколько клавиш ни одна из них не должна сработать. как это можно реализовать?

Привет. Вы не знаете, как записать в переменную, с помощью этой клавиатуры, например 7 значное число ? Если например во второй старший разряд я хочу записать ноль, допустим 7065783, это нужно записать в переменную, каким образом это сделать ? Интереснен момент с нулями в разрядах

На этой страничке рассказывается, как подключать 12-кнопочную клавиатуру к микроконтроллеру AVR, и как считывать клавиатурные нажатия на языке ассемблера. Это перевод статьи "Connecting a keypad to an AVR" [1]. Как работать с матрицей клавиатуры на языке C, см. [2].

[1. Как работает клавиатура]

Клавиатура представляет собой просто набор замыкателей. Нажали кнопку клавиатуры - соответствующий контакт замкнулся. Для того, чтобы уменьшить количество проводов, которые идут от клавиатуры, и тем самым упростить схему подключения, используют соединение замыкателей в матрицу. В нашем примере с 12-кнопочной клавиатурой матрица имеет 3 столбца (Column, провода столбцов пронумерованы как Col3..Col1) и 4 строки (Row, провода строк пронумерованы как Row4..Row1).

К примеру, если нажата кнопка "1", то замыкаются друг на друга Col1 и Row1, если нажата кнопка "2", то замыкаются Col2 и Row1, и так далее.

Чтобы определить, что нажата хотя бы одна кнопка из 12, можно соединить все столбцы на землю, а все строки подключить через нагрузочный резистор (pull-up) к + питания. Тогда на выходе Output появится лог. 0, если будет нажата любая кнопка.

Однако обычно в реальной жизни нужно не только знать, что нажата любая кнопка, нужно определить, какая именно кнопка из 12 была нажата. Для этого не все сразу столбцы подключаются к лог. 0, они подключаются к 0 поочередно, друг за другом. Такая процедура пробегания лог. 0 по столбцам называется сканированием клавиатуры. Один цикл сканирования происходит очень быстро, за время порядка 1..10 миллисекунд. Кроме того, для каждой строки используют отдельный нагрузочный резистор.

Событие нажатия определяется по чтению состояния всех строк (Row4..Row1). Если на всех RowX уровень лог. 1, то это что ни одна из клавиш не нажата. Если на одной из строк RowX появился уровень лог. 0, то сканирование прекращается, и код нажатой клавиши определяется по таблице.

Чтобы читать такую клавиатуру с использованием цифровой логики, то нужно иметь как минимум следующее:

• генератор, регистр сдвига и декодер запуска/останова для генерации сигналов столбцов,
• детектор появления нуля на сигналах строк,
• декодер для преобразования 7 цифровых сигналов в код нажатой клавиши.

Лучше всего с такой задачей справится микроконтроллер, например AVR.

[2. Подключение AVR к матрице клавиатуры]

Клавиатурная матрица может быть подключена к AVR напрямую, без каких-либо дополнительных компонентов.

На этой картинке в качестве примера показано подключение матрицы к 7 младшим разрядам порта P микроконтроллера AVR. Можно конечно также использовать любые другие порты в любой комбинации. В нашем примере GPIO PB4..PB6 работают как выходы, они обеспечивают сигналы для столбцов (сканирующий бегущий лог. 0). GPIO PB0..PB3 используются как входы, через них читается состояние строк. На этих входах программно включены внутренние нагрузочные резисторы микроконтроллера (pull-up), так что никакие внешние резисторы для создания лог. 1 не нужны.

В следующем примере кода показано, как инициализируются порты GPIO. Эта часть программы должна быть выполнена 1 раз, когда включается питание AVR.

Инициализация

Определение, нажата ли кнопка

Следующий кусок кода детектирует, была ли нажата любая из 12 кнопок клавиатуры. Эта подпрограмма должна вызываться с определенным интервалом, например с использованием цикла задержки, или по таймеру (например, в обработчике прерываний таймера).

Идентификация нажатой кнопки

Теперь нужно прочитать клавиатуру, для этого на столбцы Col3..Col1 поочередно нужно выставить лог. 0. После того, как на одном из портов столбца PB4..PB6 (Col3..Col1) выставлен 0 и на остальных портах столбца лог. 1, проверяется состояние портов строк PB0..PB3 (Row4..Row1) на наличие 0. Регистр Z (регистровая пара R31:R30) указывает на таблицу (размещена в памяти программ FLASH), содержащую коды кнопок. После того, как код определения кнопки закончит работу, регистр Z будет указывать на код нажатой кнопки. С помощью инструкции LPM можно прочитать этот код и сохранить его в регистр R0.

Как избавиться от дребезга контактов (дебоунсинг)

У подпрограммы KeyProc и NoKeyPressed можно добавить дебоусинг нажатой кнопки. Это можно сделать, к примеру, добавив счетчик, который будет считать вверх, пока будет идентифицирована одна и та же кнопка. Эти повторы идентификации должны продолжаться в течение примерно 50 миллисекунд. Подпрограмма NoKeyPressed очистит счетчик, и этим будут исключены ложные срабатывания определения нажатой кнопки. Поскольку время опроса зависит от других требований по интервалам времени обработки в общей программе AVR, то конкретная реализация подавления дребезга здесь не показана.

Недостатки, что еще можно улучшить

Эти подпрограммы, показанные выше, не предоставляют никакого времени между установкой состояния столбцов и чтением информации строк. Так что при большой емкости соединительных проводов до клавиатуры и/или на высокой частоте опроса клавиатуры (если используется высокая тактовая частота ядра AVR) нужно добавить дополнительную задержку между записью и чтением (это можно сделать простым добавлением инструкций NOP или циклами задержки).

Внутренние нагрузочные резисторы микроконтроллера AVR (pull-up) имеют номинал около 50 кОм. Длинные провода или работа в условиях сильных помех могут привести к неустойчивому опросу клавиатуры. Чтобы снизить чувствительность к шумам и помехам, добавьте на сигналы строк Row4..Row1 внешние нагрузочные резисторы подходящего номинала (1..10 кОм).

Еще один недостаток схемы в том, что для работы клавиатуры требуется 7 портов GPIO микроконтроллера. Модификация с использованием цифроаналогового преобразователя (ЦАП, Analog-Digital Convertor, ADC) и цепочки резисторов более экономична и позволяет задействовать меньше ножек микроконтроллера.

[3. Подключение к ADC с использованием резисторной матрицы]

Большинство микроконтроллеров AVR серий Tiny и Mega в настоящее время имеют в своем составе ЦАП (ADC). Без дополнительной внешней аппаратуры ADC может измерить аналоговое напряжение с точностью 10 бит. Если хотите сохранить ножки GPIO и применить для чтения клавиатуры ADC, то Вы как-то должны заставить клавиатуру генерировать разное напряжение в зависимости от того, какая кнопка нажата. Это задача для резисторной матрицы.

Резисторная матрица

Рассмотрим на примере, как работает чтение клавиатуры с использованием ADC. Если нажата кнопка "5", то активизируется следующий делитель напряжения:

1 кОм + 820 Ом = 1.82 кОм нижнее плечо (подключенное к земле),
3,3 кОм + 680 Ом + 180 Ом = 4.16 кОм верхнее плечо (подключенное к +5V).

При рабочем напряжении 5V на выходе делителя получится напряжение:
(5 * 1.82) / (1.82 + 4.16) = 1.522V

Напряжения и распознавание кнопок

Как можно видеть из таблицы, нет перекрытия уровней напряжения при детектировании нажатий различных кнопок, при учете допуска на номинал резисторов 5%. Если захотите поэкспериментировать с другими комбинациями резисторов, то можете скачать лист в формате Excel и Open Office [4], который может производить нужные вычисления для составления таблицы преобразования.

Советы по использованию аппаратного АЦП микроконтроллеров AVR

Чипы ATtiny часто позволяют использовать для опорного напряжения АЦП только внутреннее опорное напряжение или напряжение источника питания. Для опроса клавиатуры в качестве опорного напряжения для АЦП подходит только вариант с напряжением источника питания в качестве опорного. Эта опция должна быть настроена при инициализации ADC, как только программа стартует (после сброса или включения питания).

Многие чипы ATmega могут брать опорное напряжение для ADC с внешнего вывода, AREF. Этот вывод может работать либо как вход, либо как выход. Он будет выходом, если в качестве опорного для ADC выбрано внутренне опорное напряжение, или напряжение источника питания. В этом случае к ножке AREF и к земле должен быть подключен конденсатор, чтобы уменьшить шумы и помехи на опорном напряжении. AREF работает как вход, если настроен выбор внешнего источника опорного напряжения. В этом случае опорное напряжение для ADC поступает от внешнего источника. Если опорное напряжение предоставляет внешний источник, то и матрица резисторов клавиатуры также должна быть запитана от этого же источника. Имейте в виду, что приведенная схема матрицы может потреблять ток до 10 мА, это сделано для уменьшения чувствительности к помехам.

Чипы ATmega позволяют питать ADC от внешнего отдельного источника питания через дополнительный вывод корпуса (AVCC), чтобы дополнительно снизить шумы. Если ADC используется только для клавиатуры, то из-за низкой используемой точности преобразования (8 бит) нет необходимости применять отдельный источник питания для вывода AVCC, и этот вывод может быть напрямую подключен к обычному напряжению питания. Если все же другие каналы ADC используются для других точных измерений, то рекомендуется подключить вывод AVCC к напряжению питания через дроссель номиналом около 22 мкГн, и между выводом AVCC и землей должен быть подключен блокирующий конденсатор 100 нФ (0.1 мкФ).

Инициализация и чтение результата преобразования ADC

Для чтения напряжений, которые генерирует матрица клавиатуры, нужен только один канал ADC. ADC инициализируется один раз, когда запускается программа микроконтроллера (включение питания или сброс). Два примера кода показывают: в одном примере последовательность запуска одиночного преобразования, где используется ATmega8, и в другом примере управляемый прерываниями запуск ADC, этот пример для ATtiny13.

ATmega8: ручной запуск преобразования ADC

Первый пример показывает подпрограмму для ATmega8, без прерываний, с ручным запуском и остановом ADC. Сигнал от клавиатурной матрицы приходит на канал ADC0.

Пожалуйста имейте в виду, что это одиночное преобразование требует примерно 25 * 128 тактовых циклов, на тактовой частоте 1 МГц время преобразования составит 3.2 миллисекунды. Это довольно значительная трата времени, поэтому такой способ получения результата ADC допустим только в том случае, если Вам ничего не надо делать во время задержки на ожидание преобразования (за исключением того, что будет происходить в обработчиках прерываний).

ATtiny13: автозапуск конверсии ADC, с использованием прерываний

Даже ATtiny13 с её 8 ножками может прочитать матрицу клавиатуры, если задействовать ADC (мы не сможем традиционным образом подключить матрицу клавиатуры 3x4, потому что у ATtiny13 не хватит ножек I/O).

В этом примере выбран следующий способ преобразования: напряжение постоянно считывается с канала ADC3 (вывод 2 ATtiny13), и после того как преобразование завершится, следующее преобразование запустится автоматически.

Использование прерывания по завершению преобразования требует определения таблицы прерываний, где будет вектор соответствующего обработчика прерывания (rjmp intadc).

Само собой, для использования прерываний должен быть инициализирован стек, и должен быть установлен флаг общего разрешения прерываний (SEI).

Обработчик прерывания ADC читает результат преобразования. Поскольку выбрано левое выравнивание результата, то достаточно прочитать только старший (MSB) байт результата:

Регистр rKey постоянно дает текущее состояние резисторной матрицы клавиатуры.

Декодирование результата ARC, получения кода нажатой кнопки

Результат преобразования ADC сам по себе не очень-то полезен. Напряжения и соответствующие им результаты преобразования ADC не укладываются в простые математические правила (должно быть, номиналы резисторов 4.7 - 5.6 - 6.8 - 8.2 придумал пьяный профессор математики, и формула V = R1 / (R1 + R2) не очень проста для обработки). Поэтому лучше всего использовать табличный метод для получения кодов кнопок. Таблица не получится примитивной, потому что у нас есть 256 возможных различных результатов преобразования ADC, и нам хотелось бы получить таблицу поменьше.

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

Здесь первый байт это значение сравнения для результата преобразования, а второй байт это код кнопки, если значение сравнения больше, чем наш результат. Если результат находится в диапазоне между 0 и < 7, то это означает, что не была нажата ни одна из кнопок (код клавиши 255), если между 7 и < 18, то код кнопки 1, и так далее.

Или можно использовать сразу ASCII-коды для кнопок:

Код декодирования будет наподобие такого:

Здесь конечно же есть проверка, что не одна их кнопок не нажата (в этом случае R0 = 0xFF, а если используется кодировка кнопок ASCII, то R0 = 0) и мы можем устранить ложные срабатывания (если проверить, что один и тот же код клавиши прочитан 20 или большее количество раз).
Experiences

Первые эксперименты показали, что слишком большие значения резисторов (сначала резисторы были в 10 раз больше по номиналу) делают чтение клавиатуры с использованием АЦП слишком чувствительной к высокочастотным помехам. К примеру, клавиатура отказывалась нормально работать, когда рядом находился передатчик VHF (УКВ) с мощностью около 2 Вт.

Вот так у меня выглядел тестовый код:
Про LCD дисплей я пока ничего не скажу, так как процедуры еще не доведены до ума, но будут выложены и разжеваны в ближайшее время.

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

Вначале загружаем данные из регистра порта , чтобы иметь на руках первоначальную конфигурацию порта. Также нам нужно выставить все сканирующие биты порта в 1, это делается посредством операции ИЛИ по сканирующей маске. В той части где стояли единицы после операции ИЛИ по маске 11110000 (мое значение SCANMASK ) все биты станут единицами, а где был ноль останутся без изменений.

Вот посредством таких извратов вся программа, с обработкой клавиатуры, декодированием скан кода, чтением/записью в LCD индикатор и обнулением оперативки (нужно для того, чтобы точно быть увереным, что память равна нулю) заняло всего 354 байта . Кто сможет меньше?

Спасибо. Вы потрясающие! Всего за месяц мы собрали нужную сумму в 500000 на хоккейную коробку для детского дома Аистенок. Из которых 125000+ было от вас, читателей EasyElectronics. Были даже переводы на 25000+ и просто поток платежей на 251 рубль. Это невероятно круто. Сейчас идет заключение договора и подготовка к строительству!

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

53 thoughts on “AVR. Учебный курс. Процедура сканирования клавиатуры”

/*Фунуция чиатающих портов*/
void keyread()
int byte=0;
delay_us(400);
//Читаем линию [1]
if (PIND.2 == 0)
byte = port + 1;
port = 0;
>

//Читаем линию [2]
if (PIND.3 == 0)
byte = port + 2;
port = 0;
>

//Читаем линию [3]
if (PIND.4 == 0)
byte = port + 3;
port = 0;
>

//Читаем линию [4]
if (PIND.5 == 0)
byte = port + 4;
port = 0;
>
if (byte > 0)
decodecod(byte);
>
>

/*Функция сканирующих портов*/
void keyscan()
//Сканируем столбик [1]
PIND.6=0;
PINB.0=1;
PINB.1=1;
port = 30;
keyread();

//Сканируем столбик [2]
PIND.6=1;
PINB.0=0;
PINB.1=1;
port = 20;
keyread();
//Сканируем столбик [3]
PIND.6=1;
PINB.0=1;
PINB.1=0;
port = 10;
keyread();
>

Прогу не читал, т.к. Си читаю плохо. Попробуй протрассировать программу в отладчике. Мне это помогает выловить 99% багов.

Поставь задержку между сканированием столбцов.

трассировал, там как бы все нормально, на всякий пожарный щаз плату переделываю, ещё мне посоветовали в функции keyscan(), поменять PIN на PORT, сказали якобы PORT изменяет лог, на ноге!?
Задержку ставил таже картина.

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

Будет многократным нажатием. Такие вещи надо разруливать уже уровнем выше, на этапе проверки скан кода. И, если надо, игнорировать повторные нажатия.

В любом случае, у нас же последовательное исполнение кода, с опросом, это не ПЛИС, так что тут нет такого понятия как длительное нажатие. Есть только частое многократное и другого не дано.

Потому что скан кода FF быть не может. Это запрещенная комбинация. Ну и потому, что я так захотел. =)

Вопрос удобности и неудобности тут не стоит, т.к. задачи могут быть разные. Да и по другому же все равно никак.

А что мешает добавить обработку одиночного нажатия? Например если скан_код(t)=скан_код(t-1) то пропустить это нажатие. Вклинить это дело перед генерацией скан кода и нет проблем.

Это же простейший пример, показывающий работу с клавиатурной матрицей. А вывод на ЛСД тут как индикация работы. Можно было и в уарт рыгать. Мне просто влом было провода тащить до компа.

делаю диплом на мк АВР (опыта в программировании нет никакого да и с мк общаюсь около месяца)в нем должна быть клавиатура, написал программу сканирования..
для проверки работоспособность подключил светодиоды показывающие код, в АВР студио все работает как задумано а вот в железе не хочет работать, реагирует всего на пару клавиш а на остальные нет, может кто подскажет в чем проблема..

Всю программу целенаправленно не разбирал, так бегло глянул. Вот что бросилось в глаза:

1) Не стоит в цикле майн каждый раз инициализировать стек. И каждый раз перенаправлять порты.

2) Чет не догоняю как ты сделал сканирование? У тебя же только один вывод в порт вначале число 0F потом F0. Где бегающий нолик?

3) А ты учел емкость и время на переключение? Между сменой активного столбца должно быть около 1мс, между сменой столбца и снятием значения с порта тоже бы выдержку в 1мс сделать. Можно,конечно, и меньше, но лишь после того как будет все проверено и алгоритм без ошибок окажется.

Сканирование осуществлено следующим образом:
-сначала сканируется строка и в зависимости от номера присеваиваться значение
типа 0b00000100
-потом столбец, значение 0b01000000
-потом идет операция сложение двух регистров столбца и строки в результате имеем код клавиши вида 0b01000100

П.С. Большое спасибо за ответ.

Строки и столбцы нельзя считывать по отдельности. Результат будет непредсказуем. В матрице строки замыкаются на столбцы. ПОэтому мы на столбцах задаем уровень (обычно низкий), а на строках этот уровень считываем по считанной строке опеределяя что нажато.

То есть считываем мы только строки, а столбец задаем. Сканирующий порт определяет активный столбец.

2) Имелась ввиду емкость проводов, дорожек и самих выводов. Она небольшая, но есть. И на больших скоростях влияет очень сильно

изменив значение PORTA на FF и добавив задержку в 1мс программа все таки заработала но глючит, строку определяет через раз, а столбец четко.

Не догоняю как оно вообще может работать. Покажи схему матрицы.

У тебя одновременно по столбцу зажигаются два нуля. Соответственно нельзя определить на каком именно столбце идет нажатие.

в первом случае стандартная матрица 4Х4, все ножки PORTA посажены на землю через резистор в 1КОм а между резистором и МК уже подключаться клавиатура.
Во втором случаи матрица 3Х4 подключена практически также как в первом случаи за исключением резисторов на столбцах (на столбцах их нет)..

Смотри что мне не нравится:
ldi temp, 0b1001 1111 ;1столбец
out porta, temp

ldi temp,0b1010 1111 ;2столбец
out porta, temp

***
ldi temp,0b1100 1111 ;3столбец
out porta, temp

Если учесть, что младшие биты это у тебя строки, то сканирование столбцов должно быть вида:
1110 1111 первый столбец
1101 1111 второй столбец
1011 1111 третий столбец
0111 1111 четвертый столбец

Почему у тебя там по два нуля?

7 пин не задействован т.к. клавиатура 3х4. а сканирование идет не по 0 а по 1..
мне все таки непонятно, почему при инициализации портов вне цикла маин программа не работает. а в цикле маин все прекрасно работает
в чем может быть косяк?

.macro outi ; макрос вывода числа в порт
ldi temp, @1
out @0, temp
.endm

reset:
outi ddrc, 0b11111111

outi SPH, 0x04 ; почему не high(RAMEND) ?
out SPL, 0x5F ; а тут low(RAMEND)
loop: ; ну а тут тот самый цикл
rcall scan
clt
rjmp loop

кусок кода, где происходит непосредственно сканирование всей матрицы 1 раз:

scan_loop: in kbdval, portA mov scanval, cval ;копирование счётчика в маску swap scanval ;сдвиг на тетраду влево (перестановка тетрад) ori scanval, 0b00001111;маска для бегущего нуля ori kbdval, 0b11110000;маска для значения строк and kbdval, scanval ;старое значение строк+новое бегущего нуля out portA, kbdval ;вывод в порт rcall delay ;задержка на переключение ножек nop nop nop nop in kbdval, pinA ;формируем скан-код из кода строки ori kbdval, 0b11110000 ;сбрасываем биты колонок com kbdval ;инвертируем, получаем !=0 при нажатой кнопке cpi kbdval, 0x00 ;сравниваем с нулём brne kboff ;если !=0 (нажатие), то выходим из цикла lsl cval ;двигаем единицу в счётчике cpi cval, 0b00010000 ;сравниваем положение счётной единицы breq kboff ;если 5-й шаг, то выходим rjmp scan_loop ;иначе прыгаем в начало цикла kboff: ret

;начальные настройки ;настройка portA (клавиатура) ldi R16, 0xF0 ;старшие 4 бита - выходы out DDRA,R16 com R16 ;подтяжка на младшие 4 бита (инверсия регистра) out PORTA,R16

чтобы раз и навсегда: как правильно сконфигурить вход с подтяжкой, выход, куда писать и откуда читать?

Эмм.. а в чем проблема то?

КОнфиг с подтяжкой DDR=0, PORT=1
Читаешь из PIN

с hapsim почему-то не получается так :/

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

Шпрот 7.. хз, я его сто лет не юзал.

слипмод требует внешнего кварца или RC
слипмод не работает в протеусе.

;***** Register used by all programs
;******Global variable used by all routines

.def temp =r16 ;general scratch space

.equ ROW1 =3 ;keypad input rows
.equ ROW2 =2
.equ ROW3 =1
.equ ROW4 =0
.equ COL1 =7 ;keypad output columns
.equ COL2 =6
.equ COL3 =5
.equ COL4 =4

.equ GREEN =0 ;green LED
.equ RED =1 ;red LED
.equ INTR =2 ;interrupt input

;***** Registers used by interrupt service routine

.def key =r17 ;key pointer for EEPROM
.def status =r21 ;preserve sreg here

;***** Registers used by delay subroutine
;***** as local variables

.def fine =r18 ;loop delay counters
.def medium =r19
.def coarse =r20

cli ;disable global interrupts
ldi r16, high(RAMEND)
out SPH, r16 ; Set Stack Pointer to top of RAM
ldi r16, low(RAMEND)
out SPL, r16
ldi temp,0xFB ;initialise port D as O/I
out DDRD,temp ;all OUT except PD2 ext.int.
ldi temp,0x30 ;turn on sleep mode and power
out MCUCR,temp ;down plus interrupt on low level.
ldi temp,0x40 ;enable external interrupts
out GIMSK,temp
sbi ACSR,ACD ;shut down comparator to save power
main:
ldi temp,0xF0 ;initialise port B as I/O
out DDRB,temp ; 4 OUT 4 IN
ldi temp,0x0F ;key columns all low and
out PORTB,temp ;active pull ups on rows enabled
ldi temp,0x07 ;enable pull up on PD2 and
out PORTD,temp ;turn off LEDs
sei ;enable global interrupts ready
sleep ;fall asleep
rcall flash ;flash LEDs for example usage
ldi temp,0x40
out GIMSK,temp ;enable external interrupt
rjmp main ;go back to sleep after keyscan

;****Interrupt service routine***************************************
scan:
in status,SREG ;preserve status register
sbis PINB,ROW1 ;find row of keypress
ldi key,0 ;and set ROW pointer
sbis PINB,ROW2
ldi key,4
sbis PINB,ROW3
ldi key,8
sbis PINB,ROW4
ldi key,12
ldi temp,0x0F ;change port B I/O to
out DDRB,temp ;find column press
ldi temp,0xF0 ;enable pull ups and
out PORTB,temp ;write 0s to rows
rcall settle ;allow time for port to settle
sbis PINB,COL1 ;find column of keypress
ldi temp,0 ;and set COL pointer
sbis PINB,COL2
ldi temp,1
sbis PINB,COL3
ldi temp,2
sbis PINB,COL4
ldi temp,3
add key,temp ;merge ROW and COL for pointer
ldi temp,0xF0 ;reinitialise port B as I/O
out DDRB,temp ; 4 OUT 4 IN
ldi temp,0x0F ;key columns all low and
out PORTB,temp ;active pull ups on rows enabled
out SREG,status ;restore status register

reti ;go back to main for example program

;***Example test program to flash LEDs using key press data************

;****Time Delay Subroutine for LED flash*********************************
delay:
ldi coarse,8 ;triple nested FOR loop
cagain: ldi medium,255 ;giving about 1/2 second
magain: ldi fine,255 ;delay on 4 MHz clock
fagain: dec fine
brne fagain
dec medium
brne magain
dec coarse
brne cagain
ret

;***Settling time delay for port to stabilise******************************
settle:
ldi temp,255
tagain: dec temp
brne tagain
ret

Читайте также: