Проект GreyBox[Часть 2] - Создание полноценного функционального меню

09.10.2017 22:39

jazon



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

        И вот, на основе данных, полученных из этих статей(или описанных в этих статьях, это уж кому как, лол) мы пришли к выводу что всё.... Пора... Пора сделать хоть что то серьёзное для проекта GreyBox. Что у нас из этого вышло, об этом и будет данная статья. Вообще, в дальнейших планах, много работы с серым ящиком, и возможно это не последняя статья про него. И да, сразу хотим предупредить вас - в статье будет много видео, точнее больше чем обычно мы выкладывали в других статьях. Итак начнем, нижеследующий скетч послужил основой для базы создания меню, в нем реализовано корректное считывание нажатий кнопок, подавление дребезга контактов, работа с дисплеем LCD2004 и другим оборудованием установленном в сером ящике. Сразу же нужно отметить что, лист меню, реализованный в скетче, вызывается нажатием кнопки ENT на клавиатуре серого ящика, и выглядит он так:

  • Set Date        
  • Date Format
  • Date Divider
  • Set Time
  • Time Format
  • Time Divider
  • More Options
  • Set Relays
  • Buzzer
  • Quit

Передвигаться по пунктам меню можно с помощью кнопок 2(вверх) и 8(вниз), кнопка ENT служит для подтверждения/входа, кнопка ESC служит для отмены/выхода. 

Листинг №1. Базовый скетч, основа создания меню для LCD2004 и клавиатурного модуля
#include 

#define BUZZER 4

LiquidCrystal LCDisplay(13, 12, 11, 10, 9, 8);

//Функция определения нажатой кнопки
int getPressedButton();

//Функция обработки нажатой кнопки
void ButtonEventProc(int analogValue);

short int Mode = 0;

//Символ указателя закодирован здесь - arrowToLeft
byte arrowToLeft[8] = {B00010, B00100, B01000, B11111, B01000, B00100, B00010, B00000};

int button;
const int BUTTON_0 = 0;
const int BUTTON_1 = 1;
const int BUTTON_2 = 2;
const int BUTTON_3 = 3;
const int BUTTON_4 = 4;
const int BUTTON_5 = 5;
const int BUTTON_6 = 6;
const int BUTTON_7 = 7;
const int BUTTON_8 = 8;
const int BUTTON_9 = 9;
const int BUTTON_ENT = 10;
const int BUTTON_ESC = 11;
const int BUTTON_NONE = 12;

unsigned char RELOUT1 = A2;
unsigned char RELOUT2 = A3;

short int COLS = 20;
short int ROWS = 4;

int currentButtonState, previousButtonState;

char rootMenu[10][20] = 
{
  "Set Date            ",
  "Date Format         ",
  "Date Divider        ",
  "Set Time            ",
  "Time Format         ",
  "Time Divider        ",
  "More Options        ",
  "Set Relays          ",
  "Buzzer              ",
  "Quit                "  
};
short SizeOfRootMenu = sizeof(rootMenu) / 20;

short point = 0;
short list  = 0;
short IndexCheckSum = 0;

void setup() 
{
  pinMode(BUZZER, OUTPUT);
    
  LCDisplay.begin(COLS, ROWS);
  LCDisplay.clear();
  LCDisplay.createChar(0, arrowToLeft);
}

void loop() 
{
  //Инструкции подавления дребезга контактов кнопки
  currentButtonState = getPressedButton();
  if(currentButtonState != previousButtonState)
  {
    delay(5);
    currentButtonState = getPressedButton();
    if(currentButtonState != previousButtonState)
    {
      //LCDisplay.clear();
      ButtonEventProc(currentButtonState);
      previousButtonState = currentButtonState;
    }
  }

  switch(Mode)
  {
    //Точка начала отрисовки основного экрана
    case(0):
      //Инструкции для отображения/отрисовки главного экрана
      break;
    //Режим отрисовки корневого меню
    case(1):  
      MenuRendering(rootMenu, list, point);
      break;
  }
  //Обязательное условие для создания индексации
  IndexCheckSum = list + point;
}
//Функция для получения кода нажатой кнопки
int getPressedButton()
{
  // считываем значения с аналогового входа(A0)
  int buttonValue = analogRead(0);  

  if(buttonValue < 480)
    return BUTTON_NONE;    
  else if(buttonValue < 500)
    return BUTTON_ESC;     
  else if(buttonValue < 525)
    return BUTTON_0;    
  else if(buttonValue < 551)
    return BUTTON_ENT; 
  else if(buttonValue < 582)
    return BUTTON_3;
  else if(buttonValue < 615)
    return BUTTON_6;
  else if(buttonValue < 652)
    return BUTTON_9;
  else if(buttonValue < 695)
    return BUTTON_2;
  else if(buttonValue < 744)
    return BUTTON_5;
  else if(buttonValue < 800)
    return BUTTON_8;
  else if(buttonValue < 865)
    return BUTTON_1;
  else if(buttonValue < 940)
    return BUTTON_4;
  else if(buttonValue < 1024)
    return BUTTON_7;
}

