Notice: Memcache::get(): Server localhost (tcp 11211) failed with: Connection refused (111) in /home/a146/www/we.easyelectronics.ru/engine/lib/external/DklabCache/Zend/Cache/Backend/Memcached.php on line 134
AVRASM: Библиотека процедур для интеллектуальной обработки ВВОДА в МК: событий от Кнопок и Энкодеров (часть 2: порядок внедрения и использования) / AVR / Сообщество EasyElectronics.ru

AVRASM: Библиотека процедур для интеллектуальной обработки ВВОДА в МК: событий от Кнопок и Энкодеров (часть 2: порядок внедрения и использования)

AVR
Большинству микроконтроллерных устройств требуется поддерживать интерфейс с пользователем-человеком: для вывода используются светодиоды или дисплеи, а для ввода — обычно, традиционные Кнопки и Энкодеры.
Данная реализация «Библиотеки процедур для интеллектуальной обработки ВВОДА» написана на языке ассемблера, для компилятора AVRASM. Соответственно, она предназначена для разработки программных прошивок (firmware) на языке ассемблер, для микроконтроллеров Atmel AVR (8-bit).

Содержание:



Это продолжение…
< — Возврат к предыдущей части статьи



Порядок внедрения библиотеки


Скачайте
дистрибутив библиотеки «celeronkeyinputlib.inc».
Скачайте, по зависимостям, дистрибутив библиотеки «macrobaselib.inc».

Из них возьмите:
  1. Файл <celeronkeyinputlib.inc> поместите в папку с вашим проектом.
    (Здесь расположена процедура KEY_SCAN_INPUT, код которой вам следует переписать под свою схему).
  2. Из файла <data.inc> возьмите только секцию <.DSEG>, с определением регистров данных, констант и символов — интегрируйте в «секцию данных» вашей программы.
    (Эти определения вам следует переделать под свою схему.)
    Примечание: Здесь, я могу лишь рекомендовать, но не обязываю: хранить определения данных в отдельном исходном файле «data.inc»...
  3. Файл внешней используемой библиотеки <macrobaselib.inc> поместите в папку с вашим проектом, как есть.
    (Примечание: Использование этой библиотеки рекомендует/накладывает на вас определённый стиль кодирования.)


Подключите приведенные библиотеки, добавив такие строки в основной ASM-файл (см. пример):

; В самом начале программы:
.include "macrobaselib.inc"		; Библиотека базовых Макроопределений.

; В сегмент кода (CSEG):
.include "celeronkeyinputlib.inc"	; Библиотека процедур для интеллектуальной обработки ВВОДА: сканирование Кнопок и Энкодеров.


Сконфигурируйте один из аппаратных таймеров вашего микроконтроллера, или обеспечьте иной механизм, для периодического вызова обработчика KEY_ENHANCE_TIME_FOR_ALL_BUTTONS (точно, каждые 0.5 сек):
; Прерывание: отсчёт полусекунд
TIMER0_OVERFLOW_HANDLER:
		; Сохранить в Стеке регистры, которые используются в данном обработчике:
		PUSHF		; сохраняет часто используемые регистры: SREG и TEMP (TEMP1)
		PUSH	temp2	; регистр используется в INVB и др.
		PUSH	temp3	;       регистр используется в KEY_ENHANCE_TIME_FOR_ALL_BUTTONS
		PUSH	R28	; (YL)	регистр используется в KEY_ENHANCE_TIME_FOR_ALL_BUTTONS
		PUSH	R29	; (YH)	регистр используется в KEY_ENHANCE_TIME_FOR_ALL_BUTTONS

		; Головная процедура конвейера обработки кнопок:	Наращивает таймеры для удерживаемых кнопок (запускать каждые полсекунды)
		RCALL	KEY_ENHANCE_TIME_FOR_ALL_BUTTONS

		; Выход из обработчика
		POP	R29
		POP	R28
		POP	temp3
		POP	temp2
		POPF
		RETI


Допилите библиотеку <celeronkeyinputlib.inc> под вашу схему:
Приведенные здесь процедуры являются универсальными, их код модифицировать [обычно] не требуется! Единственная процедура, в этой библиотеке, код которой требуется адаптировать к вашей конкретной физической схеме — это KEY_SCAN_INPUT (через неё осуществляется связь с физическими каналами ввода).

