Динамический опрос клавиатуры
Сильно умных названий приводить не буду. Не
способствуют они пониманию у новичков, да я этих
названий и сам не очень знаю. Речь пойдёт о
том, как реализовать клавиатуру на
микроконтроллере (в примерах используется МК
AT90S1200 фирмы Atmel) и какие при этом могут
возникнуть трудности. Сначала о железе. Способ
подключения кнопок к портам МК выбирается на
усмотрение разработчика в зависимости от
требуемого количества кнопок и от количества
выводов портов. Ниже описаны только два способа.
Рисунок 1.
При небольшом количестве
кнопок и равном или большем количестве пинов (пин
- вывод) МК, кнопки подключаются непосредственно к
входам. На рисунке 1 - схема подключения кнопки,
при которой в ненажатом положении на вход подаётся
логическая единица (напряжение 5 вольт
относительно общего провода), а в нажатом -
логический ноль (уровень напряжение 0 вольт
относительно общего провода). Резистор
"подтягивает" вход МК к единице. Это делается для
того, чтобы избежать т.н. третьего состояния
(состояние "Z" или просто "обрыв") на входе. При Z
на входах возникают помехи
- очень короткие импульсы тока, которые могут
свести систему с ума. Помехи возникают от
статического электричества, от прикосновения
пальцами к проводникам, от работающих поблизости
приборов. Подтяжка работает так: в ненажатом
состоянии сопротивление между нулём и входом очень
велико, и через резистор на входе создаётся
потенциал, воспринимаемый МК как лог. 1. При
нажатии картина меняется - теперь резистор -
относительно бесконечное сопротивление, а на пине
- потенциал нуля.
Рисунок 2.
Матрица кнопок позволяет
использовать кнопок вдвое больше количества
доступных пинов портов(если матрица квадратная).
На рисунке 2 изображена матрица для клавиатуры из
11 кнопок, взятая из реального устройства. Для
работы с ней используется 7 пинов порта B. Четыре
пина (PB0-PB3) программно сконфигурированы как
выхода и три пина (PB4-PB6) - как входы.
Резисторы показаны как внешние, но имеется
возможность программно подключить внутренние
подтягивающие к единице резисторы, а внешние при
этом можно убрать. Принцип опроса матрицы
таков. Группы кнопок условно разбиты на
"линейки" и "колонки". Сначала программно на
выходах PB1-PB3 выставляются единицы, а на PB0 -
ноль. При этом включена первая колонка. Если
сейчас нажать в любом сочетании кнопки этой
колонки, то на входах сформируется трёхбитный код,
который программа сохраняет в массиве. Затем
первая колонка отключается, и подключается
следующая, и т.д. Также полезно избавляться от
дребезга контактов. Это не
требуется тогда, когда нужно, к примеру, просто
зажечь светодиод. Но необходимо, если считанный
сигнал МК использует для управления какой-либо
логикой. Дребезг - это механическое отскакивание
металлических контактов при замыкании, при этом
формируется пачка коротких импульсов, что не есть
хорошо. Для формирования "чистого" фронта из
аппаратных средств чаще всего используются
повторители с гистерезисом (здесь о нём не будем).
Если время исполнения программы некритично, то от
дребезга можно избавиться программно, что здесь и
сделано. Применённый здесь алгоритм защиты от
дребезга таков: перед опросом клавиатуры
сбрасывается некоторый флаг, показывающий
изменение текущего состояния клавы по с равнению с
предыдущим. Каждый считанный бит перед записью в
массив сравнивается со старым значением это бита в
массиве, и если они не равны, то флаг
устанавливается. Если после опроса всей клавиатуры
флаг установлен, то, возможно был дребезг, и опрос
начинается заново. Если состояние кнопок не
поменялось N раз, то считается, что дребезг
окончился. N - подбирается экспериментально.
Вот код, осуществляющий опрос клавиатуры.
Синим цветом выделены
участки кода, которые относятся к избавлению от
дребезга.
.include "1200def.inc"
;16...31 можно загружать ldi
.DEF line1=r16 ;--
.DEF line2=r17 ; -- массив битов для хранения нажатых кнопок
.DEF line3=r18 ;--
.DEF inmask=r19 ;маска ввода
.DEF Nc=r20 ;счётчик повторений сканирования матрицы кнопок
.DEF temp=r21 ;--
.DEF temp1=r22 ; -- врЕменные регистры
.DEF temp2=r23 ;--
.DEF inside_pushed=r24 ;триггер нажатия кнопки "внутр"
.DEF groupnum_port_bit=r25 ;номер группы столбца(номер бита порта) и
.DEF line_num_integer=r26 ;адрес соответствующего регистра line
.EQU safecount=255 ;количество повторений опроса для устранения дребезга
.EQU subsafecount=150 ;дополнительная задержка на каждое повторение
;-------------------------------------------------------------------------
;векторА прерывания
.org 0x00
rjmp start
rjmp start
rjmp start
rjmp start
;запрет прерываний
start: cli
;инициализация защёлок портов
; 76543210
ldi temp,0b10001111
out ddrb,temp
ldi temp,0b01111111
out ddrd,temp
;инициализация портов
; 76543210
ldi temp,0b11110000
ldi temp2,0b00001111 ;!!!
eor temp,temp2 ;!!!
ori temp,0b01110000 ;!!!***
out portb,temp
ldi temp,0b00001110
out portd,temp
;инициализация массива кнопок
8 4 2 1
ldi line1,0+0+0+1 ;line1<4,0>= 1кГц |100Гц |10Гц |1Гц
ldi line2,8+0+0+0 ;line2<4,0>= sin |1МГц |100кГц |10кГц
ldi line3,0+4+0+0 ;line3<4,1>= 0 |внут |треуг |прямоуг
ldi inside_pushed,0 ;до включения кнопка была отжата
;-----------------------------------------------------------------------
opros: ;опрос матрицы кнопок
;установить номер группы столбца =1
ldi groupnum_port_bit,0b00000001
setmask:
;задать 3-битную маску ввода для опроса 1-й кнопки текущей группы кнопок
ldi inmask,0b00010000
ldi line_num_integer,16 ;адрес line1
;--------------
curropros:
;опрос состояния текущей кнопки с защитой от дребезга
;и сохранением данных о состоянии кнопки
safeopros1:
;установить содержимое счётчика опросов равным safecount
ldi Nc,safecount
safeopros2:
;опрос сотояния текущей кнопки
in temp,pinb
andi temp,0b10000000 ;оставляем "внутр (сигн)" неизменённым
or temp,groupnum_port_bit
ldi temp2,0b00001111 ;!!!
eor temp,temp2 ;!!!
ori temp,0b01110000 ;!!!***
out portb,temp
nop ;задержка для выполнения записи в порт
nop
in temp,pinb
ldi temp2,0b01110000 ;!!!
eor temp,temp2 ;!!!
and temp,inmask ;temp - состояние кнопки (bool)
;проверка: состояние кнопки изменилось?
mov r30,line_num_integer
ld temp1,z
and temp1,groupnum_port_bit ;temp1 - старое сотояние (бит из массива)
;проверка temp==temp1
cpi temp,0
brne safeopros4
cpi temp1,0
brne safeopros_false
rjmp safeopros_true
safeopros4:
;temp!=0
cpi temp1,0
breq safeopros_false
safeopros_true:
rjmp safeopros3 ;да: переход safeopros3
safeopros_false:
ld temp2,z ;нет: установить значение предыдущего
eor temp2,groupnum_port_bit ;состояния равным текущему(инверт)
st z,temp2
rjmp safeopros1 ;переход safeopros1
safeopros3:
;задержка перед уменьшением содержимого счётчика опросов
ldi temp2,subsafecount
safeopros5:
dec temp2
brne safeopros5
;уменьшить содержимое счётчика опросов на 1
dec Nc
;проверка:содержимое счётчика стало равным нулю?
brne safeopros2 ;нет: переход safeopros2
;да: данные о состоянии кнопки сохранены, дребезга нет
;задать значение 3-битной маски ввода для опроса
;следующей кнопки текущей группы
;проверка:маска ввода настроена на последнюю (3)кнопку группы?
cpi inmask,0b01000000
breq endgroup ;да: переход endgroup
lsl inmask ;нет: задать значение 3-битной маски ввода ля опроса
inc line_num_integer ;следующей кнопки текущей группы (сдвинуть влево)
rjmp curropros
endgroup:
;проверка: проверена последняя группа (4-я)?
cpi groupnum_port_bit,0b00001000
breq setsignals ;да: переход setsignals
lsl groupnum_port_bit ;нет: "увеличить на 1" содержимое счётчика групп
rjmp setmask
;--------------------------------
setsignals:
;выставить выходные сигналы в выходном порту
;в соответствии с состоянием клавиатуры
...
...
...
end_of_set:
;переход opros
rjmp opros
Возможно вы заметите некоторую нелогичность
- в массиве нажатая кнопка - это 1, отпущ
енная - 0, хотя должно, вроде бы быть
наоборот. Это связано с моей ошибкой при
разработке - программа была написаны для входов,
подтянутых к нулю (тогда ещё использовались
внешние резисторы). Когда программа заработала
правильно, я стал подключать внутренние резисторы.
Обратите внимание на строки, отмеченные ";!!!***":
ori temp,0b01110000 ;!!!***
Так перед выводом регистра temp в порт B
обеспечивается постоянное подключение внутренних
резисторов. Но, убрав внешние резисторы, я
получил фигню. Позже выяснилось, что у AT90S1200
внутренние резисторы притянуты к 1... Т.к. при
притяжке к нулю столбцы включаются единицей, то при притяжке к нулю
были вставлены строки, отмеченные ";!!!":
;инициализация портов
...
ldi temp2,0b00001111 ;!!!
eor temp,temp2 ;!!!
...
...
ldi temp2,0b00001111 ;!!!
eor temp,temp2 ;!!!
ori temp,0b01110000 ;!!!***
out portb,temp
nop ;задержка для выполнения записи в порт
nop
in temp,pinb
ldi temp2,0b01110000 ;!!!
eor temp,temp2 ;!!!
Таким образом перед выводом в порт четыре
бита для включения столбцов инвертировались, а
при чтении из порта инвертировались три бита
линеек - и старая теперь программа работала
правильно. То есть изменения программы
минимальны, что, несомненно, радует ленивого
программиста :) . Комментарии к коду. Всего в
AT90S1200 тридцать два регистра общего назначения
(РОН). В качестве "переменных" (написано в
кавычках, т.к. название не совсем применимо к
регистрам) выбраны регистры с r16 по r31, так как
их можно загружать непосредственно, тогда как
остальные - только через аккумулятор W. Им
присваиваем понятные нам идентификаторы. В
младших тетрадах line1, line2 и line3 расположен
массив битов (соответственно для трёх линеек).
Инициализация массива кнопок - по весАм битов.
Назначение битов:
весА битов|___8__|__4_____|__2_______|__1_____
___________________________________________
line1<4,0>_|_1кГц_|_100Гц_|__10Гц___|_1Гц____
line2<4,0>_|_sin___|_1МГц_|__100кГц_|_10кГц__
line3<4,0>_|_0____|_внут__|___треуг__|_прямоуг
т.е. семь первых битов массива хранят
состояние кнопок выбора частоты, затем три бита -
для формы сигнала и один бит - для режима
измерения частотомера - внутренней частоты или
внешней. Последний бит не используется и всегда
сброшен. Производится конфигурирование и
инициализация портов. В portb биты 4,5,6
настраиваются на вывод записью в регистр-защёлку
ddrb нулей на в соотв. биты. Для включения
внутренних подтягивающих резисторов в portb соотв.
биты устанавливаем. Программа не использует
обработку прерываний (они запрещены командой cli)
и представляет собой бесконечный цикл
1)
опрос клавы с защитой от дребезга 2) установка
выходных сигналов (бит 7 порта B и весь порт D)
3) переход к 1)
Nc - счётчик
повторений опроса. Регистр groupnum_port_bit
("номер" активного столбца) содержит бит-указатель
на выходной бит порта активного столбца и
одновременно на соотв. бит регистра активной
линейки. Регистр inmask ("номер" активной линейки)
содержит бит-маску для входного бита активной
линейки. Регистр line_num_integer в некотором
смысле дублирует inmask и содержит адрес регистра
активной линейки для доступа по косвенной
адресации. Вот некий эквивалент опроса на C++
bool prev; //этой переменной в проге на ассемблере нет
for(Nc=255; Nc; Nc--)
{
for(groupnum_port_bit=0; groupnum_port_bit<4; groupnum_port_bit++)
{
for(inmask=0; inmask<3; inmask++)
{
prev=line123[inmask *4+ groupnum_port_bit];
if (prev== line123[inmask *4+ groupnum_port_bit]=
клава[inmask *4+ groupnum_port_bit] )
Nc=255;
}
}
}
// здесь установка сигналов.
// далее цикл замыкается
Автор: Алексей1153
Источник: www.realcoding.net
|