Подключение матричной клавиатуры к Arduino. Матричная клавиатура Расширение количества клавиш на матричной клавиатуре

Пора бы рассказать как организовать опрос такой клавы. Напомню, что клава представляет из себя строки, висящие на портах и столбцы, которые сканируются другим портом. Код написан для контроллера ATMega8535 , но благодаря тому, что все там указано в виде макросов его можно быстро портировать под любой другой контроллер класса Mega , а также под большую часть современных Tiny . Хотя в случае с Tiny может быть некоторый затык ввиду неполного набора команд у них. Придется чуток дорабатывать напильником.

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

Теперь коротко о файлах:
keyboard_define.inc — файл конфигурации клавиатуры.
В этом файле хранятся все макроопределения используемые клавиатурой. Здесь мы задаем какие ножки микроконтроллера к какой линии подключены. Одна тонкость — выводы на столбцы (сканирующий порт ) должны быть последовательным набором линий одного порта. То есть, например, ножки 0,1,2,3 или 4,5,6,7 , или 3,4,5,6 . Неважно какого порта, главное чтобы последовательно.
С определением ножек, думаю проблем не возникнет, а вот по поводу параметра KEYMASK я хочу рассказать особо.
Это маска по которой будет выделяться сканируемый порт. В ней должны быть 6 единиц и один 0. Ноль выставляется в крайне правую позицию сканирующего порта.

Пример:
У меня сканирующий порт висит на битах 7,6,5,4 крайне правый бит сканирующего порта это бит 4, следовательно маска равна 0b11101111 — ноль стоит на 4й позиции. Если сканирующие линии будут висеть на ножках 5,4,3,2, то маска уже будет 0b11111011 — ноль на второй позиции. Зачем это все будет объяснено ниже.

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

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

Вот так у меня выглядел тестовый код:

Main: SEI ; Разрешаем прерывания.

RCALL KeyScan ; Сканируем клавиатуру
CPI R16,0 ; Если вернулся 0 значит нажатия не было
BREQ Main ; В этом случае переход на начало
RCALL CodeGen ; Если вернулся скан код, то переводим его в
; ASCII код.

MOV R17,R16 ; Загружаем в приемный регистр LCD обработчика
RCALL DATA_WR ; Выводим на дисплей.

RJMP Main ; Зацикливаем все нафиг.

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

Теперь расскажу как работает процедура KeyScan

Def COUNT = R18
KeyScan: LDI COUNT,4 ; Сканим 4 колонки
LDI R16,KEYMASK ; Загружаем маску на скан 0 колонки.

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

KeyLoop: IN R17,COL_PORT ; Берем из порта прежнее значение
ORI R17,SCANMSK ; Выставляем в 1 биты сканируемой части.


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

AND R17,R16 ; Сбрасываем бит сканируемого столбца
OUT COL_PORT,R17 ; Выводим сформированный байт из порта.


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

NOP ; Задержка на переключение ноги.
NOP
NOP
NOP

SBIS ROW0_PIN,ROW0 ; Проверяем на какой строке нажата
RJMP bt0

SBIS ROW1_PIN,ROW1
RJMP bt1

SBIS ROW2_PIN,ROW2
RJMP bt2

SBIS ROW3_PIN,ROW3
RJMP bt3


Серия NOP нужна для того, чтобы перед проверкой дать ножке время на то, чтобы занять нужный уровень. Дело в том, что реальная цепь имеет некоторое значение емкости и индуктивности, которое делает невозможным мгновенное изменение уровня , небольшая задержка все же есть. А на скорости в 8Мгц и выше процессор щелкает команды с такой скоростью, что напряжение на ноге еще не спало, а мы уже проверяем состояние вывода. Вот я и влепил несколько пустых операций. На 8Мгц все работает отлично. На большую частоту, наверное, надо будет поставить еще штук пять шесть NOP или влепить простенький цикл. Впрочем, тут надо поглядеть на то, что по байтам будет экономичней.
После циклов идет четыре проверки на строки. И переход на соответствующую обработку события.

ROL R16 ; Сдвигаем маску сканирования
DEC COUNT ; Уменьшаем счетчик столбцов
BRNE KeyLoop ; Если еще не все перебрали делаем еще одну итерацию

CLR R16 ; Если нажатий не было возвращаем 0
RET
.undef COUNT

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


