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: Библиотека процедур для интеллектуальной обработки ВВОДА в МК: событий от Кнопок и Энкодеров (часть 1: авторская методика и реализация) / AVR / Сообщество EasyElectronics.ru

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

AVR
Микроконтроллерное устройство может работать исключительно в автономном режиме: получать сигналы с датчиков, и выдавать управляющие импульсы, иногда оно ещё взаимодействует с ЭВМ или другими микроконтроллерами… Но большинству микроконтроллерных устройств требуется поддерживать интерфейс с пользователем-человеком: для вывода используются светодиоды или дисплеи, а для ввода — традиционные Кнопки и Энкодеры, редко используются и другие экзотические устройства ввода
В данной работе будут рассматриваться только традиционные инструменты ввода: «цифровые Кнопки / Клавиатуры» и «инкрементальные Энкодеры», поскольку именно они используются почти всегда.

Содержание:


Назначение


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

Особенности


Среди прочего, библиотека реализует следующие функции:

1) Математическое подавление ошибок, от наведенных электрических помех и дребезга контактов, для всех «физических каналов» подключённых Кнопок и Энкодеров, с заданной и регулируемой величиной помехоустойчивости. (используется оригинальная авторская методика «интегрирующей защёлки»: на двунаправленном инкрементальном счётчике)

2) Сложные поведения реальных кнопок — сводятся к стандартизованным кодам в «Статусных Регистрах» Кнопок, на основании которых можно легко реализовывать очень сложную и гибкую логику поведения устройства:
  • распознаются разные жесты с кнопками: серии нажатий и удержаний кнопок;
  • различаются варианты кнопочных аккордов: одновременное нажатие/удерживание нескольких кнопок;
  • кнопки различается по времени удержания: обычное, «короткое» или «длинное» удержание. А для продвинутых случаев, для наиболее функциональных кнопок, также допустимо различать ситуации: «сколько КОНКРЕТНО удерживается кнопка, в пределах 0..16сек?» и программировать различные реакции...
  • и что особенно полезно для низкоуровневого ассемблерного кода: дана чёткая и прозрачная методика написания прикладного кода обработчиков «реакции на события» (предлагаются макросы для тестирования и обработки статус-кодов кнопок). Также, имеется простая возможность назначать одинаковые обработчики различным альтернативным кнопочным жестам.
  • Простой и понятный механизм «сброса статусных кодов» для уже обработанных кнопок — исключает «побочные эффекты»: типа «залипания» или «ошибочные повторные нажатия» кнопок… (защита от дурака: при правильно организованном коде обработчиков, пользователь может жать на всё подряд — приложение отреагирует чётко и правильно)


3) Для распознавания и обработки кодовой последовательности, поступающей от
инкрементального Энкодера по двум физическим каналам — предлагаются три разные реализации кода, обладающие разными функциональными возможностями и подходящие для разных ситуаций (лёгкий и простой код, для хорошего и быстрого железа; или усложнённый код, с коррекцией ошибок, для медленного или сбойного железа).
Разработчик выбирает вариант кода обработки энкодера — только директивами условной компиляции. Вся активность энкодера обрабатывается библиотекой автоматически и сводит все особенности к простому «Счётчику тиков», который затем используется в прикладном коде обработки событий. (Формат счётчика особой специальной структуры не содержит — это просто знаковое целое число: Signed Int = [-128..127]).

4) КОД библиотеки <celeronkeyinputlib.inc> УНИВЕРСАЛЕН и не требует (даже не рекомендует) каких-либо вмешательств и переделок под ваше конкретное устройство!
Единственная процедура, код которой требуется адаптировать к вашей конкретной физической схеме — это KEY_SCAN_INPUT (через неё осуществляется связь с физическими каналами ввода).
Также, под вашу конкретную физическую схему, требуется адаптировать блок определения данных в DSEG (см. в файле <data.inc>): определения блоков регистров Интегратора, статусных регистров Кнопок и Энкодеров, и некоторые константы.

5) Данная библиотека испытана в реальном физическом устройстве — и зарекомендовала себя как «реально работоспособная», гибкая и эффективная! Пример использования данной библиотеки, реализация клиентского прикладного кода, приведен в файлах: <example.asm> и <data.inc>.

Код и Зависимости


Код библиотеки процедур для интеллектуальной обработки ВВОДА «celeronkeyinputlib.inc» опубликован на GitHub (это веб-сервис для хостинга IT-проектов и их совместной разработки), на условиях лицензии MIT (разрешительной opensource, т.е. практически без ограничений к использованию). Ответвляйтесь!

Используется, и требует подключения, нестандартная внешняя Библиотека базовых Макроопределений «macrobaselib.inc» — расширяющая стандартный набор ассемблерных инструкций микроконтроллеров Atmel AVR (8-bit AVR Instruction Set), и рекомендующая парадигму программирования: с хранением «модели прикладных данных» в ОЗУ и использованием нескольких «временных регистров»…

Примечание: GitHub был выбран для распространения кода — как наиболее прогрессивный, удобный и функциональный метод взаимодействия opensource-разработчиков. Развивайте и дополняйте библиотеку — затем, сможете легко контрибутить...