Также, под вашу конкретную физическую схему, требуется адаптировать блок определения данных в <.DSEG> из файла <data.inc> (определения блоков регистров Интегратора, статусных регистров Кнопок и Энкодеров, и некоторые константы):
  • Блоки регистров в DSEG следует располагать последовательно и начинать с характерных «системных меток»: DInputIntegrator, DEncoderStatus, DButtonStatus.
    (Примечание: в блоках важен лишь порядок и количество! названия самих прикладных регистров — произвольные, придумываете сами, как удобно использовать в прикладном коде.)
  • Затем, если требуется, подстройте «режимные константы»: CButtonLatchDepth, CEncoderLatchDepth, CShortButtonTouchDuration.
  • Наконец, в зависимости от реализации кода KEY_SCAN_INPUT, здесь может понадобится ввести дополнительные данные и определения…
    (Например, если вы используете клавиатуру на сдвиговых регистрах, как в стандартном примере, то следует сконфигурировать ещё и количества входных физических каналов/кнопок/энкодеров: CEncoderInputChannelCount, CButtonInputChannelCount, CSkipInputChannelCount...)


Спроектируйте и закодируйте процедуры с обработчиками событий: код прикладных реакций на нажатия кнопок, энкодеры, обслуживание интерфейса… Предположим, для примера, что все обработчики расположены в процедуре SWITCH_MODES, или вызываются из неё (удобно иметь одну общую точку входа в обработчики событий).

В прошивке требуется разместить код, запускающий сканирование Кнопок и переключение Режимов — он должен запускаться часто! Например:
; (Суперцикл: сканирование Кнопок и переключение Режимов)
MAIN:
		; Сканирование Кнопок
		OUTI	DKeyScanCnt,	20	; DKeyScanCnt = количество циклов "сканирования кнопок".	(а затем будет один цикл "реакции на события")
	LoopKeyScan__MAIN:
		RCALL	KEY_SCAN_INPUT		; Головная процедура конвейера обработки кнопок: Сканирует физические кнопки и устанавливает их статус-регистры (запускать часто)
		DEC8M	DKeyScanCnt
		BRNE	LoopKeyScan__MAIN
		
		; Переключение Режимов ("реакция на события")
		RCALL	SWITCH_MODES		; Важно: DKeyScanCnt, выше, следует подбирать так, чтобы "SWITCH_MODES" выполнялся ~10-20раз/сек. Тогда будет эргономичная "инерционность" реакции кнопок...
		
		RJMP	MAIN						
; (обработка событий завершена)




Методика проектирования прикладного кода (обработчики событий)


Кнопки в интерфейсе Устройства

При разработке пользовательского интерфейса Устройства, первым делом, планируются все возможные режимы интерфейса и условия переключения между ними.

Схема переключения режимов интерфейса (
«машина состояний» конечного автомата) — удобнее всего описывается UML диаграммой:
Схема переключения режимов Устройства
(Примечание для любопытствующих: я использовал UML-редактор «Altova MissionKit UModel» — самый безглючный, функциональный и эргономичный, среди всех что я пробовал. Правда, требует лечение...)

Для описания реального сложного устройства — обычно, требуется множество подобных диаграмм (для всех подсистем, и их детализаций). Выше представлена только одна «Диаграмма состояний в нотации UML». И мы рассмотрим только один аспект из этой диаграммы, обведенный красным овалом: часть подсистемы «переключение Функций».

Для тех, кто не совсем понял нарисованное, поясню задачу:
  • На передней панели Устройства есть три физические кнопки: «DButtonRTCStatus», «DButtonTimer1Status», «DButtonTimer2Status» (я перечислил названия соответствующих им «статусных регистров» — это, фактически, внутренние имена кнопок, для программы)
  • Я хочу запрограммировать реакции на следующие «жесты» с этими кнопками: короткое законченное нажатие «BSC_ShortPress» (клик), и длительное удержание «BSC_LongHold» (дольше 1.5 сек). В первом случае, событие сработает как только пользователь отпустит кнопку, после короткого удержания. Во втором случае, событие сработает ещё до отпускания кнопки, если пользователь будет удерживать её дольше 1.5 сек.
    Замечу: Событие длинного нажатия на кнопку «BSC_LongPress» (нажать + долго подержать + отпустить), в данном контексте, бессмысленно — не сработает никогда, потому что раньше отработает «BSC_LongHold». Такие вещи нужно учитывать, при планировании...
  • Как видим из диаграммы, эти кнопки имеют «глобальную область действия»: отрабатывают из любого режима. Каждая кнопка жёстко связана с конкретной «функцией», в которую следует переключить интерфейс, при её нажатии. Поэтому прикладной код будет довольно простым: условие срабатывания «обработчика события» не содержит проверки текущего режима, а содержит только проверки статус-кодов кнопок.


