Клавиатурная матрица. Подключение матричной клавиатуры к микроконтроллерам AVR

Клавиатурная матрица. Подключение матричной клавиатуры к микроконтроллерам AVR

20.07.2023

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Большая часть текста содержит объяснение программного кода, его можно скачать либо посмотреть видео под статьей.

Сделать такую клавиатуру можно и самому. Для этого понадобится печатная плата, 12 или 16 обычных кнопок и соединительные провода. Я же буду использовать готовую.

Для чего нужна матричная клавиатура?

Для примера возьмем обычную кнопку. Как вы знаете, это простейшее электромеханическое устройство. Чтобы подключить ее к плате, нужно использовать стягивающий резистор, а также задействовать по одному контакту питания и «земли». Нажатием такой кнопки можно выполнить определенное действие, например можно управлять светодиодом, различными приводами, механизмами и так далее. А что, если нам необходимо подключить несколько кнопок? Тогда придется задействовать больше контактов и большее число проводов, да и без макетной платы уже не обойтись, плюс еще резисторы придется использовать в большем количестве.

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

Подключать ее к плате следует 8 выводами, каждый из них считывает значения с определенных строк и столбцов. Подключать их следует к выводам на панели Digital. Я подключу, например, к выводам от 2 до 9 включительно. Нулевой и первый трогать не желательно, поскольку они предназначены для UART интерфейса (например, для подключения блютуз-модуля). Рациональнее оставить их свободными.

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

После того, как вы установили в библиотеку, можно зайти в Ардуино IDE (программа с сайта Arduino) и посмотреть примеры скетчей.

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

#include // подключаем нашу библиотеку




{"2","5","8","0"},
{"3","6","9","#"},
{"A","B","C","D"}
};




void setup(){
Serial.begin(9600);
}
void loop(){

if (customKey){
Serial.println(customKey);
}
}

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

Если это сделать неправильно, то, например, при нажатии цифры 4, в порт выйдет цифра 6 или любой другой символ. Это можно определить опытным путем и расположить символы, как они расположены на клавиатуре.

В функции void setup указываем скорость последовательного соединения с монитором порта 9600 бод . Функция нужна только для подачи питания на модули. В функции Void Loop прописываем условие. Переменная Char используется для хранения только одного символа, например, 1, А или 5, что подходит к ситуации. Если нажатие зафиксировано, то происходит вывод символа в монитор порта с помощью функции Serial Print. В скобках нужно указывать, какую переменную выводим в порт. Если все сделано верно, в мониторе порта получим символ, на который нажимали. Не забудьте в мониторе порта внизу справа указать скорость передачи данных такую же, как в скетче.

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

Давайте выведем данные на дисплей.

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

Далее нужно указать размерность дисплея. Используемый в примере дисплей вмещает по 16 символов в каждой из 2-ух строк, это я и указываю. В функции Void Setup нужно подать питание на дисплей и включить подсветку. Делается это с помощью двух функций: lcd.begin и lcd.backlight .

#include // подключаем нашу библиотеку
#include
LiquidCrystal_I2C lcd(0x27, 16, 2);
const byte ROWS = 4; //число строк у нашей клавиатуры
const byte COLS = 4; //число столбцов у нашей клавиатуры
char hexaKeys = {
{"S","4","7","*"}, // здесь мы располагаем названия наших клавиш, как на клавиатуре,для удобства пользования
{"O","5","8","0"},
{"S","6","9","#"},
{"I","B","C","D"}
};
byte rowPins = {5, 4, 3, 2}; //к каким выводам подключаем управление строками
byte colPins = {9, 8, 7, 6}; //к каким выводам подключаем управление столбцами
//initialize an instance of class NewKeypad
Keypad customKeypad = Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);
void setup(){
Serial.begin(9600);
lcd.begin(); // Инициализируем экран
lcd.backlight();
}
void loop(){
char customKey = customKeypad.getKey();
if (customKey){
Serial.println(customKey);
lcd.setCursor(1,4); //устанавливаем курсор
lcd.print(customKey);
}
}