Ликбез для неискушённых пользователей: те кто не используют системы управления версиями, могут просто скачать архив с кодом: нажав на кнопку «Download ZIP» на странице репозитория GitHub, по ссылкам выше.



Как это работает?


1. Физический канал

Предположим, к вашему микроконтроллеру подключена ЛЮБАЯ кнопка. Это может быть
одна или много кнопок. Схемотехнически, Кнопка может быть организована по разному:

Простая кнопкаГруппа кнопок на Сдвиговом регистреКлавиатура на резистивных делителях

Однако, какой бы ни была схема подключения и мультиплексирования кнопок, в результате сканирования физического канала Кнопки — получаем цифровой код её состояния (принятая в библиотеке кодировка: «0»-кнопка отпущена или «1»-нажата).
Замечу, что если сканируется клавиатура на резистивных делителях, то после преобразования через АЦП и «табличку соответствия напряжений кодам кнопок» — также получаем цифровые коды «нажатых» и «отпущенных» кнопок…

1.1. Код сканирования «физических каналов» программист пишет сам

Замечание!Замечу: код сканирования «физических каналов» находится в процедуре KEY_SCAN_INPUT библиотеки. Эта процедура обеспечивает взаимодействие микроконтроллера с внешней аппаратной периферией ввода. При адаптации библиотеки к своему «железу», программист должен переписать код этой процедуры, с нуля! Таким образом, достигается универсальность и гибкость применения данной библиотеки, адаптивность к любым схемотехническим решениям подключений кнопок и энкодеров, без ограничений! Причём, это единственная процедура, в библиотеке, требующая вмешательства в код. Остальной код математического конвейера — универсален и неизменен, он подвязывается и запускается из этой процедуры KEY_SCAN_INPUT...

В самом простом случае, для одной Кнопки, подключенной непосредственно к порту, код процедуры KEY_SCAN_INPUT выглядит следующим образом:
; Инициализация:
LDI	IntegratorAddressLow,	Low(DInputIntegrator)	; начало блока регистров Интегратора,
LDI	IntegratorAddressHigh,	High(DInputIntegrator)	; где регистр IntegratorAddress = X(R27:R26)
LDI	StatusAddressLow,	Low(DButtonStatus)	; начало блока регистров Кнопок,
LDI	StatusAddressHigh,	High(DButtonStatus)	; где регистр StatusAddress = Y(R29:R28)
LDI	IntegratorLatchDepth,	5			; "глубина защёлки" интегратора

; Сканирование аппаратной периферии:
IN	temp,	PINB
BST	temp,	PinSense				; Прочитать текущее состояние "физического канала" -> в T
; Или, для входных каналов читаемых в инверсной кодировке, следует также добавить безусловную инверсию, чтобы привести сигнал к стандартной кодировке ("0"-отпущена, "1"-нажата):
IN	temp1,	PINB
LDI	temp2,	1<<PinSense
EOR	temp1,	temp2
BST	temp1,	PinSense

; Обработка:
RCALL	KEY_PROCESS_INTEGRATOR				; Проинтегрировать сигнал
RCALL	KEY_UPDATE_BUTTON_STATUS			; Фиксировать статус Кнопки


; Далее, как вариант, можно отработать ещё несколько кнопок:
LD	temp,	X+					; адрес в регистре X++
LD	temp,	Y+					; адрес в регистре Y++
STOREB	PINB,	PinSense_xxx				; Прочитать текущее состояние "физического канала" -> в T
RCALL	KEY_PROCESS_INTEGRATOR				; Проинтегрировать сигнал
RCALL	KEY_UPDATE_BUTTON_STATUS			; Фиксировать статус Кнопки
и т.д.


При обработке Энкодеров — отличия небольшие: нужно помнить, что канала два; и вызывается другой метод Обработки…
; Инициализация:
LDI	IntegratorAddressLow,	Low(DInputIntegrator)	; начало блока регистров Интегратора,
LDI	IntegratorAddressHigh,	High(DInputIntegrator)	; где регистр IntegratorAddress = X(R27:R26)
LDI	StatusAddressLow,	Low(DEncoderStatus)	; начало блока регистров Энкодеров,
LDI	StatusAddressHigh,	High(DEncoderStatus)	; где регистр StatusAddress = Y(R29:R28)
LDI	IntegratorLatchDepth,	2			; "глубина защёлки" интегратора

; Сканирование аппаратной периферии:
STOREB	PINB,	PinSense1				; Прочитать текущее состояние "физического канала" №1 -> в T
RCALL	KEY_PROCESS_INTEGRATOR				; Проинтегрировать сигнал
LD	temp,	X+					; адрес в регистре X++
STOREB	PINB,	PinSense2				; Прочитать текущее состояние "физического канала" №2 -> в T
RCALL	KEY_PROCESS_INTEGRATOR				; Проинтегрировать сигнал
LD	temp,	-X					; адрес в регистре X-- (коррекция: шаг назад)

