AVR. Учебный курс. Процедура сканирования клавиатуры
Автор DI HALT
Опубликовано 21 Сен 2008
Рубрики: AVR. Учебный курс
Метки: Assembler, AVR, Алгоритм, Клавиатура, Программирование
Теперь коротко о файлах:
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 находится скан код клавиши.
Вот так у меня выглядел тестовый код:
Про LCD дисплей я пока ничего не скажу, так как процедуры еще не доведены до ума, но будут выложены и разжеваны в ближайшее время.Main: SEI ; Разрешаем прерывания.RCALL KeyScan ; Сканируем клавиатуру
CPI R16,0 ; Если вернулся 0 значит нажатия не было
BREQ Main ; В этом случае переход на начало
RCALL CodeGen ; Если вернулся скан код, то переводим его в
; ASCII код.MOV R17,R16 ; Загружаем в приемный регистр LCD обработчика
RCALL DATA_WR ; Выводим на дисплей.RJMP Main ; Зацикливаем все нафиг.
Теперь расскажу как работает процедура 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
NOPSBIS ROW0_PIN,ROW0 ; Проверяем на какой строке нажата
RJMP bt0SBIS ROW1_PIN,ROW1
RJMP bt1SBIS ROW2_PIN,ROW2
RJMP bt2SBIS ROW3_PIN,ROW3
RJMP bt3
Серия NOP нужна для того, чтобы перед проверкой дать ножке время на то, чтобы занять нужный уровень. Дело в том, что реальная цепь имеет некоторое значение емкости и индуктивности, которое делает невозможным мгновенное изменение уровня, небольшая задержка все же есть. А на скорости в 8Мгц и выше процессор щелкает команды с такой скоростью, что напряжение на ноге еще не спало, а мы уже проверяем состояние вывода. Вот я и влепил несколько пустых операций. На 8Мгц все работает отлично. На большую частоту, наверное, надо будет поставить еще штук пять шесть NOP или влепить простенький цикл. Впрочем, тут надо поглядеть на то, что по байтам будет экономичней.
После циклов идет четыре проверки на строки. И переход на соответствующую обработку события.
Вот тут происходит сдвиг маски влево командой циклического сдвига ROL. После чего мы уменьшаем счетчик итераций (изначально равен четырем, так как у нас четыре столбца). Если нажатий не было, то по окончании всех четырех итераций мы вываливаемся из цикла, обнуляем регистр R16 и возвращаемся.ROL R16 ; Сдвигаем маску сканирования
DEC COUNT ; Уменьшаем счетчик столбцов
BRNE KeyLoop ; Если еще не все перебрали делаем еще одну итерациюCLR R16 ; Если нажатий не было возвращаем 0
RET
.undef COUNT
А вот один из возможных концов при нажатии. Тут формируется скан код который вернется в регистре R16. Я решил не заморачиваться, а как всегда зажать десяток байт и сделать как можно быстрей и короче. Итак, что мы имеем по приходу в этот кусок кода. А имеем мы один из вариантов сканирующего порта (1110,1101,1011,0111), а также знаем номер строки по которой мы попали сюда. Конкретно в этот кусок можно попасть только из первой строки по команде RJMP bt0.
bt0: ANDI R16,SCANMSK ; Формируем скан код
ORI R16,0×01 ; Возвращаем его в регистре 16
RET
Так давай сделаем скан код из сканирующей комбинации и номера строки! Сказано - сделано! Сначала нам надо выделить из значения порта сканирующую комбинацию - она у нас хранится в регистре R16, поэтому выковыривать из порта ее нет нужды. Продавливаем операцией И значение R16 через SCANMASK и все что было под единичками прошло без изменений, а где были нули - занулилось. Опа, и у нас выведен сканирующий кусок - старший полубайт. Теперь вклеим туда номер строки - операцией ИЛИ. Раз, и получили конструкцию вида [скан][строка]
Вот ее и оставляем в регистре R16, а сами выходим прочь! Также и с остальными строками. Погляди в исходнике, я их не буду тут дублировать.
Декодирование скан кода.
Отлично, скан код есть, но что с ним делать? Его же никуда не приткнуть. Мы то знаем, что вот эта шняга вида 01110001 это код единички, а какой нибудь LCD экран или стандартная терминалка скорчит нам жуткую кракозябру и скажет, нам все что она думает о нашей системе обозначений — ей видите ли ASCII подавай. Ладно, будет ей ASCII.
Как быть? Прогнать всю конструкцию по CASE где на каждый скан код присвоить по ASCII коду меня давит жаба - это же сколько надо проверок сделать! Это же сколько байт уйдет на всю эту тряхомудию? А память у нас не резиновая, жалкие восемь килобайт, да по два байта на команду, это в лучшем случае. Я мог все это сделать прям в обработчике клавиатуры. НЕТ!!! В ТОПКУ!!! Мы пойдем своим путем.
Ок, а что у нас есть в запасе? Метод таблиц перехода не катит, по причине жуткой неупорядоченности скан кодов. Почесал я тыковку, пошарился по квартире… и тут меня осенило. Конечно же!!! Брутфорс!!!
Брутфорсим скан код.
Итак, у нас есть жутко несваримый скан код, а также стройная таблица ASCII символов. Как скрестить ужа с ежом? Да все просто! Разместим в памяти таблицу символов в связке [скан код]:[ascii код] , а потом каждый нужный скан код будем прогонять через эту таблицу и при совпадении подставлять на выходе нужный ASCII из связки. Классический пример программизма - потеряли во времени, зато выиграли в памяти.
Вот так это выглядит:
Тут мы загрузили в индексный регистр адрес нашей таблицы. Умножение на два для того, чтобы адрес был в байтах, т.к. в среде компилятора пространство кода адресуется в словах.CodeGen:LDI ZH,High(Code_Table*2) ; Загрузил адрес кодовой таблицы
LDI ZL,Low(Code_Table*2) ; Старший и младший байты
Загружаем из таблицы первый скан код и нычим его в регистр R17, попутно увеличиваем адрес в регистре Z (выбор следующей ячейки таблицы) и первым делом сравниваем его с FF - это код конца таблицы. Если таблица закончилась, то выходим отсюда. Если мы не всю таблицу перебрали, то начинаем сравнивать входное значение (в регистре R16) вначале с нулем (нет нажатия), если ноль тоже выходим. И со скан кодом из таблицы. Если скан таблицы совпадает со сканом на входе, то переходим на Equal.Brute: LPM R17,Z+ ; Взял из таблицы первый символ - скан кодCPI R17,0xFF ; Если конец таблицы
BREQ CG_Exit ; То выходимCPI R16,0 ; Если ноль,
BREQ CG_Exit ; то выходимCP R16,R17 ; Сравнил его со скан кодом клавиши.
BREQ Equal ; Если равен, то идем подставлять ascii код
А в случае если ничо не обнаружено, то мы повторно вызываем команду LPM R17,Z+ лишь для того, чтобы она увеличила Z на единичку — нам же надо перешагнуть через ASCII код и взять следующий скан код из таблицы. Просто INC Z не прокатит, так как Z у нас двубайтный. ZL и ZH. В некторых случаях достаточно INC ZL, но это в случае когда мы точно уверены в том, что адрес находится недалеко от начала и переполнения младшего байта не произойдет (иначе мы вместо адреса 00000001:00000000 получим просто 00000000:0000000, что в корне неверно), а команда LPM все сделает за нас, так что тут мы сэкономили еще пару байт. Потом мы вернемся в начало цикла, а там будет опять LPM которая загрузит уже следующий скан код.LPM R17,Z+ ; Увеличиваем Z на 1
RJMP Brute ; Повтор цикла
Если же было совпадение, то в результате LPM Z+ у нас Z указывает на следующую ячейку — с ASCII кодом. Ее мы и загружаем в регистр R16 и выходим наружу.Equal: LPM R16,Z ; Загружаем из памяти ASCII код.
RET ; Возвращаемся
А в случае нулевого исхода, когда либо таблица кончилась, а скан код так и не подобрался, либо ноль был в регистре R16 на входе - возвращаемся с тем же нулем на выходе. Вот так вот.CG_Exit: CLR R16 ; Сбрасываем 0 = возвращаем 0
RET ; Возвращаемся
Тут просто табличка статичных данных, на границе памяти. Как видишь данные сгруппированы по два байта - сканкод/ASCII
;========================================
; STATIC DATA
;========================================
Code_Table: .db 0×71,0×31 ;1
.db 0xB1,0×32 ;2
.db 0xD1,0×33 ;3
.db 0×72,0×34 ;4
.db 0xB2,0×35 ;5
.db 0xD2,0×36 ;6
.db 0×73,0×37 ;7
.db 0xB3,0×38 ;8
.db 0xD3,0×39 ;9
.db 0×74,0×30 ;0
.db 0xFF,0 ;END
Вот посредством таких извратов вся программа, с обработкой клавиатуры, декодированием скан кода, чтением/записью в LCD индикатор и обнулением оперативки (нужно для того, чтобы точно быть увереным, что память равна нулю) заняло всего 354 байта. Кто сможет меньше?
Комментарии
40 комментариев на «AVR. Учебный курс. Процедура сканирования клавиатуры»
Оставьте свой отзыв
Вы должны войти, чтобы оставлять комментарии.