//Функция обработчик события нажатой кнопки
void ButtonEventProc(int analogValue)
{  
  switch(analogValue)
  {
    case BUTTON_NONE: 
      //Serial.println();
      break;
    case BUTTON_0:
      Peak();
      break;
  case BUTTON_1:
      Peak();
      break;
  case BUTTON_2:
      Peak();
      upListScroll();
      break;
  case BUTTON_3:
      Peak();
      break;
  case BUTTON_4:
      Peak();
      break;
  case BUTTON_5:
      Peak();
      break;
  case BUTTON_6:
      Peak();
      break;
  case BUTTON_7:
      Peak();
      break;
  case BUTTON_8:
      Peak();
      switch(Mode)
      {
        case(1):
          downListScroll(SizeOfRootMenu);
          break;
      }
      break;
  case BUTTON_9:
      Peak();
      break;
  case BUTTON_ENT:
      Peak();
      switch(Mode)
      {
        case(0):
          //Обработка входа в режим 1
          LCDisplay.clear();
          Mode = 1;
          break;
        case(1):
          //Обработка входов в режиме 1
          RootMenuHandler(IndexCheckSum);
          break;
      }
      break;
  case BUTTON_ESC:
      Peak();
      LCDisplay.clear();
      Mode = 0;
      point = 0;
      list = 0;
      break;
  }
}

void Peak()
{
  tone(BUZZER, 2349, 100);
}

//Функция отрисовки листов корневого меню и подменю
void MenuRendering(char DispItem[4][20], short listIndex, short pointIndex)
{
  for(int Row = 0; Row < 4; Row ++)
  {
    for(int Col = 0; Col < 20; Col ++)
    {
      LCDisplay.setCursor(Col, Row);
      LCDisplay.write(DispItem[Row + listIndex][Col]);
      if(Row == pointIndex)
      {
        LCDisplay.setCursor(19, pointIndex);
        LCDisplay.write(byte(0));
      }
    }
  }
}

//Прокрутка листа меню вниз
void downListScroll(short numbIndexes)
{
  if(point >= 3)
  {
    point = 3;
    if(list >= (numbIndexes - 4))  list = (numbIndexes - 4);
    else  list ++;
  }
  else  point ++;
}
//Прокрутка листа меню вверх
void upListScroll()
{
  if(point <= 0)  
  {
    point = 0;
    if(list <= 0) list = 0;
    else list --;
  }
  else point --;
}

void RootMenuHandler(short itemIndex)
{
  
  switch(itemIndex)
  {
    //Точка выхода из корневого меню на главный экран
    case(9):
      LCDisplay.clear();
      Mode = 0;
      list = 0;
      point = 0;
      break;
  }
}

Как работает этот скетч? Это можно увидеть из нижеследующего видео.

GreyBox[Часть 1] - Создание простого нефункционального одноуровневого меню 

         Конечно же автор проекта на этом не остановился, когда создана основа, можно только наращивать функционал и потенциал. Дальнейшие действия были направлены на то чтобы реализовать функционал для пунктов уже имеющегося одноуровневого меню, так сказать создать для каждого пункта свой подпункт. Следующим шагом стало добавление функционала к пунктам меню Set Date и Set Time, соответственно при помощи пункты открывают доступ к настройкам/установкам времени/даты.

GreyBox[Часть 2] - Делаем функциональным подменю Set Date и Set Time

Наверно все знают такой компонент как RadioButton из нашей всеми любимой "винды", реализация этого компонента показана в нижеследующем видео при входе в пункты меню Date Format, Date Divider, Time Format, Time Divider и Buzzer.

GreyBox[Часть 3] - Реализуем компонент RadioButton для других пунктов подменю

В одном из экранов установки опций было решено реализовать движение курсора не только вверх/вниз при выборе нужных подпунктов но и вправо/влево, эта функция была прописана и дополнена к кнопкам 4(влево) и 5(вправо).

GreyBox[Часть 4] - Реализация движения курсора во всех направлениях, подменю More Options

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

GreyBox[Часть 5] - Управляем реле с помощью временных уставок, подменю Set Relays


        Итак, однозначно можно сказать что мощность платы Arduino Uno в этом проекте не была использована на все 100%. Остались еще свободны 6 портов ввода/вывода и шина I2C. Но всё же, в качестве теста технологии создания меню и управления подключенным к плате оборудованием - эта разработка очень даже себя показала с позитивной стороны. На этом пока всё, мы надеемся это не последний наш проект с использованием GreyBox, если у вас появятся какие либо вопросы, либо предложения пишите на нашу электронную почту или в комментариях, мы обязательно ответим вам.



Расскажи о нас

Сообщение

Если у Вас есть опыт в работе с Arduino и собственно есть время для творчества, мы приглашаем всех желающих стать авторами статей публикуемых на нашем портале. Это могут быть как уроки, так и рассказы о ваших экспериментах с Arduino. Описание различных датчиков и модулей. Советы и наставления начинающим. Пишите и размещайте свои статьи в соответсвующей ветке форума.