; Обработка:
RCALL	KEY_UPDATE_ENCODER_STATUS			; Фиксировать статус и счётчик Энкодера


; Далее, как вариант, можно отработать ещё несколько энкодеров:
LD	temp,	X+					; адрес в регистре X++ (здесь: только один шаг вперёд)
LD	temp,	Y+					; адрес в регистре Y++ (здесь: только один шаг вперёд)
STOREB	PINB,	PinSense_xxx1				; Прочитать текущее состояние "физического канала" №1 -> в T
RCALL	KEY_PROCESS_INTEGRATOR				; Проинтегрировать сигнал
LD	temp,	X+					; адрес в регистре X++
STOREB	PINB,	PinSense_xxx2				; Прочитать текущее состояние "физического канала" №2 -> в T
RCALL	KEY_PROCESS_INTEGRATOR				; Проинтегрировать сигнал
LD	temp,	-X					; адрес в регистре X-- (коррекция: шаг назад)
RCALL	KEY_UPDATE_ENCODER_STATUS			; Фиксировать статус и счётчик Энкодера
и т.д.


2. Интегратор канала

Интеграция «сырого» статуса входного канала (процедура математического подавления звона и помех KEY_PROCESS_INTEGRATOR) вызывается после опроса физической кнопки (любой реализации) и получения цифрового кода её текущего статуса: «0» или «1».

В процедуру KEY_PROCESS_INTEGRATOR передаются следующие параметры:
  • Через status bit «T» передаётся «сырой» цифровой код текущего состояния кнопки = «0» или «1».
  • Регистровым параметром «IntegratorAddress» передаётся адрес «регистра интегратора» в памяти (ОЗУ).
  • Регистровым параметром «IntegratorLatchDepth» специфицируется «глубина защёлки» для текущего канала. (Примечание: наличие этого параметра позволяет дифференцированно применять разную «глубину защёлки» для разных «физических каналов». Предполагается, что сигналы от тактовых кнопок и энкодера, например, требуют разных «глубин защёлки» — ввиду их различной динамичности и надёжности...)


2.1. Кодировка входного сигнала

Кодировка входного сигнала:

Замечание!Вопрос: А зачем так жёстко фиксировать СТАНДАРТ кодировки сигнала? Ведь достаточно изменить только одну инструкцию BRTS/BRTC, чтобы кодировка входного сигнала стала инверсной? Можно было бы настраивать директивой условной компиляции…
Ответ: Для минимизации путаницы — код конвейера обработки кнопок не модифицируется, логика неизменна! Также, допускается, что разные группы Кнопок и Энкодеров, могут возвращать свои статусы в разных кодировках, вперемешку… Гораздо проще, понятнее и универсальнее: добавить безусловные XOR-инверсии к некоторым конкретным «физическим каналам»… Простота -> Ясность -> Выгода!


2.2. Формат «Регистра Интегратора»
(Справка: Расположены в секции DInputIntegrator в файле
<data.inc>)

76543210
Snnnnnnn
В целом, регистр хранит одно число signed short int = [-128, 0, +127].
Результат интеграции — отражается в Sign bit (он же Negative Flag «N»), означает: «0»=(кнопка отпущена) или «1»=(кнопка нажата)…

Семантически — это инкрементальный двунаправленный счётчик:
  • Если физический канал (кнопка) возвращает статус «1» (нажата) — то на этой итерации производится декрементация (-1) регистра счётчика.
  • Если физический канал (кнопка) возвращает статус «0» (отпущена) — то на этой итерации производится инкрементация (+1) регистра счётчика.
  • Максимальное, по модулю, значение в регистре счётчика определяется константой «глубины защёлки» (LatchDepth) = [-LatchDepth, 0..+(LatchDepth-1)].
    Причём замечу: здесь, значение =0 включается в статус «кнопка отпущена»!
  • Начальное состояние = 0b00000000. Что означает: примерно среднее неопределённое положение защёлки, но кнопка всё же на стороне «отпущена» (что соответствует обычной ситуации: когда кнопки, конструктивно, «нормально разомкнутые»).

Схема работы Интегратора

2.3. Настройка интегратора: выбор «глубины защёлки»

«Глубина Защёлки» интегратора, по своему действию, аналогична «постоянной времени» помехоподавляющей RC-цепи…

Допустимые значения для регистрового параметра IntegratorLatchDepth = [1..128].