bt0: ANDI R16,SCANMSK ; Формируем скан код
ORI R16,0x01 ; Возвращаем его в регистре 16
RET

А вот один из возможных концов при нажатии. Тут формируется скан код который вернется в регистре R16. Я решил не заморачиваться, а как всегда зажать десяток байт и сделать как можно быстрей и короче. Итак, что мы имеем по приходу в этот кусок кода. А имеем мы один из вариантов сканирующего порта (1110,1101,1011,0111 ), а также знаем номер строки по которой мы попали сюда. Конкретно в этот кусок можно попасть только из первой строки по команде RJMP bt0.
Так давай сделаем скан код из сканирующей комбинации и номера строки! Сказано — сделано! Сначала нам надо выделить из значения порта сканирующую комбинацию — она у нас хранится в регистре R16 , поэтому выковыривать из порта ее нет нужды. Продавливаем операцией И значение R16 через SCANMASK и все что было под единичками прошло без изменений, а где были нули — занулилось. Опа, и у нас выведен сканирующий кусок — старший полубайт. Теперь вклеим туда номер строки — операцией ИЛИ . Раз, и получили конструкцию вида [скан][строка]
Вот ее и оставляем в регистре R16 , а сами выходим прочь! Также и с остальными строками. Погляди в исходнике, я их не буду тут дублировать.

Декодирование скан кода.
Отлично, скан код есть, но что с ним делать? Его же никуда не приткнуть. Мы то знаем, что вот эта шняга вида 01110001 это код единички, а какой нибудь LCD экран или стандартная терминалка скорчит нам жуткую кракозябру и скажет, нам все что она думает о нашей системе обозначений — ей видите ли ASCII подавай. Ладно, будет ей ASCII.

Как быть? Прогнать всю конструкцию по CASE где на каждый скан код присвоить по ASCII коду меня давит жаба — это же сколько надо проверок сделать! Это же сколько байт уйдет на всю эту тряхомудию? А память у нас не резиновая, жалкие восемь килобайт, да по два байта на команду, это в лучшем случае. Я мог все это сделать прям в обработчике клавиатуры. НЕТ!!! В ТОПКУ!!! Мы пойдем своим путем.
Ок, а что у нас есть в запасе? Метод таблиц перехода не катит, по причине жуткой неупорядоченности скан кодов. Почесал я тыковку, пошарился по квартире… и тут меня осенило. Конечно же!!! Брутфорс!!!

Брутфорсим скан код.
Итак, у нас есть жутко несваримый скан код, а также стройная таблица ASCII символов. Как скрестить ужа с ежом? Да все просто! Разместим в памяти таблицу символов в связке [скан код]: , а потом каждый нужный скан код будем прогонять через эту таблицу и при совпадении подставлять на выходе нужный ASCII из связки. Классический пример программизма — потеряли во времени, зато выиграли в памяти.

Вот так это выглядит:

CodeGen:LDI ZH,High(Code_Table*2) ; Загрузил адрес кодовой таблицы
LDI ZL,Low(Code_Table*2) ; Старший и младший байты

Тут мы загрузили в индексный регистр адрес нашей таблицы. Умножение на два для того, чтобы адрес был в байтах, т.к. в среде компилятора пространство кода адресуется в словах.

Brute: LPM R17,Z+ ; Взял из таблицы первый символ — скан код

CPI R17,0xFF ; Если конец таблицы
BREQ CG_Exit ; То выходим

CPI R16,0 ; Если ноль,
BREQ CG_Exit ; то выходим

CP R16,R17 ; Сравнил его со скан кодом клавиши.
BREQ Equal ; Если равен, то идем подставлять ascii код

Загружаем из таблицы первый скан код и нычим его в регистр R17 , попутно увеличиваем адрес в регистре Z (выбор следующей ячейки таблицы) и первым делом сравниваем его с FF — это код конца таблицы. Если таблица закончилась, то выходим отсюда. Если мы не всю таблицу перебрали, то начинаем сравнивать входное значение (в регистре R16 ) вначале с нулем (нет нажатия), если ноль тоже выходим. И со скан кодом из таблицы. Если скан таблицы совпадает со сканом на входе, то переходим на Equal .

LPM R17,Z+ ; Увеличиваем Z на 1
RJMP Brute ; Повтор цикла