В функции Voil Loop нужно в самом условии прописать строчку lcd.print для вывода данных на дисплей. И еще нужно предварительно установить положение курсора. В скобках идут 2 цифры: первая — это номер символа, а вторая — номер строки. Нужно помнить, что у этого дисплея отсчет строк и столбцов начинается не с единицы, а с нуля. То есть здесь имеются строчки под номерами 0 и 1, а не 1 и 2, как может показаться сначала. Затем загрузим код в плату и посмотрим, что будет.

Так как дисплей работает по интерфейсу I2C, подключаем к аналоговым выводам. Выходы SDA и SCL соответственно подключаем к А4 и А5, а остальные два — это уже питание и «земля».

Как видим, нажимая на любой символ, видим его отображение на дисплее.

Чтобы стереть строчку, вспомним калькулятор. Когда нужно было удалить значение, мы нажимали на кнопку сброса. Нажмем на такую кнопку в плате и можем заново набирать символы.

Подключение клавиатуры к Arduino и управляющее действие

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

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

Подключение светодиода

Я буду использовать макетную плату и резистор (желательно использовать от 150 до 220 Ом). Двумя перемычками замкну схему, подключив их к пинам питания и земли на плате Ардуино.

Схема будет работать так: при нажатии на 1 включается светодиод, при нажатии на 2 — выключается.

Светодиод в примере подключен к пину 8 на плате Ардуино.

#include
const byte ROWS = 4; // Four rows
const byte COLS = 4; // Three columns
char keys = { // Define the Keymap
{"1","4","7","*"}, // здесь мы располагаем названия наших клавиш, как на клавиатуре,для удобства пользования
{"2","5","8","0"},
{"3","6","9","#"},
{"A","B","C","D"}
};
byte rowPins = { 5, 4, 3, 2 };// Connect keypad ROW0, ROW1, ROW2 and ROW3 to these Arduino pins.
byte colPins = { 9, 8, 7 ,6}; // Connect keypad COL0, COL1 and COL2 to these Arduino pins.
Keypad kpd = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);// Create the Keypad
#define ledpin 8
void setup()
{
pinMode(ledpin,OUTPUT);
Serial.begin(9600);
}
void loop()
{
char key = kpd.getKey();
if(key) // Check for a valid key.
{
switch (key)
{
case "1":
digitalWrite(ledpin, HIGH);
break;
case "2":
digitalWrite(ledpin, LOW);
break;
}
}
}

Давайте разберем скетч.

Возьмем первый скетч урока и просто его дополним. В начале с помощью полезной функции Define присвоим название подключенному к пину 8 светодиоду ledpin . В функции Void setup указываем сигнал со светодиода как выход.

Если бы не библиотека Кейпад для клавиатуры, пришлось бы прописывать то же самое для 8 пинов, с которыми связана клавиатура. В функции void loop условие. Нажатие определенной клавиши «приравнивается» к значению переменной key. Оператор Switch сравнивает значения переменной key и «дает» определенные команды в зависимости от этого значения. Состоит он из двух вспомогательных операторов Case и Break . Проще говоря, если будет найдено значение переменной, равное 1 , то будет выполняться действие. Оператор break служит командой выхода из оператора Case .

Соответственно при нажатии на 1 будет выполняться максимальная подача напряжения на светодиод и он будет гореть. При нажатии на 2 он гореть не будет. Это указывается в функции Digitat write , где в скобках задается название переменной и «указание» ей. Можно таким образом написать определенные команды для каждой кнопки и управлять большим количеством светодиодов или создать команду для включения всех светодиодов сразу.

Пора бы рассказать как организовать опрос такой клавы. Напомню, что клава представляет из себя строки, висящие на портах и столбцы, которые сканируются другим портом. Код написан для контроллера 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 байта . Кто сможет меньше?

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

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

Немного теории

Чтобы разобраться, как работает цифровая электроника, надо усвоить два базовых принципа.

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

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

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

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

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