Выбор оптимального значения «глубины защёлки» зависит от разных факторов:
  1. Следует учитывать, по какому событию запускается процедура сканирования «физических каналов» KEY_SCAN_INPUT: по таймеру, периодически или по «Pin change Interrupt (INT0)»?
    • Если по таймеру, периодически — тогда нужно брать IntegratorLatchDepth > больше количества «замеров», за период самой длинной помехи (это требуется замерять экспериментально).
    • Если по изменению уровня на порте ввода-вывода (INT0), то выбор проще: на «тактовую частоту» микроконтроллера можно не смотреть, а выбрать IntegratorLatchDepth=[2..3].
  2. «Глубину защёлки» следует устанавливать и в расчёте на будущий износ кнопок: для плохого железа и лучшей помехозащищённости — требуется бОльшая разрешительная «глубина». Лучше взять значение IntegratorLatchDepth чуть больше, чем отрабатывается на лабораторном стенде (на порядок больше, для худших случаев).
  3. Но следует помнить, что большАя «глубина защёлки» — сильно замедляет реакцию микроконтроллера на события: кнопки становятся значительно «инертнее», а кратковременные нажатия могут быть и вовсе пропущены, что сильно ухудшает эргономику! Также, на медленных микроконтроллерах или при редкой частоте сканирования «физических каналов» — «глубину защёлки» следует брать поменьше.
  4. При опросе контактных групп Энкодера — «глубина защёлки» интегратора, обычно, задаётся на порядок меньше, чем для кнопок! Поскольку контакты Энкодера более стабильны и помехоустойчивы, а кроме того, Энкодер переключается ГОРАЗДО чаще, чем тактовые Кнопки (требуется выше быстродействие).
  5. Замечу: при IntegratorLatchDepth=1 — интегратор, фактически, отключён! При этом, код Интегратора будет отрабатываться, как часть конвейера, и потреблять ресурсы АЛУ, но исправлять ошибки не будет.
  6. Напомню: максимальное значение IntegratorLatchDepth=128. Однако, если микроконтроллер работает слишком быстро, и процедура KEY_SCAN_INPUT запускается слишком часто — то вам, возможно, не хватит этого максимума, для демпфирования долгих помех. Тогда, очевидно, запуск процедуры KEY_SCAN_INPUT следует запланировать пореже...


3. Работа с Кнопками

Всё что происходит с кнопкой — отражается в её
«статусном регистре». Этот регистр имеет сложную структуру и семантическое наполнение, кодирует множество ситуаций…

Статус-код кнопки формируют две процедуры: KEY_UPDATE_BUTTON_STATUS (определяет нажатие-отжатие кнопки) и KEY_ENHANCE_TIME_FOR_ALL_BUTTONS (наращивает счётчики «времени удержания» кнопок).

Прикладной код обрабатывает события, опираясь, исключительно, на значения в «статусных регистрах» кнопок. Условие для запуска обработчика может приниматься, анализируя статусы сразу нескольких Кнопок — так появляется возможность различать «аккорды» и кнопки-модификаторы. А наличие в статусном регистре кодов, различающих события: «нажатие», «отжатие», «время удержания», отдельно для каждой кнопки — позволяет довольно гибко и функционально комбинировать начальные условия для запуска обработчика события!

После обработки событий, связанных с кнопкой, прикладной код сбрасывает «регистр статуса», соответствующий кнопке. Имеется также глобальная процедура KEY_RESET_STATUS_FOR_ALL_BUTTONS, которая сбрасывает статусы ВСЕМ кнопкам и энкодерам в системе — вызывается прикладным кодом асинхронно, и используется для подавления «ошибочных реакций» из-за того, что «пользователи давят на всё подряд»…

3.1. Формат «регистра Статуса» Кнопки
(Справка: Расположены в секции DButtonStatus в файле
<data.inc>)

76543210
HPTnnnnn


Описание битов «регистра статуса» кнопки:
.equ	BUTTON_HOLDING_TIME	= 0	; Пять битов   DButtonStatus[4:0] = счётчик количества полусекунд, в течение которых Кнопка удержива(лась/ется) "нажатой".	(фиксирует время до 16сек!)
.equ	BUTTON_STATUS_CODE	= 5	; В трёх битах DButtonStatus[7:5] = кодируется итоговый "статус-код кнопки" (см. ниже макроопределения констант).
.equ	BUTTON_HOLDEN_LONG	= 5	; Флаг "времени удержания" кнопки: 0-короткое или 1-длинное.
.equ	BUTTON_IS_PRESSED	= 6	; Флаг "зафиксировано полноценное нажатие кнопки": "0" - кнопка не нажималась, "1" - было нажатие.
.equ	BUTTON_IS_HOLDDOWN	= 7	; Флаг "кнопка удерживается в нажатом состоянии": "0" - сейчас кнопка "отпущена", "1" - сейчас кнопка "нажата и удерживается".


Три бита DButtonStatus[7:5], кодирующие итоговый «статус-код кнопки», могут принимать следующие значения:
.equ	BSC_NotPressed	= 0b000	; "не нажата"		(исходное положение для всех кнопок - бывает только после "сброса")
.equ	BSC_ShortHold	= 0b100	; "короткое удержание"	(кнопка нажата, и всё ещё удерживается, пока "короткое" время)
.equ	BSC_LongHold	= 0b101	; "длинное удержание"	(кнопка нажата, и всё ещё удерживается, уже "длительное" время)
.equ	BSC_ShortPress	= 0b010	; "короткое нажатие"	(кнопка была нажата, и затем отпущена, а время её удержания было "незначительным")
.equ	BSC_LongPress	= 0b011	; "длинное нажатие"	(кнопка была нажата, и затем отпущена, а время её удержания было "длительным")


Диаграмма переключения Статус-кодов Кнопки

