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 байта. Кто сможет меньше?

Запись опубликована в рубрике AVR. Учебный курс с метками , , , , . Добавьте в закладки постоянную ссылку.

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

  1. Xenom0rph говорит:

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

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

  3. Alexiy говорит:

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

    • DI HALT говорит:

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

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

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

  4. Alexiy говорит:

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

    • DI HALT говорит:

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

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

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

  5. paschen говорит:

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

    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, 0x5F ; а тут low(RAMEND)
            loop: ; ну а тут тот самый цикл
            rcall scan
            clt
            rjmp loop

  6. berrymorr говорит:

    здравствуйте, давно не был)) прошу свежего стороннего взгляда на код, т.к. запутался, моск клинит уже сильно, может, и не там вообще смотрю… :/
    или я недопонял основные принципы сканирования, т.к. в аврстудии вместе с хапсимовской клавой происходит чушь: 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,0x0F ;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,0x0F ;change port B I/O to
            out DDRB,temp ;find column press
            ldi temp,0xF0 ;enable pull ups and
            out PORTB,temp ;write 0s to rows
            rcall settle ;allow time for port to settle
            sbis PINB,COL1 ;find column of keypress
            ldi temp,0 ;and set COL pointer
            sbis PINB,COL2
            ldi temp,1
            sbis PINB,COL3
            ldi temp,2
            sbis PINB,COL4
            ldi temp,3
            add key,temp ;merge ROW and COL for pointer
            ldi temp,0xF0 ;reinitialise port B as I/O
            out DDRB,temp ; 4 OUT 4 IN
            ldi temp,0x0F ;key columns all low and
            out PORTB,temp ;active pull ups on rows enabled
            out SREG,status ;restore status register

            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 говорит:

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

  8. KEIS говорит:

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

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

  9. KeFiR говорит:

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

    • Jael.Dace говорит:

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

  10. Jyraf говорит:

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

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

  11. ilus говорит:

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

  12. R_ura говорит:

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

  13. R_ura говорит:

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

  14. 486DX говорит:

    А можно по подробнее рассказать про файл конфигурации клавиатуры?

  15. demiurg1978 говорит:

    Мне вот жутко интересно. А где в этом примере обрабатывается удержание, автоповтор, комбниции клавиш….

      • demiurg1978 говорит:

        DI, а почему бы не написать статью по этому поводу?

        • DI HALT говорит:

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

          • demiurg1978 говорит:

            Как ты думаешь, нужно ли для кнопок делать битовое поле? То есть, если клавиатура 4х4, то получается 16 битов. Я поспорил. Считаю, что достаточно кода полученного с матрицы. 0b01110111 это верхняя левая кнопа. Если нажать две три кнопы, то все равно можно понять, какие кнопы нажаты. Например 0b00110011 это нажаты две верхние левые кнопы. Маски 0b01110111
            и 0b10111011. Соответственно верхняя самая левая и вехняя вторая слева кнопы.
            К чему весь этот спич. Я считаю не целесообразным делать отдавать 8 байтов, если клава будет 8х8. Соответственно раздуется код обработки 64 битов.

          • DI HALT говорит:

            Очень сильно зависит от реализации. И того чего ты хочешь достичь. Если надо отслеживать нажатие клавиши, длительное нажатие и тыды, то битовое поле возможно необходимо. Если же просто опрос, то дублировать клавиатуру в памяти смысла нет. Но опять же от реализации зависит. Если клава обрабатывается в прерывании, а задача клавиатуры по диспетчеру ядра, то битовое поле нужно, для записи скан кода.

  16. alkinoy говорит:

    а как насчет такого варианта сканирования матрицы кнопок:
    1. на строках выставили 0. столбцы подтянули к питанию. прочитали столбцы.
    2. на столбцах выставили 0. строки подтянули к питанию. прочитали строки.

    получили скан код.

Добавить комментарий