Кнопка

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

Ну, и как мы будем подключать кнопку ко входу микроконтроллера? Принципиально, любой вход может оперировать двумя возможными логическими уровнями: «0» и «1». Обычной практикой является принятие напряжений, близких к 0 В, за логический ноль, а напряжений, близких к напряжению питания (как правило, 5 В или 3,3 В) - за логическую единицу. Но проблема состоит в том, что управлять напряжением механическая кнопка сама по себе не может. Она управляет током - то есть, если мы соединим два узла с разным электрическим потенциалом через кнопку, то ток между этими узлами потечёт тогда и только тогда, когда кнопка будет нажата.

Но если мы добавим резистор, то очень просто сможем преобразовать ток в напряжение.

Кнопка с подтягивающим резистором

Взгляните на эту схему. Когда кнопка не нажата (цепь разорвана), ток через неё не течёт, поэтому напряжение в узле «A» будет близким к напряжению питания VCC (на самом деле, меньше VCC на величину падения напряжения на резисторе, равную R I). Когда кнопка нажата (цепь замкнута), ток течёт через резистор на землю. С учётом того, что сопротивление замкнутой кнопки очень мало (порядка сотен миллиом), напряжение в узле «A» будет близким к «0».

В данной конфигурации резистор называют подтягивающим, потому что он «подтягивает напряжение вверх» до уровня VCC. А без подключения к питанию через резистор никакого напряжения в узле «A» вообще бы не было (в таком случае вывод микросхемы называют «висящим в воздухе» или находящимся в высокоимпедансном состоянии).

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

Обратите внимание, что если мы оставим один из выводов кнопки висящим в воздухе, то есть никуда его не подключим, то эта кнопка работать не будет вообще: сколько её не нажимай, она никак не повлияет на электрическое состояние вывода микроконтроллера. Мы ещё воспользуемся этим свойством, когда будем составлять матрицу.

Принцип матрицы

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

При помощи транзисторов или правильно сконфигурированных выходов микроконтроллера мы можем сделать так, что кнопки к земле будут подключаться по одной, в то время как остальные будут висеть в воздухе. Оставшиеся выводы каждой из кнопок объединены в один узел и подключены ко входу микроконтроллера. Я опустил подтягивающий резистор, так как мы знаем, что он уже присутствует внутри микроконтроллера, в его входном каскаде. Мы по очереди «включаем» каждую кнопку, соединяя её вывод номер 1 с землёй через соответствующий выход микроконтроллера, после чего на входе мы можем прочесть состояние кнопки, сняв напряжение с её вывода номер 2. Другие кнопки, подключённые к этому же входу, не влияют на его состояние, потому что их выводы номер 1 в данный момент времени висят в воздухе. Следующая схема иллюстрирует эту идею.


Много кнопок, один вход (узел «A»)

«A» - это единственный вход микроконтроллера, а «C1»..«Cn» - его выходы. На одном из выходов установлен логический «0», то есть внутри микросхемы этот контакт каким-то образом соединён с землёй - следовательно, ток всегда будет течь к этому контакту (в соответствии с первым базовым принципом). Теперь, когда нажмётся кнопка, подключённая к данному выходу, то через неё вход «A» сразу же «придавится» к земле, и он установится в «0». Нажатия на другие кнопки ничего не изменят, потому что их земляные выводы в данный момент времени никуда не подключены. Когда нам потребуется опросить следующую кнопку, мы убираем с текущего выхода логический «0» и устанавливаем этот уровень на следующий выход, так что в любой момент времени будет задействован только один из выходов микроконтроллера.