Специальные значения:
  • «регистр статуса» = 0b00000000, означает начальное состояние: кнопка «не нажата и не нажималась», исходное положение для всех кнопок — бывает только после «сброса в ноль» или при включении микроконтроллера.
    (Здесь, статус-код кнопки = «не нажата»; счётчик времени предыдущего нажатия = обнулён.)
  • «регистр статуса» = 0b11111111, означает служебное исключительное состояние «ОТЛОЖЕННЫЙ СБРОС»: «фиксация, до ожидания следующего отпускания» кнопки
    (Здесь, якобы, кнопка «удерживается и отпущена одновременно» — это запрещённый «статус-код кнопки», что запрещает прикладному коду реакцию на эту кнопку.)
    (Замечу: При следующем отпускании физической кнопки, статус-регистр будет АВТОМАТИЧЕСКИ «сброшен в ноль» — в исходное положение! А если кнопка уже отпущена — то «сброс в ноль» будет осуществлён сразу же.)


Процедура KEY_UPDATE_BUTTON_STATUS — фиксирует статус кнопки в «статусном регистре», в зависимости от динамически изменяющегося [проинтегрированного] состояния «физического канала» (определяет нажатия-отжатия). В неё передаются следующие параметры:
  • Регистровым параметром «IntegratorAddress» передаётся адрес «регистра интегратора» (в памяти ОЗУ), который кодирует текущее «физическое» состояние кнопки.
  • Регистровым параметром «StatusAddress» передаётся адрес «статусного регистра кнопки» (в памяти ОЗУ), который следует обновить.


3.2. «Время удержания» Кнопки

Процедура KEY_ENHANCE_TIME_FOR_ALL_BUTTONS наращивает таймеры для удерживаемых кнопок (должна запускаться по событию таймера, точно раз в 0.5 сек).
Для Энкодеров не используется — предназначена только для обработки Кнопок! С физическими кнопками также не работает, а только перебирает/обрабатывает их Статусные регистры!

Алгоритм её работы таков:


Процедура KEY_ENHANCE_TIME_FOR_ALL_BUTTONS не имеет регистровых параметров, но использует один константный параметр (захардкоден в прошивке), для настройки времени «длительно удерживаемых» кнопок:
.equ	CShortButtonTouchDuration = 3	;<1.5 сек	; максимальное время удержания кнопки (количество полных полусекунд) до которого ещё фиксируется статус-код "ShortPress/ShortHold", а свыше или равно - уже "LongPress/LongHold"...


3.3. Обработка статус-кодов Кнопок («жесты» и «аккорды»)

Советы по порядку тестирования Кнопок:

Замечание!Совет: В большинстве случаев, обработчик события привязывается к значению BUTTON_STATUS_CODE в
«статусном регистре» кнопки — это самый простой для кодирования и функциональный метод. Полагаю, для описания большинства распространённых «жестов» с кнопками — хватит 4 ситуации…
Для тестирования состояния кнопок по паттерну BUTTON_STATUS_CODE, рекомендуется использовать библиотечные макросы: IF_BUTTON_HAVE_STATUS, AND_BUTTON_HAVE_STATUS, OR_BUTTON_HAVE_STATUS (последние два используются в комбинации, для тестирования «кнопочных аккордов»). Пример, см. ниже...

Замечание!Совет: кроме тестирования чистого «статус-кода кнопки», можно также смотреть на «счётчик времени удержания кнопки» — и различать также ситуации «очень длительных» удерживаний кнопок! Но замечу, что эти ситуации уже не кодируются в «статус-коде кнопки», т.к. довольно редки. Если требуется данный функционал — его следует самостоятельно реализовать в прикладном коде, на основании арифметического сравнения с абсолютным значением счётчика BUTTON_HOLDING_TIME (инструкцией CPI). Это самый функциональный способ! Пример, см. ниже...

Замечание!Совет: чтобы разнообразить варианты ввода, прикладной код также может воспринимать «кнопочные аккорды» — тестировать группы кнопок на наличие одновременных нажатий/удержаний. Однако, делать это нужно в определённом порядке приоритетов:
  1. Сначала, проверить были ли нажатия отдельных кнопок, при удержании других «кнопок-модификаторов» (подобно: «Shift/Ctrl/Alt + X»)?
    Подсказка: У основной кнопки, которая «нажималась» — статус BUTTON_STATUS_CODE==«ShortPress/LongPress»; при этом, у модификаторов, при «удержании в нажатом состоянии» — установлен бит BUTTON_IS_HOLDDOWN==1...
  2. Затем, проверить простые одиночные нажатия отдельных кнопок: у которых статус==«ShortPress/LongPress»?
  3. Затем, проверить группы кнопок на одновременное удержание: у которых статус==«LongHold»? Причём, таким образом, можно проверить, последовательно, несколько групп: от более общих множеств, к более частным — с меньшим числом входящих кнопок...
  4. И наконец, проверить отдельные кнопки на длительное удержание: у которых статус==«LongHold»?