Здорово это все, только ничего не понял) млин надо учить асм, но с эти позже, вот у меня ничего не работает, может кто подскажет!
Вот мой код:
void decodecod (int code)
{
unsigned char symbol;
switch (code)
{
case 11: symbol = ‘1′; break;
case 21: symbol = ‘2′; break;
case 31: symbol = ‘3′; break;
case 12: symbol = ‘4′; break;
case 22: symbol = ‘5′; break;
case 32: symbol = ‘6′; break;
case 13: symbol = ‘7′; break;
case 23: symbol = ‘8′; break;
case 33: symbol = ‘9′; break;
case 14: symbol = ‘0′; break;
case 24: symbol = ‘*’; break;
case 34: symbol = ‘#’; break;
default : break;
}
UDR = symbol;
}
/*Фунуция чиатающих портов*/
void keyread()
{
int byte=0;
delay_us(400);
//Читаем линию [1]
if (PIND.2 == 0)
{
byte = port + 1;
port = 0;
}
//Читаем линию [2]
if (PIND.3 == 0)
{
byte = port + 2;
port = 0;
}
//Читаем линию [3]
if (PIND.4 == 0)
{
byte = port + 3;
port = 0;
}
//Читаем линию [4]
if (PIND.5 == 0)
{
byte = port + 4;
port = 0;
}
if (byte > 0)
{
decodecod(byte);
}
}
/*Функция сканирующих портов*/
void keyscan()
{
//Сканируем столбик [1]
PIND.6=0;
PINB.0=1;
PINB.1=1;
port = 30;
keyread();
//Сканируем столбик [2]
PIND.6=1;
PINB.0=0;
PINB.1=1;
port = 20;
keyread();
//Сканируем столбик [3]
PIND.6=1;
PINB.0=1;
PINB.1=0;
port = 10;
keyread();
}
Да знаю, код детский, все можно было сделать по другому, но решил писать в лоб, так как времени мало. Вот почему не работают эти функции. Я нажимаю клавишу, а у меня черти что в UART сыпется, допустим, нажимаю на клавишу 1, а он мне выводит 21222, нажимаю клавишу 4 он 54555 и т.д. это если первый столбец а второй и третий так вообще отказываются работать либо суют в UART символ “я” чаще всего. Зато 0 работает всегда как надо))) Вот боюсь, не в схеме ли дело ((
P.S. Di Halt собирал по схеме которую тебе прислал на e-mail.
Прогу не читал, т.к. Си читаю плохо. Попробуй протрассировать программу в отладчике. Мне это помогает выловить 99% багов.
Поставь задержку между сканированием столбцов.
трассировал, там как бы все нормально, на всякий пожарный щаз плату переделываю, ещё мне посоветовали в функции keyscan(), поменять PIN на PORT, сказали якобы PORT изменяет лог, на ноге!?
Задержку ставил таже картина.
А ну дык конечно. PIN это регистр только для чтения. А чтобы поменять уровень надо менять PORT
Есть небольшое сомнение.
Предположим нажата клавиша и мы получили [скан][строка] - на выходе код соответствующего символа.
Далее будет [скан][строка + 1]. Здесь ничего нет и в r16 ноль.
Далее будет [скан][строка + 2]. Здесь ничего нет и в r16 ноль.
Далее будет [скан][строка + 3]. Здесь ничего нет и в r16 ноль.
Клавиша все еще нажата.
Дальше опять будет [скан][строка] и в r16 - снова код символа.
Это будет интерпретироваться “внешней” програмой как многократное нажатие клавиши.
Или я не прав?
Будет многократным нажатием. Такие вещи надо разруливать уже уровнем выше, на этапе проверки скан кода. И, если надо, игнорировать повторные нажатия.
В любом случае, у нас же последовательное исполнение кода, с опросом, это не ПЛИС, так что тут нет такого понятия как длительное нажатие. Есть только частое многократное и другого не дано.
Потому что скан кода FF быть не может. Это запрещенная комбинация. Ну и потому, что я так захотел. =)
Насчет FF - вопрос снят, не доглядел в таблице. Виноват за глупый вопрос.
А насчет многократного нажатия - честно говоря неудобно. Тяжело отличить снаружи было ли повторное нажатие или нет. Нужна еще информация наружу, а не только r16. Кроме того у Вас на LCD выходит - значит вместо одного символа за нажатие - их будет ооочень много.
Хотя может я и придираюсь….. =)
Вопрос удобности и неудобности тут не стоит, т.к. задачи могут быть разные. Да и по другому же все равно никак.
А что мешает добавить обработку одиночного нажатия? Например если скан_код(t)=скан_код(t-1) то пропустить это нажатие. Вклинить это дело перед генерацией скан кода и нет проблем.
Это же простейший пример, показывающий работу с клавиатурной матрицей. А вывод на ЛСД тут как индикация работы. Можно было и в уарт рыгать. Мне просто влом было провода тащить до компа.
делаю диплом на мк АВР (опыта в программировании нет никакого да и с мк общаюсь около месяца)в нем должна быть клавиатура, написал программу сканирования..
для проверки работоспособность подключил светодиоды показывающие код, в АВР студио все работает как задумано а вот в железе не хочет работать, реагирует всего на пару клавиш а на остальные нет, может кто подскажет в чем проблема..
Всю программу целенаправленно не разбирал, так бегло глянул. Вот что бросилось в глаза:
1) Не стоит в цикле майн каждый раз инициализировать стек. И каждый раз перенаправлять порты.
2) Чет не догоняю как ты сделал сканирование? У тебя же только один вывод в порт вначале число 0F потом F0. Где бегающий нолик?
3) А ты учел емкость и время на переключение? Между сменой активного столбца должно быть около 1мс, между сменой столбца и снятием значения с порта тоже бы выдержку в 1мс сделать. Можно,конечно, и меньше, но лишь после того как будет все проверено и алгоритм без ошибок окажется.
Сканирование осуществлено следующим образом:
-сначала сканируется строка и в зависимости от номера присеваиваться значение
типа 0b00000100
-потом столбец, значение 0b01000000
-потом идет операция сложение двух регистров столбца и строки в результате имеем код клавиши вида 0b01000100
Время не учел, сейчас попробую исправить.
А про емкость не понял. Емкость чего ?
П.С. Большое спасибо за ответ.
Строки и столбцы нельзя считывать по отдельности. Результат будет непредсказуем. В матрице строки замыкаются на столбцы. ПОэтому мы на столбцах задаем уровень (обычно низкий), а на строках этот уровень считываем по считанной строке опеределяя что нажато.
То есть считываем мы только строки, а столбец задаем. Сканирующий порт определяет активный столбец.
У тебя же идет считывание сначала со столбца, потом со строки. В результате - чушь. Т.к. уровень ничем не задан.
2) Имелась ввиду емкость проводов, дорожек и самих выводов. Она небольшая, но есть. И на больших скоростях влияет очень сильно
изменив значение PORTA на FF и добавив задержку в 1мс программа все таки заработала но глючит, строку определяет через раз, а столбец четко.
написал другую программу там все работает идеально даже с задкержами примерно в 230мкс, но если воспользоваться Вашим советом про инициализацию стека и портов в цикле майн, и вынести его за пределы цикла то ничего не работает…
Не догоняю как оно вообще может работать. Покажи схему матрицы.
У тебя одновременно по столбцу зажигаются два нуля. Соответственно нельзя определить на каком именно столбце идет нажатие.
в первом случае стандартная матрица 4Х4, все ножки PORTA посажены на землю через резистор в 1КОм а между резистором и МК уже подключаться клавиатура.
Во втором случаи матрица 3Х4 подключена практически также как в первом случаи за исключением резисторов на столбцах (на столбцах их нет)..
А почему все таки если вынести инициализацию стека и портов из цикла маин все перестает работать?
Если вынести инициализацию стека то вроде понятно, там возвращение из подпрограммы zad выполняться неправильно…
а вот с портами вообще непонятно…
Смотри что мне не нравится:
ldi temp, 0b1001 1111 ;1столбец
out porta, temp
***
ldi temp,0b1010 1111 ;2столбец
out porta, temp
***
ldi temp,0b1100 1111 ;3столбец
out porta, temp
Если учесть, что младшие биты это у тебя строки, то сканирование столбцов должно быть вида:
1110 1111 первый столбец
1101 1111 второй столбец
1011 1111 третий столбец
0111 1111 четвертый столбец
Почему у тебя там по два нуля?
7 пин не задействован т.к. клавиатура 3х4. а сканирование идет не по 0 а по 1..
мне все таки непонятно, почему при инициализации портов вне цикла маин программа не работает. а в цикле маин все прекрасно работает
в чем может быть косяк?
include “m16def.inc”
.def temp = R16
.org 0×0 rjmp reset ; стартуем с этой метки
.macro outi ; макрос вывода числа в порт
ldi temp, @1
out @0, temp
.endm
.org 0×100
reset:
outi ddrc, 0b11111111
outi SPH, 0×04 ; почему не high(RAMEND) ?
out SPL, 0×5F ; а тут low(RAMEND)
loop: ; ну а тут тот самый цикл
rcall scan
clt
rjmp loop
здравствуйте, давно не был)) прошу свежего стороннего взгляда на код, т.к. запутался, моск клинит уже сильно, может, и не там вообще смотрю… :/
или я недопонял основные принципы сканирования, т.к. в аврстудии вместе с хапсимовской клавой происходит чушь: kbdval при ненажатой кнопке !=0. получается, что вывод в PORT не меняет состояние PIN при ноге, сконфигуренной как выход?
кусок кода, где происходит непосредственно сканирование всей матрицы 1 раз:
косяк тут:
чтобы раз и навсегда: как правильно сконфигурить вход с подтяжкой, выход, куда писать и откуда читать?
Эмм.. а в чем проблема то?
Хочешь записать в порт - пишешь в PORT
Хочешь считать читаешь из PIN
DDR - вход/выход.
КОнфиг с подтяжкой DDR=0, PORT=1
Читаешь из PIN
с hapsim почему-то не получается так :/
Ну а чо ты хотел от второй бета версии третьего альфа релиза кустарной поделки? Хапсим ваще не очень стабильная штука. Что либо сложное на нем довольно сложно отлаживать. ПРавь все в железе.
так там же просто модуль клавы да пара светодиодных… светодиоды даже необязательны - вставать по брейкпоинтам. может, хоть Шпрот 7-й вканает?..
не успеваю банально, опять дотянул… :/
Прогони вручную в студии. Предварительно, когда ноги встанут на подтяг протыкай сам вручную PIN’ы на то как они должны встать при этом. Т.к. студия при внутреннем подтяге пины не трогает, это надо учитывать! По идее хапсим это должен делать сам, но бывает лажает. Либо ты что то не то сконфигурировал там.
Шпрот 7.. хз, я его сто лет не юзал.
Попробовал запустить в протеусе переделанный на мегу8 аппноут avr 240 со слип-модом - сразу начинает мигать светодиод, который должен был светиться после нажатия “0″.
Может я чего не заметил - помогите разобраться.
Версии:
слипмод требует внешнего кварца или RC
слипмод не работает в протеусе.
;**** A P P L I C A T I O N N O T E A V R 2 4 0 ************************
;*
;* Title: 4×4 keypad, wake-up on keypress
;* Version: 1.2
;* Last Updated: 2004.11.11
;* Target: All AVR Devices
;*
;* Support E-mail: avr@atmel.com
;*
;* DESCRIPTION
;* This Application note scans a 4 x 4 keypad and uses sleep mode
;* causing the AVR to wake up on keypress. The design uses a minimum of
;* external components. Included is a test program that wakes up the AVR
;* and performs a scan when a key is pressed and flashes one of two LEDs
;* the number of the key pressed. The external interrupt line is used for
;* wake-up. The example runs on the AT90S1200 but can be any AVR with suitable
;* changes in vectors, EEPROM and stack pointer. The timing assumes a 4 MHz clock.
;* A look up table is used in EEPROM to enable the same structure to be used
;* with more advanced programs e.g ASCII output to displays.
;***************************************************************************
;***** Register used by all programs
;******Global variable used by all routines
.def temp =r16 ;general scratch space
;Port B pins
.equ ROW1 =3 ;keypad input rows
.equ ROW2 =2
.equ ROW3 =1
.equ ROW4 =0
.equ COL1 =7 ;keypad output columns
.equ COL2 =6
.equ COL3 =5
.equ COL4 =4
;Port D pins
.equ GREEN =0 ;green LED
.equ RED =1 ;red LED
.equ INTR =2 ;interrupt input
.include “M8def.inc”
;***** Registers used by interrupt service routine
.def key =r17 ;key pointer for EEPROM
.def status =r21 ;preserve sreg here
;***** Registers used by delay subroutine
;***** as local variables
.def fine =r18 ;loop delay counters
.def medium =r19
.def coarse =r20
;*****Look up table for key conversion******************************
.eseg ;EEPROM segment
.org 0
.db 1,2,3,15,4,5,6,14,7,8,9,13,10,0,11,12
;****Source code***************************************************
.cseg ;CODE segment
.org 0
rjmp reset ;Reset handler
rjmp scan ;interrupt service routine
reti ;unused timer interrupt
reti ;unused analogue interrupt
;*** Reset handler **************************************************
reset:
cli ;disable global interrupts
ldi r16, high(RAMEND)
out SPH, r16 ; Set Stack Pointer to top of RAM
ldi r16, low(RAMEND)
out SPL, r16
ldi temp,0xFB ;initialise port D as O/I
out DDRD,temp ;all OUT except PD2 ext.int.
ldi temp,0×30 ;turn on sleep mode and power
out MCUCR,temp ;down plus interrupt on low level.
ldi temp,0×40 ;enable external interrupts
out GIMSK,temp
sbi ACSR,ACD ;shut down comparator to save power
main:
ldi temp,0xF0 ;initialise port B as I/O
out DDRB,temp ; 4 OUT 4 IN
ldi temp,0×0F ;key columns all low and
out PORTB,temp ;active pull ups on rows enabled
ldi temp,0×07 ;enable pull up on PD2 and
out PORTD,temp ;turn off LEDs
sei ;enable global interrupts ready
sleep ;fall asleep
rcall flash ;flash LEDs for example usage
ldi temp,0×40
out GIMSK,temp ;enable external interrupt
rjmp main ;go back to sleep after keyscan
;****Interrupt service routine***************************************
scan:
in status,SREG ;preserve status register
sbis PINB,ROW1 ;find row of keypress
ldi key,0 ;and set ROW pointer
sbis PINB,ROW2
ldi key,4
sbis PINB,ROW3
ldi key,8
sbis PINB,ROW4
ldi key,12
ldi temp,0×0F ;change port B I/O to
out DDRB,temp ;find column press
ldi temp,0xF0 ;enable pull ups and
out PORTB,temp ;write 0s to rows
rcall settle ;allow time for port to settle
sbis PINB,COL1 ;find column of keypress
ldi temp,0 ;and set COL pointer
sbis PINB,COL2
ldi temp,1
sbis PINB,COL3
ldi temp,2
sbis PINB,COL4
ldi temp,3
add key,temp ;merge ROW and COL for pointer
ldi temp,0xF0 ;reinitialise port B as I/O
out DDRB,temp ; 4 OUT 4 IN
ldi temp,0×0F ;key columns all low and
out PORTB,temp ;active pull ups on rows enabled
out SREG,status ;restore status register
ldi temp,0×00
out GIMSK,temp ;disable external interrupt
;have to do this, because we’re
;using a level-triggered interrupt
reti ;go back to main for example program
;***Example test program to flash LEDs using key press data************
flash: out EEARL,key ;address EEPROM
sbi EECR,EERE ;strobe EEPROM
in temp,EEDR ;set number of flashes
tst temp ;is it zero?
breq zero ;do RED LED
green_flash:
cbi PORTD,GREEN ;flash green LED ‘temp’ times
rcall delay
sbi PORTD,GREEN
rcall delay
dec temp
brne green_flash
exit: ret
zero: ldi temp,10
flash_again: cbi PORTD,RED ;flash red LED ten times
rcall delay
sbi PORTD,RED
rcall delay
dec temp
brne flash_again
rjmp exit
;****Time Delay Subroutine for LED flash*********************************
delay:
ldi coarse,8 ;triple nested FOR loop
cagain: ldi medium,255 ;giving about 1/2 second
magain: ldi fine,255 ;delay on 4 MHz clock
fagain: dec fine
brne fagain
dec medium
brne magain
dec coarse
brne cagain
ret
;***Settling time delay for port to stabilise******************************
settle:
ldi temp,255
tagain: dec temp
brne tagain
ret
заменил команду слип на rjmp main - светодиод больше не светится - все ок - это слип не работает в протеусе.
Чем же можно потестить прошивку с учетом слип?
Гхм, а в железе собрать не айс? Или тебе надо замерить потребление? Думаю микроамперметр покаже что проц ушел в слип.
А как осуществляется процедура сбора информации с компьюетрной клавы? как ее подключить к МК USB шиной?
ПРИВЕТ. ЧЕЛОВЕК В МИКРОКОНТРОЛЛЕРАХ Я НАЧИНАЮЩИЙ, ТАК ЧТО СТРОГО НЕ СУДИТЕ…А ВОПРОС ТАКОЙ:
“Одна тонкость - выводы на столбцы (сканирующий порт) должны быть последовательным набором линий одного порта. То есть, например, ножки 0,1,2,3 или 4,5,6,7, или 3,4,5,6. Неважно какого порта, главное чтобы последовательно.”.
СО СТОЛБЦАМИ ЯСНО, А ВОТ СТРОКИ МОГУТ СИДЕТЬ НА PD3,PD6? (КЛАВА НА 6 КНОПОК, 2 СТРОКИ)КАМЕНЬ - МЕГА 16…СТОЛБЦЫ КАК И СКАЗАНО: РС2-РС4
Видел прикольную клавиатуру, где инфа о нажатой кнопке передаётся по одному проводу с подтяжкой к 5 вольтам. К каждой кнопке подключён по стабилитрону с разным напряжением, а ацп считывает напряжение и по нём вычисляется какая кнопка нажата. Это конечно геморойно,но реальная экономия портов.
Видел подобную в телевизоре, только вместо стабилитронов были резисторные делители. Использовалось 2 провода и общий для шести кнопок. Так вот, очень не стабильная штука - много ложных срабатываний, достаточно сухим пальцем взяться за плату… Хозяева телека протёрли его влажной тряпкой, после чего начала сама добавляться громкость и переключаться каналы.
В моём случае с монитором такого бока не наблюдалось)))
Уважаемые знатоки! Подскажите в чем может быть затык…
Матрица 4х2, в студии все отрабатывается правильно, запускаю в Протеусе - начинает тупить. То есть вроде бы все нормально срабатывает но жутко медленно начинает работать программа. За минуту (реального времени) протеус отсчитывает 0,01-0,018 секунды. В чем может быть проблема? Глюк самого протеуса, глюк модели, мои кривые ручки???
Вот код:
Skan_cod:
ldi temp2,0b00001111 ; маска для клавиатуры
in temp1,ButtonPin ; копируем состояние порта С
and temp1,temp2 ; выделяем биты клавиатуры
eor temp1,temp2 ; определяем старший полубайт
mov button,temp1 ; сохраняем в регистр кнопок
swap button ; меняем местами тетрады
sbi ButtonPort,str1 ; переключаем скан-бит
cbi ButtonPort,str2 ;
nop ; ожидаем переключения пинов
nop ;
nop ;
nop ;
nop ;
nop ;
nop ;
nop ;
in temp1,ButtonPin ; копируем состояние порта С
and temp1,temp2 ; выделяем биты клавиатуры
eor temp1,temp2 ; определяем младший полубайт
add button,temp1 ; сохраняем в регистр кнопок
sbi ButtonPort,str2 ; переключаем скан-бит
cbi ButtonPort,str1 ;
cpi button,0 ; клавиша нажата?
breq PC+3 ; не нажата -> перейти
sbr flag_reg,$01 ; нажата -> установить признак наж. клавиши
ret ; выйти
cbr flag_reg,$01 ; не нажата -> сбросить признак наж. клавиши
ret ; выйти
Контроллер Мега8; сканирующие столбцы - PC4-PC5; сканируемые строки - PC0-PC3.
Заранее ПАСИБА!!!
вживую прогони и узнаешь. Мало ли чего протеус там себе тупит. Я им вообще не пользуюсь.
Опять же… для клавиатур 4х4 есть вполне достойное недорогое аппаратное решение. Микруха 74С922. Она сама сканит клаву. Как замечает нажатую клавишу, опускает ногу “фас” и выдаёт 4-х битный код нажатой клавиши. Можно повесить ентот “фас” на прерывание по смене уравня (по-моему работает в лог “0″, как того требует 8051-подобный комп, а не как AVR в “1″), а потом читать данные с шины. Более подробно в даташит.
А как посчитать кол-во байт? Может у меня и короче сканирование клавы вышла. Уж проще и понятней, так точно.. ))) Твой вариант явно не для начинающих - много сложных слов..)))
Кстати, не увидел я защиты от дребезга. Не досмотрел или нету ?
А тут защита от дребезга осуществляется скан циклом.
Не заметил сразу - а зачем надо код кнопки отображать на ЖКИ ? Это текстовая клавиатура, что ли? Да нет же, вроде - 4х4 кнопки всего..
Захотелось так.