Такая конфигурация (когда выход либо подключён к земле, либо вообще отключён) называется выходом с открытым стоком (ОС) (исторически - с открытым коллектором (ОК)). Здесь я допустил некоторое упрощение - на самом деле, контакт не может быть полностью отключён от схемы, если только он физически от неё не отрезан. Но для простого цифрового ввода-вывода такое упрощение вполне подходит. Большинство микроконтроллеров предоставляют возможность программной конфигурации своих выходных контактов для работы в режиме ОС. Но что, если у нас нет такой возможности? Существует и другая конфигурация - двухтактный режим; на сегодняшний день этот вариант - один из самых популярных. Работает такая конфигурация немного по-другому. В состоянии «0» выход всё так же придавлен к земле, но когда наступает состояние «1», он подтягивается к напряжению питания VCC, так что выход в воздухе больше не висит, теперь он сам может стать источником тока.

Какие изменения это повлечёт в конструкции матрицы? Если мы не собираемся нажимать несколько кнопок одновременно, то никаких. Но если собираемся, то взгляните на рисунок и представьте на секунду, что тогда произойдёт. Нажимая на две кнопки, мы соединяем два нижних выхода в замкнутый контур. Если при этом один из них в состоянии «0», а другой - в состоянии «1», то от выхода, установленного в «1», к выходу, установленному в «0», потечёт электрический ток. А так как этот ток ничем не ограничен (нет резисторов), то мало того, что схема станет нестабильной, так ещё и чип может вылететь. Ну и, само собой, вряд ли получится нормально считать логическое состояние.

Добавление строк

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

Если мы расположим кнопки в форме обычной матрицы, то это условие выполнится автоматически. А на следующем рисунке показано, как будет выглядеть матрица кнопок, состоящая из n столбцов и m строк (на зелёные фигуры внимания пока не обращайте ).


Простая клавиатурная матрица

Считывать эту матрицу очень просто. Мы опрашиваем по одному столбцу за раз. Столбец для считывания выбирается путём соединения одного из выходов «C1»..«Cn» с землёй (выход в режиме ОС переводится в «0»). Теперь, поочерёдно опрашивая строки «R1»..«Rm», мы можем определить состояние каждой кнопки выбранного столбца. Кнопки из других столбцов ни на что не влияют, даже если они нажаты, потому что в данный момент их земляные выводы висят в воздухе (или, как говорят в электронике, находятся в состоянии Hi-Z).

После того, как будет опрошен весь столбец, мы переходим к следующему, отпустив текущий выход и придавив к нулю очередной. Сканирование матрицы считается оконченным, когда будут опрошены все столбцы. Если всё делать достаточно быстро, то интервалы между опросами столбцов не заметит и самый быстрый наборщик. Даже имея микроконтроллер 16 МГц, мы легко сможем сканировать всю матрицу тысячи раз в секунду, тогда как самый быстрый наборщик в тесте на hi-games.net добился скорости 203 слова в минуту (wpm) - то есть, чуть меньше, чем 17 нажатий в секунду.

Проектируя клавиатуру с использованием матрицы, мы уменьшаем количество выводов, требуемых для подключения всех клавиш. Но для того, чтобы свести число выводов к минимально возможному, нам надо составить матрицу таким образом, чтобы количество столбцов было как можно ближе к количеству строк. В идеальной ситуации, если количество кнопок равно , то лучшее, чего мы сможем добиться при помощи матрицы - это 2n занятых выводов микроконтроллера. Однако сегодня при составлении матриц редко кто стремится максимально уменьшить количество задействованных ног, ведь современные микроконтроллеры, как правило, обладают достаточным количеством свободных выводов. Вдобавок, максимальная оптимизация матрицы может впоследствии привести к неудобствам - заметно усложнится разводка платы или распределение проводов в конечном устройстве. Поэтому стоит пойти более удобным путём: при составлении матрицы пытаться следовать физическому расположению кнопок. В таком случае, для стандартной компьютерной клавиатуры самая простая матрица будет иметь всего 6 строк и некоторое количество столбцов, в зависимости от желаемой компоновки. Конечно, с точки зрения количества задействованных выводов это решение - не самое оптимальное, но зато в дальнейшем хотя бы упростится разводка.

А что, если выходов с открытым стоком у нас нет?