Замечание!Совет: во избежание ложных повторных срабатываний событий, после обработки статуса кнопки прикладным кодом и произведения ответной реакции, "СБРОСьте" статусный регистр — это простой способ сообщить остальному прикладному коду, что данное событие уже обработано и не требует дальнейшего участия…

3.4. Сброс «регистра статуса» Кнопки (завершение обработки события)
Сделал дело — прибери за собой!

После отработки события — обработчик сбрасывает статусные регистры вовлечённых кнопок: обычно для всех кнопок, составляющих «аккорд». Сброс статусного регистра — фактически, обнуляет историю произведённого с кнопкой действия ДО обработки события. Теперь, кнопки готовы к новым отдельным действиям с ними.

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

В то же время, нужно помнить, что если прикладной код вовремя не обнулит «статусный регистр» отдельной кнопки — то она продолжит считаться «нажатой» (что особенно опасно) или «удерживаемой», что в будущем, может вызвать «побочную реакцию»!
Обычно, интерфейс устройства состоит из разных подсистем, и не во всех режимах используются [и обрабатываются] все кнопки интерфейса. Если, в некотором режиме интерфейса, часть кнопок не задействована — то они не опрашиваются, не срабатывают, их статус не обнуляется, а запоминается и хранится (такая кнопка становится «миной отложенного действия»). Следует также учитывать «защиту от дурака»: пользователь будет нажимать на всё подряд!
Поэтому, при реализации прикладной логики вашего Устройства: когда переключаете интерфейс в другую Подсистему — следует безусловно обнулять все статусные регистры, вызовом процедуры KEY_RESET_STATUS_FOR_ALL_BUTTONS (это «разминирует все заложенные ловушки»).

Замечание!Это важный концептуальный момент: Обработчики событий программируются так, чтобы срабатывать по наступлению некоторого события в срезе времени. Обработчики не контролируют никаких состояний и данных ДО и ПОСЛЕ события — следовательно, их код проще и прозрачнее (аспектно-ориентированный). Но поэтому, так важно разделять события, во времени, на атомарные реакции… После отработки события, обработчик просто сбрасывает статусные регистры вовлечённых кнопок — таким образом, «подсистеме обработки ввода» указывается, что событие было обработано и данная кнопка готова к принятию нового, следующего ввода.

Есть два вида сброса:
  1. «Сброс статус-регистра В НУЛИ» (мгновенный) Такие кнопки не требуют явного отпускания кнопки, перед тем как они смогут повторно зафиксировать событие «нажатие». Здесь, возможен побочный эффект: если сбросить статус кнопки «в ноль» до того как физическая кнопка будет реально отпущена (т.е. пока она «удерживается»), то уже на следующей итерации, по состоянию «физического канала» = «1»(нажата) — будет опять зафиксирована новая ситуация «нажатие»…
    Этот способ не рекомендуется использовать НЕПОСРЕДСТВЕННО. Вместо этого: Для тактовых кнопок рассчитанных на одиночные «законченные нажатия (BSC_*Press)» — всегда используйте «сброс В ЕДИНИЦЫ» (отложенный, автоматический). А если требуются серийные реакции на событие «удерживание кнопки (BSC_*Hold)» — просто не сбрасывайте статус-регистр, пока кнопка удерживается.
  2. «Сброс статус-регистра В ЕДИНИЦЫ» (отложенный) Такие кнопки ждут «отдельных нажатий» (реальная кнопка должна быть отпущена и нажата заново) — это исключает «побочные эффекты», типа «залипания» или «ошибочные повторные нажатия» кнопок...


Для понимания ситуации, как это работает, рассмотрим несколько сценариев:
  1. После обработки статус-кодов BSC_*Press (замечу, что физическая кнопка уже отпущена), статус-регистр можно «сбросить в ноль (CLR)» -> так сразу установится исходное положение для всех кнопок «не нажата».
  2. После обработки статус-кодов BSC_*Hold (замечу, что физическая кнопка ещё удерживается), статус-регистр требуется «сбросить в единицы (SER)» -> так в регистре установится ошибочный статус-код, который не будет спутан ни с одной из разрешённых комбинаций, что запретит прикладному коду реагировать на эту кнопку, в дальнейшем. (Это служебное, исключительное состояние статус-регистра: «фиксация, до ожидания следующего отпускания» кнопки — ОТЛОЖЕННЫЙ СБРОС.)
  3. В то же время, как только физическая кнопка будет отпущена, сразу же, статус-регистр АВТОМАТИЧЕСКИ будет «сброшен в ноль», в исходное положение. (Это исключительная реакция конвейера обработки кнопок: лишнее нажатие на кнопку не будет зафиксировано!)


Рекомендуется: ВО ВСЕХ СЛУЧАЯХ, после обработки любых статус-кодов, СБРАСЫВАТЬ статус-регистры отработанных кнопок «В ЕДИНИЦЫ»! Так установится статус «ОТЛОЖЕННЫЙ СБРОС», который, даже если кнопка уже отжата, то при следующем цикле конвейера — будет автоматически «сброшен в ноль». Это безопасный стиль программирования!
Замечу: При таком стиле программирования — кнопка работает «с триггером-защёлкой состояния». Вариант поведения «с триггером»: заставляет пользователя отпускать кнопку каждый раз, перед её следующим нажатием — что обычно полезно, ибо предотвращает серии ошибочных повторных срабатываний кнопки.

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

