Панель оператора (HMI) с шиной I2C для Arduino

/*
Древовидное меню, работа снопками и LCD по шине i2c
LCD подключен через плату FC-113, адрес 0x27 
Клавиатура подключена через расширитель портов PCF8574P, адрес 0x20
*/

#include 
#include 

#define   led   13     //светодиод на плате Ардуно нано; будет мигать, показывая этим, что система не зависла 
#define   ADDR_KBRD  0x20   
#define   ADDR_LCD   0x27

#define   PORT_D2    2 
#define   PORT_D3    3
#define   PORT_D4    4

#define    POINT_ON_ROOT_MENU_ITEM   0   // 0/1= запретить/разрешить вывод указателя позиции(* или +) на главном экране меню

byte dio_in;
bool b;
byte i;

//bool переменные, которыми можно управлять из меню
bool  BoolVal[9]={0,0,0, 0,0,0, 0,0,0};   

#define  ValSvet1 BoolVal[0]
#define  ValSvet2 BoolVal[1]
#define  ValSvet3 BoolVal[2]

#define  ValRozetka1 BoolVal[3]
#define  ValRozetka2 BoolVal[4]
#define  ValRozetka3 BoolVal[5]

#define  ValClapan1 BoolVal[6]
#define  ValClapan2 BoolVal[7]
#define  ValClapan3 BoolVal[8]

//
struct STRUCT_KEY{
  bool StateCur;  //Текущее состояние кнопки  
  bool StateOld;  //Состояние кнопки при прошлом опросе
  bool Imp;       //Было нажатие кнопки (переход из 0 в 1)
  };

//кнопки
STRUCT_KEY Key[5]={0,0,0,
            0,0,0,
            0,0,0,
            0,0,0,
            0,0,0
           }; 
//---

/*Текстовые строки меню
 * Допустимы теги, например:
 * '#A1'  bool переменная, где 
 * '#'- тип переменной bool, 
 * 'A'- адрес(HEX) переменной в массиве BoolVal, 
 * '1'- редактирование переменной разрешено
 * при выводе текста, вместо тега автоматически подставляется значение переменной
 */
 
String StrNull=" ";      //пустая строка

String StrRoot1="COMP-MAN.INFO";    
String StrRoot2="PLC-BLOG.COM.UA";

String StrSvet= "СВЕТ";      //Свет
 String StrSvet1="СВЕТ 1   #01";    
 String StrSvet2="СВЕТ 2   #10";    
 String StrSvet3="СВЕТ 3   #21";    

String StrRozetka="РОЗЕТКИ";    //Розетки
 String StrRozetka1="РОЗЕТКА 1  #30";
 String StrRozetka2="РОЗЕТКА 2  #40";
 String StrRozetka3="РОЗЕТКА 3  #50";

String StrClapan="КЛАПАНЫ";       //Клапаны
 String StrClapan1="КЛАПАН 1  #60";    //
 String StrClapan2="КЛАПАН 2  #70";
 String StrClapan3="КЛАПАН 3  #80";

struct MENU_ITEM      //Пункт меню(экран), состоит из 2 строк и координат перехода при нажатии кнопок
  {   
    byte KeyUp;       //№ пункта меню, куда переходить по кнопке "вверх"
    byte KeyDwn;      //№ пункта меню, куда переходить по кнопке "вниз"
    byte KeyCancel;   //№ пункта меню, куда переходить по кнопке "отмена"(cancel)
    byte KeyEnter;    //№ пункта меню, куда переходить по кнопке "ввод"(enter)
    byte KeyEdit;     //кнопка "edit", резерв
  
    String *pstr1;    //указатель на верхнюю строку меню(экрана)
    String *pstr2;    //указатель на нижнюю строку меню(экрана)
  };

//
MENU_ITEM  Menu[]={0,0,0,1,0,  &StrRoot1,&StrRoot2,               //0  Главный экран
                     1,8,0,2,0, &StrSvet,&StrRozetka,             //1  СВЕТ
                       2,3,1,2,0, &StrSvet1,&StrSvet2,              //2
                       2,4,1,3,0, &StrSvet2,&StrSvet3,              //3
                       3,4,1,4,0, &StrSvet3,&StrNull,               //4
                        0,0,0,0,0, &StrNull,&StrNull,                //5  РЕЗЕРВ
                        0,0,0,0,0, &StrNull,&StrNull,                //6
                        0,0,0,0,0, &StrNull,&StrNull,                //7
                     1,15,0,9,0, &StrRozetka,&StrClapan,          //8   РОЗЕТКИ
                       9,10,8,9,0,  &StrRozetka1, &StrRozetka2,     //9 
                       9,11,8,10,0, &StrRozetka2, &StrRozetka3,     //10
                       10,11,8,11,0, &StrRozetka3, &StrNull,        //11                        
                        0,0,0,0,0, &StrNull,&StrNull,                //12 РЕЗЕРВ
                        0,0,0,0,0, &StrNull,&StrNull,                //13
                        0,0,0,0,0, &StrNull,&StrNull,                //14
                     8,15,0,16,0,  &StrClapan, &StrNull,          //15   КЛАПАНЫ
                       16,17,15,0,0,  &StrClapan1,&StrClapan2,      //16
                       16,18,15,0,0,  &StrClapan2,&StrClapan3,      //17
                       17,18,15,0,0,  &StrClapan3,&StrNull,         //18
                        0,0,0,0,0, &StrNull,&StrNull,                //19  РЕЗЕРВ
                        0,0,0,0,0, &StrNull,&StrNull,                //20
                        0,0,0,0,0, &StrNull,&StrNull,                //21
                         
                    };

