InfoCity
InfoCity - виртуальный город компьютерной документации
Реклама на сайте







Размещение сквозной ссылки

 

Динамический опрос клавиатуры

Алексей

Сильно умных названий приводить не буду. Не способствуют они пониманию у новичков, да я этих названий и сам не очень знаю.
Речь пойдёт о том, как реализовать клавиатуру на микроконтроллере (в примерах используется МК 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;
}
}
}
// здесь установка сигналов.
// далее цикл замыкается



Реклама на InfoCity

Яндекс цитирования



Финансы: форекс для тебя








1999-2009 © InfoCity.kiev.ua