А в случае если ничо не обнаружено, то мы повторно вызываем команду LPM R17,Z+ лишь для того, чтобы она увеличила Z на единичку — нам же надо перешагнуть через ASCII код и взять следующий скан код из таблицы. Просто INC Z не прокатит, так как Z у нас двубайтный . ZL и ZH . В некторых случаях достаточно INC ZL , но это в случае когда мы точно уверены в том, что адрес находится недалеко от начала и переполнения младшего байта не произойдет (иначе мы вместо адреса 00000001:00000000 получим просто 00000000:0000000, что в корне неверно), а команда LPM все сделает за нас, так что тут мы сэкономили еще пару байт. Потом мы вернемся в начало цикла, а там будет опять LPM которая загрузит уже следующий скан код.

Equal: LPM R16,Z ; Загружаем из памяти ASCII код.
RET ; Возвращаемся

Если же было совпадение, то в результате LPM Z+ у нас Z указывает на следующую ячейку — с ASCII кодом. Ее мы и загружаем в регистр R16 и выходим наружу.

CG_Exit: CLR R16 ; Сбрасываем 0 = возвращаем 0
RET ; Возвращаемся

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



; STATIC DATA
;========================================
Code_Table: .db 0x71,0x31 ;1
.db 0xB1,0x32 ;2
.db 0xD1,0x33 ;3
.db 0x72,0x34 ;4
.db 0xB2,0x35 ;5
.db 0xD2,0x36 ;6
.db 0x73,0x37 ;7
.db 0xB3,0x38 ;8
.db 0xD3,0x39 ;9
.db 0x74,0x30 ;0
.db 0xFF,0 ;END

Тут просто табличка статичных данных, на границе памяти. Как видишь данные сгруппированы по два байта — сканкод/ASCII

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

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

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

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

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

Работает следующим образом.

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

Теперь считываем сразу все значение из читающего порта. Если на столбце А не нажата ни одна кнопка, то в порту будут все единички. Но стоит нажать любую кнопку из столбца А, так она сразу же замкнет линию А, на этот вывод порта. В линии А у нас в данный момент 0, это обеспечивает ноль на сканирующем выводе контроллера. Поэтому и на соответствующем выводе порта будет 0
Так что, если будет нажата кнопка, например, 6, то на линии Р1 будет 0.

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

Можно определить одновременные нажатия многих кнопок — надо просто делать проверку не по байту, а по конкретному биту.

Увеличение разрядности
Но что делать если у нас кнопок не просто много, а очень много. Что даже матрицирование не спасает от огромного расхода линий порта. Тут приходится либо жертвовать несколько портов, либо вводить дополнительную логику. Например дешифратор с инверсным выходом .

Дешифратор , это такая микросхема, принимающая на вход двоичный код, а на выходе выдает единицу в выбранный разряд. Т.е. подали число «101» — получили «1» на выводе номер 5. Ну, а у инверсного дешифратора будет 0.

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

Если сканируется обычная клавиатура, нажимаемая человеком, то можно не заморачиваться на скорость опроса и сделать его в качестве побочного продукта, повесив на какое-нибудь левое прерывание. Достаточно чтобы клава опрашивалась хотя бы 10-20 раз в секунду. Этого уже достаточно, для комфортной работы.

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

Но микроконтроллер работает с такой скоростью, что успевает посчитать эти всплески как устойчивые состояния. Решить эту проблему можно аппаратно, с помощью RS триггера, так и программно — внеся небольшую задержку перед следующим опросом кнопки . Задержка подбирается такой, чтобы дребезг успел прекратиться к ее окончанию.

  1. Изучить особенности работы параллельных портов микроконтроллера.
  2. Изучить схемы подключения кнопок и матричной клавиатуры к микроконтроллеру.
  3. Научиться определять состояние кнопок при помощи программы.
  4. Изучить способы отладки программ на лабораторном стенде LESO1.
  5. Изучить принцип работы матричной клавиатуры.

