Динамический опрос клавиатуры - Направления и технологии - Shelek
Сильно умных названий приводить не буду. Не способствуют они пониманию у новичков, да я этих названий и сам не очень знаю.
Речь пойдёт о том, как реализовать клавиатуру на микроконтроллере (в примерах используется МК AT90S1200 фирмы Atmel) и какие при этом могут возникнуть трудности.
Сначала о железе. Способ подключения кнопок к портам МК выбирается на усмотрение разработчика в зависимости от требуемого количества кнопок и от количества выводов портов. Ниже описаны только два способа.


Рисунок 1.

При небольшом количестве кнопок и равном или большем количестве пинов (пин - это вывод) МК, кнопки подключаются непосредственно к входам. На рисунке 1 - схема подключения кнопки, при которой в ненажатом положении на вход подаётся логическая единица (напряжение 5 вольт относительно общего провода), а в нажатом - логический ноль (уровень напряжение 0 вольт относительно общего провода). Резистор "подтягивает" вход МК к единице. Это делается для того, чтобы избежать так называемого третьего состояния (состояние "Z" или просто "обрыв") на входе. При наличии состояния Z на входах возникают помехи - очень короткие импульсы тока, которые могут свести систему с ума, а то и вообще вывести из строя. Помехи возникают от статического электричества, от прикосновения пальцами к проводникам, от работающих поблизости приборов. Подтяжка работает так: в ненажатом состоянии сопротивление между нулём и входом очень велико, и через резистор на входе создаётся потенциал, воспринимаемый МК как логическая единица. При нажатии картина меняется: теперь резистор - относительно бесконечное сопротивление, а на пине - потенциал нуля.


Рисунок 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
; 76543210
ldi temp,0b01111111
out ddrd,temp
;инициализация портов
; 76543210
ldi temp,0b11110000
; 76543210
ldi temp2,0b00001111 ;!!!
eor temp,temp2 ;!!!
; 76543210
ori temp,0b01110000 ;!!!***
out portb,temp
; 76543210
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 ;до включения кнопка была отжата
;-----------------------------------------------------------------------
;начальное выставление сигналов
rjmp setsignals
;-----------------------------------------------------------------------
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... (RTFM!!! :) )
Т.к. при притяжке к нулю столбцы включаются единицей, то при притяжке к нулю были вставлены строки, отмеченные ";!!!":

Код:
;инициализация портов
...
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 ;!!!

Таким образом, перед выводом в порт четыре бита для включения столбцов инвертировались, а при чтении из порта инвертировались три бита линеек - и старая теперь программа работала правильно. То есть изменения программы минимальны, что, несомненно, радует ленивого программиста (меня, меня! :) ).

[p]Комментарии к коду.[/p] Всего в 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) и представляет собой бесконечный цикл

* опрос клавы с защитой от дребезга
* установка выходных сигналов (бит 7 порта B и весь порт D)
* переход к 1)


Nc - счётчик повторений опроса. Регистр groupnum_port_bit ("номер" активного столбца) содержит бит-указатель на выходной бит порта активного столбца и одновременно на соотв. бит регистра активной линейки. Регистр inmask ("номер" активной линейки) содержит бит-маску для входного бита активной линейки. Регистр line_num_integer в некотором смысле дублирует inmask и содержит адрес регистра активной линейки для доступа по косвенной адресации. Вот некий эквивалент такого опроса на C++

Код:
bool prev; //этой переменной нет в нашей программе на ассемблере
for(;;)
{
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];

line123[inmask *4+ groupnum_port_bit]=
клавиатура[inmask *4+ groupnum_port_bit];

if (prev== line123[inmask *4+ groupnum_port_bit] )
{
Nc=255;
}
}
}
}
...
// здесь установка сигналов для панели индикации.
...

}// далее основной цикл замыкается



Автор: Алексей1153
Information
  • Posted on 01.02.2010 00:53
  • Просмотры: 4158