Меню для дисплея усилителя

В данной статье представлены муки по созданию меню для дисплея усилителя. Главная проблема здесь – то что мы имеем всего три кнопки (энкодер), причем две из которых и не кнопки вовсе по сути.

Для начала нарисуем как должно выглядеть наше меню (влево-вправо это вращение энкодера, вниз – нажатие на кнопку):

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

  • Основной элемент управления – энкодер, утаскиваем процедуры его обработки из соответствующей статьи.
  • На энкодере есть кнопка, которая так же является нашим элементом управления, а чтобы ее обрабатывать четко и моментально я ее завел на пин RB0 и завел соответствующий обработчик прерываний INTIF.
  • Завел две переменные индикатора вращения энкодера – up и down.
  • Завел переменную статус для входа в меню регулировки уровня громкости volume_flag изначально равную нулю. Вот что примерно получилось (код находится в главной функции в бесконечном цикле):

if ((up)&&(!volume_flag))   //если крутили вправо
{
if (volume<100) volume++;   //до 100 процентов увеличиваем
setup_volume(volume);       //процедура установки громкости
up = 0;                     //обнуляем нашу переменную
}

if ((down)&&(!volume_flag)) //аналогично предыдущей функции, но в другом направлении
{
if (volume>0) volume--;
setup_volume(volume);
down = 0;
}

  • Дальше по нажатию кнопки мы должны попадать в меню, как я уже говорил кнопка обрабатывается в прерывании INTIF:

if (!volume_flag) {  //Если нажали кнопку и volume_flag = 0
volume_flag = 1;     //то присваиваем ей 1
menu(0);             //и вызываем меню на экран
}

Некрасиво конечно пихать вызов функции в обработчик прерываний, но у меня этот вариант оказался самым работоспосбным и я решил оставить его именно таким. Дальше нам нужно скроллить пункты меню, делать будем с помощью тех же переменных up и down (опять же в главной функции в бесконечном цикле):


if ((up)&&(volume_flag)) {
  if (itemp < 7) {                  //новая переменная для скролла пунктов меню
      if (itemp<6) {
                   itemp++;
                   exit_status = 0; //переменная инициализирующая выход из меню
                   }
      else {
                   itemp = 7;
                   exit_status = 1; //если мы в седьмом пункте то если что выходим :)
            }
  }
else {                              //если пытаемся скролить еще выше, то перескакиваем в начало
      itemp = 1;
      exit_status = 0;
     }
menu(itemp);                        //отображаем соответствующий пункт
up = 0;
}

if ((down)&&(volume_flag)) {       //аналогично предыдущему, только в другую сторону
  if (itemp > 1 ) {
                   itemp--;
                   exit_status = 0;
                  }
  else {
         itemp = 7;
         exit_status = 1;
        }
menu(itemp);
down = 0;
}

В коде попутно введена еще одна статусная переменная – exit_status, которая отвечает за выход из меню если потребуется.

Вход в меню выполнен, скролл организован, идем дальше по пунктам:

  1. Первым у нас стоит операция Mute, добавляем новую переменную, ответственную за этот пункт и вставим кусок кода в главный цикл:

if (itemp == 1) mute_status = 1; else mute_status = 0;

Есть еще мысль вставить присваивание статусным переменным в прошлый код и организовать все с помощью оператора swtich, попозже попробую провернуть это, а то код монструозный получается и так.

Но крутанув энкодер и установив флажок на этот пункт пока ничего не произойдет, нужно нажать на кнопку, добавляем в обработчик код:


if (mute_status) {
itemp = 0;
mute_status = 0;     //сбрасываем переменную, дабы не было проблем
volume_flag = 0;     //если еще раз кнопка будет нажата, попадем в меню
mute();              //функция mute()
}

Таким образом, если будет нажата кнопка то девайс перейдет в состояния mute, и для того, чтобы его из него вывести нужно еще раз будет нажать на эту же кнопку.

2. Снова регулировка громкости, как уже упоминалось ранее за нее отвечает переменная volume_flag, но здесь ее так просто заюзать не получится, добавляем еще одну статусную для регулировки громкости из меню:

if (itemp == 2) volume_status = 1; else volume_status = 0;

Ну и как обычно код в обработчике нажатия кнопки:

if (volume_status) {
itemp = 0;            //можно и без этого
volume_flag = 0;      //включаем дефолтный режим изменения громкости
volume_status = 0;    //обнуляемся
}

Как только кнопка будет нажата, управление будет передано дефолтному режиму изменения уровня громкости в главном цикле. А про него уже написано сверху.

3. Изменение басов. Идем по уже накатанной дорожке:

if (itemp == 3) bass_status = 1; else bass_status = 0;

Обработчик прерывания по кнопке:

if (bass_status) {
itemp = 0;
volume_flag = 0;     //чтобы не попасть в чужую функцию
bass_status = 0;     //обнуляемся
bass_flag = 1;       //переменная для главного цикла
}

bass_flag заведена для того, чтобы в главном цикле корректно обрабатывалось вращение энкодера в этом пункте, вот собственно что в этой обработке у меня есть:

if ((down)&&(bass_flag)) {        //все прозрачно, ничего необычного
bass--;
bass_setup(bass);
down = 0;
}

if ((up)&&(bass_flag)) {
bass++;
bass_setup(bass);
up = 0;
}

4. Контроль тембра. Здесь совершенно идиентично пункту 3, поэтому описывать все не вижу смысла, в архиве есть весь код и можно посмотреть на общую картину.

5. Настройки времени. Здесь особый случай, дело в том, что для настройки времени используется своя специфическая функция, поэтому я использовал примерно такую реализацию:

if ((time_flag)&&(!time_status)) {
GIE = 0;                           //Выключаем прерывания, работаем только в нашей функции
time_flag=0;
setup_time();                      //функция установки времени
}

Две переменные здесь нужны для того, чтобы корректно настроить микроконтроллер на будущую работу. Ну и как обычно одна из переменных присвается в обработчике:


if (time_status) {

itemp = 0;
time_status = 0;
time_flag = 1;
}

Саму процедуру настройки рассмотрим немного позже, а пока продолжим по меню.

6. Stand By – аналогично Mute

7. Exit , установка статусной переменной для выхода уже рассматривалась, так что перейдем сразу к обработчику нажатия RB0:

if (exit_status) {
exit_flag = 1;
exit_status = 0;
volume_flag = 0;          //выброс из меню в установку громкости
}

а в главном цикле добавим следующие строки:

if (exit_flag) {
display_tt();            //дежурная функция отображения времени и температуры внутри корпуса
exit_flag = 0;
}

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

По этой диаграмке удобно отлаживать программу, можно диагностировать состояние переменных через уарт и сравнивать с ней. Функция display_tt() – уже упоминаемая дежурная функция вывода времени и температуры. Еще следует сделать акцент на то, что переменных здесь как грязи, а память не резиновая, поэтому все статусные переменные имеют тип bit.

Ну что, структура меню готова, скролл организован, осталось наполнить его чем-то осмысленным. Привожу реализацию вывода на дисплей и не только соответствующих функций.

  • Главная функция – регулировка громкости:
setup_volume(unsigned char value) {
lcd_clear();
LCD_RS = 0;
lcd_write(0b00001100);                     //выключаем курсор
__delay_us(100);
lcd_goto(0x00);
lcd_puts("Volume");
if (value<100) display_digit(value, 0x0D); //если значение меньше 100, то все стандартно
else
{
lcd_goto(0x0C);                            //иначе отображаем цифру 100
lcd_putch(0b00110001);
lcd_putch(0b00110000);
lcd_putch(0b00110000);
}

lcd_goto(0x0F);                           //создаем красивое заполнение нижней строки
lcd_putch(0b00100101);
lcd_goto(0x40);
lcd_putch(0b01111110);
lcd_goto(0x46);
if (value == 0) lcd_puts("----------");
if ((value > 0) && (value <= 10)) lcd_puts("O---------");
if ((value > 10) && (value <= 20)) lcd_puts("OO--------");
if ((value > 20) && (value <= 30)) lcd_puts("OOO-------");
if ((value > 30) && (value <= 40)) lcd_puts("OOOO------");
if ((value > 40) && (value <= 50)) lcd_puts("OOOOO-----");
if ((value > 50) && (value <= 60)) lcd_puts("OOOOOO----");
if ((value > 60) && (value <= 70)) lcd_puts("OOOOOOO---");
if ((value > 70) && (value <= 80)) lcd_puts("OOOOOOOO--");
if ((value > 80) && (value < 100)) lcd_puts("OOOOOOOOO-");
if (value == 100) lcd_puts("OOOOOOOOOO");
}

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

  • Отображение самого меню:

void menu(unsigned char i) {
lcd_clear();
LCD_RS = 0;
lcd_write(0b00001100);         //Выключаем курсор
__delay_us(100);
switch (i) {
  case 0 : lcd_goto(0x00);     //организовываем статические картинки для скроллинга
           lcd_puts("Menu:");
           lcd_goto(0x08);
           lcd_puts("1.Mute");
           lcd_goto(0x48);
           lcd_puts("2.Volume");
           break;
  case 1 : lcd_goto(0x00);
           lcd_puts("Menu:");
           lcd_goto(0x07);
           lcd_putch(0b00111110); //указатель пункта меню
           lcd_goto(0x08);
           lcd_puts("1.Mute");
           lcd_goto(0x48);
           lcd_puts("2.Volume");
           break;
  case 2 : lcd_goto(0x00);
           lcd_puts("Menu:");
           lcd_goto(0x08);
           lcd_puts("1.Mute");
           lcd_goto(0x47);
           lcd_putch(0b00111110);
           lcd_goto(0x48);
           lcd_puts("2.Volume");
           break;
  case 3 : lcd_goto(0x00);
           lcd_puts("Menu:");
           lcd_goto(0x07);
           lcd_putch(0b00111110);
           lcd_goto(0x08);
           lcd_puts("3.Bass");
           lcd_goto(0x48);
           lcd_puts("4.Treble");
           break;
  case 4 : lcd_goto(0x00);
           lcd_puts("Menu:");
           lcd_goto(0x08);
           lcd_puts("3.Bass");
           lcd_goto(0x47);
           lcd_putch(0b00111110);
           lcd_goto(0x48);
           lcd_puts("4.Treble");
           break;
  case 5 : lcd_goto(0x00);
           lcd_puts("Menu:");
           lcd_goto(0x07);
           lcd_putch(0b00111110);
           lcd_goto(0x08);
           lcd_puts("5.Time");
           lcd_goto(0x48);
           lcd_puts("6.StBy");
           break;
  case 6 : lcd_goto(0x00);
           lcd_puts("Menu:");
           lcd_goto(0x08);
           lcd_puts("5.Time");
           lcd_goto(0x47);
           lcd_putch(0b00111110);
           lcd_goto(0x48);
           lcd_puts("6.StBy");
           break;
  case 7 : lcd_goto(0x00);
           lcd_puts("Menu:");
           lcd_goto(0x07);
           lcd_putch(0b00111110);
           lcd_goto(0x08);
           lcd_puts("7.Exit");
           break;
}
}

Функция отображения меню, по сути набор статичных кадров, которые меняются в зависимости от направления вращения энкодера.

  • Функция Mute. Так как во время этой функции громкости изменяться не должна, то надо вырубить вращение энкодера:
void mute()
{
TRISB2 = 0;               //делаем так, чтобы вращение энкодера ни к чему не приводило
TRISB1 = 0;
lcd_clear();              //очищаем экран и ждем еще одного нажатия на кнопку для выхода
lcd_goto(0x06);
lcd_puts("MUTE");
}

Басы и тембр я пока оставил в зачаточном состоянии, т.к. аудиопроца на руках еще не имею, поэтому пока пропустим их.

  • Установка времени:
void setup_time() {
unsigned char temp = 0;
char i;
static bit exit = 0;
static bit shr = 0;
static bit smin = 0;
static bit ssec = 0;
unsigned char OldEncData = 3;
static bit smenu = 0;
i = 0;
exit = 0;
smenu = 0;
temp = ReadHour();                //Выводим значение часов
lcd_clear();
lcd_goto(0x01);
lcd_puts("Hr:");
lcd_goto(0x00);
lcd_putch(0b00111110);
display_digit(temp, 0x05);       //Функция отображения цифр на LCD
temp = ReadMin();                //Выводим значения минут
lcd_goto(0x41);
lcd_puts("Min:");
display_digit(temp, 0x45);
temp = ReadSeconds();            //Выводим значения секунд
lcd_goto(0x0A);
lcd_puts("Sec:");
display_digit(temp, 0x0E);
lcd_goto(0x4A);
lcd_puts("Exit?");
while(!exit) {
/*Процедура обработки вращения энкодера*/
__delay_ms(1);
EncData = PORTB & 0b00000110;
EncData >>= 1;

if (OldEncData != EncData) {

switch (OldEncData) {
 case 0 : if (EncData == 1) {upcount++; downcount=0; }
          if (EncData == 2) {downcount++; upcount = 0; }
          break;
 case 1 : if (EncData == 3) {upcount++; downcount=0; }
          if (EncData == 0) {downcount++; upcount = 0; }
          break;
 case 2 : if (EncData == 0) {upcount++; downcount=0; }
          if (EncData == 3) {downcount++; upcount = 0; }
          break;
 case 3 : if (EncData == 2) {upcount++; downcount=0; }
          if (EncData == 1) {downcount++; upcount = 0; }
          break;
 }

OldEncData = EncData;
}
/* Конец процедуры обработки вращения энкодера */

if (!smenu) {                     //Скроллим пункты меню

if (upcount >= 4) {
  if (i<3) i++; else i = 0;
  upcount = 0;
}
if (downcount >= 4 ) {
  if (i>0) i--; else i = 4;
  downcount = 0;
}

pointer_time(i);                  //Функция отображения указателя

} else {                          //Если не меню, то увеличиваем/уменьшаем временную переменную

if (upcount >= 4) {
  temp++;
  upcount = 0;
}
if (downcount >= 4 ) {
  temp--;
  downcount = 0;
}

if (shr) {                       //если shr = 1, то отображаем/устанавливаем часы
  if (temp >= 24) temp = 0;
  display_digit(temp, 0x05);
  i = 5;
}

if (smin) {
  if (temp >= 60) temp = 0;      //если smin = 1, то отображаем/устанавливаем минуты
  display_digit(temp, 0x45);
  i = 6;
}

if (ssec) {                      //если ssec = 1, то отображаем/устанавливаем секунды
if (temp >= 60) temp = 0;
display_digit(temp, 0x0E);
i = 7;
}

}

if (!ebutton) {                 //если нажали на кнопку
__delay_ms(200);                //антидребезг
smenu = !smenu;                 //переключаем статусную переменную
switch (i)  {
 case 0 : shr = 1; smin = 0; ssec = 0;
          temp = ReadHour();
          lcd_goto(0x00);
          lcd_putch(0b00101010);
          break;
 case 1 : smin = 1; shr = 0; ssec = 0;
          temp = ReadMin();
          lcd_goto(0x40);
          lcd_putch(0b00101010);
          break;
 case 2 : ssec = 1; shr = 0; smin = 0;
          temp = ReadSeconds();
          lcd_goto(0x09);
          lcd_putch(0b00101010);
          break;
 case 3 : exit = 1;
          break;
 case 5 : SetHour(temp);
          shr = 0;
          i = 0;
          pointer_time(i);
          break;
 case 6 : SetMin(temp);
          smin = 0;
          i = 1;
          pointer_time(i);
          break;
 case 7 : SetSeconds(temp);
          ssec = 0;
          i = 2;
          pointer_time(i);
          break;
}
}

}

GIE = 1;                           //Включаем все прерывания
}

Ну вот пожалуй и хватит, напоследок выкладываю видео с работающим девайсом (качество только вот его подкачало):

Меню для дисплея усилителя: 2 комментария

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.