2 Предварительная подготовка к работе

  1. По конспекту лекций и рекомендуемой литературе изучить схемы параллельных портов микроконтроллеров.
  2. По конспекту лекций и рекомендуемой литературе изучить схемы подключения кнопок и клавиатуры к параллельным портам.
  3. Изучить архитектуру микроконтроллера ADuC842 .
  4. Изучить принципиальную схему лабораторного стенда LESO1.
  5. Составить алгоритм работы программы: при нажатии на кнопку, согласно варианту, загорается комбинация светодиодов, соответствующая в бинарном виде номеру кнопки; при отпускании кнопки, светодиоды должны погаснуть.
  6. Составить программу на языке программирования С.

3 Краткие теоретические сведения

3.1 Применение матричной клавиатуры для ввода информации в микропроцессорную систему

Для реализации взаимодействия пользователя с микропроцессорной системой используют различные устройства ввода-вывода информации. В самом простом случае в роли устройства ввода может выступать кнопка, представляющая собой элементарный механизм, осуществляющий замыкание-размыкание контактов под действием внешней механической силы. Схема подключения кнопки к линии ввода параллельного порта ввода микроконтроллера показана на рисунке 1. Когда контакты кнопки S1 разомкнуты через резистор R1 на вход контроллера поступает высокий логический уровень "1", когда же контакты замкнуты, то вход оказывается соединенным с общим проводом, что соответствует логическому уровню "0". Если параллельный порт микроконтроллера имеет встроенный генератор тока, то в схеме можно обойтись без резистора R1.

Рисунок 1 – Подключение одиночной кнопки к параллельному порту

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

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

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


Рисунок 2 – Подключение матричной клавиатуры к параллельному порту

Временная диаграмма напряжений на портах вывода при выполнении программы опроса клавиатуры приведена на рисунке 3.


Рисунок 3 – Временные диаграммы работы порта вывода

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

Программа для микроконтроллера жестко зависит от принципиальной схемы разрабатываемого устройства. Невозможно написать программу для микроконтроллерного устройства не имея перед глазами его схемы. Поэтому, перед началом работы по принципиальной схеме учебного стенда LESO1 следует изучить способ подключения клавиатуры и светодиодов к микроконтроллеру: определить, к каким портам подключены светодиоды, столбцы и строки клавиатуры. Затем по таблице SFR нужно узнать адреса регистров задействованных портов ввода-вывода.

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

При написании программы нужно помнить об особенности параллельного порта P1 в микроконтроллере ADuC842 . Этот порт по умолчанию настроен на ввод аналоговых сигналов (функция АЦП). Для того чтобы перевести порт в режим цифрового входа, в соответствующий бит порта необходимо записать логический ноль. Сделать это нужно один раз при инициализации микроконтроллера. Порт не имеет внутреннего усиливающего транзистора, и потому при вводе дискретной информации через него не требуется записывать в разряды логическую единицу.

4 Задание к работе в лаборатории

  1. По принципиальной схеме установите, к каким портам микроконтроллера подключены светодиоды, а также столбцы и строки клавиатуры.
  2. По таблице регистров специальных функций (SFR) определите адреса регистров требуемых портов.
  3. Войдите в интегрированную среду программирования Keil-C.
  4. Создайте и настройте должным образом проект.
  5. Введите текст программы в соответствии с заданием: При нажатии на кнопку, согласно варианту, загорается комбинация светодиодов, соответствующая в бинарном виде номеру кнопки; при отпускании кнопки, светодиоды должны погаснуть.
  6. Оттранслируйте программу, и исправьте синтаксические ошибки.
  7. Загрузите полученный *.hex файл в лабораторный стенд LESO1 .
  8. Убедитесь, что программа функционирует должным образом.

5 Указания к составлению отчета

Отчет должен содержать:

  1. Цель работы.
  2. Принципиальную схему подключения клавиатуры к микроконтроллеру.
  3. Графическую схему алгоритма работы программы.
  4. Исходный текст программы.
  5. Содержимое файла листинга программного проекта.
  6. Выводы по выполненной лабораторной работе.

Схемы, а также отчет в целом, выполняются согласно нормам ЕСКД.

Aruna Rubasinghe

Матричные клавиатуры до сих пор еще применяются в качестве устройств ввода информации в проектах на микроконтроллерах. Обычный способ подключения такой клавиатуры к микроконтроллеру заключается в использовании нескольких портов ввода/вывода. В свою очередь, микроконтроллер выполняет алгоритм сканирования, чтобы определить, какая кнопка была нажата пользователем. Недостаток такого способа - необходимость использования достаточно большого количества портов микроконтроллера для обслуживания клавиатуры. Например, для подключения клавиатуры 4×3 потребуется семь свободных линий ввода/вывода. Это становится проблемой, когда в проекте планируется использование микроконтроллера с малым количеством выводов, или когда микроконтроллер не имеет свободных портов.