4. Работа с Энкодерами

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

От энкодера, по двум «физическим каналам», поступает «2-битный рефлексивный код Грэя». Обычно, канал «B» сдвинут на 90°, относительно канала «A», при повороте по часовой стрелке:
2-битный рефлексивный код Грэя

4.1. Выбор алгоритма обработки Энкодера

Вся магия обработки энкодера реализована в процедуре KEY_UPDATE_ENCODER_STATUS — она Фиксирует статус Энкодера (устанавливает значения в «статус-регистре» и «счётчике тиков» Энкодера), в зависимости от динамически изменяющихся [проинтегрированных] состояний его двух управляющих «физических каналов»: AC и BC.

В процедуру KEY_UPDATE_ENCODER_STATUS передаются следующие параметры:
  • Регистровым параметром «IntegratorAddress» передаётся адрес ПЕРВОГО «регистра интегратора» (из пары последовательно расположенных регистров в памяти ОЗУ, которые кодируют текущее «физическое» состояние Энкодера).
    (Примечание: в целях оптимизации кода, при исполнении процедуры, указатель «IntegratorAddress» передвигается на (+1) байт вперёд. И после исполнения процедуры — указывает на следующую ячейку памяти.)
  • Регистровым параметром «StatusAddress» передаётся адрес «статусного регистра энкодера» (в памяти ОЗУ). При этом, предполагается, что следом за ним расположен регистр «счётчика тиков» этого Энкодера. Их оба — следует обновить…
    (Примечание: в целях оптимизации кода, при исполнении процедуры, указатель «StatusAddress» передвигается на (+1) байт вперёд. И после исполнения процедуры — указывает на следующую ячейку памяти.)


Существует три варианта реализации кода процедуры KEY_UPDATE_ENCODER_STATUS, которые выбираются директивами условной компиляции:
;.EQU	ENCODER_USE_SIMPLIFIED_CODE = 1		; "Упрощённый код"
;.EQU	ENCODER_USE_ACADEMIC_CODE = 2		; "Академический код"
.EQU	ENCODER_USE_PARANOID_CODE = 3		; "Параноидальный код"
Выберите один из трёх вариантов, раскомментировав определение соответствующего символа в файле
<celeronkeyinputlib.inc>:
  1. .EQU ENCODER_USE_SIMPLIFIED_CODE = 1 «Упрощённый код» (простой алгоритм, маленький и быстрый код! не защищён от ошибочных входных сигналов; предделитель фиксирован = 4 раза)
    Рекомендуется использовать на стабильном железе: быстром Микроконтроллере и при низком уровне помех по входным каналам. Примечание: в этом варианте, код «предварительного делителя счётчика» не используется — но делитель есть и фиксирован: показания «счётчика тиков» делятся в 4 раза.
  2. .EQU ENCODER_USE_ACADEMIC_CODE = 2 «Академический код» (самый компактный код и поражает воображение своим изяществом! но не защищён от ошибочных входных сигналов)
    Преимущества: он проще/меньше/быстрее параноидального кода, и с ним работает полноценный «предварительный делитель счётчика»! Но обычно, не рекомендуется его использовать: т.к. он является «серединкой на половинку» среди других вариантов, и особых преимуществ не даёт…
    Замечу что, на плохом железе: «Академический код» глючит столь же интенсивно, как и «Упрощённый код», но они по разному ощущаются — выбери себе глюки по вкусу! ;)
  3. .EQU ENCODER_USE_PARANOID_CODE = 3 «Параноидальный код» (тупой алгоритм и избыточный код, перебирающий все комбинации входных сигналов; защищён от «ОДНОКРАТНЫХ ошибок» по входу!)
    Предполагается, будет математически сглаживать и преодолевать последствия некоторых ошибок опроса физических каналов энкодера (при пропуске одного такта — запрещённая комбинация по входу, будет проигнорирована). Используйте этот вариант на плохом железе: при медленном микроконтроллере, или когда опрос энкодера происходит с ошибками.


Во-вторых, выберите коэффициент «предварительного делителя счётчика тиков» энкодера:
.EQU	ENCODER_USE_PRECOUNTER = 4
  • Здесь, допустимые значения предделителя = 2,3,4 переключения / на один тик счётчика. Рекомендуется = 4.
  • Если символ закомментирован — то предделитель отключён (т.е. установлен = 1 переключение/тик счётчика)!
  • Примечания: Предделитель не используется в режиме ENCODER_USE_SIMPLIFIED_CODE (там коэффициент деления фиксирован и, вследствие особенностей алгоритма, установлен = 4 раза).


4.2. Формат «регистра Статуса» Энкодера
(Справка: Расположены в секции DEncoderStatus в файле
<data.inc>, первый из пары)