Вы можете спросить: а как нам быть, если в выбранном микроконтроллере всё-таки нет режима ОС? Ну, мы уже выяснили, какая неприятность может случиться, и что подтягивать выходы к VCC (то есть, «соединять» их с питанием) - не самая лучшая идея. Существует множество способов решения этой проблемы; среди них встречаются не только специальные выходные буферы ОС, но даже сдвиговые регистры с ОС-выходами - для ещё большей минимизации количества задействованных ног. Но есть и ещё один очень популярный приём, который довольно часто применяется в конструкциях компьютерных клавиатур (на самом деле, широко он применялся в былые времена).

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


Проблема двухтактных выходов

Из выхода, который подтянут к «1» (а не висит в состоянии Hi-Z), начинает течь ток. Он проходит через ближайшую замкнутую кнопку, затем - через замкнутую кнопку в опрашиваемом столбце, и, наконец, втекает прямиком в выход, установленный в «0». Это может привести к чему угодно - начиная с невозможности надёжного считывания состояния клавиатуры и заканчивая выходом из строя самого микроконтроллера.

Но раз уж мы вынуждены как-то бороться с этой проблемой, то можем применить отличный трюк: превратить двухтактный выход в подобие выхода с открытым стоком. Я часто сталкивался с этим решением в конструкциях старых клавиатур. Учитывая, что ток должен только втекать в выходы (подключённые к столбцам), но никогда не вытекать из них, мы можем ограничить направление тока при помощи диодов. Диод - это простое устройство, которое позволяет току течь только в одном направлении. В изображении диода на электрической схеме треугольник символизирует стрелку, которая указывает это направление. Если поставить по диоду между каждым выходом микроконтроллера и проводником соответствующего столбца матрицы, то мы добьёмся поставленной цели - теперь ток может только втекать в выходы, ведь указанные проводники дотягиваются до каждой кнопки своего столбца. Получается, что из выхода, установленного в «1», ток никуда не потечёт, а это почти что превращает его в выход с открытым стоком. Конечно, до настоящего ОС он не дотягивает, но зато решает нашу проблему замкнутого токового контура в клавиатурной матрице. Вернитесь к предпоследнему рисунку матрицы, но на этот раз примите во внимание зелёные диоды , чтобы понять, как работает этот трюк.

Естественно, имеет смысл минимизировать количество диодов. Для этого можно уменьшить число столбцов, увеличив количество строк. А если получившийся результат плохо вписывается в реальную компоновку клавиатуры, то можно «развернуть» матрицу на 90° (поменять местами строки со столбцами). Возможности безграничны. Но в наши дни появилось огромное количество микроконтроллеров, которые просты в использовании и удобны для хоббистов, поэтому сегодня в проектах доморощенных клавиатур описанный приём уже практически не используется. К счастью.

Фантомные нажатия (Ghosting)

Если вы интересуетесь клавами, то, должно быть, уже встречались с этим термином. К сожалению, в мире клавиатур его часто интерпретируют неправильно.

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


Пример фантомного нажатия

На рисунке три кнопки нажаты одновременно. Две из них находятся в столбце «C2», и две - в строке «Rm». Это значит, что одна из нажатых кнопок делит строку с другой нажатой кнопкой, а столбец - с третьей нажатой кнопкой. Именно в такой ситуации и происходит фантомное нажатие . Представьте, что в данный момент мы опрашиваем столбец «C1» (то есть, он придавлен к земле - находится в состоянии «0»). Такой характерный аккорд нажатых кнопок приводит к следующему: даже несмотря на то, что левая верхняя кнопка не нажата, строка «R2» находится в состоянии «0», потому что она придавлена к земле столбцом «C1» (находящемся в «0») через три нажатые кнопки. В результате клавиатурный контроллер считывает левую верхнюю кнопку, как «нажатую», вне зависимости от того, нажата она на самом деле или нет. Описанная ситуация называется фантомным нажатием (ghosting ), потому что некоторые комбинации одновременно нажатых кнопок вызывают фантомные срабатывания других кнопок (ghost keypresses), хотя физически эти другие кнопки и не нажаты.

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