Для решения этой проблемы можно использовать специализированные микросхемы расширителей портов или цепочки резисторов для подачи определенного напряжения на каждую кнопку совместно с АЦП микроконтроллера для определения нажатия конкретной кнопки. Каждое решение имеет свои недостатки.

Многие расширители портов для обмена данными требуют коммуникации по специальным протоколам (например, I 2 C или SPI), потому в этом случае микроконтроллер должен иметь встроенные коммуникационные интерфейсы, или пользователь должен реализовать их программно, что повысит нагрузку на ядро. С другой стороны, назначение каждой кнопке определенного напряжения с помощью резисторов может быть затруднительным при большом количестве кнопок и приводит к сужению диапазона напряжений для каждой кнопки. Кроме того, сопротивление резисторов может изменяться с температурой, что совместно с узким диапазоном напряжений приведет к неправильному определению нажатий. Другой существенный недостаток этого способа - обязательное наличие в микроконтроллере аналогового входа (АЦП).

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

На Рисунке 1 изображена схема модуля матричной клавиатуры 4×3. Резисторы R 1 , R 4 , R 5 и R 6 используются для ограничения тока; резистор R 7 , диоды D 4 , D 5 и D 6 образуют логический элемент ИЛИ.

Далее мы рассмотрим, как реализовать этот метод для сканирования кнопок матричной клавиатуры 4×3. Одна микросхема счетчика CD4017 используется для контроля строк (IC 2), другая - для контроля столбцов клавиатуры (IC 1).

Микроконтроллер генерирует тактовый сигнал и подает его на вход CLK счетчика, контролирующего столбцы. Первоначально выходы Q0 двух счетчиков находятся в состоянии лог. 1 и значение счетчика IC 1 инкрементируется на 1 с каждым тактовым импульсом. На четвертый импульс счетчик IC 1 сбрасывается и одновременно инкрементирует значение счетчика IC 2 , управляющего строками клавиатуры. Счетчик IC 2 сбросится через пять импульсов, поступающих от счетчика IC 1 , обслуживающего столбцы. При генерировании одного тактового импульса микроконтроллер должен инкрементировать значение переменной COUNT (Рисунок 2) и сбрасывать ее в 1 на пятом тактовом импульсе для счетчика IC 2 . Выход матричной клавиатуры через логический элемент ИЛИ подключается в выводу внешнего прерывания микроконтроллера.

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

Счетчик тактовых импульсов, реализованный в микроконтроллере, инкрементируется в моменты их генерирования. Значение в этом счетчике соответствует номеру нажатой кнопки на клавиатуре. Блок-схема программы микроконтроллера на Рисунке 2 наглядно демонстрирует этот алгоритм.

Обратите внимание, несмотря на то, что в примере мы рассматриваем клавиатуру 4×3, при данном способе можно управлять клавиатурой 10×10, задействовав оставшиеся выходы обеих микросхем CD4017. Кроме того, применив каскадное включение счетчиков, количество кнопок можно увеличить, если в этом есть необходимость.

Дополнительные материалы