Данные, обслуживающие энкодер, хранятся в паре байтовых регистров, последовательно расположенных в памяти, в блоке DEncoderStatus:
DEncoder0Status:	.byte	1	; пара регистров: регистр "Статуса"		(Примечание: статус-регистр энкодера, напрямую, никогда не используется прикладным кодом.)
DEncoder0Counter:	.byte	1	; и сразу следом: регистр "Счётчик тиков"	(Формат счётчика: особой специальной структуры не содержит. Это просто знаковое целое число: Signed Int = [-128..127])


Замечание!Замечу, однако, что прикладной код никогда не использует «статусный регистр энкодера» напрямую! Он, вспомогательный, нужен лишь для вычисления направления вращения энкодера, например, используя булеву функцию: F2(x1,x2,y1,y2) = (НЕ(x1) * y2) + (x1 * НЕ(y2)). Вращения энкодера, если таковые были произведены, и их направление — инкрементируются к регистру «двунаправленного аддитивного счётчика тиков» энкодера. (Всё это происходит в служебных процедурах «конвейера обработки кнопок», в частности, в коде процедуры KEY_UPDATE_ENCODER_STATUS.)

7   6   5   4  |   3   2   1   0
F2  .  y2  x2  | PreCntr  y1  x1
8-битный регистр разделён на две «тетрады»:
  • В первой тетраде DEncoderStatus[3:0] = закодировано предыдущее состояние энкодера.
  • Во второй тетраде DEncoderStatus[7:4] = закодировано последнее состояние энкодера.
  • Биты DEncoderStatus[2:3] содержат «предварительный делитель счётчика» = 1..4, через который «по 4 переключения входных сигналов на один тик» преобразуются к фактическому числу тиков, накапливаемому в регистре «счётчика тиков» энкодера.
  • (бит номер 6 — не используется, и равен 0)


Описание битов «регистра статуса» энкодера:
.equ	ENCODER_STATUS_X1	= 0	; Предыдущее        состояние энкодера: значение входного канала AC(X1)
.equ	ENCODER_STATUS_Y1	= 1	; Предыдущее        состояние энкодера: значение входного канала BC(Y1)
.equ	ENCODER_STATUS_PRECOUNTER = 2	; Следующие два бита - это предварительный счётчик =1..4, через который "по 4 переключения входных сигналов на один тик" преобразуются к фактическому числу тиков, накапливаемому в регистре "счётчика тиков" энкодера...
.equ	ENCODER_STATUS_X2	= 4	; Последнее/текущее состояние энкодера: значение входного канала AC(X2)
.equ	ENCODER_STATUS_Y2	= 5	; Последнее/текущее состояние энкодера: значение входного канала BC(Y2)
.equ	ENCODER_STATUS_F2	= 7	; Последнее/текущее состояние энкодера: вычисленное направление вращения (F2) - это самая нужная, итоговая статусная информация: в какую сторону осуществлён последний тик поворота?


Кодировка значения функции вращения:
  • F = 0 C.W., вращение по часовой («правый винт») -> (+1) к счётчику
  • F = 1 C.C.W., вращение против часовой («левый винт») -> (-1) к счётчику
Причём, кодировка входных каналов X и Y (=0 или 1?), здесь, в принципе, не важна — используется лишь порядок их переключения...

Предупреждение: Начальное состояние статус-регистра = 0b00000000, не является «разрешённой комбинацией» входных сигналов, но алгоритм не сломает. Ну, в худшем случае: сразу после RESET, в «регистр счётчика» энкодера может попасть +1/-1… (не важно!)

4.3. Формат «регистра Счётчика» Энкодера
(Справка: Расположены в секции DEncoderStatus в файле
<data.inc>, второй из пары)

76543210
Snnnnnnn
Регистр «счётчика тиков» особой специальной структуры не содержит — это просто знаковое целое число: Signed short int = [-128..127].
Начальное состояние = 0b00000000.

«Счётчик тиков» является конечным в конвейере обработки энкодера, и его использует прикладной код для реакции на жесты с энкодером:
  • если DEncoderCounter == 0, то вращений [с момента последней обработки] не было, ничего делать не нужно.
  • если DEncoderCounter > 0, то зафиксированы вращения, причём в сумме, больше провернули по часовой стрелке: положительное число [1..127] в регистре равно числу тиков [с момента последней обработки].
  • если DEncoderCounter < 0, то зафиксированы вращения, причём в сумме, больше провернули против часовой стрелке: отрицательное число [-128..-1] в регистре равно числу тиков [с момента последней обработки].
Причём, между обработками событий энкодера прикладным кодом (которые запускаются гораздо реже, чем опрос энкодера) пользователь может колбасить ручку энкодера произвольно в разные стороны, а итоговый результат будет правильно отражён в «Счётчике тиков»!

Запускайте обработчик событий энкодера чаще, чем переполняется его регистр «счётчика тиков» (чтобы значение в нём не превышало границы [-128..127]).

В обработчике событий — просто приплюсуйте результат из «Счётчика тиков» к вашей управляемой переменной величине…

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


Продолжение следует…
Переход к следующей части статьи -->

  • 0
  • 07 декабря 2013, 17:07
  • Celeron

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

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