Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
241 lines (173 sloc) 20.1 KB
;=== BEGIN "data.inc" ======================================================
;***************************************************************************
;*
;* Глобальные константы и Псевдонимы регистров
;*
;***************************************************************************
; Распиновка выводов ПортаB и соответствующие им названия схемных Сигналов:
; Для простоты, будем полагать, что номера битов в регистрах порта всегда совпадают: PORTBx = DDBx = PINBx.
; Примечание: PinScan/PinSense - это, на самом деле, один физический вывод Порта, но исполняющий две функции (мультиплексирование).
.equ PinClock = PORTB2 ; (OUT)
.equ PinScan = PORTB0 ; (OUT)
.equ PinSense = PORTB0 ; (IN)
; Временные регистры, используемые в основном прикладном коде:
;.def temp = R16 ; (определено в macrobaselib.inc)
;.def temp1 = R16 ; (определено в macrobaselib.inc)
;.def temp2 = R17 ; (определено в macrobaselib.inc)
;.def temp3 = R18 ; (определено в macrobaselib.inc)
;.def temp4 = R19 ; (определено в macrobaselib.inc)
;***************************************************************************
;*
;* Энергонезависимая память EEPROM (сегмент ППЗУ)
;*
;***************************************************************************
.ESEG
; <здесь, выделяется память и определяются константные данные в энергонезависимой памяти, вспомогательные для вашей программы>
;***************************************************************************
;*
;* Константные массивы в памяти FLASH (сегмент кода)
;*
;***************************************************************************
.CSEG
; Зачем это?
; Поскольку в Микроконтроллере зашито больше всего объёма памяти типа Flash, используемой обычно для программного кода, то зачастую, часть из этого пространства остатся незанятой.
; Неиспользуемый остаток может быть использован для хранения массивов неизменных данных: таблица значений функции, псевдокод программных автоматов, таблицы (пере)кодировок, и т.п.
;***************************************************************************
;*
;* Оперативная память SRAM (сегмент ОЗУ)
;*
;***************************************************************************
.DSEG
;***** Раздел данных прикладной модели:
; <здесь, выделяется память под прикладную модель данных вашей программы>
;***** Сканирование кнопок (ввод)
; Примечание:
; Здесь, приводится кусок кода из конкретной реализации работающего устройства!
; Данный код, конечно, зависит от состава Кнопок и Энкодеров, в вашем устройстве, и от схемы их подключения к микроконтроллеру.
; Поэтому, данный код, конечно, ещё требует переделки под схему вашего устройства... А здесь приводится - в качестве примера.
; В ДАННОЙ РЕАЛИЗАЦИИ:
; С целью экономии выводов микроконтроллера, все 5 кнопок и 1 энкодер были подключены к восьмиразрядному последовательно-параллельному сдвиговому регистру.
; В сдвиговый регистр, по выходной шине данных PinScan, загружалась одна "бегущая единичка", которая подавала высокий уровень напряжения, последовательно, на все кнопки (сканировала).
; Второй стороной, все Кнопки были подключены ко входной шине данных PinSense (если соответствующая кнопка было нажата, то уровень "логической единицы" фиксировался на входной шине).
; Через выходную шину PinClock осуществляется тактирование внешней микросхемы сдвигового регистра...
DKeyScanCnt: .byte 1 ; Счётчик циклов запуска процедуры сканирующей кнопки
; Настройка количеств входных каналов, которые обрабатываются в данной схеме:
.equ CEncoderInputChannelCount = 2 ; здесь, всегда чётное число - поскольку у Энкодеров по два входных канала!
.equ CButtonInputChannelCount = 5 ; здесь, количество простых "тактовых кнопок".
.equ CSkipInputChannelCount = 1 ; здесь, остаток до полной разрядности сдвигового регистр (или больше - главное, чтоб не меньше).
; (Примечание: данные константы используются в [текущей реализации] процедуры сканирующей кнопки KEY_SCAN_INPUT.)
; (Важно: СУММА данных количеств должна быть РАВНА разрядности сдвигового регистра - чтобы бегущая сканирующая "1" прошла весь регистр!)
; (Внимание: по особенностям реализации, порядок обработки каналов захардкоден в KEY_SCAN_INPUT - в том же порядке, как расположены регистры данных в блоках памяти, ниже. А эти параметры выведены сюда - для наглядности и удобства поддержки кода.)
; Символические имена Каналов, в зависимости от порядка их опроса: (приведены для справки, не используются)
; Cоответствуют индексам регистров в блоке Интегратора... (нумерация начинается с =0)
; И также, косвенно описывают порядок следования статус-регистров Кнопок и Энкодера, в блоках данных, ниже...
;.equ CEncoderAC = 0 ; выход сдвигового регистра: 3-Qa
;.equ CEncoderBC = 1 ; выход сдвигового регистра: 4-Qb
;.equ CButtonEncoder = 2 ; выход сдвигового регистра: 5-Qc
;.equ CButtonStart = 3 ; выход сдвигового регистра: 6-Qd
;.equ CButtonRTC = 4 ; выход сдвигового регистра: 10-Qe
;.equ CButtonTimer1 = 5 ; выход сдвигового регистра: 11-Qf
;.equ CButtonTimer2 = 6 ; выход сдвигового регистра: 12-Qg
;.equ CButtonNone = 7 ; выход сдвигового регистра: 13-Qh
; (Примечание: в [текущей реализации] "конвеера опроса кнопок" эти константы не используются - тут Каналы безличные.)
;** Регистры Интегратора (внимание: по особенностям данной реализации, наличие этого блока в памяти требуется всегда, даже если функция Интегратора отключена / не требуется)
DInputIntegrator: ; доступ по имени Вектора + Смещение (индекс)... или
DInput0Integrator: .byte 1 ; доступ по прямому адресу Именованной ячейки
DInput1Integrator: .byte 1
DInput2Integrator: .byte 1
DInput3Integrator: .byte 1
DInput4Integrator: .byte 1
DInput5Integrator: .byte 1
DInput6Integrator: .byte 1
;DInput7Integrator: .byte 1 ; Примечание: этот канал физически не подключён (не используется)
.equ INTEGRATOR_IS_PRESSED = 7 ; Флаг "интегральное состояние канала" (он же Negative Flag "N"), означает: ="0" (кнопка отпущена) или ="1" (кнопка нажата).
; Настройка Интегратора (функции математического подавления звона и помех):
; "Глубина Защёлки" интегратора - аналогична "постоянной времени" помехоподавляющей RC-цепи...
; Допустимые значения: = [1..128]
.equ CButtonLatchDepth = 3 ; глубина защёлки интегратора, для опроса тактовых Кнопок = количество циклов опроса, в течение которых кнопка должна возвращать статус "1", чтобы посчитаться "нажатой" или "0", чтобы посчитаться "отпущенной".
.equ CEncoderLatchDepth = 2 ; глубина защёлки интегратора, для опроса контактных групп Энкодера... в целом, то же, что и для обычных тактовых кнопок, но для Энкодера - "глубина защёлки" задаётся отдельно и на порядок меньше! Поскольку контакты Энкодера более стабильны и помехоустойчивы, а кроме того, Энкодер переключается ГОРАЗДО чаще, чем тактовые Кнопки!
; (Важно: нельзя делать значения этих констант слишком большими, а частоту запуска процедуры сканирующей кнопки KEY_SCAN_INPUT слишком редкой - иначе, можно получить ситуацию "долгого отклика" на нажатия или, хуже того, что нажатия кнопок или поворот энкодера - перестанут фиксироваться вообще!)
;** Регистры Статуса Энкодера (внимание: по особенностям данной реализации, в памяти этот блок располагаются сразу перед блоком статус-регистров кнопок - в том же порядке, как расположены исходные регистры в блоке Интегратора)
DEncoderStatus:
DEncoder0Status: .byte 1 ; пара регистров: регистр "Статуса" (Примечание: статус-регистр энкодера, напрямую, никогда не используется прикладным кодом.)
DEncoder0Counter: .byte 1 ; и сразу следом: регистр "Счётчик тиков" (Формат счётчика: особой специальной структуры не содержит. Это просто знаковое целое число: Signed Int = [-128..127])
.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), здесь, в принципе, не важна - используется лишь порядок их переключения...
; Советы по порядку тестирования Энкодера:
; Замечу, однако, что прикладной код никогда не использует "статусный регистр энкодера" напрямую!
; Он, вспомогательный, нужен лишь для вычисления направления вращения энкодера, используя булеву функцию: F2(x1,x2,y1,y2) = (НЕ(x1) * y2) + (x1 * НЕ(y2)).
; Вращения энкодера (если таковые были произведены, и их направление) - инкрементируются к регистру "двунаправленного аддитивного счётчика тиков" энкодера.
; (Всё это происходит в служебных функциях "конвеера обработки кнопок".)
; "Счётчик тиков" является конечным в конвеере обработки кнопок, и его использует прикладной код для реакции на жесты с энкодером:
; если DEncoderCounter == 0, то вращений [с момента последней обработки] не было, ничего делать не нужно.
; если DEncoderCounter > 0, то зафиксированы вращения, причём в сумме, больше провернули по часовой стрелке: положительное число [1..127] в регистре равно числу тиков [с момента последней обработки].
; если DEncoderCounter < 0, то зафиксированы вращения, причём в сумме, больше провернули против часовой стрелке: отрицательное число [-128..-1] в регистре равно числу тиков [с момента последней обработки].
; Причём, между обработками событий энкодера прикладным кодом (которые запускается гораздо реже, чем опрос энкодера) пользователь может колбасить ручку энкодера произвольно в разные стороны, а итоговый результат будет правильно отражён в "Счётчике тиков"!
; В обработчике событий - просто приплюсуйте результат из "Счётчика тиков" к вашей управляемой переменной величине...
; А затем, после обработки энкодера прикладным кодом и произведения ответной реакции, "СБРОСьте" регистр "Счётчик тиков" в ноль (но "статусный регистр" энкодера не трогайте!) -> так снова устанавливается статус-кво: энкодер = "не вращался".
; Это простой способ сообщить остальному прикладному коду, что данное событие уже обработано и не требует дальнейшего участия!
;** Регистры Статуса Кнопок (внимание: по особенностям данной реализации, в памяти этот блок располагаются сразу после блока статус-регистров энкодеров - в том же порядке, как расположены исходные регистры в блоке Интегратора)
DButtonStatus:
DButtonStartStatus: .byte 1 ; "СТАРТ/СТОП" - кнопка на Энкодере, самая большая заметная и эргономичная (ожидается, что пользователь будет её нажимать чаще всего)
DButtonSetStatus: .byte 1 ; "УСТАНОВКА" - кнопка справа под Энкодером. Используется для ввода интерфейса в режим подстройки часов/таймеров.
DButtonRTCStatus: .byte 1 ; перекл.на функцию "Часов/Будильника"
DButtonTimer1Status: .byte 1 ; перекл.на функцию "Секундомера/Таймера №1"
DButtonTimer2Status: .byte 1 ; перекл.на функцию "Секундомера/Таймера №2"
.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" - сейчас кнопка "нажата и удерживается".
; "Button Status Codes" = BUTTON_STATUS_CODE (полагаю: для описания большинства распространённых "жестов" с кнопками - хватит 4 ситуации):
.equ BSC_NotPressed = 0b000 ; "не нажата" (исходное положение для всех кнопок - бывает только после "сброса")
.equ BSC_ShortHold = 0b100 ; "короткое удержание" (кнопка нажата, и всё ещё удерживается, пока "короткое" время)
.equ BSC_LongHold = 0b101 ; "длинное удержание" (кнопка нажата, и всё ещё удерживается, уже "длительное" время)
.equ BSC_ShortPress = 0b010 ; "короткое нажатие" (кнопка была нажата, и затем отпущена, а время её удержания было "незначительным")
.equ BSC_LongPress = 0b011 ; "длинное нажатие" (кнопка была нажата, и затем отпущена, а время её удержания было "длительным")
; Советы по порядку тестирования Кнопок:
; Совет: для тестирования состояния кнопок по паттерну BUTTON_STATUS_CODE - можно использовать библиотечные макросы: IF_BUTTON_HAVE_STATUS, AND_BUTTON_HAVE_STATUS, OR_BUTTON_HAVE_STATUS (последние два используются в комбинации, для тестирования "кнопочных аккордов" - см.ниже).
; Совет: (кроме сравнения по паттерну BUTTON_STATUS_CODE) в прикладном коде, для тестирования статуса кнопки - можно воспользоваться инструкцией "побитового сдвига влево" регистра статуса (LSL DButtonStatus):
; После первого сдвига: флаг "C" = удерживается ли кнопка в данный момент?
; После второго сдвига: флаг "C" = было ли зафиксировано полноценное нажатие кнопки (keypress=нажал+отпустил)?
; После третьего сдвига: флаг "C" = длительность времени удержания?
; Замечу: этот метод оптимальнее для реализации одновременного перебора всех возможных вариантов состояния ОДНОЙ кнопки...
; Совет: кроме тестирования чистого "статус-кода кнопки", можно также смотреть на "счётчик времени удержания кнопки" - и различать также ситуации "очень длительных" удерживаний кнопок.
; Но замечу, что эти ситуации уже не кодируются в "статус-коде кнопки", т.к. довольно редки - если требуется, данный функционал следует самостоятельно реализовать в прикладном коде, на основании арифметического сравнения (инструкцией CPI) со значением счётчика BUTTON_HOLDING_TIME... (Это самый функциональный способ!)
; Также, при оценке прошедшего "времени удержания кнопки", на основании "счётчика времени удержания кнопки" - можно ориентироваться не на абсолютное значение счётчика BUTTON_HOLDING_TIME, а на его отдельные биты:
; номер бита: 4 3 2 1 0
; вес двоичного разряда: 16 8 4 2 1
; время, в секундах: 8сек 4сек 2сек 1сек 0.5сек
.equ BUTTON_HOLDEN_SLIGHTLY = BUTTON_HOLDING_TIME+0 ; при удержании кнопки свыше >=0.5сек, когда в этом разряде ВПЕРВЫЕ появляется единичка - наступает ситуация: кнопка удерживается "совсем чуть-чуть".
.equ BUTTON_HOLDEN_BRIEFLY = BUTTON_HOLDING_TIME+1 ; при удержании кнопки свыше >=1сек, когда в этом разряде ВПЕРВЫЕ появляется единичка - наступает ситуация: кнопка удерживается "кратковременно".
.equ BUTTON_HOLDEN_MODERATELY= BUTTON_HOLDING_TIME+2 ; при удержании кнопки свыше >=2сек, когда в этом разряде ВПЕРВЫЕ появляется единичка - наступает ситуация: кнопка удерживается "умеренно".
.equ BUTTON_HOLDEN_PROLONGED = BUTTON_HOLDING_TIME+3 ; при удержании кнопки свыше >=4сек, когда в этом разряде ВПЕРВЫЕ появляется единичка - наступает ситуация: кнопка удерживается "продолжительно".
.equ BUTTON_HOLDEN_SUSTAINED = BUTTON_HOLDING_TIME+4 ; при удержании кнопки свыше >=8сек, когда в этом разряде ВПЕРВЫЕ появляется единичка - наступает ситуация: кнопка удерживается "непрерывно".
; Примечание: но нужно помнить, что это биты "двоичного счётчика", который постоянно увеличивается - и соответственно, позиционные биты периодически то включаются, то гасятся...
; Поэтому, чтобы отреагировать на ПЕРВЫЙ прошедший интервал Xполусекунд - нужно периодически опрашивать статус-регистр кнопки (гораздо чаще, чем каждые 0.5сек) и отловить ПЕРВОЕ появление единички в выбранном разряде. И тут же: отреагировать на событие прикладным кодом, а затем, сразу "СБРОСить статус-регистр".
; (Иначе, если не сбрасывать регистр, то счётчик продолжит набегать и будет глюкодром: единица будет сменятся нулём, затем опять единица - путаница... Кроме того, не удобно отслеживать серийные реакции прикладного кода... Т.е. не рекомендую побитово использовать счётчик, без сброса после первого события!
; Если же нужно более сложное поведение кнопки, серийные реакции и т.п., - используйте полноценные арифметические сравнения со значением счётчика...)
; Совет: чтобы разнообразить варианты входных сигналов, прикладной код также может воспринимать "кнопочные аккорды" - тестировать группы кнопок на наличие одновременных нажатий/удержаний. Однако, делать это нужно в определённом приоритетном порядке:
; Cначала, проверить были ли нажатия отдельных кнопок, при удержании других "кнопок-модификаторов - типа как Shift/Ctrl/Alt"? Подсказка: У основной кнопки, которая "нажималась" - статус BUTTON_STATUS_CODE=="ShortPress/LongPress"; при этом, у модификаторов, при "удержании в нажатом состоянии" - установлен бит BUTTON_IS_HOLDDOWN==1...
; Затем, проверить простые одиночные нажатия отдельных кнопок: у которых статус=="ShortPress/LongPress"?
; Затем, проверить группы кнопок на одновременное удержание: у которых статус=="LongHold"? Причём, таким образом, можно проверить, последовательно, несколько групп: от более общих множеств, к более частным - с меньшим числом входящих кнопок...
; И наконец, проверить отдельные кнопки на длительное удержание: статус=="LongHold"?
; Совет: во избежание ложных повторных срабатываний событий, после обработки статуса кнопки прикладным кодом и произведения ответной реакции, "СБРОСьте" статусный регистр - это простой способ сообщить остальному прикладному коду, что данное событие уже обработано и не требует дальнейшего участия... Причём:
; После обработки статус-кодов BSC_*Press (замечу, что физическая кнопка уже отпущена), статус-регистр следует "сбросить в ноль" (CLR) -> так снова устанавливается статус-кво "не нажата" (исходное положение для всех кнопок)...
; После обработки статус-кодов BSC_*Hold (замечу, что физическая кнопка ещё удерживается), статус-регистр следует "сбросить в единицы" (SER) -> так в регистре установится ошибочный статус-код, который не будет спутан ни с одной из разрешённых комбинаций, и запретит прикладному коду реакцию на эту кнопку. (Это служебное, исключительное состояние статус-регистра: "фиксация, до ожидания следующего отпускания" кнопки - ОТЛОЖЕННЫЙ СБРОС...)
; Но в то же время, при следующем отпускании физической кнопки, статус-регистр будет АВТОМАТИЧЕСКИ "сброшен в ноль", в исходное положение! (Это исключительная реакция конвеера обработки кнопок: лишнее нажатие на кнопку не будет зафиксировано!)
; А вообще, чтобы не заморачиваться: В ЛЮБЫХ СЛУЧАЯХ (нажата кнопка или отжата, или аккорд отработали), после обработки статус-кодов - СБРАСЫВАЙТЕ статус-регистры всех отработанных кнопок "В ЕДИНИЦЫ (SER)"! Так установится статус "ОТЛОЖЕННЫЙ СБРОС", который, даже если кнопка уже отжата, то при следующем цикле конвеера - будет "сброшен в ноль", когда требуется, автоматически. Это безопасный стиль программирования!
; При таком стиле программирования - кнопка работает "с триггером защёлкой-состояния". Вариант поведения "с триггером": заставляет пользователя отпускать кнопку каждый раз, перед её следующим нажатием - что обычно полезно, ибо предотвращает серии ошибочных повторных срабатываний кнопки...
; (Хотя, в некоторых специфических случаях применения, можно и отходить от этого правила - бывает полезно использовать кнопки "без триггера защёлки-состояния": для умышленной организации серийных реакций на нажатую и удерживаемую кнопку...)
; Настройка Таймаута "времени удержания" Кнопки, для генерации статус-кодов:
.equ CShortButtonTouchDuration = 3 ;<1.5 сек ; максимальное время удержания кнопки (количество полных полусекунд) до которого ещё фиксируется статус-код "ShortPress/ShortHold", а свыше или равно - уже "LongPress/LongHold"...
; (Примечание: данная константа используется в [текущей реализации] процедуры KEY_ENHANCE_TIME_FOR_ALL_BUTTONS.)
;=== END "data.inc" ========================================================
; coded by (c) Celeron, 2013 http://inventproject.info/