Чтение состояния 10 и более кнопок с использованием двух линий ввода/вывода микроконтроллера - Журнал Радиолоцман (еще не публиковался) .

  • Первая же импульсная помеха от бритвы соседа - и кнопки меняются местами:)
  • Купи соседу бритву Жилет с антистатикой:-) По твоему, например, если сосед электросваркой дуговой увлекается, вокруг никто цифровой техникой не пользуется??? Судя по схеме - один через 3, другой через 4 такта ресетятся, что в динамике почти мгновенно, а про МК ты вообще забываешь при этом, хотя он на несколько порядков сложнее. На крайняк, хотя и существуют фильтры по питанию, но ManualReset никто не запрещал
  • Две микросхемы, пять резисторов и 10 диодов большая "цена" за экономию пяти выводов микроконтроллера за 12 кнопок. Конечно, можно сказать и за 100 кнопок эта плата. Но можно ли придумать такое устройство на 100 кнопок на микроконтроллере с 8 выводами? Такие устройства, как правило, используют отдельный микропроцессор, обслуживающий клавиатуру и соединяются с основным процессором интерфейсом, используя небольшое количество выводов, минимум один. Для познавательных целей схема интересна, для практического применения - не уверен.
  • Похоже, никто не понял непостижимой глубины моей эпической сентенции. Схема имеет 12 устойчивых состояний, в каждое из которых может перейти под управлением МК, или под действием помехи. Каждому состоянию будет соответствовать индивидуальная раскладка клавиш. В схеме отсутствуют средства сброса в начальное состояние! Даже после подачи питания!
  • Теоретически возможно, значит - желательно предусмотреть, а т.к. аппаратный сброс задействован в схеме, то монтажное ИЛИ +RC-цепочку для питания, но на практике что-то не сталкивался с хаосом в работе ИЕ8 и ещё раз повторю, как бы не встали триггеры, первый счётчик сбросится, за ним - второй, и всё хоккей...Прошла помеха...Да, есть маловероятный шанс, что ты успеешь нажать кнопку до сброса(который есть!) и с такой быстротой...Ну тогда ты -Шаман:-) Да, а по поводу цены вопроса...Разумеется, в проммасштабе, т.е. если всё покупать+монтаж+габариты+и т.д. - то на то и выйдет, но если в загашнике мешок старых микрух, то почему бы на МК не съэкономить?
  • Нет, не "теоретически возможно", а практически и принципиально эта схема не будет работать. Всё будет ОК, только если сброс триггеров произойдет вместе с подачей питания. Тогда после 12 импульсов опроса они вернутся в исходное состояние и синхронизм между схемой и программой сохранится. Но, во-первых, триггеры не обязаны сбрасываться по подаче питания (и на практике встают как попало). И во-вторых, любая помеха изменяет состояние триггеров в одно из 12-ти возможных. В итоге: в схеме не предусмотрена начальная установка состояния и у МК нет какого-либо способа прочитать текущее состояние или установить любое известное состояние. Поэтому синхронизм между программой и схемой будет наступать с вероятностью 8%. Неплохо для игрового автомата:)
  • Что-то кто-то где-то недоперепонял:-) Внутри 4017 все пять тригеров объединены по входу Reset, который выведен наружу, в данном варианте под именем MR, (куда можно вставить простейший Reset через кондёр(+) и резистор(-) Проц постоянно выдаёт такт (CLOCK), 4-й импульс положительным перепадом ресетит до нуля счётчик1 с выхода дешифратора1(Q3) и одновременно выдаёт первый такт на счётчик2, который через 4 на пятый обнулится всё по тому же входуMR с выхода дешифратора2(Q4), в наихудшем варианте счётчик2 по помехе станет в состояние дешифратора Q5=1, то ему придётся пройти почти полный круг, но так как всё это происходит постоянно и циклически, а внешнее прерывание происходит не по помехе, а по совпадению в момент нажатия единиц и там и там, то очень затруднительно нажать кнопку за время первого цикла после включения или помехи...Наверно ты имел ввиду возможность сдвига значений внутри МК и снаружи, но ведь и внутрений счётчик сбрасывается внешним прерыванием... Так что работать будет...
  • Это ещё надо подумать, как его туда прицепить, чтоб не было конфликта с синалами на других выводах, куда подключен этот сброс...
  • Но мы обсуждаем приведенную схему, и именно про нее я сказал, что она нерабочая. Ты начинаешь понимать, что косяк есть, и предлагаешь ее изменить, чтобы этот косяк исправить. Эти подробности рассматривать нет необходимости. Счетчик работает по кругу, проходя 12 равноценных состояний под управлением одного внешнего сигнала. Смотрите в общем. Между двумя схемами, между которыми происходит обмен, теоретически и практически возможна рассинхронизация, которая нарушит ее работу, и нет средств приведения частей в соответствие. Когда-нибудь ремонтировал видик Панасоник? Вынул шестеренки, вставил обратно - и привет. Меток на них не ставят, а стоять должны строго в определенном положении, о котором знает только Панасоник и управляющий процессор. Только схема об этом не может узнать, вот ведь засада какая. Исправить схему можно тремя способами. 1. Добавить линию из клавы в проц, означающую "начальное состояние". Тогда проц обязан долбить клоки до тех пор, пока на этой линии не появится сигнал - вот тогда можно начинать сканирование. 2. Добавить линию от проца в клаву и организовать по ней сброс в начальное состояние - тут всё очевидно. 3. Добавить одновибратор, который бы разрешал счет. После первого обращения он запускается и во время импульса можно произвести опрос. После окончания импульса счетчики сбрасываются в исходное. Все три способа будут работать. PS. Видоизменение 1-го способа без кардинальных изменений. Пожертвовать одной кнопкой, замкнув ее на постоянно. Тогда проц будет знать (в большинстве случаев), откуда начать сканирование. Так никто не делает и это лажа и моветон, но если будет стоять задача "ничего не менять, но чтобы работало", то так сделать можно.
  • Автору исходной схемы пользователи задают тот же самый вопрос - как насчет синхронизации? http://www.edn.com/design/power-mana...g-two-I-O-pins На что он советует применить линию с тремя состояниями:) То есть, автор косвенно признает, что есть проблема, но, желая оставаться в рамках идеи минимизации количества проводов, вынужден увеличивать число состояний одной линии с 2 до 3, что равносильно увеличению количества самих линий. http://www.edn.com/design/components...icrocontroller
  • Ну вот и добрались до сути, ты говоришь вообще не будет работать, я говорю будет, но с минимальными переделками (у журнала Радио научился не верить в то что видишь) и без использования 3-го порта...
  • Внутренняя схема счетчика CD4017 построена таким образом, что после прохождения 10, максимум, счетных импульсов он установится в одно из 10 разрешенных состояний, включая и нулевое. Произойдет это потому, что внутри счетчика присутствуют обратные связи, исключающие запрещенные состояния. Ну а дальше все будет синхронизироваться как пишет автор. Схема рабочая, кто не уверен может собрать и проверить, хотя и имеется одно "скользкое" место - схема сброса самого себя, работающая на задержках переключения. При таком построении импульс сброса будет очень коротким и теоретически возможен сброс не всех триггеров в ноль.
  • Вы меня не слышите. Я не говорил о кривых состояниях, которые теоретически возможны в триггерах без сброса. Я говорил о принципиальном отсутствии средств синхронизации между МК и схемой. Ну, тогда "на пальцах". Предположим, триггеры встали в состояние 5. МК об этом не знает и узнать не может - нет такого проводка. МК может только прогнать весь цикл сканирования, состоящий из 12 импульсов тактирования, и прочувствовать, на каком из импульсов на выходе появится логический уровень, соответствующий нажатой кнопке. ОК. После всего цикла сканирования триггеры окажутся опять в состоянии 5. Понимаете? Ну и что, что они во время сканирования прошли через сброс? МК об этом не знает! Нужен не сам сброс, а нужно четкое соответствие начала цикла сканирования и состояния внутреннего счетчика тактов в МК, как это обеспечивается в предложенной схеме? Покажите.
  • Да все всё слышат! Очень мало схем сразу начинают работать или работают нормально в одних условиях и совершенно не хотят(не могут) в других... Может, действительно, у автора нет вредных соседей.. ИМХО, приведённая схемка, приведёт к небольшому удорожанию, но позволит разрешить некоторые разногласия, хотя и не является вершиной конструкторской мысли:-))) PS. А если, вообще, Начинать отсчёт тактов с момента появления сигнала Interrupt, который одновременно коротким импульсом обнулит и счётчики, то за время нажатия вполне можно определить кнопку(не в этой схемке) PPS. Меня терзают смутные сомнения...А зачем вообще считать всё время? Или я не доглядел? Кстати, что это за ФункшнБаттон у автора? в левой ветке алгоритма, если это и есть инициализация счётчика тактов от нажатия любой кнопки, то весь наш спор почти бред, если нет, то так и надо сделать програмно, заменив счётчики на обычные, с предустановкой в 1111 и 1111 ну и используя только однозначные пересечения.
  • Вы все правильно говорите, но мы обсуждаем конкретную схему, а синхронизация это задача проца и программиста. Самое простое договориться (написать в эксплуатационной документации), что после включения питания нажимается конкретная любая кнопка, от которой проц начинает отсчет.
  • Ну слава б-гу. Нет, мои схемы я буду делать так, чтобы они работали всегда. Представьте, вы сделали разработку, всё готово, можно показывать шефу. Шеф включает прибор, нажимает кнопку 5, на табло вылетает цифра 9. Шеф смотрит на табло, на вас и говорит - "а чё за х-нь?" И вы начинаете объяснять, что мы, мол, напишем инструкцию, как правильно включать прибор:) Ладно, написали, запустили. Потом прибегает операторша и говорит: "Здесь клавиатура постоянно глючит! Работает 5 минут, а потом вместо одной цифры бьётся другая!" - а вы ей терпеливо начинаете объяснять, что состояние регистров процессора не обязано соответствовать актуальному состоянию счетчиков в клавиатуре и что синхронизация - это дело программиста:) А может, лучше не экономить проводок, а сделать сразу, чтобы работало?
  • В первом своем посте в теме я об этом и писал.
  • Ребята, давайте жить дружно! И не путать работу с хобби, ведь не управление реактором и не постройка храма обсуждается, а всего лишь какая-то хренотень(Прим. Тень, отбрасываемая кустом Хрена на близко расположенные предметы), которая якобы чем-то управляет, и дружно последуем совету из темы про клеточные автоматы, не дадим победить Матрице!!!, и будем держать в кармане кнопку "MasterReset"...

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

