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

Комментарии   

# Guest 26.11.2009 19:36
Спасибо! А я пытаюсь сделать меню для 3-го семисегментного индикатора (динамическая индикация)...
Ответить | Ответить с цитатой | Цитировать
# alexandershahbazov 08.12.2009 22:19
Приблизительно тоже так делал , но потом
окончательно перешел на управление
устройством через UART для связи с
управляющей программой на PC .
Конечно если это удобно по заданию .
Ответить | Ответить с цитатой | Цитировать
# foxit 11.04.2010 19:40
Цитирую nemo:
Спасибо! А я пытаюсь сделать меню для 3-го семисегментного индикатора (динамическая индикация)...


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


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


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


напишите свое мыло, все кому интересна работа. Проект делался на диплом.
Ответить | Ответить с цитатой | Цитировать
# foxit 13.07.2010 18:16
Цитирую Nemo:
Цитирую foxit:
Конечно, поделись.
Может даже есть проект
и его реализация в Proteus
для наглядности?


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


foxit@gala.net
Ответить | Ответить с цитатой | Цитировать
# Alishka03031993@yand 26.01.2012 19:02
очень интересно)
Ответить | Ответить с цитатой | Цитировать
# guest 26.01.2012 19:03
Alishka03031993@yandex.ru
Ответить | Ответить с цитатой | Цитировать
# Nemo 13.07.2010 18:29

foxit@gala.net

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


"Устройство защиты бытовых приборов от перенапряжения " С помощью меню задаеться "макс", "мин"- вставки и "время задержки". При выходе напряжения за пределы диапазона, устройсто отключает прибор от сети.
Ответить | Ответить с цитатой | Цитировать
# Guest 15.09.2010 16:28
Хм, смутил только один момент - проверка каждого case'а подряд, по-моему абсолютно излишне и неэффективно, хотя для маленьких менюшек на это можно забить:) В любом случае, спасибо за статью.
Ответить | Ответить с цитатой | Цитировать
# Pashgan 06.12.2010 22:03
Вот здесь по поводу эффективности такого подхода http://chipenable.ru/index.php/programming-asm-avr/10-programming-menu-avr.html
Ответить | Ответить с цитатой | Цитировать
# ruslan 06.12.2010 18:13
Хорошая статья. Спасибо за полезные советы.
Ответить | Ответить с цитатой | Цитировать
# Pashgan 06.12.2010 22:03
Да пожалуйста
Ответить | Ответить с цитатой | Цитировать
# roman2040 15.12.2010 17:13
Можно мне материалы исходники и протеус. И вобще что не Жалко сам новичок, но менюшку очень хочется..
avrmk@inbox.ru
Ответить | Ответить с цитатой | Цитировать
# Pashgan 19.12.2010 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
Ответить | Ответить с цитатой | Цитировать
# Вадим 24.11.2011 22:12
Большое спасибо за материал!
Ответить | Ответить с цитатой | Цитировать
# son313 20.01.2012 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 25.04.2014 11:11
Привет Pashgan. Пытаюсь прикрутить твое меню к другой LCD библиотеке. Подскажи для чего нужно в r17 записывать 200?


WriteButtonBuf:
sts pButtonBuf, r16
ldi r17, 200
sts pCompBuf, r17
ret
Ответить | Ответить с цитатой | Цитировать
# Pashgan 26.04.2014 17:24
Откуда этот код взят? Он же не из этой статьи.
Ответить | Ответить с цитатой | Цитировать
# Archi 29.04.2014 12:07
Извиняюсь)))"Ре ализация меню на [switch] конструкции. Практический пример"
Ответить | Ответить с цитатой | Цитировать
# Pashgan 29.04.2014 21:14
Это типа "заглушки". Если кнопка будет нажата в течении 10 циклов, в буфер запишется ее номер, а в "антидребезговы й" счетчик запишется число 200. Таким образом при следующем выполнении подпрограммы, если кнопка будет все еще нажата, часть не будет выполняться.
Код: ...
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 01.05.2014 11:27
В Дебагере в AVR studio на этом месте при нажатой кнопке затыкался))).
А это что? [rjmp PC+1]

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

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

Добавить комментарий

Защитный код
Обновить