Итоговый код обработчиков будет выглядеть, примерно, следующим образом:

		;** Подсистема: "переключение Функций"
	SwitchFunc_RTC_Alarm__SWITCH_MODES:
		IF_BUTTON_HAVE_STATUS	DButtonRTCStatus,	BSC_ShortPress
		OR_BUTTON_HAVE_STATUS	DButtonRTCStatus,	BSC_LongHold
		BRTC	SwitchFunc_Timer1__SWITCH_MODES				; если кнопки не были нажаты...
		OUTI	DButtonRTCStatus,	0b11111111			; После обработки состояния кнопки - сделать "ОТЛОЖЕННЫЙ СБРОС" её статусного регистра.		(Примечание: это другой вариант поведения, "с триггером защёлкой-состояния": заставлять пользователя отпускать кнопку, перед следующим нажатием - что обычно полезно, ибо предотвращает серии ошибочных повторных срабатываний кнопки...)
		; <здесь располагается прикладной код обработки События>
		RJMP	EventButtonHavePressed__SWITCH_MODES


	SwitchFunc_Timer1__SWITCH_MODES:
		IF_BUTTON_HAVE_STATUS	DButtonTimer1Status,	BSC_ShortPress
		OR_BUTTON_HAVE_STATUS	DButtonTimer1Status,	BSC_LongHold
		BRTC	SwitchFunc_Timer2__SWITCH_MODES				; если кнопки не были нажаты...
		OUTI	DButtonTimer1Status,	0b11111111			; После обработки состояния кнопки - сделать "ОТЛОЖЕННЫЙ СБРОС" её статусного регистра.
		; <здесь располагается прикладной код обработки События>
		RJMP	EventButtonHavePressed__SWITCH_MODES


	SwitchFunc_Timer2__SWITCH_MODES:
		IF_BUTTON_HAVE_STATUS	DButtonTimer2Status,	BSC_ShortPress		
		OR_BUTTON_HAVE_STATUS	DButtonTimer2Status,	BSC_LongHold		
		BRTC	SwitchFunc_End__SWITCH_MODES				; если кнопки не были нажаты...
		OUTI	DButtonTimer2Status,	0b11111111			; После обработки состояния кнопки - сделать "ОТЛОЖЕННЫЙ СБРОС" её статусного регистра.
		; <здесь располагается прикладной код обработки События>
		RJMP	EventButtonHavePressed__SWITCH_MODES

	SwitchFunc_End__SWITCH_MODES:


Здесь, три обработчика событий, каждый из которых подвязан к условиям проверки состояния одной из трёх кнопок. Для тестирования статус-кодов кнопок по паттерну BUTTON_STATUS_CODE, используются вспомогательные библиотечные макросы IF_BUTTON_HAVE_STATUS, AND_BUTTON_HAVE_STATUS, OR_BUTTON_HAVE_STATUS (последние два используются в комбинации, для тестирования «кнопочных аккордов»):
; Установить status bit "T" = в значение булевого выражения: (Если статус-регистр @0 хранит статус-код == @1 ?)
; Пример вызова:  IF_BUTTON_HAVE_STATUS  DButtonStatus, BSC_ShortPress

; Установить status bit "T" = в значение булевого выражения: T && (Если статус-регистр @0 хранит статус-код == @1 ?)
; Пример вызова:  AND_BUTTON_HAVE_STATUS  DButtonStatus, BSC_ShortPress

; Установить status bit "T" = в значение булевого выражения: T || (Если статус-регистр @0 хранит статус-код == @1 ?)
; Пример вызова:  OR_BUTTON_HAVE_STATUS  DButtonStatus, BSC_ShortPress