Есть и другой подход. Можно выявить ситуацию, когда срабатывает «третья» кнопка, и заблокировать как это нажатие, так и все последующие. Блокировку не снимать до тех пор, пока фантомное срабатывание не пропадёт, и ситуация опять не станет нормальной. Для реализации этого подхода программа должна помнить, какие именно кнопки сейчас нажаты (а обычно она это помнит и так), чтобы отклонять последующие срабатывания кнопок, в строках и в столбцах которых уже зафиксированы одновременные нажатия. Если написать программу с умом, то блокироваться будут только проблемные срабатывания, а все последующие будут приниматься как обычно - кроме тех, что снова нарушат правила. Раз контроллер отвергает нажатие каждой кнопки, уже имеющей одновременные срабатывания в своей строке и столбце, то выходит, что в зависимости от статуса одних кнопок, другие могут не регистрироваться вообще. Эта ситуация широко известна как блокирование (jamming ). То есть кнопки, которые препятствуют одновременным срабатываниям других кнопок, получается, «блокируют» часть клавиатуры.

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

Люди часто путают «фантомное срабатывание » и «блокирование срабатывания » («ghosting » и «jamming »), что лично для меня является загадкой - по-моему, эти термины вполне интуитивны.

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

Диоды на все кнопки - кардинальное решение

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

К сожалению, сэкономить тут не получится - придётся ставить по диоду на каждую кнопку. Если же говорить о полярности, то вы уже должны были разобраться, что в данном конкретном случае диоды должны пропускать ток «сверху вниз», то есть их катоды («кончики стрелок») должны смотреть на выходы микроконтроллера (столбцы), тогда как аноды должны быть обращены ко входам (строкам).


Итоговая матрица

Не имеет значения, с какой стороны кнопки располагать диод; главное - соблюсти верную полярность. Просто запомните, что в данном случае ток всегда будет течь от входов к выходам. К слову, если входы подтянуты к питанию, а выходы - в режиме ОС, то так происходить будет всегда.

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

Ролловер (Rollover)

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

xKRO

KRO означает клавишный ролловер (key rollover ), и обычно эта аббревиатура озаглавлена каким-то числом. Например, 2KRO означает двухклавишный ролловер. Клавиатура обладает x -клавишным ролловером тогда и только тогда, когда она способна зарегистрировать x одновременных нажатий, причём - вне зависимости от того, какие кнопки и в какой последовательности были задействованы. Большинство современных резинокупольных клавиатур обладают 2KRO. Из этого, однако, не следует, что допустимое количество нажатий у этих клавиатур всегда будет ограничено числом x . На самом деле это означает, что поддержка x одновременных нажатий гарантируется точно.

В зависимости от конструкции матрицы и от типа контроллера, две клавиатуры 2KRO от разных производителей могут вести себя совершенно по-разному. Одна может всегда воспринимать только два одновременных нажатия, а другая с лёгкостью переварит и больше. Думается, последняя клавиатура была заявлена 2KRO потому, что некоторые комбинации клавиш (скорее всего, маловероятные) всё-таки будут вызывать у неё блокирование нажатий других кнопок. А так как это означает, что при работе с данной клавиатурой определённые комбинации из трёх клавиш и более срабатывать не будут, то компания и объявила её 2KRO.

NKRO

Термин NKRO означает N-клавишный ролловер , и его используют для обозначения клавиатур, поддерживающих, несмотря ни на что, любую комбинацию одновременно нажатых клавиш. Но имейте в виду, что NKRO матрицы и NKRO клавиатуры - это две большие разницы. К примеру, если итоговая реализация матрицы, рассмотренная нами в статье, поддерживает NKRO (с учётом корректно написанной прошивки), то из этого совсем не следует, что клавиатура, использующая данную матрицу, тоже будет поддерживать NKRO. Это может происходить из-за ограничений, накладываемых коммуникационными портами, к которым подключается клавиатура; другой причиной может стать бережливость производителей, вечно экономящих то здесь, то там. Проблему NKRO при соединении с PS/2 и USB я попытаюсь разъяснить в следующих частях.

