Некоторое время назад я делал проект кодера для радиоуправляемых моделей Vcoder и мне понадобилось использовать кнопки для управления работой прошивки. тогда я не сильно задумывался о usability своей прошивки, и написал драйвер который организовывал опрос кнопок анализирую время их удержания. Фактически алгоритм выглядел так 1. Проверяем нажата ли хоть одна кнопка: -прочитали байт из порта к которому подключены кнопки -проверим по маске нажата ли хоть одна кнопка --если не нажата - то счетчик удержания нажатия кнопки =0, и идем на выход из подпрограммы 2. если хоть одна кнопка нажата то: - запоминаем состояние порта (битовую конфигурацию нажатых и не нажатых кнопок) - увеличиваем счетчик проверки удержания кнопки. 3. Если счетчик удержания кнопки достиг некоторого порогового значения - то выставляем флаг того что кнопка нажата и сбрасываем счетчик удержания. В принципе работал этот принцип примерно так же как работает клавиатура на компьютере: при нажатии на кнопку происходит замер длительности нажатия для устранения дребезга, и если кнопка какое то время не отпускалась то генерируется нажатие, далее по истечении еще какого то промежутка времени, если кнопка не отпускалась то нажатие генерируется снова... удобно? - тогда показалось что вполне ! драйвер кнопок с таким алгоритмом и был реализован в прошивке VCoder Но прошло некоторое время и это решение показало себя не очень удобным.
Например, нажатие на кнопку @MENU@ приводит к переходу в меню настроек аппаратуры, а в самом меню к переходу по выбранному пункту меню.. и пользователь аппаратуры зачастую чуть передержав кнопку @MENU@ входил не только в меню, но и в первый его пункт (обычно это был пункт RESET MODEL - который сбрасывал все настройки модели !)
Сначала проблему решали полумерами в виде внедрения диалога проверки в пункт RESET MODEL (требовалось ответить YES для сброса), пункт YES конечно же располагался у кнопки @MENU@...
Потом этот пункт переместили на кнопку @EXIT@, а пункт отказа от сброса (NO) разместили у кнопки @MENU@... но ясности и простоты это не добавило, всегда находился пользователь который перепутал бы нажимаемые кнопки, либо не вчитывался особо в написанное и все таки сбрасывал настроенную модель...
постепенно пользователи привыкли, однако думаю что моя икота иногда вызывается гневом именно пользователей прошивки Vcoder при работе их с кнопкой @MENU@
Через какое то время я начал писать еще одну прошивку (A-Coder) и тогда решил что с этим нужно что то делать - негоже иметь такие ляпы в такой казалось бы простой задачке как опрос кнопок управления.
И придумался мне следующий функционал: Должны быть кнопки которые при нажатии генерируют автоповтор (после нажатия и удержания кнопки генерируется повторяющееся коды нажатой кнопки), а должны быть те кто не генерируют (то есть при нажатии и удержании кнопки генерируется только одно нажатие, и повторного, сколько бы мы кнопку не удерживали - не будет)
Первый режим (автоповтор при удержании) я назвал push\hold, второй режим, при котором нажатие генерируется только один раз, независимо от времени удержания - push\up
Этот функционал реализовывался несколько раз и алгоритм каждый раз мне нравился все больше и больше :) предлагаю вам текст этой подпрограммы для использования в ваших проектах
константы:конфигурация подключения кнопок:-
PIN_MN_KEY - порт PINx к которому подключены кнопки, все кнопки управления должны быть подключены к одному порту, но не обязательно к пинам порта идущим подряд -
MN_KEY_MASK - битовая маска кнопок порта (соответствующие биты кнопок установлены в "1") -
MN_KEY_UP, MN_KEY_DN, MN_KEY_LF, MN_KEY_RG - номера бит к которым подключены кнопки, внимание в константах именно номера бит!! внешние переменные (размещаются в DSEG, изменяются другой подпрограммой) переменные драйвера (размещаются в DSEG): - KR1_KEY_MASK - входной параметр: маска кнопок которые опрашиваются в режиме push\hold
- KR1_KEY_READ - выходной параметр: зарегистрированный код нажатой кнопки (выходная переменная модуля !)
- KR1_OLD_KEY - временно хранимый код нажатой кнопки
- KR1_PH_ONTIME - количество циклов вызова для регистрации кнопки в режиме push\hold
- KR1_PU_ONTIME - количество циклов вызова для регистрации кнопки в режиме push\up
- KR1_PU_WAIT - вспомогательная переменная, флаг того что кнопка в режиме push\hold еще не отпускалась
; key_reader.asm - модуль чтения нажатых кнопок меню
.equ COUNTER = T1_COUNTER ; имя переменной-счетчика в DSEG KEY_READER: ; прочитаем состояние кнопок IN R16 , PIN_MN_KEY ANDI R16 , MN_KEY_MASK ; маска кнопок меню ; проверим нажата ли хоть одна кнопка
CPI R16 , (1<<MN_KEY_UP)|(1<<MN_KEY_DN)|(1<<MN_KEY_LF)|(1<<MN_KEY_RG) BREQ KR1_EXIT ; ни одна из кнопок не нажата, идем на выход ; зарегистрировано нажатие какой-то кнопки LDS R17 , KR1_OLD_KEY ; нажатые кнопки в прошлый период CP R16 , R17 ; сравним текущие и старые нажатые кнопки BREQ KR1_TIMER_LIST ; кнопки удерживаются - проверим время ; нажатые кнопки сейчас и нажатые ранее кнопки - отличаются
STS KR1_OLD_KEY , R16 ; сохраним нажатые кнопки LDS R16 , COUNTER STS KR1_KEY_COUNTER , R16 ; сохраним момент нажатия RJMP KR1_END ; идем на выход KR1_TIMER_LIST: ; кнопка нажата и удерживается, проверим достаточное ли времени прошло для
; регистрации нажатия в обоих режимах сканирования клавиатуры KR1_TIMER_PH_LIST: ; режим push\hold
; проверка достаточности нажатия в режиме push\hold MOV R15 , R16 ; сохраним нажатые кнопки меню LDS R17 , KR1_KEY_MASK ; прочитаем маску чтения push\hold
AND R16 , R17 ; наложим маску CP R16 , R17 ; есть кнопки нажатые для этого режима? BREQ KR1_TIMER_PU_LIST ; перейдем к проверке нажатых кнопок в режиме ; push\up ; у нас нажаты кнопки в режиме push\hold LDS R16 , COUNTER ; загрузим текущий счетчик LDS R17 , KR1_KEY_COUNTER ; загрузим счетчик когда было зарегистрировано ; нажатие кнопок SUB R16 , R17 ; определим время нажатия LDS R17 , KR1_PH_ONTIME ; берем необходимую длительность нажатия CP R16 , R17 BRLO KR1_TIMER_PH_LIST_NO ; кнопка удерживается недостаточное время, выходим KR1_REG_KEY_PRESS: ; кнопка удерживается достаточное время, зарегистрируем нажатие STS KR1_KEY_READ , R15 ; сохраним нажатые кнопки LDI R16 , (1<<MN_KEY_UP)|(1<<MN_KEY_DN)|(1<<MN_KEY_LF)|(1<<MN_KEY_RG) STS KR1_OLD_KEY , R16 ; сбросим временный код нажатых кнопок RJMP KR1_END ; идем на выход KR1_TIMER_PU_LIST: ; режим push\up
LDS R16 , KR1_PU_WAIT ; проверим не ожидаем ли мы отпускания кнопок CPI R16 , 1 ; меню после регистрации нажатия BREQ KR1_PROC_WAIT ; выходим ожидая отпускания кнопок ; проверим нужное время ли удерживается кнопка LDS R16 , COUNTER ; загрузим текущий счетчик LDS R17 , KR1_KEY_COUNTER ; загрузим счетчик когда было зарегистрировано ; нажатие кнопок SUB R16 , R17 ; определим время нажатия LDS R17 , KR1_PU_ONTIME ; берем необходимую длительность нажатия CP R16 , R17 BRLO KR1_TIMER_PH_LIST_NO ; кнопка удерживается недостаточное время, выходим ; кнопка удерживается достаточное время установим флаг ожидания отпускания LDI R16 , 1 STS KR1_PU_WAIT , R16 RJMP KR1_REG_KEY_PRESS ; и зарегистрируем нажатие кнопки
KR1_EXIT: ; выход, по отпусканию кнопок STS KR1_OLD_KEY , R16 ; сбросим временные нажатые кнопки LDI R16 , 0 STS KR1_PU_WAIT , R16 ; сбросим флаг ожидания отпускания (уже отпустили) ; для режима push\up KR1_END: ; выход
KR1_TIMER_PH_LIST_NO: ; выходим по недостаточному времени удержания кнопок KR1_PROC_WAIT: ; ожидаем отпускание нажатых кнопок в режиме push\up RET
И следом идет процедура которая считывает код кнопок нажатие которых было зарегистрировано
GET_KEY: ; функция чтения считанной кнопки PUSH R17 LDS R16 , KR1_KEY_READ LDI R17 , (1<<MN_KEY_UP)|(1<<MN_KEY_DN)|(1<<MN_KEY_LF)|(1<<MN_KEY_RG) STS KR1_KEY_READ , R17 POP R17 RET
Файл драйвера на языке ассемблера можно скачать здесь -> http://vg.ucoz.ru/load/iskhodnye_teksty_programm_na_assemblere_avr/drajver_knopok_ustrojstva/4-1-0-8
|