Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
903 lines (735 sloc) 56.9 KB
.IFNDEF _CELERONKEYINPUTLIB__INCLUDED_
.EQU _CELERONKEYINPUTLIB__INCLUDED_ = 1
.MESSAGE "Note: <celeronkeyinputlib.inc> (ver.1.0 beta) have included, only once!"
.include "macrobaselib.inc" ; Библиотека базовых Макроопределений (требуется)
;=== BEGIN "celeronkeyinputlib.inc" ========================================
; Библиотека процедур для интеллектуальной обработки ВВОДА: сканирование Кнопок и Энкодеров.
; Брифинг:
; Приведенные здесь процедуры являются универсальными, их код модифицировать [обычно] не требуется!
; Единственная процедура, в этой библиотеке, код которой требуется адаптировать к вашей конкретной физической схеме - это KEY_SCAN_INPUT (через неё осуществляется связь с физическими каналами ввода).
; Также, под вашу конкретную физическую схему, требуется адаптировать блок определения данных в DSEG (в файле "data.inc"): определения блоков регистров Интегратора, статусных регистров Кнопок и Энкодеров, и некоторые константы.
; Рекомендация: при правильной организации адресного пространства регистров (в DSEG),
; процедура KEY_SCAN_INPUT (обеспечивающая взаимодействие микроконтроллера с внешней аппаратной периферией ввода) - единственная требует переделки/адаптации, при переносе прошивки на новое "железо",
; все другие процедуры этой библиотеки (конвеера обработки кнопок) - абстрактные и универсальные, не требуют модификации их кода!
; Правильная "организация адресного пространства регистров" (в DSEG) следующая:
; Обязательно, сконфигурировать количества физических каналов/кнопок/энкодеров: CEncoderInputChannelCount, CButtonInputChannelCount, [и возможно понадобится CSkipInputChannelCount (в зависимости от реализации кода KEY_SCAN_INPUT)].
; Блоки регистров в DSEG следует располагать последовательно и начинать с характерных "системных меток": DInputIntegrator, DEncoderStatus, DButtonStatus. (Примечание: в блоках важен лишь порядок и количество! названия самих прикладных регистров - произвольные, придумываете сами, как удобно использовать в прикладном коде.)
; Затем, если требуется, подстройте "режимные константы": CButtonLatchDepth, CEncoderLatchDepth, CShortButtonTouchDuration.
; (больше ничего переделывать не требуется, при правильном подходе)
;---------------------------------------------------------------------------
;---------------------------------------------------------------------------
; (Головная процедура конвеера обработки кнопок)
;
; KEY_SCAN_INPUT
; Главная процедура, сканирующая кнопки (должна запускаться часто)
; опрашивает состояние физических кнопок,
; запускает конвеер математической обработки (помехоподавление и т.п.),
; устанавливает значения статусных-регистров Кнопок и Энкодеров
;
;---------------------------------------------------------------------------
;----- Subroutine Register Variables
; Без параметров.
; Памятка: также использует/портит содержимое регистров TEMP1, TEMP2,
; TEMP4, (под переменные цикла)
; TEMP3, (опосредованно в KEY_UPDATE_ENCODER_STATUS)
; R25, X(R27:R26), Y(R29:R28) (под параметры вложенных функций конвеера).
;----- Code
; Памятка по текущей реализации:
; SETB DDRB, PinScan ;(OUT) ; Пин Scan/Sence - будет периодически переключаться на OUT/IN...
; CLRB DDRB, PinSense ;(IN) ; Здесь, номера пинов PinScan=PinSense - одинаковые. Но регистры ввода (PINB) и вывода (PORTB) данных - разные.
;
; CLRB PORTB, PinSense ;(OFF) ; При Sence(IN), подтяг к шине "Питания" должен всегда быть выключен!
; ; Т.к. в моей схеме используется *обратный* подтяг: шины "Sense" к "Земле", реализованный внешней цепью (чтобы зафиксировать и усилить лог."0", при отсутствии замыкания кнопок).
;
; STOREB PINB, PinSense ; Прочитать текущее состояние текущего Канала -> в T.
; LOADB PORTB, PinScan ; T -> Вывести бит на шину Данных.
; SETB PORTB, PinScan ; "1" -> Вывести бит на шину Данных.
; CLRB PORTB, PinScan ; "0" -> Вывести бит на шину Данных.
;
; Для фиксации выводимого бита сдвиговым регистром - нужно Передёрнуть Синхроимпульс на выводе PinClock (Datasheet на 74HC164 гласит: "Data is entered on each rising edge of the clock.")
; SETB PORTB, PinClock ; вывести "1" в порт (Сформировать фронт синхроимпульса, по которому бит данных протолкнётся в сдвиговый регистр!)
; CLRB PORTB, PinClock ; вывести "0" в порт (Замечу: На ATTiny25/45/85, ширина Синхроимпульса выходит = 2 такта CPU = длительность инструкций SBI/CBI)
KEY_SCAN_INPUT:
; Инициализация
CLRB PORTB, PinClock ; исходное положение
SETB DDRB, PinScan ; (OUT)
SETB PORTB, PinScan ; "1" -> Вывести бит на шину Данных.
LDI IntegratorAddressLow, Low(DInputIntegrator) ; (примечание: здесь загружаем в регистр адрес, а не значение)
LDI IntegratorAddressHigh, High(DInputIntegrator) ; где регистр IntegratorAddress = X(R27:R26)
LDI StatusAddressLow, Low(DEncoderStatus) ; (примечание: здесь загружаем в регистр адрес, а не значение)
LDI StatusAddressHigh, High(DEncoderStatus) ; где регистр StatusAddress = Y(R29:R28)
; (Примечание: замечу, что разные группы статусных регистров, и кнопок и энкодера - индексируются через один и тот же индексный регистр Y. Причём, их обработка производится одним сквозным проходом - т.е. данный регистр больше не будет, так грубо, инициализироваться физическим адресом.)
; Сканирование физических каналов Энкодера:
LDI IntegratorLatchDepth, CEncoderLatchDepth ; глубина защёлки интегратора, для опроса контактных групп Энкодера
LDI TEMP4, CEncoderInputChannelCount ; Количество входных каналов, которое обрабатывается (Примечание: здесь, всегда чётное число - поскольку у Энкодеров по два входных канала!)
EncoderLoop__KEY_SCAN_INPUT:
SETB PORTB, PinClock ; Для фиксации выводимого бита сдвиговым регистром - Передёрнуть Синхроимпульс...
CLRB PORTB, PinClock
CLRB PORTB, PinScan ; "0" -> Вывести бит на шину Данных. А также, При Sence(IN), отключает подтяг Пина к шине "Питания" - он должен всегда быть выключен (OFF)...
CLRB DDRB, PinSense ; (IN) - установить направление шины Данных: на вход
NOP ; (Insert nop for synchronization - см. datasheet: требуется задержка, после переключения DDR на вход, до чтения из PIN)
STOREB PINB, PinSense ; Прочитать текущее состояние текущего Канала -> в T.
SETB DDRB, PinScan ; (OUT) - вернуть направление шины Данных: на выход
; Проинтегрировать сигнал
RCALL KEY_PROCESS_INTEGRATOR
BST TEMP4, 0 ; Проверка значения счётчика TEMP4==чёт/нечет? Для отсчёта пар каналов - чтобы после Интеграции второго канала, вызвать Фиксацию статуса Энкодера.
BRTC NotHavePairYet__KEY_SCAN_INPUT ; Если T==0? То TEMP4==чётное. Значит, обрабатывается ещё только первый канал из пары...
; (состояние: пара каналов текущего энкодера уже проинтегрирована)
; Фиксировать статус Энкодера
LD TEMP1, -X ; адрес в регистре X-- (коррекция: шаг назад, указатель на первый канал)
RCALL KEY_UPDATE_ENCODER_STATUS
;LD TEMP1, X+ ; адрес в регистре X++ (возвращаемся: шаг вперёд...)
;LD TEMP1, Y+ ; адрес в регистре Y++ (минуем: статус-регистр)
LD TEMP1, Y+ ; адрес в регистре Y++ (минуем: счётчик тиков)
NotHavePairYet__KEY_SCAN_INPUT:
LD TEMP1, X+ ; адрес в регистре X++ (переход: указатель на следующий регистр интегратора)
DEC TEMP4 ; счётчик цикла--
BRNE EncoderLoop__KEY_SCAN_INPUT
; Сканирование физических каналов Кнопок:
LDI IntegratorLatchDepth, CButtonLatchDepth ; глубина защёлки интегратора, для опроса тактовых Кнопок
LDI TEMP4, CButtonInputChannelCount ; Количество входных каналов, которое обрабатывается
ButtonLoop__KEY_SCAN_INPUT:
SETB PORTB, PinClock ; Для фиксации выводимого бита сдвиговым регистром - Передёрнуть Синхроимпульс...
CLRB PORTB, PinClock
;CLRB PORTB, PinScan ; "0" -> Вывести бит на шину Данных. А также, При Sence(IN), отключает подтяг Пина к шине "Питания" - он должен всегда быть выключен (OFF)...
CLRB DDRB, PinSense ; (IN) - установить направление шины Данных: на вход
NOP ; (Insert nop for synchronization - см. datasheet: требуется задержка, после переключения DDR на вход, до чтения из PIN)
STOREB PINB, PinSense ; Прочитать текущее состояние текущего Канала -> в T.
SETB DDRB, PinScan ; (OUT) - вернуть направление шины Данных: на выход
; Проинтегрировать сигнал
RCALL KEY_PROCESS_INTEGRATOR
; Фиксировать статус Кнопки
RCALL KEY_UPDATE_BUTTON_STATUS
LD TEMP1, Y+ ; адрес в регистре Y++ (минуем: статус-регистр)
LD TEMP1, X+ ; адрес в регистре X++ (переход: указатель на следующий регистр интегратора)
DEC TEMP4 ; счётчик цикла--
BRNE ButtonLoop__KEY_SCAN_INPUT
; Прогоняем остаток циклов - обнуляем сдвиговый регистр:
LDI TEMP4, CSkipInputChannelCount ; Количество входных каналов, которое обрабатывается
SkipLoop__KEY_SCAN_INPUT:
SETB PORTB, PinClock ; Для фиксации выводимого бита сдвиговым регистром - Передёрнуть Синхроимпульс...
CLRB PORTB, PinClock
;CLRB PORTB, PinScan ; "0" -> Вывести бит на шину Данных. А также, При Sence(IN), отключает подтяг Пина к шине "Питания" - он должен всегда быть выключен (OFF)...
DEC TEMP4 ; счётчик цикла--
BRNE SkipLoop__KEY_SCAN_INPUT
RET
;---------------------------------------------------------------------------
;
; KEY_PROCESS_INTEGRATOR
; Интеграция "сырого" статуса входного канала (функция математического подавления звона и помех)
; Вызывается после опроса физической кнопки (любой реализации), и получения цифрового кода её текущего статуса: "0" или "1".
;
; ПАРАМЕТРЫ:
; Через status bit "T" передаётся "сырой" цифровой код текущего статуса кнопки = "0" или "1".
; Регистровым параметром "IntegratorAddress" передаётся адрес "регистра интегратора" в памяти (ОЗУ).
; Регистровым параметром "IntegratorLatchDepth" специфицируется "глубина защёлки" для текущего канала.
; (Примечание: наличие последнего параметра предполагает, что сигналы от тактовых кнопок и энкодера, например, требуют разных "глубин защёлки" - ввиду их различной динамичности и надёжности...)
;
;---------------------------------------------------------------------------
;
; ФОРМАТ РЕГИСТРА ИНТЕГРАТОРА (DInputIntegrator):
;
; 76543210
; Snnnnnnn
;
; В целом, регистр хранит одно число (signed short int) = [-128,0,+127]
; Результат интеграции - отражается в Sign bit (он же Negative Flag "N"), означает: ="0" (кнопка отпущена) или ="1" (кнопка нажата)...
;
; Начальное состояние = 0b00000000, что означает: примерно среднее неопределённое положение защёлки, но кнопка всё же на стороне "отпущена" (что соответствует обычной ситуации: кнопки, конструктивно, "нормально разомкнутые").
;
; Кодировка входного сигнала:
; СТАНДАРТ: Статус входного физического канала приходит в следующей кодировке: ="0" (кнопка отпущена) или ="1" (кнопка нажата).
; (Примечание: И только так! И не перенастраивается! Рекомендация: А если ваша реализация схемы кнопок возвращает другой, инверсный код? То просто добавьте, в ваш код, безусловную математическую XOR-инверсию, после чтения кода из Порта.)
; (Вопрос: А зачем так жёстко фиксировать, ведь в процедуре достаточно изменить только одну инструкцию BRTS/BRTC, чтобы кодировка входного сигнала стала инверсной? Можно было бы настраивать директивой условной компиляции...
; Ответ: Для минимизации путаницы - код конвеера обработки кнопок не модифицируется, логика неизменна!
; Напомню, что кроме Кнопок, через интеграторы обрабатываются также Энкодеры, у которых свои рекомендуемые схемы подключения и кодировки сигналов...
; Допускается, что разные группы Кнопок/Энкодеров, в системе, могут быть подключены разными схемами, и возвращать свои статусы в разных кодировках, вперемешку...
; Гораздо проще и понятнее, и универсальнее: при написании кода процедуры KEY_SCAN_INPUT, добавить безусловные XOR-инверсии к некоторым определённым "физическим каналам", для приведения кодировки к единому СТАНДАРТУ!)
; Семантически - это инкрементальный двунаправленный счётчик:
; Если физический канал (кнопка) возвращает статус "1" ("нажата") - то на этой итерации производится декрементация (-1) регистра счётчика.
; Если физический канал (кнопка) возвращает статус "0" ("отпущена") - то на этой итерации производится инкрементация (+1) регистра счётчика.
; Максимальное, по модулю, значение в регистре счётчика определяется константой "глубины защёлки" (LatchDepth) = [-LatchDepth, 0..+(LatchDepth-1)]. Причём, замечу, что здесь значение =0 включается в статус "кнопка отпущена"!
;
;---------------------------------------------------------------------------
;----- Subroutine Register Variables
.def IntegratorLatchDepth = R25 ; входной параметр: "глубина защёлки" для текущего канала (примечание: не изменяется внутри процедуры)
.def IntegratorAddressLow = R26 ; XL (примечание: не изменяется внутри процедуры)
.def IntegratorAddressHigh = R27 ; XH (примечание: не изменяется внутри процедуры)
; Также, параметром является status bit "T" = цифровой код текущего статуса кнопки ("0" или "1").
; Памятка: также использует/портит содержимое регистров TEMP1, TEMP2.
;----- Code
KEY_PROCESS_INTEGRATOR:
LD temp1, X ; Загружаем регистр Интегратора
MOV temp2, IntegratorLatchDepth ; это значение мы будем менять походу - поэтому перекинем его во временный регистр, во избежание side effects...
BRTS PressedDecrement__KEY_PROCESS_INTEGRATOR ; Выбираем режим модификации
ReleasedIncrement__KEY_PROCESS_INTEGRATOR:
DEC temp2 ; =(LatchDepth-1) перед сравнением, предекрементируем значение "защёлки": потому что верхняя граница счётчика должна быть <= (LatchDepth-1)
CP temp1, temp2
BREQ End__KEY_PROCESS_INTEGRATOR ; temp1 == (LatchDepth-1), защёлка уже на пределе - модификация запрещена...
INC temp1 ; инкрементируем счётчик интегратора
RJMP End__KEY_PROCESS_INTEGRATOR
PressedDecrement__KEY_PROCESS_INTEGRATOR:
NEG temp2 ; =(-LatchDepth) перед сравнением, переводим значение границы в дополнительный код
CP temp1, temp2
BREQ End__KEY_PROCESS_INTEGRATOR ; temp1 == (-LatchDepth), защёлка уже на пределе - модификация запрещена...
DEC temp1 ; декрементируем счётчик интегратора
;RJMP End__KEY_PROCESS_INTEGRATOR
End__KEY_PROCESS_INTEGRATOR:
ST X, temp1 ; Сохраняем регистр Интегратора
RET
;---------------------------------------------------------------------------
;---------------------------------------------------------------------------
;
; KEY_UPDATE_ENCODER_STATUS
; Фиксация статуса Энкодера (устанавливает значения в "статус-регистре" и "счётчике тиков" Энкодера).
; в зависимости от, динамически изменяющегося, [интегрированного] состояния его двух управляющих каналов: AC и BC.
;
; ПАРАМЕТРЫ:
; Регистровым параметром "IntegratorAddress" передаётся адрес ПЕРВОГО "регистра интегратора" (из пары последовательно расположенных регистров в памяти ОЗУ, которые кодируют текущее "физическое" состояние Энкодера).
; Регистровым параметром "StatusAddress" передаётся адрес "статусного регистра энкодера" (в памяти ОЗУ). При этом, предполагается, что следом за ним расположен регистр "счётчика тиков" этого Энкодера. Их оба - следует обновить...
;
;---------------------------------------------------------------------------
;
; ФОРМАТ СТАТУСНОГО РЕГИСТРА ЭНКОДЕРА (DEncoderStatus):
;
; 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)
;
; АЛГОРИТМ:
; Программа проверяет текущие состояния входных управляющих каналов энкодера: BC(Y) и AC(X).
; Если эти значения изменились, относительно текущего "последнего" состояния энкодера (Y2,X2), закодированного в статус-регистре? То происходит фиксация очередного тика:
; 1) "Тетрады" 8-битного статус-регистра меняются местами - таким образом, значения (y2,x2) -> переписываются на место (y1,x1), в часть младших битов... (А что происходит, при этом, с первой тетрадой битов - не важно, они будут переписаны.)
; 2) Новые, актуальные, значения состояний входных управляющих каналов энкодера BC(Y) и AC(X) -> записываются в биты (Y2,X2).
; 3) Наконец, по формуле, вычисляется булева функция от четырёх переменных F2(x1,x2,y1,y2), которая кодирует направление вращения энкодера.
; 4) Затем, также, наращивается "счётчик тиков", в зависимости от направления вращения...
;
;
; Кодировка:
; F = 0 C.W., вращение по часовой ("правый винт") (+1) к счётчику
; F = 1 C.C.W., вращение против часовой ("левый винт") (-1) к счётчику
; кодировка входных каналов X,Y (=0 или 1), здесь, в принципе, не важна - используется лишь порядок их переключения (функция F симметрична)...
;
; Временные диаграммы:
; F = 0 C.W., вращение по часовой ("правый винт") (+1) к счётчику
; случай: 1) 2) 3) 4)
; AC(X): 1 0 0 1 1
; BC(Y): 1 1 0 0 1
;
; F = 1 C.C.W., вращение против часовой ("левый винт") (-1) к счётчику
; случай: 5) 6) 7) 8)
; AC(X): 1 1 0 0 1
; BC(Y): 1 0 0 1 1
;
; Таблица истинности:
; -----------------------------------------------------------------------
; | F2 | y2 x2 | y1 x1 | случай |
; -----------------------------------------------------------------------
; | - | 0 0 | 0 0 | после RESET, запрещена
; | 1 | 0 0 | 0 1 | 6) 4/4й такт, перед фиксацией положений
; | 0 | 0 0 | 1 0 | 2) 4/4й такт, перед фиксацией положений
; | - | 0 0 | 1 1 |
; | 0 | 0 1 | 0 0 | 3)
; | - | 0 1 | 0 1 |
; | - | 0 1 | 1 0 |
; | 1 | 0 1 | 1 1 | 5)
; -----------------------------------------------------------------------
; | 1 | 1 0 | 0 0 | 7)
; | - | 1 0 | 0 1 |
; | - | 1 0 | 1 0 |
; | 0 | 1 0 | 1 1 | 1)
; | - | 1 1 | 0 0 |
; | 0 | 1 1 | 0 1 | 4)
; | 1 | 1 1 | 1 0 | 8)
; | - | 1 1 | 1 1 |
; -----------------------------------------------------------------------
; Примечание: начальное состояние статус-регистра = 0b00000000 - не является "разрешённой комбинацией" входных сигнала, но алгоритм это не сломает. Ну, в худшем случае: сразу после RESET, в "регистр счётчика" энкодера может попасть +1/-1... (не важно!)
;
; Возможные "минимальные нормальные формы" булевой функции:
; МДНФ: F2(y2,x2,y1,x1) = (НЕ(x2) * НЕ(y1)) + ( x2 * y1 )
; F2(y2,x2,y1,x1) = (НЕ(x1) * y2 ) + ( x1 * НЕ(y2)) <- мой выбор! (выражение, которое показалось мне удобнее для реализации)
; МКНФ: F2(y2,x2,y1,x1) = (НЕ(x2) + y1 ) * ( x2 + НЕ(y1))
; F2(y2,x2,y1,x1) = (НЕ(x1) + НЕ(y2)) * ( x1 + y2 )
;
;
; Примечание: используемый в данном макете Энкодер "RE1203XC1-H01" обладает следующими особенностями:
; Оба канала "нормально разомкнутые": в фазе фиксированного положения ручки энкодера (наиболее вероятные положения) - оба канала разомкнуты, ENCODER_STATUS_X2==ENCODER_STATUS_Y2==0;
; При повороте ручки на один тик: замыкается сначала один канал, потом другой, потом также размыкается первый, затем второй... (итого, 4 переключения сигналов на один тик)
; Направление вращения ручки (CW/CCW) определяет: который из каналов переключается первым, а второй - следом, за ним...
; (Используйте "ПРЕДВАРИТЕЛЬНЫЙ ДЕЛИТЕЛЬ СЧЁТЧИКА", для компенсации лишних переключений.)
;
; Замечу, что если быстро крутануть ручку энкодера так, что не все промежуточные состояния могут быть чётко зафиксированы - то показания глючит и колбасит!
; Показания в статус-регистре будут искажаться относительно физического состояния, будут фиксироваться ошибочные комбинации переключений сигналов - появятся ложные срабатывания энкодера: не только пропуски счёта, но показания даже будет колбасить туда-сюда, в обратную сторону...
; Поэтому, Пользователь будет вынужден медленнее крутить ручку, чтобы показания фиксировались правильно... Залог правильной работы энкодера в том - чтобы МК работал на порядки быстрее, чем переключаются каналы!
; (Выбирайте соответствующий "ВАРИАНТ РЕАЛИЗАЦИИ КОДА", для реализации требуемой помехозащищённости.)
;
;
; Замечу, однако, что прикладной код никогда не использует "статусный регистр энкодера" напрямую!
; Этот регистр - вспомогательный, и нужен лишь для вычисления направления вращения энкодера, используя булеву функцию: F2(x1,x2,y1,y2) = (НЕ(x1) * y2) + (x1 * НЕ(y2)).
; Вращения энкодера (если таковые были произведены, и их направление) - инкрементируются к регистру "двунаправленного аддитивного счётчика тиков" энкодера.
; (Всё это происходит в служебных функциях "конвеера обработки кнопок".)
;
;---------------------------------------------------------------------------
;
; ФОРМАТ РЕГИСТРА "СЧЁТЧИК ТИКОВ" ЭНКОДЕРА (DEncoderCounter):
;
; Особой специальной структуры не содержит! Это просто знаковое целое число: Signed Int = [-128..127]
;
; Начальное состояние = 0b00000000.
;
; "Счётчик тиков" является конечным в конвеере обработки энкодера, и его использует прикладной код для реакции на жесты с энкодером:
; если DEncoderCounter == 0, то вращений [с момента последней обработки] не было, ничего делать не нужно.
; если DEncoderCounter > 0, то зафиксированы вращения, причём в сумме, больше провернули по часовой стрелке: положительное число [1..127] в регистре равно числу тиков [с момента последней обработки].
; если DEncoderCounter < 0, то зафиксированы вращения, причём в сумме, больше провернули против часовой стрелке: отрицательное число [-128..-1] в регистре равно числу тиков [с момента последней обработки].
; Причём, между обработками событий энкодера прикладным кодом (которые запускается гораздо реже, чем опрос энкодера) пользователь может колбасить ручку энкодера произвольно в разные стороны, а итоговый результат будет правильно отражён в "Счётчике тиков"!
; В обработчике событий - просто приплюсуйте результат из "Счётчика тиков" к вашей управляемой переменной величине...
; А затем, после обработки энкодера прикладным кодом и произведения ответной реакции, "СБРОСьте" регистр "Счётчик тиков" в ноль (но "статусный регистр" энкодера не трогайте!) -> так снова устанавливается статус-кво: энкодер = "не вращался".
; Это простой способ сообщить остальному прикладному коду, что данное событие уже обработано и не требует дальнейшего участия!
;
;---------------------------------------------------------------------------
;----- Subroutine Register Variables
;.def IntegratorAddressLow = R26 ; XL (примечание: в целях оптимизации кода, внутри процедуры указатель X изменяется на +1, и после исполнения процедуры - указывает на следующую ячейку памяти)
;.def IntegratorAddressHigh = R27 ; XH
.def StatusAddressLow = R28 ; YL (примечание: в целях оптимизации кода, внутри процедуры указатель Y изменяется на +1, и после исполнения процедуры - указывает на следующую ячейку памяти)
.def StatusAddressHigh = R29 ; YH
; Памятка: также использует/портит содержимое регистров TEMP1, TEMP2, TEMP3.
;----- Code
; Выберите, один из трёх, вариантов реализации кода (раскомментируйте соответствующий символ):
;.EQU ENCODER_USE_SIMPLIFIED_CODE = 1 ; "Упрощённый код" (простой алгоритм, маленький и быстрый код! не защищён от ошибочных входных сигналов; предделитель фиксирован = 4 раза) - рекомендуется использовать на стабильном железе: быстром Микроконтроллере и при низком уровне помех по входным каналам. Примечание: в этом варианте, код "предварительного делителя счётчика" не используется - но делитель есть и фиксирован: показания "счётчика тиков" делятся в 4 раза.
;.EQU ENCODER_USE_ACADEMIC_CODE = 2 ; "Академический код" (самый компактный код и поражает воображение своим изяществом! но не защищён от ошибочных входных сигналов) - преимущества: он проще/меньше/быстрее параноидального кода, и с ним работает полноценный "предварительный делитель счётчика"! Обычно, не рекомендуется его использовать: т.к. он является "серединкой на половинку" среди других вариантов, и особых преимуществ не даёт... (На плохом железе: "Академический код" глючит столь же интенсивно, как и "Упрощённый код", но они по разному ощущаются - выбери себе глюки по вкусу!)
.EQU ENCODER_USE_PARANOID_CODE = 3 ; "Параноидальный код" (тупой алгоритм и избыточный код, перебирающий все комбинации входных сигналов; защищён от "ОДНОКРАТНЫХ ошибок" по входу!) - предполагается, будет математически сглаживать и преодолевать последствия некоторых ошибок опроса физических каналов энкодера (при пропуске одного такта - запрещённая комбинация по входу, будет проигнорирована). Используйте этот вариант на плохом железе: при медленном микроконтроллере, или когда опрос энкодера происходит с ошибками.
; Выберите коэффициент "предварительного делителя счётчика тиков" энкодера:
; Если символ закомментирован - то предделитель отключён!
; Примечание: Также, предделитель не используется в режиме ENCODER_USE_SIMPLIFIED_CODE! (Там коэффициент деления фиксирован, в следствие особенностей алгоритма: устанавливается = 4 раза.)
.EQU ENCODER_USE_PRECOUNTER = 4 ; (Здесь, допустимые значения предделителя = 2,3,4 переключения / на один тик счётчика. Рекомендуется = 4.)
KEY_UPDATE_ENCODER_STATUS:
; Проверить входные каналы:
CLR temp2
LD temp1, X+ ; Загружаем регистр Интегратора первого канала
BST temp1, INTEGRATOR_IS_PRESSED
BLD temp2, ENCODER_STATUS_X2 ; состояние канала (замкнут или разомкнут) -> temp2
LD temp1, X ; Загружаем регистр Интегратора второго канала
BST temp1, INTEGRATOR_IS_PRESSED
BLD temp2, ENCODER_STATUS_Y2 ; состояние канала (замкнут или разомкнут) -> temp2
;LD temp1, -X ; вернуть указатель в индексном регистре к исходному значению (нет, уже не надо возвращать! потому что передумал концепцию интерфейса функции...)
LD temp1, Y+ ; Загружаем статус-регистр Энкодера
MOV temp3, temp1 ; сохраним оригинальное значение, на будущее...
ANDI temp1, (1<<ENCODER_STATUS_Y2)|(1<<ENCODER_STATUS_X2) ; из всего регистра выделить только нужные биты, остальные обнулить
CP temp1, temp2 ; сравнить текущее состояние энкодера и зафиксированое в его статусном регистре
BRNE InputHaveChanged__KEY_UPDATE_ENCODER_STATUS
RJMP Exit__KEY_UPDATE_ENCODER_STATUS ; если они одинаковые, то обновлять регистры состояния энкодера не нужно - выходим...
InputHaveChanged__KEY_UPDATE_ENCODER_STATUS:
; (состояние: состояние энкодера изменилось - нужно обновить его регистры)
; Сформировать значения "состояний входных каналов" в статус-регистре:
SWAP temp1 ; "Тетрады" 8-битного статус-регистра меняются местами - таким образом, значения (y2,x2) -> переписываются на место (y1,x1), в часть младших битов...
OR temp1, temp2 ; Новые, актуальные, значения состояний входных управляющих каналов энкодера BC(Y) и AC(X) -> записываются в биты (Y2,X2).
; Сформировать значение функции "направление вращения" (F2) в статус-регистре:
; Вход в блок:
; temp3 = | (F2) 0 (y2) (x2) | PreCntr (y1) (x1) | (старое оригинальное значение статус-регистра)
; temp1 = | 0 0 y2 x2 | 0 0 y1 x1 | (новые значения "состояний входных каналов")
; Выход из блока:
; temp1 = | F2 0 y2 x2 | 0 0 y1 x1 | (новые значения "состояний входных каналов" и просчитанная "функция направления")
; Временные в блоке: temp1, temp2.
.ifdef ENCODER_USE_SIMPLIFIED_CODE
; (упрощенная реализация: изменяющая значение функции только на 4/4й такт переключения входных сигналов - только в фазе фиксированного положения, когда ENCODER_STATUS_X2==ENCODER_STATUS_Y2==0)
; Из восьми "разрешённых входных комбинаций" для булевой функции F2(x1,x2,y1,y2) - рассматриваются только две "характерных комбинации"...
; temp1 = | 0 0 y2 x2 | 0 0 y1 x1 | (изначально)
MOV temp2, temp3 ; temp3 = | (F2) 0 (y2) (x2) | PreCntr (y1) (x1) | (старое оригинальное значение статус-регистра)
ANDI temp2, (1<<ENCODER_STATUS_F2) ; temp2 = | F1 0 0 0 | 0 0 0 0 | (прежнее значение функции)
CLT ; сбросить флаг "обновлено значение функции" (T=0)
;Check2__KEY_UPDATE_ENCODER_STATUS: ; pattern=| 0 0 0 0 | 0 0 1 0 | случай 2) 4/4й такт, перед фиксацией положений: когда переключается канал Y==1..0, то фиксируем новое значение функции F2=0
CPI temp1, (0<<ENCODER_STATUS_Y2)|(0<<ENCODER_STATUS_X2)|(1<<ENCODER_STATUS_Y1)|(0<<ENCODER_STATUS_X1)
BRNE Check6__KEY_UPDATE_ENCODER_STATUS ; (не оно?)
LDI temp2, (0<<ENCODER_STATUS_F2) ; присвоить новое значение функции F2=0
SET ; поднять флаг "обновлено значение функции" (T=1)
RJMP SaveStatus__KEY_UPDATE_ENCODER_STATUS
Check6__KEY_UPDATE_ENCODER_STATUS: ; pattern=| 0 0 0 0 | 0 0 0 1 | случай 6) 4/4й такт, перед фиксацией положений: когда переключается канал X==1..0, то фиксируем новое значение функции F2=1
CPI temp1, (0<<ENCODER_STATUS_Y2)|(0<<ENCODER_STATUS_X2)|(0<<ENCODER_STATUS_Y1)|(1<<ENCODER_STATUS_X1)
BRNE SaveStatus__KEY_UPDATE_ENCODER_STATUS ; (не оно?) А варианты уже закончились - идём сохранять статус-регистр со старым значением функции...
LDI temp2, (1<<ENCODER_STATUS_F2) ; присвоить новое значение функции F2=1
SET ; поднять флаг "обновлено значение функции" (T=1)
;RJMP SaveStatus__KEY_UPDATE_ENCODER_STATUS
SaveStatus__KEY_UPDATE_ENCODER_STATUS:
OR temp1, temp2 ; temp1 = | F2 0 y2 x2 | 0 0 y1 x1 | (искомый результат)
ST -Y, temp1 ; Сохраняем статус-регистр Энкодера
LD temp2, Y+ ; коррекция: увеличиваем значение указателя (переходим со "статус-регистра" на "счётчик тиков")
BRTC Exit__KEY_UPDATE_ENCODER_STATUS ; если "значение функции НЕ было обновлено", осталось прежним (T==0?) - то сразу идём на выход и значение "счётчика тиков" оставляем прежним...
; (Иначе, если было "обновлено значение функции" (хотя само значение может остаться прежним) - то исполняем дальше: попадём на код модификации "счётчика тиков"...) (а предделитель, в этом варианте, вообще не используется - нулевые биты)
.else //end of ENCODER_USE_SIMPLIFIED_CODE
.ifdef ENCODER_USE_PARANOID_CODE
; (параноидальная реализация: тупой алгоритм, явно перебирающий все 8шт. "разрешённых комбинаций" входных сигналов.
; "Запрещённые комбинации" 8шт. - игнорируются, что обеспечивает защиту от "однократных ошибок" по входу.)
; temp1 = | 0 0 y2 x2 | 0 0 y1 x1 | (изначально)
MOV temp2, temp3 ; temp3 = | (F2) 0 (y2) (x2) | PreCntr (y1) (x1) | (старое оригинальное значение статус-регистра)
ANDI temp2, (1<<ENCODER_STATUS_F2) ; temp2 = | F1 0 0 0 | 0 0 0 0 | (прежнее значение функции)
CLT ; сбросить флаг "обновлено значение функции" (T=0)
;Check1__KEY_UPDATE_ENCODER_STATUS: ; pattern=| 0 0 1 0 | 0 0 1 1 | случай 1)
CPI temp1, (1<<ENCODER_STATUS_Y2)|(0<<ENCODER_STATUS_X2)|(1<<ENCODER_STATUS_Y1)|(1<<ENCODER_STATUS_X1)
BRNE Check2__KEY_UPDATE_ENCODER_STATUS ; (не оно?)
LDI temp2, (0<<ENCODER_STATUS_F2) ; присвоить новое значение функции F2=0
SET ; поднять флаг "обновлено значение функции" (T=1)
RJMP SaveStatus__KEY_UPDATE_ENCODER_STATUS
Check2__KEY_UPDATE_ENCODER_STATUS: ; pattern=| 0 0 0 0 | 0 0 1 0 | случай 2) 4/4й такт, перед фиксацией положений
CPI temp1, (0<<ENCODER_STATUS_Y2)|(0<<ENCODER_STATUS_X2)|(1<<ENCODER_STATUS_Y1)|(0<<ENCODER_STATUS_X1)
BRNE Check3__KEY_UPDATE_ENCODER_STATUS ; (не оно?)
LDI temp2, (0<<ENCODER_STATUS_F2) ; присвоить новое значение функции F2=0
SET ; поднять флаг "обновлено значение функции" (T=1)
RJMP SaveStatus__KEY_UPDATE_ENCODER_STATUS
Check3__KEY_UPDATE_ENCODER_STATUS: ; pattern=| 0 0 0 1 | 0 0 0 0 | случай 3)
CPI temp1, (0<<ENCODER_STATUS_Y2)|(1<<ENCODER_STATUS_X2)|(0<<ENCODER_STATUS_Y1)|(0<<ENCODER_STATUS_X1)
BRNE Check4__KEY_UPDATE_ENCODER_STATUS ; (не оно?)
LDI temp2, (0<<ENCODER_STATUS_F2) ; присвоить новое значение функции F2=0
SET ; поднять флаг "обновлено значение функции" (T=1)
RJMP SaveStatus__KEY_UPDATE_ENCODER_STATUS
Check4__KEY_UPDATE_ENCODER_STATUS: ; pattern=| 0 0 1 1 | 0 0 0 1 | случай 4)
CPI temp1, (1<<ENCODER_STATUS_Y2)|(1<<ENCODER_STATUS_X2)|(0<<ENCODER_STATUS_Y1)|(1<<ENCODER_STATUS_X1)
BRNE Check5__KEY_UPDATE_ENCODER_STATUS ; (не оно?)
LDI temp2, (0<<ENCODER_STATUS_F2) ; присвоить новое значение функции F2=0
SET ; поднять флаг "обновлено значение функции" (T=1)
RJMP SaveStatus__KEY_UPDATE_ENCODER_STATUS
Check5__KEY_UPDATE_ENCODER_STATUS: ; pattern=| 0 0 0 1 | 0 0 1 1 | случай 5)
CPI temp1, (0<<ENCODER_STATUS_Y2)|(1<<ENCODER_STATUS_X2)|(1<<ENCODER_STATUS_Y1)|(1<<ENCODER_STATUS_X1)
BRNE Check6__KEY_UPDATE_ENCODER_STATUS ; (не оно?)
LDI temp2, (1<<ENCODER_STATUS_F2) ; присвоить новое значение функции F2=1
SET ; поднять флаг "обновлено значение функции" (T=1)
RJMP SaveStatus__KEY_UPDATE_ENCODER_STATUS
Check6__KEY_UPDATE_ENCODER_STATUS: ; pattern=| 0 0 0 0 | 0 0 0 1 | случай 6) 4/4й такт, перед фиксацией положений
CPI temp1, (0<<ENCODER_STATUS_Y2)|(0<<ENCODER_STATUS_X2)|(0<<ENCODER_STATUS_Y1)|(1<<ENCODER_STATUS_X1)
BRNE Check7__KEY_UPDATE_ENCODER_STATUS ; (не оно?)
LDI temp2, (1<<ENCODER_STATUS_F2) ; присвоить новое значение функции F2=1
SET ; поднять флаг "обновлено значение функции" (T=1)
RJMP SaveStatus__KEY_UPDATE_ENCODER_STATUS
Check7__KEY_UPDATE_ENCODER_STATUS: ; pattern=| 0 0 1 0 | 0 0 0 0 | случай 7)
CPI temp1, (1<<ENCODER_STATUS_Y2)|(0<<ENCODER_STATUS_X2)|(0<<ENCODER_STATUS_Y1)|(0<<ENCODER_STATUS_X1)
BRNE Check8__KEY_UPDATE_ENCODER_STATUS ; (не оно?)
LDI temp2, (1<<ENCODER_STATUS_F2) ; присвоить новое значение функции F2=1
SET ; поднять флаг "обновлено значение функции" (T=1)
RJMP SaveStatus__KEY_UPDATE_ENCODER_STATUS
Check8__KEY_UPDATE_ENCODER_STATUS: ; pattern=| 0 0 1 1 | 0 0 1 0 | случай 8)
CPI temp1, (1<<ENCODER_STATUS_Y2)|(1<<ENCODER_STATUS_X2)|(1<<ENCODER_STATUS_Y1)|(0<<ENCODER_STATUS_X1)
BRNE SaveStatus__KEY_UPDATE_ENCODER_STATUS ; (не оно?) А варианты уже закончились - идём сохранять статус-регистр со старым значением функции...
LDI temp2, (1<<ENCODER_STATUS_F2) ; присвоить новое значение функции F2=1
SET ; поднять флаг "обновлено значение функции" (T=1)
;RJMP SaveStatus__KEY_UPDATE_ENCODER_STATUS
SaveStatus__KEY_UPDATE_ENCODER_STATUS:
OR temp1, temp2 ; temp1 = | F2 0 y2 x2 | 0 0 y1 x1 | (искомый результат)
ST -Y, temp1 ; Сохраняем статус-регистр Энкодера
LD temp2, Y+ ; коррекция: увеличиваем значение указателя (переходим со "статус-регистра" на "счётчик тиков")
BRTC Exit__KEY_UPDATE_ENCODER_STATUS ; если "значение функции НЕ было обновлено", осталось прежним (T==0?) - значит, встретилась запрещённая входная комбинация - тогда сразу идём на выход и значение "счётчика тиков" оставляем прежним...
; (Иначе, если было "обновлено значение функции" (хотя само значение может остаться прежним) - то исполняем дальше: попадём на код предделителя, и далее, на код модификации "счётчика тиков"...)
.else //end of ENCODER_USE_PARANOID_CODE
.ifdef ENCODER_USE_ACADEMIC_CODE
; (академическая реализация: реагирующая на любое изменение входных сигналов)
; Вычислить значение булевой функции: F2(x1,x2,y1,y2) = (НЕ(x1) * y2) + (x1 * НЕ(y2))
; temp1 = | 0 0 y2 x2 | 0 0 y1 x1 | (изначально)
MOV temp2, temp1
COM temp2 ; temp2 = | .. .. y2' x2' | .. .. y1' x1' |
SWAP temp2 ; temp2 = | .. .. y1' x1' | .. .. y2' x2' |
LSR temp2 ; temp2 = | .. .. .. y1' | x1' .. .. y2' |
BST temp2, 3 ; x1' -> T
BLD temp2, 5 ; temp2 = | .. .. x1' y1' | x1' .. .. y2' |
AND temp2, temp1 ; temp2 = | .. .. x1'*y2 .. | .. .. .. x1*y2' |
BST temp2, 5 ; x1'*y2 -> T
BLD temp1, ENCODER_STATUS_F2 ; temp1 = | x1'*y2 0 y2 x2 | 0 0 y1 x1 |
BST temp2, 0 ; x1*y2' -> T
CLR temp2
BLD temp2, ENCODER_STATUS_F2 ; temp2 = | x1*y2' 0 0 0 | 0 0 0 0 |
OR temp1, temp2 ; temp1 = | F2 0 y2 x2 | 0 0 y1 x1 | (искомый результат)
ST -Y, temp1 ; Сохраняем статус-регистр Энкодера
LD temp2, Y+ ; коррекция: увеличиваем значение указателя (переходим со "статус-регистра" на "счётчик тиков")
.endif //end of ENCODER_USE_ACADEMIC_CODE
.endif //ENCODER_USE_PARANOID_CODE
.ifdef ENCODER_USE_PRECOUNTER
; Нарастить счётчик "предварительного делителя":
; Имейте в виду, что большинство (если не все?) "инкрементальных энкодеров" осуществляют по 4 переключения сигналов на один тик (поворот ручки между "положениями с фиксацией")!
; Поэтому, чувствительнось энкодера, конечно, повышается... но число регистрируемое в "Счётчике тиков" (используемое в прикладном коде) - потребуется делить на 4 раза, чтобы получить фактическое число переключений!
; Здесь используется специальный "предварительный счётчик" (=1..4), через который "по 4 переключения входных сигналов на один тик" преобразуются к фактическому числу тиков, в регистре "счётчика тиков" энкодера...
; Вход в блок:
; temp3 = | (F2) 0 (y2) (x2) | PreCntr (y1) (x1) | (старое оригинальное значение статус-регистра)
; temp1 = | F2 0 y2 x2 | 0 0 y1 x1 | (новые значения "состояний входных каналов" и просчитанная "функция направления")
; Выход из блока:
; temp1 = | F2 0 y2 x2 | PreCntr y1 x1 |
; Временные в блоке: temp1, temp2, temp3.
;PreCounter__KEY_UPDATE_ENCODER_STATUS:
MOV temp2, temp3
ANDI temp2, (0b11<<ENCODER_STATUS_PRECOUNTER) ; выделить только значение "предварительного счётчика" (предделителя)
BST temp1, ENCODER_STATUS_F2 ; значение функции F2 -> T
BRTS CCWPreCounter__KEY_UPDATE_ENCODER_STATUS ; если F2==1 (status bit "T"==1)?
;CWPreCounter__KEY_UPDATE_ENCODER_STATUS:
SUBI temp2, -(1<<ENCODER_STATUS_PRECOUNTER) ; (+1) к счётчику
.if ENCODER_USE_PRECOUNTER==3
CPI temp2, (0b11<<ENCODER_STATUS_PRECOUNTER) ; оптимизация: хочу пред.счётчик "по модулю 3"! (так эргономичнее переключается: Исключает пустые тики, даже если был пропущен один такт из четырёх. Зато, иногда переключается с неединичной "ценой деления".)
BRNE SavePreCounter__KEY_UPDATE_ENCODER_STATUS
SUBI temp2, -(1<<ENCODER_STATUS_PRECOUNTER) ; коррекция: (+1) к счётчику
.endif
RJMP SavePreCounter__KEY_UPDATE_ENCODER_STATUS
CCWPreCounter__KEY_UPDATE_ENCODER_STATUS:
SUBI temp2, (1<<ENCODER_STATUS_PRECOUNTER) ; (-1) к счётчику
.if ENCODER_USE_PRECOUNTER==3
CPI temp2, (0b00<<ENCODER_STATUS_PRECOUNTER) ; оптимизация: хочу пред.счётчик "по модулю 3"! (так эргономичнее переключается: Исключает пустые тики, даже если был пропущен один такт из четырёх. Зато, иногда переключается с неединичной "ценой деления".)
BRNE SavePreCounter__KEY_UPDATE_ENCODER_STATUS
SUBI temp2, (1<<ENCODER_STATUS_PRECOUNTER) ; коррекция: (-1) к счётчику
.endif
;RJMP SavePreCounter__KEY_UPDATE_ENCODER_STATUS
SavePreCounter__KEY_UPDATE_ENCODER_STATUS:
.if ENCODER_USE_PRECOUNTER==2
BST temp2, ENCODER_STATUS_PRECOUNTER+1 ; бит переноса "предварительного счётчика" -> T
.else //3 или 4
BST temp2, ENCODER_STATUS_PRECOUNTER+2 ; бит переноса "предварительного счётчика" -> T
.endif
ANDI temp2, (0b11<<ENCODER_STATUS_PRECOUNTER) ; выделить только значение "предварительного счётчика"
OR temp1, temp2 ; temp1 = | F2 0 y2 x2 | PreCntr y1 x1 |
ST -Y, temp1 ; Сохраняем статус-регистр Энкодера
LD temp2, Y+ ; коррекция: увеличиваем значение указателя (переходим со "статус-регистра" на "счётчик тиков")
BRTC Exit__KEY_UPDATE_ENCODER_STATUS ; определиться: если не было переноса счётчика предделителя? (значит, предделитель ещё не достиг заданной границы) - тогда значение "счётчика тиков" не трогаем, идём на выход...
.endif //end of ENCODER_USE_PRECOUNTER
.endif //ENCODER_USE_SIMPLIFIED_CODE
; (состояние: значение в статус-регистре энкодера обновлено и сохранено)
; Нарастить "счётчик тиков", в зависимости от направления вращения:
; Вход в блок:
; temp1 = | F2 0 y2 x2 | PreCntr y1 x1 |
; Временные в блоке: temp1, temp2, temp3.
;Counter__KEY_UPDATE_ENCODER_STATUS:
BST temp1, ENCODER_STATUS_F2 ; новое значение функции F2 -> T
LD temp1, Y ; Загружаем "Счётчик тиков" Энкодера
BRTS CCWCounter__KEY_UPDATE_ENCODER_STATUS ; если F2==1 (status bit "T"==1)?
;CWCounter__KEY_UPDATE_ENCODER_STATUS:
INC temp1 ; (+1) к счётчику
BRVS Exit__KEY_UPDATE_ENCODER_STATUS ; если было переполнение счётчика (достигли верхнего хранимого предела), тогда не сохраняем это значение...
RJMP SaveCounter__KEY_UPDATE_ENCODER_STATUS
CCWCounter__KEY_UPDATE_ENCODER_STATUS:
DEC temp1 ; (-1) к счётчику
BRVS Exit__KEY_UPDATE_ENCODER_STATUS ; если было переполнение счётчика (достигли нижнего хранимого предела), тогда не сохраняем это значение...
;RJMP SaveCounter__KEY_UPDATE_ENCODER_STATUS
SaveCounter__KEY_UPDATE_ENCODER_STATUS:
ST Y, temp1 ; Сохраняем "Счётчик тиков" Энкодера
Exit__KEY_UPDATE_ENCODER_STATUS:
RET
;---------------------------------------------------------------------------
;---------------------------------------------------------------------------
;
; KEY_UPDATE_BUTTON_STATUS
; Фиксация статуса Кнопки (устанавливает значения в статус-регистре Кнопки),
; в зависимости от, динамически изменяющегося, [интегрированного] состояния её канала.
;
; ПАРАМЕТРЫ:
; Регистровым параметром "IntegratorAddress" передаётся адрес "регистра интегратора" (в памяти ОЗУ), который кодирует текущее "физическое" состояние кнопки.
; Регистровым параметром "StatusAddress" передаётся адрес "статусного регистра кнопки" (в памяти ОЗУ), который следует обновить.
;
;---------------------------------------------------------------------------
;
; ФОРМАТ СТАТУСНОГО РЕГИСТРА КНОПКИ (DButtonStatus):
;
; 76543210
; HPTnnnnn
;
; nnnnn = Пять битов DButtonStatus[4:0] = счётчик количества полусекунд, в течение которых Кнопка удержива(лась/ется) "нажатой". (фиксирует время до 16сек!)
; HPT = В трёх битах DButtonStatus[7:5] = кодируется итоговый "статус-код кнопки" (см. макроопределения констант).
; T = Флаг "времени удержания" кнопки: 0-короткое или 1-длинное.
; P = Флаг "зафиксировано полноценное нажатие кнопки": "0" - кнопка не нажималась, "1" - было нажатие.
; H = Флаг "кнопка удерживается в нажатом состоянии": "0" - сейчас кнопка "отпущена", "1" - сейчас кнопка "нажата и удерживается".
;
; Начальное состояние = 0b00000000 означает: кнопка "не нажата и не нажималась", исходное положение для всех кнопок - бывает только после "сброса"... (статус-код кнопки = "не нажата"; счётчик времени предыдущего нажатия = обнулён)
; Начальное состояние = 0b11111111 означает: кнопка "удерживается и отпущена одновременно" - это служебное, исключительное состояние: "фиксация, до ожидания следующего отпускания" кнопки - ОТЛОЖЕННЫЙ СБРОС... (запрещённый статус-код кнопки = запрещает прикладному коду реакцию на эту кнопку) (примечание: а при следующем отпускании физической кнопки, статус-регистр будет АВТОМАТИЧЕСКИ "сброшен в ноль", в исходное положение!)
; (Примечание: также, после обработки статуса кнопки прикладным кодом, статусный регистр РЕКОМЕНДУЕТСЯ просто сбрасываеть "в ноль" или "в единицы" - это простой способ сообщить остальному прикладному коду, что данное событие уже обработано и не требует дальнейшего участия...)
;
;---------------------------------------------------------------------------
;----- Subroutine Register Variables
;.def IntegratorAddressLow = R26 ; XL (примечание: не изменяется внутри процедуры)
;.def IntegratorAddressHigh = R27 ; XH (примечание: не изменяется внутри процедуры)
;.def StatusAddressLow = R28 ; YL (примечание: не изменяется внутри процедуры)
;.def StatusAddressHigh = R29 ; YH (примечание: не изменяется внутри процедуры)
; Памятка: также использует/портит содержимое регистров TEMP1, TEMP2.
;----- Code
KEY_UPDATE_BUTTON_STATUS:
LD temp2, Y ; Загружаем статус-регистр Кнопки
BST temp2, BUTTON_IS_HOLDDOWN ; Флаг "кнопка удерживается в нажатом состоянии" -> T
LD temp1, X ; Загружаем регистр Интегратора
TST temp1 ; Текущее состояние канала кнопки -> N
BRMI ButtonHold__KEY_UPDATE_BUTTON_STATUS
ButtonReleased__KEY_UPDATE_BUTTON_STATUS:
; (состояние: сейчас кнопка "отпущена")
BRTC Exit__KEY_UPDATE_BUTTON_STATUS ; Кнопка УЖЕ считалась "отпущенной" - поэтому ничего сейчас делать не нужно...
; (состояние: но в статус-регистре ещё хранится старый статус "удерживается")
SBRC temp2, BUTTON_IS_PRESSED ; состояние: кнопка "удерживается и отпущена одновременно"?
RJMP ResetStatus__KEY_UPDATE_BUTTON_STATUS
ANDI temp2, ~(1<<BUTTON_IS_HOLDDOWN) ; установить статус = кнопка "отпущена",
ORI temp2, (1<<BUTTON_IS_PRESSED) ; ... но фиксируем, что "было нажатие".
; при этом, флаг "времени удержания" (короткое или длинное) уже правильно установлен в регистре - мы его не трогаем.
RJMP Save__KEY_UPDATE_BUTTON_STATUS
ResetStatus__KEY_UPDATE_BUTTON_STATUS:
; (установлено служебное и исключительное состояние: "фиксация, до ожидания следующего отпускания" кнопки - ОТЛОЖЕННЫЙ СБРОС...)
; (и при этом, физическую кнопку только что отпустили!)
CLR temp2 ; статус-регистр "сбросить в ноль", в исходное положение.
RJMP Save__KEY_UPDATE_BUTTON_STATUS
ButtonHold__KEY_UPDATE_BUTTON_STATUS:
; (состояние: сейчас кнопка "удерживается")
BRTS Exit__KEY_UPDATE_BUTTON_STATUS ; Кнопка УЖЕ считалась "удерживаемой" - поэтому ничего сейчас делать не нужно...
; (состояние: но в статус-регистре ещё хранится старый статус "отпущена")
LDI temp2, (1<<BUTTON_IS_HOLDDOWN) ; установить статус = "кнопка удерживается в нажатом состоянии";
; при этом, счётчик "времени предыдущего удержания" принудительно обнуляется;
; и т.к. кнопку только-только нажали, то также сбрасывается её "статус-код" (до прояснения ситуации)...
;RJMP Save__KEY_UPDATE_BUTTON_STATUS
Save__KEY_UPDATE_BUTTON_STATUS:
ST Y, temp2 ; Сохраняем статус-регистр Кнопки
Exit__KEY_UPDATE_BUTTON_STATUS:
RET
;---------------------------------------------------------------------------
; (Головная процедура конвеера обработки кнопок)
;
; KEY_ENHANCE_TIME_FOR_ALL_BUTTONS
; Наращивает таймеры для удерживаемых кнопок (должна запускаться по событию таймера, точно раз в 0.5 сек)
; Для Энкодеров не используется - предназначена только для обработки Кнопок!
; С физическими кнопками также не работает, а только перебирает/обрабатывает их Статусные регистры!
;
; АЛГОРИТМ:
; для кнопок удерживаемых в нажатом состоянии,
; наращивает "счётчик времени удержания" (если он ещё меньше своего максимума < 0b11111),
; и также, обновляет значение Флага "времени удержания" кнопки = 0-короткое или 1-длинное.
;
;---------------------------------------------------------------------------
;----- Subroutine Register Variables
; Без параметров.
; Памятка: также использует/портит содержимое регистров TEMP1, TEMP2, TEMP3,
; Y(R29:R28).
;----- Code
KEY_ENHANCE_TIME_FOR_ALL_BUTTONS:
LDI StatusAddressLow, Low(DButtonStatus) ; (примечание: здесь загружаем в регистр адрес, а не значение)
LDI StatusAddressHigh, High(DButtonStatus) ; где регистр StatusAddress = Y(R29:R28)
LDI temp3, CButtonInputChannelCount ; temp3 = счётчик цикла
Loop__KEY_ENHANCE_TIME_FOR_ALL_BUTTONS:
LD temp2, Y+ ; temp2 = Загружаем текущий статус-регистр Кнопки
BST temp2, BUTTON_IS_HOLDDOWN ; Флаг "кнопка удерживается в нажатом состоянии" -> T
BRTC Skip__KEY_ENHANCE_TIME_FOR_ALL_BUTTONS ; ещё "не нажатые" кнопки пропускаем...
BST temp2, BUTTON_IS_PRESSED ; Флаг "зафиксировано полноценное нажатие кнопки" -> T
BRTS Skip__KEY_ENHANCE_TIME_FOR_ALL_BUTTONS ; уже "отпущенные" кнопки пропускаем...
; (состояние: текущая кнопка нажата и удерживается)
MOV temp1, temp2
ANDI temp1, (0b11111<<BUTTON_HOLDING_TIME) ; temp1 = выделить значение "счётчика времени удержания" кнопки
CPI temp1, 0b11111 ; сравнить значение "счётчика времени удержания" с его максимумом
BREQ Skip__KEY_ENHANCE_TIME_FOR_ALL_BUTTONS ; если значение счётчика уже на максимуме - то статус этой кнопки больше не меняется...
; (состояние: требуется увеличить значение "счётчика времени удержания")
INC temp1
ANDI temp2, ~(0b11111<<BUTTON_HOLDING_TIME) ; обнулить биты счётчика в статус-регистре
OR temp2, temp1 ; побитово скопировать в статус-регистр новое значение счётчика
; (состояние: требуется обновить статус-код)
CPI temp1, CShortButtonTouchDuration ; сравнить значение "счётчика времени удержания" с эталонным граничным значением, различающим статусы "Short/Long"
; если temp1< CShortButtonTouchDuration ("Short" time), то C = 1 -> Флаг "времени удержания" кнопки: BUTTON_HOLDEN_LONG = 0-короткое
; если temp1>=CShortButtonTouchDuration ("Long" time), то C = 0 -> Флаг "времени удержания" кнопки: BUTTON_HOLDEN_LONG = 1-длинное
; Реализация 1: (универсальная и избыточная)
;STOREB SREG, SREG_C ; C (Carry Flag) -> T (Transfer bit, Bit Copy Storage)
;BLD temp2, BUTTON_HOLDEN_LONG ; T -> копируем в статус-регистр кнопки, в бит Флага "времени удержания"
;LDI temp1, 1<<BUTTON_HOLDEN_LONG
;EOR temp2, temp1 ; инвертируем бит Флага "времени удержания" = теперь он равен требуемому значению
; Реализация 2: (упрощённая и оптимальная: т.к. у нас счётчик растёт только вверх, а изначально он всегда обнулён, следовательно, здесь бит BUTTON_HOLDEN_LONG может только установиться из 0 в 1)
BRLO Save__KEY_ENHANCE_TIME_FOR_ALL_BUTTONS ; если temp1< CShortButtonTouchDuration ("Short" time), то пропускаем установку Флага "времени удержания"...
ORI temp2, (1<<BUTTON_HOLDEN_LONG) ; Флаг "времени удержания" = 1
Save__KEY_ENHANCE_TIME_FOR_ALL_BUTTONS:
ST -Y, temp2 ; Сохраняем текущий статус-регистр Кнопки
LD temp2, Y+ ; (коррекция: адрес в регистре Y++)
Skip__KEY_ENHANCE_TIME_FOR_ALL_BUTTONS:
DEC temp3 ; счётчик цикла--
BRNE Loop__KEY_ENHANCE_TIME_FOR_ALL_BUTTONS
RET
;---------------------------------------------------------------------------
;---------------------------------------------------------------------------
; (Головная процедура конвеера обработки кнопок)
;
; KEY_RESET_STATUS_FOR_ALL_BUTTONS
; Обнулить все зарегистрированные события от Кнопок и Энкодеров (вызывается при переходе интерфейса в другую Подсистему, в которой задействованы другие группы кнопок).
; Использование данной процедуры подавляет "эффекты" от лишних нажатий кнопок, зарегистрированных но не обработанных, в предыдущем режиме интерфейса - чтобы они, внезапно, не были обнаружены и обработаны в новом режиме интерфейса (когда уже не актуальны).
;
;
; Инструкция к применению:
; Обычно, при обработке кнопочных событий, если распознаётся нажатие управляющей кнопки, одной или группы (IF_BUTTON_HAVE_STATUS/OR_BUTTON_HAVE_STATUS) - то реализуется соответствующая прикладная реакция, а статус обработанных кнопок - сбрасывается (но только статус обработанных кнопок!).
; Если, в некотором режиме интерфейса, часть кнопок не задействована - то они не опрашиваются, не срабатывают, их статус не обнуляется, а запоминается и хранится (такая кнопка становится "миной отложенного действия").
; Следует также учитывать "защиту от дурака": пользователь будет нажимать на всё подряд!
; При реализации прикладной логики вашего Устройства: Вызывайте данную процедуру единожды, всякий раз, когда переключаете интерфейс в другую Подсистему - это "разминирует все заложенные ловушки".
;
; Принцип действия:
; Статус-регистры всех Кнопок "СБРАСЫВАЮТСЯ В ЕДИНИЦЫ (SER)" - так установится статус "ОТЛОЖЕННЫЙ СБРОС". (Напомню: Далее, для тех кнопок которые отпущены, или как только они будут отпущены, статус-регистры автоматически установятся в исходное, нулевое состояние.)
; Счётные-регистры всех Энкодеров обнуляются ("счётчик тиков"=0).
;
;---------------------------------------------------------------------------
;----- Subroutine Register Variables
; Без параметров.
; Памятка: также использует/портит содержимое регистров TEMP1, TEMP2.
; Примечание: а вот индексные регистры Y(R29:R28), хоть и используются в процедуре, но они экранируются через Стек - не изменяются. (Это необходимость, для исключения побочных эффектов: поскольку Y(R29:R28) - не являются традиционными "временными переменными", поэтому их очень легко забыть... А данная процедура вызывается из очень многих сложных мест прикладного кода - невозможно просчитать!)
;----- Code
KEY_RESET_STATUS_FOR_ALL_BUTTONS:
PUSH YL ; запомнить значения
PUSH YH
; Счётные-регистры всех Энкодеров обнуляются:
LDI YL, Low(DEncoderStatus) ; (примечание: здесь загружаем в регистр адрес, а не значение)
LDI YH, High(DEncoderStatus)
LDI temp2, (CEncoderInputChannelCount/2) ; количество Энкодеров в системе (Примечание: Количество входных каналов энкодеров CEncoderInputChannelCount - это всегда чётное число: поскольку у Энкодеров по два входных канала, а также, по два регистра, которые мы и перебираем!)
EncoderLoop__KEY_RESET_STATUS_FOR_ALL_BUTTONS:
LD temp1, Y+ ; адрес в регистре Y++ (минуем: статус-регистр)
CLR temp1 ; сохраним ноль в следующий регистр (обнулим)
ST Y+, temp1 ; адрес в регистре Y++ (минуем: счётчик тиков)
DEC temp2 ; счётчик цикла--
BRNE EncoderLoop__KEY_RESET_STATUS_FOR_ALL_BUTTONS
; Статус-регистры всех Кнопок СБРАСЫВАЮТСЯ В "ОТЛОЖЕННЫЙ СБРОС":
LDI YL, Low(DButtonStatus) ; (примечание: здесь загружаем в регистр адрес, а не значение)
LDI YH, High(DButtonStatus)
LDI temp2, CButtonInputChannelCount ; количество Кнопок в системе
LDI temp1, 0b11111111 ; в статус-регистры загрузить код = "ОТЛОЖЕННЫЙ СБРОС"
ButtonLoop__KEY_RESET_STATUS_FOR_ALL_BUTTONS:
ST Y+, temp1 ; адрес в регистре Y++ (минуем: статус-регистр)
DEC temp2 ; счётчик цикла--
BRNE ButtonLoop__KEY_RESET_STATUS_FOR_ALL_BUTTONS
POP YH ; восстановить значения
POP YL
RET
;---------------------------------------------------------------------------
;---------------------------------------------------------------------------
;
; Макросы, помогающие тестировать статус-коды кнопок
;
;---------------------------------------------------------------------------
; (Вспомогательный макрос... Не рекомендуется использовать его напрямую! Хотя, в принципе, он полезен: когда требуется проверить статус одиночной кнопки, и не желательно портить значение status bit "T".)
; Установить status bit "Z" = в значение булевого выражения: (Если статус-регистр @0 хранит статус-код == @1 ?)
; Памятка: портит содержимое регистра TEMP.
.MACRO COMPARE_BUTTON_STATUS
INR temp, @0
ANDI temp, (0b111<<BUTTON_STATUS_CODE)
CPI temp, (@1<<BUTTON_STATUS_CODE)
; (состояние: если коды были равны, то сейчас Z==1)
.ENDMACRO
; Установить status bit "T" = в значение булевого выражения: (Если статус-регистр @0 хранит статус-код == @1 ?)
; Пример вызова: IF_BUTTON_HAVE_STATUS DButtonStatus, BSC_ShortPress
; Памятка: портит содержимое регистра TEMP.
.MACRO IF_BUTTON_HAVE_STATUS
COMPARE_BUTTON_STATUS @0, @1 ; Z = результат сравнения кодов
STOREB SREG, SREG_Z ; Z (Zero Flag) -> T (Transfer bit, Bit Copy Storage)
.ENDMACRO
; Установить status bit "T" = в значение булевого выражения: T && (Если статус-регистр @0 хранит статус-код == @1 ?)
; Пример вызова: AND_BUTTON_HAVE_STATUS DButtonStatus, BSC_ShortPress
; Памятка: портит содержимое регистра TEMP1,TEMP2.
.MACRO AND_BUTTON_HAVE_STATUS
CLR temp2
BLD temp2, SREG_Z ; temp2 = 0b000000T0 (где SREG_Z=1)
COMPARE_BUTTON_STATUS @0, @1 ; Z = результат сравнения кодов
INR temp1, SREG ; temp1 = 0b??????Z?
AND temp1, temp2 ; temp1 = 0b000000(T&Z)0
BST temp1, SREG_Z ; T = результат булевого умножения "старого" и "нового" кодов
.ENDMACRO
; Установить status bit "T" = в значение булевого выражения: T || (Если статус-регистр @0 хранит статус-код == @1 ?)
; Пример вызова: OR_BUTTON_HAVE_STATUS DButtonStatus, BSC_ShortPress
; Памятка: портит содержимое регистра TEMP1,TEMP2.
.MACRO OR_BUTTON_HAVE_STATUS
SER temp2
BLD temp2, SREG_Z ; temp2 = 0b111111T1 (где SREG_Z=1)
COMPARE_BUTTON_STATUS @0, @1 ; Z = результат сравнения кодов
INR temp1, SREG ; temp1 = 0b??????Z?
OR temp1, temp2 ; temp1 = 0b111111(T|Z)1
BST temp1, SREG_Z ; T = результат булевого умножения "старого" и "нового" кодов
.ENDMACRO
;=== END "celeronkeyinputlib.inc" ==========================================
; coded by (c) Celeron, 2013 http://inventproject.info/
.ENDIF