Реализация меню на switch конструкции.

26/09/2009 - 21:00

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

   Вариант реализации меню, который я здесь предлагаю, основан на 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:
 
    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 конструкции. Не спрашивайте, почему я излагал материал именно так – я и сам не знаю. Просто в таком порядке мысли выливались на бумагу.
Теперь можно собрать все вышеизложенное воедино в виде блок схемы.
 
блок-схема обработчика нажатий кнопок

Comments   

# Guest 2009-11-26 19:36
Спасибо! А я пытаюсь сделать меню для 3-го семисегментного индикатора (динамическая индикация)...
# alexandershahbazov 2009-12-08 22:19
Приблизительно тоже так делал , но потом
окончательно перешел на управление
устройством через UART для связи с
управляющей программой на PC .
Конечно если это удобно по заданию .
# foxit 2010-04-11 19:40
Quoting nemo:
Спасибо! А я пытаюсь сделать меню для 3-го семисегментного индикатора (динамическая индикация)...


Получилось ли меню?
поделитесь результатами.
# Nemo 2010-07-13 13:40
Quoting foxit:
Quoting nemo:
Спасибо! А я пытаюсь сделать меню для 3-го семисегментного индикатора (динамическая индикация)...


Получилось ли меню?
поделитесь результатами.


Получилось! Кому интересно - могу поделиться кодом!
# foxit 2010-07-13 13:54
Конечно, поделись.
Может даже есть проект
и его реализация в Proteus
для наглядности?
# Nemo 2010-07-13 16:14
Quoting foxit:
Конечно, поделись.
Может даже есть проект
и его реализация в Proteus
для наглядности?


напишите свое мыло, все кому интересна работа. Проект делался на диплом.
# foxit 2010-07-13 18:16
Quoting Nemo:
Quoting foxit:
Конечно, поделись.
Может даже есть проект
и его реализация в Proteus
для наглядности?


напишите свое мыло, все кому интересна работа. Проект делался на диплом.


foxit@gala.net
# Alishka03031993@yand 2012-01-26 19:02
очень интересно)
# guest 2012-01-26 19:03
Alishka03031993@yandex.ru
# Nemo 2010-07-13 18:29

foxit@gala.net

программа написана в WinAVR,схема в Proteus 7.6. Когда пользователь в меню, индикатор мигает. Будут вопросы - пишите!
# foxit 2010-07-13 18:44
спасибо
а что за диплом?
# Nemo 2010-07-13 18:53
Quoting foxit:
спасибо
а что за диплом?


"Устройство защиты бытовых приборов от перенапряжения " С помощью меню задаеться "макс", "мин"- вставки и "время задержки". При выходе напряжения за пределы диапазона, устройсто отключает прибор от сети.
# Guest 2010-09-15 16:28
Хм, смутил только один момент - проверка каждого case'а подряд, по-моему абсолютно излишне и неэффективно, хотя для маленьких менюшек на это можно забить:) В любом случае, спасибо за статью.
# Pashgan 2010-12-06 22:03
Вот здесь по поводу эффективности такого подхода http://chipenable.ru/index.php/programming-asm-avr/10-programming-menu-avr.html
# ruslan 2010-12-06 18:13
Хорошая статья. Спасибо за полезные советы.
# Pashgan 2010-12-06 22:03
Да пожалуйста
# roman2040 2010-12-15 17:13
Можно мне материалы исходники и протеус. И вобще что не Жалко сам новичок, но менюшку очень хочется..
avrmk@inbox.ru
# Pashgan 2010-12-19 19:19
Исходники вот здесь http://chipenable.ru/index.php/programming-asm-avr/10-programming-menu-avr.html и здесь http://chipenable.ru/index.php/programming-asm-avr/26-lcd-asm-menu-table.html
# Вадим 2011-11-24 22:12
Большое спасибо за материал!
# son313 2012-01-20 13:23
Приветствую. У меня вопрос не по этой теме, но если поможете буду очень рад. В книге В.Я. Хартов AVR практикум для начинающих есть программа на ассемблере с бегущими огоньками на ATmega8515. Запустил в протеусе, все работает, но как происходит переключение светодиодов уже голову сломал. Возможно это как то с флагами связано, но как и что это работает понять не могу. Жмешь кнопку START огоньки начинают почередно зажигатся туда сюда. Но логика не понятна в этом месте:
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
# Archi 2014-04-25 11:11
Привет Pashgan. Пытаюсь прикрутить твое меню к другой LCD библиотеке. Подскажи для чего нужно в r17 записывать 200?


WriteButtonBuf:
sts pButtonBuf, r16
ldi r17, 200
sts pCompBuf, r17
ret
# Pashgan 2014-04-26 17:24
Откуда этот код взят? Он же не из этой статьи.
# Archi 2014-04-29 12:07
Извиняюсь)))"Ре ализация меню на [switch] конструкции. Практический пример"
# Pashgan 2014-04-29 21:14
Это типа "заглушки". Если кнопка будет нажата в течении 10 циклов, в буфер запишется ее номер, а в "антидребезговы й" счетчик запишется число 200. Таким образом при следующем выполнении подпрограммы, если кнопка будет все еще нажата, часть не будет выполняться.
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

Код давно написан, поэтому грешит магическими числами.
# Archi 2014-05-01 11:27
В Дебагере в AVR studio на этом месте при нажатой кнопке затыкался))).
А это что? [rjmp PC+1]

Прикрутил твое меню к Attiny 2313 и к LCD библиотеке (c) Radoslaw Kwiecien.
Если интересно могу в личку отправить.
# Pashgan 2014-05-01 22:53
Не могу найти эту сроку в коде.

Прикрутил меню - пиши статью)
# Archi 2014-05-02 06:13
Рано пока статью писать. Помоги разобраться как расширить меню, если LCD 2-х строчное. У меня пока не получается.
# Pashgan 2014-05-02 18:54
А в чем сложность, не понимаю. Какая разница сколько строк.
# Archi 2014-05-09 11:53
# Pashgan Что-то у меня не получается, расширить меню. Если не трудно, можно добавить в меню LED-3? И прислать мне на мыло. На примере можно быстрее разобраться в чужом ASM коде.

У вас недостаточно прав для комментирования.