Итог

Конечно, простыня вышла изрядная, но ведь концепция не такая уж и простая - особенно для читателя, который не является инженером-электронщиком. Надеюсь, мне удалось всё объяснить; как бы то ни было, писать я старался как можно понятнее. Поэтому кому-то из вас статья могла показаться довольно скучной. Ну, а если вы шарите в электронике, то, скорее всего, читать вам её вообще не стоило;)
В любом случае, пожалуйста,

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

Чаще всего встречаются клавиатуры 3х4 и 4х4 с 12-ю и 16-ю кнопками, соответственно. У меня в руках оказалась клавиатура 4х4. Работу с ней сейчас и рассмотрим.

У меня оказалась вот такая клавиатура с мембранными контактами. Хороша тем, что в толщину составляет всего 1 мм и легко может приклеиваться к нужным поверхностям.

Потыкав по кнопкам, с помощью мультиметра установил как соединены кнопки внутри клавиатуры.


Для сканирования матричных клавиатур в Bascom-AVR существует специальная функция Getkbd () . Эта команда заточена под использование клавиатур 4х4, поэтому она использует все 8 выводов одного порта микроконтроллера. И в случае использования клавиатур c меньшим числом кнопок стоит это учитывать.

Конфигурирование команды заключается в назначении порта, к которому будет подключена клавиатура, время прохождения дребезга контактов и задержка после определения нажатия

к примеру, в следующей строке:

Config Kbd = Portd , Debounce = 20 , Delay = 100

сконфигурировано подключение клавиатуры к PortD, время дребезга установлено 20 мс, время задержки после нажатия 100 мс

Эта команда берет на себя всю работу по сканированию строк клавиатуры и возвращает переменной номер нажатой клавиши от 0 до 15. Если нажатия на кнопку не зафиксировано, тогда функция вернет число 16. Дальше, в зависимости от того как соединены кнопки, может потребоваться узнать символ нажатой кнопки.

В приведенном ниже примере, сканирование клавиатуры происходит с частотой 10 Гц и находится в главном цикле программы. Результат нажатий выводится на ЖК дисплей.

$regfile = "m8def.dat"
$crystal = 1000000

"конфигурация дисплея
Config Lcdpin = Pin , Rs = Portc . 0 , E = Portc . 1 , Db4 = Portc . 2 , Db5 = Portc . 3 , Db6 = Portc . 4 , Db7 = Portc . 5
Config Lcd = 20 * 4
Cursor Off
Cls

"конфигурация клавиатуры
Config Kbd = Portd , Debounce = 20 , Delay = 100


"переменные
Dim Key_char As Byte "номер нажатой клавиши
Dim Key_str As String * 1 "символ нажатой клавиши на клавиатуре
Dim Result As String * 20 "результат нажатий на клавиатуру

Result = ""

"Главный цикл программы
Do

Key_char = Getkbd () "клавиша не нажата и функция возвращает переменной значение 16

If Key_char <> 16 Then "если переменная не равна 16, значит была нажата кнопка
Key_str = Lookupstr (key_char , Keyboard_data ) "вытаскиваем символ нажатой клавиши
Result = Result + Key_str
End If

Locate 1 , 1

Lcd Result "выводим на дисплей результат нажатий

Waitms 100

Loop

End

Keyboard_data :
Data "1" , "4" , "7" , "*" , "2" , "5" , "8" , "0"
Data "3" , "6" , "9" , "#" , "A" , "B" , "C" , "D"

А вот тестовая схемка, по которой подключаются дисплей и клавиатура:

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

Подключаем, прошиваем, тыкаем на кнопки и видим результат - на дисплей выводятся символы нажатых клавиш:


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

А пока можно скачать архив в котором находится файл для симуляции в Proteus и прошивка.

© 2024 soundpad-voice.ru - Компьютер. Ноутбуки. ОС. Программы