В моем понимании меню – это часть интерфейса осуществляющего взаимодействие между пользователем и устройством. В обязанности этого интерфейса входит обработка нажатий кнопок, вывод информации на дисплей и выполнение различных действий инициированных пользователем. Меню имеет графическое отображение, которое представляет из себя одноуровневую или многоуровневую иерархическую структуру и часть этой структуры обычно видна на дисплее.
Вариант реализации меню, который я здесь предлагаю, основан на switch конструкции. В языке Си switch – это один из операторов ветвления. Он позволяет осуществлять выбор между несколькими заданными альтернативами. Поскольку ноги Си растут из ассемблера, в ассемблере можно реализовать точно такой же оператор, ну а точнее конструкцию. Пример ассемблерного варианта switch приведен ниже.
Switch:
Case1:
cpi r16, 1
brne Case2
rcall func1
ret
Case2:
cpi r16, 2
brne Case3
rcall func2
ret
Case3:
cpi r16, 3
brne Default
rcall func3
ret
Default:
rcall func4
ret
Эта конструкция оформлена в виде подпрограммы названной Switch и работает следующим образом. Значение регистра r16 последовательно сравнивается с константами (1, 2, 3). Если значение r16 не равно константе - микроконтроллер переходит на следующую метку (Case2, Case3, Default), если равно – запускает соответствующую продпрограмму (func1, func2, func3), выполняет ее и выходит из подрограммы Switch. На метку Default микроконтроллер попадает, только когда сравнит значение r16 со всеми константами и не обнаружит ни одного совпадения.
Ветви подпрограммы Switch (куски от метки Case до команды ret) можно оформить в виде макроса и тогда наращивать switch конструкцию станет намного удобнее.
Например, так:
//@0 – константа с которой происходит сравнение,
//@1 – подпрограмма вызываемая в данной ветви
.MACRO Case
cpi r16, @0
brne ExitCase
rcall @1
ret
ExitCase:
.ENDMACRO
……
Switch:
Switch:
Case1:
cpi r16, 1
brne Case2
rcall func1
ret
Case2:
cpi r16, 2
brne Case3
rcall func2
ret
Case3:
cpi r16, 3
brne Default
rcall func3
ret
Default:
rcall func4
ret
Эта конструкция оформлена в виде подпрограммы названной Switch и работает следующим образом. Значение регистра r16 последовательно сравнивается с константами (1, 2, 3). Если значение r16 не равно константе - микроконтроллер переходит на следующую метку (Case2, Case3, Default), если равно – запускает соответствующую продпрограмму (func1, func2, func3), выполняет ее и выходит из подрограммы Switch. На метку Default микроконтроллер попадает, только когда сравнит значение r16 со всеми константами и не обнаружит ни одного совпадения.
Ветви подпрограммы Switch (куски от метки Case до команды ret) можно оформить в виде макроса и тогда наращивать switch конструкцию станет намного удобнее.
Например, так:
//@0 – константа с которой происходит сравнение,
//@1 – подпрограмма вызываемая в данной ветви
.MACRO Case
cpi r16, @0
brne ExitCase
rcall @1
ret
ExitCase:
.ENDMACRO
……
Switch:
Case 1, func1
Case 2, func2
Case 3, func3
Default:
rcall func4
ret
……
Навигация по меню и запуск каких-то действий в микроконтроллерном девайсе осуществляется с помощью кнопок. Поэтому программа микроконтроллера имеет подпрограммы обрабатывающие их нажатия (обработчики). Собственно на switch конструкции и построен обработчик нажатия одной кнопки.
Проще всего разобраться в его работе на примере. Допустим, у нас есть одноуровневое меню из трех пунктов, которые имеют нумерацию 1, 2, 3. Есть кнопка Up, нажатие которой вызывает перемещение вверх по пунктам меню. Вот как будет выглядеть обработчик этой кнопки.
.def CurrentState = r16
.equ pCurrentState = 0x60
HandlerUpButton:
lds CurrentState, pCurrentState
Case1:
cpi CurrentState, 1
brne Case2
rcall func1
ldi CurrentState, 1
rjmp ChangeState
Case2:
cpi CurrentState, 2
brne Case3
rcall func2
ldi CurrentState, 1
rjmp ChangeState
Case3:
cpi CurrentState, 3
brne ExitHandler
rcall func3
ldi CurrentState, 2
rjmp ChangeState
ExitHandler:
ret
ChangeState:
sts pCurrentState, CurrentState
ret
pCurrentState – это адрес в оперативной памяти микроконтроллера. По этому адресу хранится номер пункта меню, в котором мы находимся в данный момент. В начале обработчика микроконтроллер считывает это значение и записывает его в переменную CurrentState. Далее начинается последовательный обход switch конструкции, каждая ветвь которой выполняет обработку нажатия кнопки Up в конкретном пункте меню. Как только микроконтроллер находит нужную ветвь, он вызывает подпрограмму. Потом записывает в переменную CurrentState номер пункта меню, в который мы должны перейти после завершения работы обработчика, переходит на метку ChangeState и сохраняет значение CurrentState в ОЗУ. Если микроконтроллер не найдет нужную ветвь, то он просто выйдет из подпрограммы HandlerUpButton.
А вот какая логика работы будет у меню. Если мы находимся в первом пункте – нажатие кнопки Up вызывает выполнение подпрограммы func1, но не вызывает перехода. Если мы находимся во втором пункте меню – нажатие Up вызывает переход в первый пункт и вызывает выполнение подпрограммы func2. И наконец, если мы находимся в третьем пункте – нажатие Up вызывает переход во второй пункт меню и выполнение подпрограммы func3.
Вообще запись обработчика в таком виде довольно громоздка и не наглядна. Исправить положение можно с помощью макроса.
.def CurrentState = r16
.equ pCurrentState = 0x60
.MACRO Case
cpi CurrentState, @0
brne ExitCase
rcall @2
ldi CurrentState, @1
rjmp ChangeState
ExitCase:
.ENDMACRO
Case – макрос одной ветви конструкции switch, где @0 – номер пункта меню, @1 – константа задающая переход, @2 – подпрограмма
Вот как будет выглядеть обработчик кнопки Up с использованием макросов.
HandlerUpButton:
lds CurrentState, pCurrentState
Case 1, 1, func1
Case 2, 1, func2
Case 3, 2, func3
ret
ChangeState:
sts pCurrentState, CurrentState
ret
Обработчик теперь стал куда более наглядным. Видно, к какому пункту меню относится каждая ветвь, куда нужно перейти и какую подпрограмму вызвать. И плюс ко всему обработчик теперь легко модифицировать и наращивать.
Одних обработчиков кнопок недостаточно - нужна еще подпрограмма (диспетчер), которая будет их вызывать, в зависимости от того какая кнопка нажата. Эту подпрограмму можно тоже в принципе сделать на switch конструкции, но лучше на таблицах перехода. Это ускорит вызов обработчика. Вот как может выглядеть такая подпрограмма для четырех кнопок – Up, Down, Enter, Cancel
.def tmp = r16
.equ pButtonBuf= 0x61
Main:
//проверяем кнопочный буфер
lds tmp, pButtonBuf
cpi tmp, 0
breq Main
cpi tmp, 5
brsh ClearBufButtons
//вычисляем адрес
//и прыгаем на него
dec tmp
lsl tmp
ldi ZL, Low(CheckButtons)
ldi ZH, High (CheckButtons)
add ZL, tmp
ijmp
CheckButtons:
rcall HandlerUpButton
rjmp ClearBufButtons
rcall HandlerDownButton
rjmp ClearBufButtons
rcall HandlerEnterButton
rjmp ClearBufButtons
rcall HandlerCancelButton
//rjmp ClearBufButtons
//очищаем кнопочный буфер
//возвращаемся на Main
ClearBufButtons:
clr tmp
sts pButtonBuf, tmp
rjmp main
PButtonBuf - это адрес буфера в ОЗУ, в котором сохраняется номер нажатой кнопки. У нас 4 кнопки и каждая имеет свой номер: Up - 1, Down - 2, Enter - 3, Cancel – 4. В начале обработчика микроконтроллер считывает содержимое буфера и проверяет его на нулевое значение, если оно равно нулю – прыгает на метку Main, если нет – проверяет что содержащееся в буфере число не выходит за заданный диапазон. Это сделано для подстраховки. Затем в регистровую пару ZH:ZL загружается адрес метки CheckButtons и добавляется смещение. Смещение предварительно вычисляется так (номер кнопки – 1)*2. По команде ijmp микроконтроллер прыгает на нужный обработчик. А после выхода из него на метку ClearBufButtons, для того чтобы очистить кнопочный буфер.
Действую от обратного, мы разобрали идеологию реализации меню на switch конструкции. Не спрашивайте, почему я излагал материал именно так – я и сам не знаю. Просто в таком порядке мысли выливались на бумагу.
Теперь можно собрать все вышеизложенное воедино в виде блок схемы.
Case 2, func2
Case 3, func3
Default:
rcall func4
ret
……
Навигация по меню и запуск каких-то действий в микроконтроллерном девайсе осуществляется с помощью кнопок. Поэтому программа микроконтроллера имеет подпрограммы обрабатывающие их нажатия (обработчики). Собственно на switch конструкции и построен обработчик нажатия одной кнопки.
Проще всего разобраться в его работе на примере. Допустим, у нас есть одноуровневое меню из трех пунктов, которые имеют нумерацию 1, 2, 3. Есть кнопка Up, нажатие которой вызывает перемещение вверх по пунктам меню. Вот как будет выглядеть обработчик этой кнопки.
.def CurrentState = r16
.equ pCurrentState = 0x60
HandlerUpButton:
lds CurrentState, pCurrentState
Case1:
cpi CurrentState, 1
brne Case2
rcall func1
ldi CurrentState, 1
rjmp ChangeState
Case2:
cpi CurrentState, 2
brne Case3
rcall func2
ldi CurrentState, 1
rjmp ChangeState
Case3:
cpi CurrentState, 3
brne ExitHandler
rcall func3
ldi CurrentState, 2
rjmp ChangeState
ExitHandler:
ret
ChangeState:
sts pCurrentState, CurrentState
ret
pCurrentState – это адрес в оперативной памяти микроконтроллера. По этому адресу хранится номер пункта меню, в котором мы находимся в данный момент. В начале обработчика микроконтроллер считывает это значение и записывает его в переменную CurrentState. Далее начинается последовательный обход switch конструкции, каждая ветвь которой выполняет обработку нажатия кнопки Up в конкретном пункте меню. Как только микроконтроллер находит нужную ветвь, он вызывает подпрограмму. Потом записывает в переменную CurrentState номер пункта меню, в который мы должны перейти после завершения работы обработчика, переходит на метку ChangeState и сохраняет значение CurrentState в ОЗУ. Если микроконтроллер не найдет нужную ветвь, то он просто выйдет из подпрограммы HandlerUpButton.
А вот какая логика работы будет у меню. Если мы находимся в первом пункте – нажатие кнопки Up вызывает выполнение подпрограммы func1, но не вызывает перехода. Если мы находимся во втором пункте меню – нажатие Up вызывает переход в первый пункт и вызывает выполнение подпрограммы func2. И наконец, если мы находимся в третьем пункте – нажатие Up вызывает переход во второй пункт меню и выполнение подпрограммы func3.
Вообще запись обработчика в таком виде довольно громоздка и не наглядна. Исправить положение можно с помощью макроса.
.def CurrentState = r16
.equ pCurrentState = 0x60
.MACRO Case
cpi CurrentState, @0
brne ExitCase
rcall @2
ldi CurrentState, @1
rjmp ChangeState
ExitCase:
.ENDMACRO
Case – макрос одной ветви конструкции switch, где @0 – номер пункта меню, @1 – константа задающая переход, @2 – подпрограмма
Вот как будет выглядеть обработчик кнопки Up с использованием макросов.
HandlerUpButton:
lds CurrentState, pCurrentState
Case 1, 1, func1
Case 2, 1, func2
Case 3, 2, func3
ret
ChangeState:
sts pCurrentState, CurrentState
ret
Обработчик теперь стал куда более наглядным. Видно, к какому пункту меню относится каждая ветвь, куда нужно перейти и какую подпрограмму вызвать. И плюс ко всему обработчик теперь легко модифицировать и наращивать.
Одних обработчиков кнопок недостаточно - нужна еще подпрограмма (диспетчер), которая будет их вызывать, в зависимости от того какая кнопка нажата. Эту подпрограмму можно тоже в принципе сделать на switch конструкции, но лучше на таблицах перехода. Это ускорит вызов обработчика. Вот как может выглядеть такая подпрограмма для четырех кнопок – Up, Down, Enter, Cancel
.def tmp = r16
.equ pButtonBuf= 0x61
Main:
//проверяем кнопочный буфер
lds tmp, pButtonBuf
cpi tmp, 0
breq Main
cpi tmp, 5
brsh ClearBufButtons
//вычисляем адрес
//и прыгаем на него
dec tmp
lsl tmp
ldi ZL, Low(CheckButtons)
ldi ZH, High (CheckButtons)
add ZL, tmp
ijmp
CheckButtons:
rcall HandlerUpButton
rjmp ClearBufButtons
rcall HandlerDownButton
rjmp ClearBufButtons
rcall HandlerEnterButton
rjmp ClearBufButtons
rcall HandlerCancelButton
//rjmp ClearBufButtons
//очищаем кнопочный буфер
//возвращаемся на Main
ClearBufButtons:
clr tmp
sts pButtonBuf, tmp
rjmp main
PButtonBuf - это адрес буфера в ОЗУ, в котором сохраняется номер нажатой кнопки. У нас 4 кнопки и каждая имеет свой номер: Up - 1, Down - 2, Enter - 3, Cancel – 4. В начале обработчика микроконтроллер считывает содержимое буфера и проверяет его на нулевое значение, если оно равно нулю – прыгает на метку Main, если нет – проверяет что содержащееся в буфере число не выходит за заданный диапазон. Это сделано для подстраховки. Затем в регистровую пару ZH:ZL загружается адрес метки CheckButtons и добавляется смещение. Смещение предварительно вычисляется так (номер кнопки – 1)*2. По команде ijmp микроконтроллер прыгает на нужный обработчик. А после выхода из него на метку ClearBufButtons, для того чтобы очистить кнопочный буфер.
Действую от обратного, мы разобрали идеологию реализации меню на switch конструкции. Не спрашивайте, почему я излагал материал именно так – я и сам не знаю. Просто в таком порядке мысли выливались на бумагу.
Теперь можно собрать все вышеизложенное воедино в виде блок схемы.
Comments
окончательно перешел на управление
устройством через UART для связи с
управляющей программой на PC .
Конечно если это удобно по заданию .
Получилось ли меню?
поделитесь результатами.
Получилось! Кому интересно - могу поделиться кодом!
Может даже есть проект
и его реализация в Proteus
для наглядности?
напишите свое мыло, все кому интересна работа. Проект делался на диплом.
foxit@gala.net
foxit@gala.net
программа написана в WinAVR,схема в Proteus 7.6. Когда пользователь в меню, индикатор мигает. Будут вопросы - пишите!
а что за диплом?
"Устройство защиты бытовых приборов от перенапряжения " С помощью меню задаеться "макс", "мин"- вставки и "время задержки". При выходе напряжения за пределы диапазона, устройсто отключает прибор от сети.
avrmk@inbox.ru
code
WAITSTART: SBIC PIND,START
rjmp WAITSTART
LOOP: out PORTB,reg_led
ldi r17,2
d1: ldi r18,2
d2: dec r18
brne d2
dec r17
brne d1
SBIC PIND,STOP
rjmp MM
rjmp WAITSTART
/code
Если нажата кнопка START то перепрыгиваем и дальше выполняем LOOP: тоесть делаем задержку временную и дальше если STOP не нажат, прыгаем обратно в WAITSTART: но где при этом берется команда зажечь следующий светодиод непонятно?
Вот полный текст программы
code
.include "m8515def.inc"
.def temp = r16
.def reg_led = r20
.equ START = 0
.equ STOP = 1
rjmp init
INIT: ldi reg_led,0xFE
sec
set
ser temp
out DDRB,temp
out PORTB,temp
clr temp
out DDRD,temp
ldi temp,0x03
out PORTD,temp
WAITSTART: SBIC PIND,START
rjmp WAITSTART
LOOP: out PORTB,reg_led
ldi r17,2
d1: ldi r18,2
d2: dec r18
brne d2
dec r17
brne d1
SBIC PIND,STOP
rjmp MM
rjmp WAITSTART
MM: ser temp
out PORTB,temp
brts LEFT
sbrs reg_led,0
set
ror reg_led
rjmp LOOP
LEFT: sbrs reg_led,7
clt
rol reg_led
rjmp LOOP
/code
WriteButtonBuf:
sts pButtonBuf, r16
ldi r17, 200
sts pCompBuf, r17
ret
Code:
...
cpi r16, 0
breq ExitDebriefButtons
lds r17, pCompBuf
cpi r17, 10
breq WriteButtonBuf
brlo IncCompButtons
ret
IncCompButtons:
inc r17
sts pCompBuf, r17
ret
WriteButtonBuf:
sts pButtonBuf, r16
ldi r17, 200
sts pCompBuf, r17
ret
Код давно написан, поэтому грешит магическими числами.
А это что? [rjmp PC+1]
Прикрутил твое меню к Attiny 2313 и к LCD библиотеке (c) Radoslaw Kwiecien.
Если интересно могу в личку отправить.
Прикрутил меню - пиши статью)
RSS feed for comments to this post