Для Arduino чаще всего используются такие клавиатуры:

Самыми распространенными являются 16 кнопочные клавиатуры 4x4. Принцип их работы достаточно прост, Arduino поочередно подает логическую единицу на каждый 4 столбцов, в этот момент 4 входа Arduino считывают значения, и только на один вход подается высокий уровень. Это довольно просто, если знать возможности управления портами вывода в Arduino , а так же портами входа/ввода.

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

Подключаем клавиатуру в любые порты ввода/вывода.

На красные порты будем подавать сигналы, а с синих будем их принимать. Зачастую на синие провода подводят подтягивающие резисторы, но мы их подключим внутри микроконтроллера Arduino .

В программе будем вычислять нажатую кнопку и записывать её в Serial порт.
В данном методе есть один значительный недостаток: контроллер уже не может выполнять других задач стандартными методами. Эта проблем решается подключением матричной клавиатуры с использованием прерываний .

Int PinOut {5, 4, 3, 2}; // пины выходы int PinIn {9, 8, 7, 6}; // пины входа int val = 0; const char value { {"1", "4", "7", "*"}, {"2", "5", "8", "0" }, {"3", "6", "9", "#"}, {"A", "B", "C", "D"} }; // двойной массив, обозначающий кнопку int b = 0; // переменная, куда кладется число из массива(номер кнопки) void setup() { pinMode (2, OUTPUT); // инициализируем порты на выход (подают нули на столбцы) pinMode (3, OUTPUT); pinMode (4, OUTPUT); pinMode (5, OUTPUT); pinMode (6, INPUT); // инициализируем порты на вход с подтяжкой к плюсу (принимают нули на строках) digitalWrite(6, HIGH); pinMode (7, INPUT); digitalWrite(7, HIGH); pinMode (8, INPUT); digitalWrite(8, HIGH); pinMode (9, INPUT); digitalWrite(9, HIGH); Serial.begin(9600); // открываем Serial порт } void matrix () // создаем функцию для чтения кнопок { for (int i = 1; i <= 4; i++) // цикл, передающий 0 по всем столбцам { digitalWrite(PinOut, LOW); // если i меньше 4 , то отправляем 0 на ножку for (int j = 1; j <= 4; j++) // цикл, принимающих 0 по строкам { if (digitalRead(PinIn) == LOW) // если один из указанных портов входа равен 0, то.. { Serial.println(value); // то b равно значению из двойного массива delay(175); } } digitalWrite(PinOut, HIGH); // подаём обратно высокий уровень } } void loop() { matrix(); // используем функцию опроса матричной клавиатуры }

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

#include const byte ROWS = 4; const byte COLS = 3; char keys = { {"1","2","3"}, {"4","5","6"}, {"7","8","9"}, {"#","0","*"} }; byte rowPins = {5, 4, 3, 2}; byte colPins = {8, 7, 6}; Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS); void setup(){ Serial.begin(9600); } void loop(){ char key = keypad.getKey(); if (key != NO_KEY){ Serial.println(key); } }

Есть вопросы?

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: