AVR. Учебный курс. Процедура сканирования клавиатурыPrint This Post

Автор DI HALT
Опубликовано 21 Сен 2008 
Рубрики: AVR. Учебный курс
Метки: , , , ,

Итак, клавиатуру я сделал и написал процедуру сканирующую клавиатурную матрицу 4х4 кнопки. Пора бы рассказать как организовать опрос такой клавы. Напомню, что клава представляет из себя строки, висящие на портах и столбцы, которые сканируются другим портом. Код написан для контроллера 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,0×01 ; Возвращаем его в регистре 16
RET
А вот один из возможных концов при нажатии. Тут формируется скан код который вернется в регистре R16. Я решил не заморачиваться, а как всегда зажать десяток байт и сделать как можно быстрей и короче. Итак, что мы имеем по приходу в этот кусок кода. А имеем мы один из вариантов сканирующего порта (1110,1101,1011,0111), а также знаем номер строки по которой мы попали сюда. Конкретно в этот кусок можно попасть только из первой строки по команде RJMP bt0.
Так давай сделаем скан код из сканирующей комбинации и номера строки! Сказано - сделано! Сначала нам надо выделить из значения порта сканирующую комбинацию - она у нас хранится в регистре 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) ; Старший и младший байты
Тут мы загрузили в индексный регистр адрес нашей таблицы. Умножение на два для того, чтобы адрес был в байтах, т.к. в среде компилятора пространство кода адресуется в словах.
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 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
Тут просто табличка статичных данных, на границе памяти. Как видишь данные сгруппированы по два байта - сканкод/ASCII

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

Комментарии

40 комментариев на «AVR. Учебный курс. Процедура сканирования клавиатуры»


  1. Xenom0rph 15 Ноя 2008 0:08

    Здорово это все, только ничего не понял) млин надо учить асм, но с эти позже, вот у меня ничего не работает, может кто подскажет!
    Вот мой код:
    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.

    DI HALT

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

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


  2. Xenom0rph 15 Ноя 2008 17:36

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

    DI HALT

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


  3. Alexiy 04 Янв 2009 3:32

    Есть небольшое сомнение.
    Предположим нажата клавиша и мы получили [скан][строка] - на выходе код соответствующего символа.
    Далее будет [скан][строка + 1]. Здесь ничего нет и в r16 ноль.
    Далее будет [скан][строка + 2]. Здесь ничего нет и в r16 ноль.
    Далее будет [скан][строка + 3]. Здесь ничего нет и в r16 ноль.
    Клавиша все еще нажата.
    Дальше опять будет [скан][строка] и в r16 - снова код символа.
    Это будет интерпретироваться “внешней” програмой как многократное нажатие клавиши.
    Или я не прав?

    DI HALT

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

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

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


  4. Alexiy 04 Янв 2009 4:22

    Насчет FF - вопрос снят, не доглядел в таблице. Виноват за глупый вопрос.
    А насчет многократного нажатия - честно говоря неудобно. Тяжело отличить снаружи было ли повторное нажатие или нет. Нужна еще информация наружу, а не только r16. Кроме того у Вас на LCD выходит - значит вместо одного символа за нажатие - их будет ооочень много.
    Хотя может я и придираюсь….. =)

    DI HALT

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

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

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


  5. paschen 23 марта 2009 21:20

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    
    .def	temp	=r16	
    .org 0x0 rjmp main
    .org 0x100
     
    main:
    	ldi temp, 0b11111111
    	out ddrc, temp
     
    	ldi temp, 0x04
    	out SPH, temp
    	ldi temp, 0x5F
    	out SPl, temp
     
    rcall scan
    clt
    rjmp main
     
    ldi temp, 0xff
    out ddrc, temp
     
    .equ	ROW1	=3	;строки вводы
    .equ	ROW2	=2
    .equ	ROW3	=1
    .equ	ROW4	=0
    .equ	COL1	=7	;столбцы вывода
    .equ	COL2	=6
    .equ	COL3	=5	
    .equ	COL4	=4	
     
    .include "m16def.inc"
     
    .def	key	=r17	
     
    		ldi temp,0xF0		
    		out DDRA,temp		
    		ldi temp,0x0F		
    		out PORTA,temp		
     
    ;сканирование
    scan:
           	rcall   zad
     
     
    		ldi temp , 0xF0
    		out ddra, temp
    		ldi temp, 0x0F
    		out porta, temp
       nop
       nop
       nop
     
    		sbic PINA,ROW1		;
    		ldi key,           0b00001000		
    		sbic PINA,ROW2
    		ldi key,           0b00000100
    		sbic PINA,ROW3
    		ldi key,           0b00000010
    		sbic PINA,ROW4
    		ldi key,           0b00000001
     
     
    		ldi temp,0x0F		;
    		out DDRA,temp		;
    		ldi temp,0xF0		;
    		out PORTA,temp		;
     
    		rcall   zad
     
    		sbic PINA,COL1		
    		ldi temp,             0b00010000		
    		sbic PINA,COL2
    		ldi temp,             0b00100000
    		sbic PINA,COL3
    		ldi temp,             0b01000000
    		sbic PINA,COL4
    		ldi temp,             0b10000000
     
     
    		add key,temp		;
    		ldi temp,0xF0		;
    		out DDRA,temp		; 
    		ldi temp,0x0F		;
    		out PORTA,temp		;
     
    ;зажигание диодов
     
    out portc, key
    rcall zad
    	ret
     
    	zad:      
    		ldi  temp,$FF    ; задержка
    tt:     dec  temp
    		brne tt
            ldi  temp,$FF    ; 
    tt1:    dec  temp
    		brne tt1
    		ldi  temp,$FF    
    tt2:    dec  temp
    		brne tt2		
    		ret
    DI HALT

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

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

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

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

    paschen

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

    Время не учел, сейчас попробую исправить.
    А про емкость не понял. Емкость чего ?

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

    DI HALT

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

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

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

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

    paschen

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    
    .include "m16def.inc"
    .def temp = R16
    .def key = R17
     
    .equ  KEY_1 = 0b11111110;     
    .equ  KEY_2 = 0b11111100
    .equ  KEY_3 = 0b11111000
    .equ  KEY_4 = 0b00001111
    .equ  KEY_5 = 0b00011100
    .equ  KEY_6 = 0b00000111
    .equ  KEY_7 = 0b11100010
    .equ  KEY_8 = 0b00111111
    .equ  KEY_9 = 0b00001111
    .equ  KEY_0 = 0b00000000
    .equ  KEY_10 =0b11111111  ;*
    .equ  KEY_11 =0b11100111  ;#
     
    .org 0x0 rjmp main
    .org 0x100
    main:
     
    	 ldi temp, 0b01110000;
    	out DDRA, temp
    	ldi temp, 0b10001111
    	out PORTA, temp	
    	ldi temp, 0b11111111
    	out ddrc, temp
     
    	ldi temp, 0x04
    	out SPH, temp
    	ldi temp, 0x5F
    	out SPl, temp
     
    rcall scan
    clt
    rjmp main
     
    scan:
     
       ldi temp, 0b10011111        ;1столбец
       out porta, temp
       rcall zad 
       sbic pina,0
       ldi  key,                KEY_1
       sbic pina,1
       ldi  key,                KEY_4
       sbic pina,2
       ldi  key,                KEY_7
       sbic pina,3
       ldi  key,                KEY_10
     
    	rcall zad
     
     
       ldi temp,0b10101111    ;2столбец
       out porta, temp
     
       rcall zad
     
       sbic pina,0
       ldi  key,                 KEY_2
       sbic pina,1
       ldi  key,                 KEY_5
       sbic pina,2
       ldi  key,                 KEY_8
       sbic pina,3
       ldi  key,                 KEY_0     
     
    		rcall zad					
     
       ldi temp,0b11001111       ;3столбец
       out porta, temp
       	rcall zad
       sbic pina,0
       ldi  key,            KEY_3
       sbic pina,1
       ldi  key,            KEY_6
       sbic pina,2
       ldi  key,            KEY_9
       sbic pina,3
       ldi  key,            KEY_11  
     
    	rcall zad
     
    ;зажигание диодов
    out portc, key
     
    ret
     
    zad:      
    		ldi  temp,$FF    ; задержка 1 вариант
    tt:     dec  temp
    		brne tt
    		ret
    DI HALT

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

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

    paschen

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

    А почему все таки если вынести инициализацию стека и портов из цикла маин все перестает работать?
    Если вынести инициализацию стека то вроде понятно, там возвращение из подпрограммы zad выполняться неправильно…
    а вот с портами вообще непонятно…

    DI HALT

    Смотри что мне не нравится:
    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 четвертый столбец

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

    paschen

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

    brunql

    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


  6. berrymorr 28 марта 2009 9:06

    здравствуйте, давно не был)) прошу свежего стороннего взгляда на код, т.к. запутался, моск клинит уже сильно, может, и не там вообще смотрю… :/
    или я недопонял основные принципы сканирования, т.к. в аврстудии вместе с хапсимовской клавой происходит чушь: kbdval при ненажатой кнопке !=0. получается, что вывод в PORT не меняет состояние PIN при ноге, сконфигуренной как выход?

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    scan_loop:
    in   kbdval, portA
    mov  scanval, cval	;копирование счётчика в маску
    swap scanval		;сдвиг на тетраду влево (перестановка тетрад)
    ori  scanval, 0b00001111;маска для бегущего нуля
    ori  kbdval,  0b11110000;маска для значения строк
    and  kbdval,  scanval	;старое значение строк+новое бегущего нуля
    out  portA,   kbdval	;вывод в порт
    rcall delay				;задержка на переключение ножек
    nop
    nop
    nop
    nop
    in   kbdval, pinA		;формируем скан-код из кода строки
    ori  kbdval, 0b11110000	;сбрасываем биты колонок
    com  kbdval				;инвертируем, получаем !=0 при нажатой кнопке
    cpi  kbdval, 0x00		;сравниваем с нулём
    brne kboff				;если !=0 (нажатие), то выходим из цикла
     
    lsl  cval				;двигаем единицу в счётчике
    cpi  cval, 0b00010000	;сравниваем положение счётной единицы
    breq kboff				;если 5-й шаг, то выходим
    rjmp scan_loop			;иначе прыгаем в начало цикла
    kboff: ret
    berrymorr

    косяк тут:

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

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

    DI HALT

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

    Хочешь записать в порт - пишешь в PORT
    Хочешь считать читаешь из PIN
    DDR - вход/выход.

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

    berrymorr

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

    DI HALT

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

    berrymorr

    так там же просто модуль клавы да пара светодиодных… светодиоды даже необязательны - вставать по брейкпоинтам. может, хоть Шпрот 7-й вканает?..
    не успеваю банально, опять дотянул… :/

    DI HALT

    Прогони вручную в студии. Предварительно, когда ноги встанут на подтяг протыкай сам вручную PIN’ы на то как они должны встать при этом. Т.к. студия при внутреннем подтяге пины не трогает, это надо учитывать! По идее хапсим это должен делать сам, но бывает лажает. Либо ты что то не то сконфигурировал там.

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

    Alexander Starchen

    Попробовал запустить в протеусе переделанный на мегу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

    Alexander Starchen

    заменил команду слип на rjmp main - светодиод больше не светится - все ок - это слип не работает в протеусе.

    Чем же можно потестить прошивку с учетом слип?

    DI HALT

    Гхм, а в железе собрать не айс? Или тебе надо замерить потребление? Думаю микроамперметр покаже что проц ушел в слип.


  7. Stalker46 01 Сен 2009 18:41

    А как осуществляется процедура сбора информации с компьюетрной клавы? как ее подключить к МК USB шиной?


  8. KEIS 17 Сен 2009 20:12

    ПРИВЕТ. ЧЕЛОВЕК В МИКРОКОНТРОЛЛЕРАХ Я НАЧИНАЮЩИЙ, ТАК ЧТО СТРОГО НЕ СУДИТЕ…А ВОПРОС ТАКОЙ:
    “Одна тонкость - выводы на столбцы (сканирующий порт) должны быть последовательным набором линий одного порта. То есть, например, ножки 0,1,2,3 или 4,5,6,7, или 3,4,5,6. Неважно какого порта, главное чтобы последовательно.”.

    СО СТОЛБЦАМИ ЯСНО, А ВОТ СТРОКИ МОГУТ СИДЕТЬ НА PD3,PD6? (КЛАВА НА 6 КНОПОК, 2 СТРОКИ)КАМЕНЬ - МЕГА 16…СТОЛБЦЫ КАК И СКАЗАНО: РС2-РС4


  9. KeFiR 15 Ноя 2009 5:42

    Видел прикольную клавиатуру, где инфа о нажатой кнопке передаётся по одному проводу с подтяжкой к 5 вольтам. К каждой кнопке подключён по стабилитрону с разным напряжением, а ацп считывает напряжение и по нём вычисляется какая кнопка нажата. Это конечно геморойно,но реальная экономия портов.

    Jael.Dace

    Видел подобную в телевизоре, только вместо стабилитронов были резисторные делители. Использовалось 2 провода и общий для шести кнопок. Так вот, очень не стабильная штука - много ложных срабатываний, достаточно сухим пальцем взяться за плату… Хозяева телека протёрли его влажной тряпкой, после чего начала сама добавляться громкость и переключаться каналы.

    KeFiR

    В моём случае с монитором такого бока не наблюдалось)))


  10. Jyraf 02 марта 2010 16:41

    Уважаемые знатоки! Подскажите в чем может быть затык…
    Матрица 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.

    Заранее ПАСИБА!!!

    DI HALT

    вживую прогони и узнаешь. Мало ли чего протеус там себе тупит. Я им вообще не пользуюсь.


  11. ilus 13 марта 2010 15:33

    Опять же… для клавиатур 4х4 есть вполне достойное недорогое аппаратное решение. Микруха 74С922. Она сама сканит клаву. Как замечает нажатую клавишу, опускает ногу “фас” и выдаёт 4-х битный код нажатой клавиши. Можно повесить ентот “фас” на прерывание по смене уравня (по-моему работает в лог “0″, как того требует 8051-подобный комп, а не как AVR в “1″), а потом читать данные с шины. Более подробно в даташит.


  12. R_ura 17 марта 2010 7:58

    А как посчитать кол-во байт? Может у меня и короче сканирование клавы вышла. Уж проще и понятней, так точно.. ))) Твой вариант явно не для начинающих - много сложных слов..)))
    Кстати, не увидел я защиты от дребезга. Не досмотрел или нету ?

    DI HALT

    А тут защита от дребезга осуществляется скан циклом.


  13. R_ura 17 марта 2010 8:21

    Не заметил сразу - а зачем надо код кнопки отображать на ЖКИ ? Это текстовая клавиатура, что ли? Да нет же, вроде - 4х4 кнопки всего..

    DI HALT

    Захотелось так.

Оставьте свой отзыв

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


Материалы сайта являются авторскими. Копирование и публикация материалов без активной ссылки на первоисточник запрещено.

Реклама: