Сильно умных названий приводить не буду. Не способствуют они
пониманию у новичков, да я этих названий и сам не очень знаю.
Речь пойдёт о том, как реализовать клавиатуру на
микроконтроллере (в примерах используется МК 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 ;!!!
ori temp,0b01110000 ;!!!***
out portb,temp
nop ;задержка для выполнения записи в порт
nop
in temp,pinb
ldi temp2,0b01110000 ;!!!
eor temp,temp2 ;!!!
Таким образом перед выводом в порт четыре бита для включения
столбцов инвертировались, а при чтении из порта инвертировались
три бита линеек - и старая теперь программа работала правильно.
То есть изменения программы минимальны, что, несомненно, радует
ленивого программиста :) . Комментарии к коду. Всего в AT90S1200
тридцать два регистра общего назначения (РОН). В качестве
"переменных" (написано в кавычках, т.к. название не совсем применимо
к регистрам) выбраны регистры с r16 по r31, так как их можно
загружать непосредственно, тогда как остальные - только через
аккумулятор W. Им присваиваем понятные нам идентификаторы. В
младших тетрадах line1, line2 и line3 расположен массив битов
(соответственно для трёх линеек). Инициализация массива кнопок - по
весАм битов. Назначение битов:
т.е. семь первых битов массива хранят состояние кнопок выбора
частоты, затем три бита - для формы сигнала и один бит - для
режима измерения частотомера - внутренней частоты или внешней.
Последний бит не используется и всегда сброшен. Производится
конфигурирование и инициализация портов. В 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;
}
}
}
// здесь установка сигналов.
// далее цикл замыкается