; Памятка: эти макросы, при выполнении, портят содержимое временных регистров TEMP1 и TEMP2.


Если обработчик события сработал, то рекомендуется сразу же сбросить статусные регистры для вовлечённых кнопок (их может быть несколько):
OUTI	DButtonRTCStatus,	0b11111111			; После обработки состояния кнопки - сделать "ОТЛОЖЕННЫЙ СБРОС" её статусного регистра.


Что означает «переключение режима» интерфейса? И как это реализовывается в прикладном коде обработчика события?
Для разработки систем логического управления на базе конечных автоматов существуют: большая и сложная «Switch-технология», парадигма «Автоматного программирования», и другие страшные слова… Но пока, об этом, не стоит особо задумываться.
Проще всего, и обычно так делают, «машину состояний» конечного автомата можно реализовать с помощью «Флагового автомата», например так:
; Проверить в каком режиме находится интерфейс и выполнить соответствующую ветку кода:
STOREB	DMain_Mode,	MODE_SETTINGS		; Флаг "находимся в режиме настройки" -> T
BRTC	NormalMode__SWITCH_MODES
; Режим настройки активен! <выполнить соответствующий код...>
RJMP	Exit__SWITCH_MODES
NormalMode__SWITCH_MODES:
; Режим настройки выключён! <выполнить соответствующий код...>
RJMP	Exit__SWITCH_MODES


; Переключить режим интерфейса: 
SETB	DMain_Mode,	MODE_SETTINGS		; Прикладная реакция: войти в "режим настройки".
; или:
CLRB	DMain_Mode,	MODE_SETTINGS		; Прикладная реакция: выйти из "режима настройки".


Принципы проектирования:
  • Обработчик события — атомарен, по своему действию.
  • Начальные условия для запуска обработчика события определяются в срезе времени.
  • Обработчики событий следует объединять в группы по подсистемам. А обработку Подсистем располагать в коде, в порядке приоритетов: сперва обслуживать главные подсистемы и глобальные реакции; затем — код ветвится, обслуживая «случаи» вложенных подсистем...
  • «Состояние» конечного автомата — можно считать отдельной Подсистемой...


Ввод через «Хитрую кнопку»

В некоторых специфических случаях применения, бывает полезно использовать кнопки «без триггера-защёлки состояния», для умышленной организации серийных реакций на нажатую и удерживаемую кнопку.

Замечание!Например, рецепт: как двумя кнопками «больше/меньше» организовать эргономичный интерфейс подстройки параметра: «пользователь нажал кнопку и удерживает — после секундной паузы, значение начинает автоматически набегать +1, +1,… а через некоторое время, начинает набегать быстрее +5, +5,...»

; Обработка МОДИФИКАЦИИ ЧИСЛОВОГО ЗНАЧЕНИЯ в памяти "Интеллектуальной Кнопкой"

		; (однократное нажатие)
		IF_BUTTON_HAVE_STATUS	DButtonStartStatus,	BSC_ShortPress
		BRTC	Button2__SWITCH_MODE_SETTINGS				; если кнопки не были нажаты...
		OUTI	DButtonStartStatus,	0b11111111			; После обработки состояния кнопки - сделать "ОТЛОЖЕННЫЙ СБРОС" её статусного регистра.
		LDI	ExtendDataByValue,	1
		RJMP	ModifyData__SWITCH_MODE_SETTINGS

		; (инерционный ввод, при удержании)
	Button2__SWITCH_MODE_SETTINGS:
		IF_BUTTON_HAVE_STATUS	DButtonStartStatus,	BSC_LongHold
		BRTC	NotButton__SWITCH_MODE_SETTINGS				; если кнопки не были нажаты...
		;OUTI	DButtonStartStatus,	0b11111111			; Внимание: в этом случае, статус кнопки НЕ сбрасываем - пусть продолжает считаться "нажатой" и набегает "таймер удержания"...
		LDI	ExtendDataByValue,	1
		; (хочу различать также ситуации "очень длительных" удерживаний кнопки)
		LDS	temp1,	DButtonStartStatus
		ANDI	temp1,	0b11111<<BUTTON_HOLDING_TIME			; выделить "счётчик времени удержания кнопки"
		CPI	temp1,	8						; при удержании кнопки свыше >=4сек, показания набегают в 2 раза быстрее.
		BRLO	SlowSpeedYet__SWITCH_MODE_SETTINGS
		LSL	ExtendDataByValue
		CPI	temp1,	16						; при удержании кнопки свыше >=8сек, показания набегают ещё в 2 раза быстрее.
		BRLO	SlowSpeedYet__SWITCH_MODE_SETTINGS
		LSL	ExtendDataByValue
	SlowSpeedYet__SWITCH_MODE_SETTINGS:
		RJMP	ModifyData__SWITCH_MODE_SETTINGS
	NotButton__SWITCH_MODE_SETTINGS:
		RJMP	Exit__SWITCH_MODES

	ModifyData__SWITCH_MODE_SETTINGS:
		; <здесь располагается прикладной код модификации "ячейки памяти" += значение из регистра ExtendDataByValue >