byte PosMenu=0;   //позиция меню
          
LiquidCrystal_I2C lcd(ADDR_LCD,16,2);  // Устанавливаем дисплей

//Чтение состояний кнопок
void ReadKey(byte dio_in)
{
  //заполняем массив кнопок значениями их состояний
  byte mask=1;
  for(i=0; i<5; i++)
  {
    Key[i].StateCur=!(dio_in & mask);
    mask=mask<<1;

    Key[i].Imp=!Key[i].StateOld & Key[i].StateCur;    //определяем нажатие кнопки (переход из 0 в 1)
     
    Key[i].StateOld=Key[i].StateCur; 
   }  
}

/*  
 *  Перекодировка UTF-8 русских букв (только заглавных) в коды LCD
 * а то Ардуино выводит их неправильно
 */
byte  MasRus[33][2]= {
                      144,  0x41,   //А
                      145,  0xa0,
                      146,  0x42,
                      147,  0xa1,
                      
                      148,  0xe0,
                      149,  0x45,
                      129,  0xa2,
                      150,  0xa3,
                      
                      151,  0xa4,
                      152,  0xa5,
                      153,  0xa6,
                      154,  0x4b,

                      155,  0xa7,
                      156,  0x4d,
                      157,  0x48,
                      158,  0x4f,
                      
                      159,  0xa8,
                      160,  0x50,
                      161,  0x43,
                      162,  0x54,
                      
                      163,  0xa9,
                      164,  0xaa,
                      165,  0x58,
                      166,  0xe1,
                      
                      167,  0xab,
                      168,  0xac,
                      169,  0xe2,
                      170,  0xad,
                      
                      171,  0xae,
                      172,  0xc4,
                      173,  0xaf,
                      174,  0xb0,
                      
                      175,  0xb1    //Я
  };

String RusStrLCD(String StrIn)
{
  String StrOut="";
  byte b1;
  byte y;

  byte l=StrIn.length();

  for(byte i=0; i dec
byte  StrHexToByte(char val)
{
    byte dec=0;
    switch (val) {
    case '0':
      dec=0;
      break;
    case '1':
      dec=1;
      break;
    case '2':
      dec=2;
      break;    
    case '3':
      dec=3;
      break;
    case '4':
      dec=4;
      break;
    case '5':
      dec=5;
      break;
    case '6':
      dec=6;
      break;
    case '7':
      dec=7;
      break;
    case '8':
      dec=8;
      break;
    case '9':
      dec=9;
      break;
    case 'A':
      dec=10;
      break;
    case 'B':
      dec=11;
      break;
    case 'C':
      dec=12;
      break;
    case 'D':
      dec=13;
      break;
    case 'E':
      dec=14;
      break;
    case 'F':
      dec=15;
      break;
          
    default: 
    dec=0;
    break;
  }
return dec;
}

//Вывод на экран пункта меню
void WriteLCD(byte num)
{
   String str[]={"*"+*Menu[num].pstr1,*Menu[num].pstr2};
  if (num==0 && POINT_ON_ROOT_MENU_ITEM==0)  //на главном эркане нужно выводить указатель?
   str[0].setCharAt(0,' ');                  //стираем указатель, если нет 
   
  //Подставляем значения переменных вместо тегов
  byte NumVal;
  byte l;
  for(byte y=0; y<2; y++)
  {
  l=str[y].length();
  for(i=0; i-1)  //так было нажатие?
  {
   if (KeyImp==4) //Кнопка "Edit"
    Edit(PosMenu);
    
   PosMenu=GoMenu((KeyImp));  
   WriteLCD(PosMenu);
  }

  b=!b;
  digitalWrite(led, b); //Мигаем светодиодом на Ардуино

  ValToPort();   //управление выходами
  
  delay(50);          
}


© Geektimes