Особенности:
  • Здесь, обработчики реагируют на два жеста («BSC_ShortPress» и «BSC_LongHold») одной кнопки («DButtonStartStatus»).
  • Заметьте, что здесь, «регистр статуса» кнопки сбрасывается только по событию «отдельное нажатие (BSC_ShortPress)»!
  • Но по событию «удерживание кнопки (BSC_LongHold)» — осуществляется только прикладная реакция, а статус кнопки НЕ сбрасываем — пусть продолжает считаться «нажатой» и набегает «таймер удержания»...


Интерфейс — одна кнопка

Извращения, типа,
«интерфейс — одна кнопка», актуальные для «миниатюрных устройств, обладающих только одной внешней кнопкой» — можно реализовать на конечных флаговых автоматах, гроздями атомарных обработчиков.
Примечание: к данной библиотеке эти трюки уже не относятся (и плохо поддерживаются) — этот рецепт, больше, для лулзов.

Ввод через Энкодер

В отличие от
Кнопок, Энкодер — гораздо проще в управлении! У него всего одна степень свободы: он может вращаться «по часовой» или «против часовой» стрелки. Скорость и динамика вращения не контролируются. Значение имеют лишь: количество сосчитанных тиков и направление вращения, которые отражаются числом в «регистре счётчика» Энкодера.

«Счётчик тиков» (DEncoder0Counter) является конечным в конвейере обработки энкодера, и его использует прикладной код для реакции на жесты с энкодером:
; Обработка МОДИФИКАЦИИ ЧИСЛОВОГО ЗНАЧЕНИЯ в памяти Энкодером
		LDS	ExtendDataByValue,	DEncoder0Counter
		TST	ExtendDataByValue
		BREQ	NotEncoder__SWITCH_MODE_SETTINGS
		OUTI	DEncoder0Counter,	0				; замечу: после прибавления к "ячейке памяти" этой аддитивной добавки, регистр "счётчика тиков" энкодера обнуляется.
		RJMP	ModifyData__SWITCH_MODE_SETTINGS
	NotEncoder__SWITCH_MODE_SETTINGS:
		RJMP	Exit__SWITCH_MODES

	ModifyData__SWITCH_MODE_SETTINGS:
		; <здесь располагается прикладной код модификации "ячейки памяти" += значение из регистра ExtendDataByValue >


Сперва, следует проверить текущее значение в «счётчике тиков» (TST): если значение равно = 0, значит пользователь не крутил ручку энкодера. Это критерий «наличия/отсутствия событий от энкодера» — важный аспект для логики прикладного кода! Если же в регистре = ненулевое значение, значит события от энкодера поступали — следует их обработать

В прикладном коде обработчика событияпросто приплюсуйте [или отминусуйте] значение из «cчётчика тиков» к вашей управляемой переменной величине.
Замечу, что значение в «счётчике тиков» — величина знаковая! Поэтому, в зависимости от знака, будет автоматически учтено направление вращения ручки энкодера: «по часовой» или «против часовой»...

И наконец, после обработки события энкодера, «СБРОСьте» регистр «Счётчика тиков» в ноль (но, при этом, «статусный регистр» энкодера не трогайте!)…


  • 0
  • 12 декабря 2013, 03:14
  • Celeron

Комментарии (0)

RSS свернуть / развернуть
Автор топика запретил добавлять комментарии