Aporte - Construcción de menus con submenus

Me encontré en un diseño que necesitaba configurar la applicación a traves de valores que el usuario seleccionara
por medio de menús y submenús. y guardar estos valores en la EEprom del Arduino. Puesto que era importante
minimizar costos en su etapa de prototipo, seleccioné el Arduino-Uno con el shield LCD-Keypad.
Como soy nuevo en la programación de Arduino C++, busque en el internet algo que me sirviera de referencia
y encontré el siguiente sketch (ver pdf anexo), y lo comenté para entender lo que hace el programa.

El sketch es muy simple, solo despliega en el LCD:
* En la primera hilera: el tiempo transcurrido desde que se enciende el Arduino con su respectiva unidad,
seleccionada através del menú.
* En la segunda hilera: la temperatura interna del Arduino-Uno con su respectiva unidad, también
seleccionada através del menú.

Lo interesante es la construcción de los menus y el uso de estructuras STRUCT y UNION para formar una
especie de
class como si fuera programación de objetos.
Además, me enseño el uso del teclado y como mostrar lo que quisiera en el LCD usando la libreria
Liquidcrystal
y algunos operadores que desconocia (operador condicional ? : ).

Creo que a muchos principiantes como lo soy yo, les puede servir y ahorrar horas en el desarrollo de sus programas.
 

Adjuntos

  • Arduino menu con submenus v1.pdf
    162.8 KB · Visitas: 106
Disculpen por los errores de dedo. Aquí les dejo el código para el Arduino IDE versión 1.85
C++:
/**

* Menú con submenus para Shield LCD-KeyPad en Arduino ver1

*  LIBRERIAS NECESARIAS PARA EL FUNCIONAMIENTO DEL CODIGO

*  Sketch uses 6016 bytes (18%) of program storage space. Maximum is 32256 bytes.

*  Global variables use 438 bytes (21%) of dynamic memory, leaving 1610 bytes for local variables.

* Name:     Arduino - Menu for Shield LCD

* Autor:    Alberto Gil Tesa

* Comentarios Adicionales : David Sánchez   Mayo 16-2018

* Web:      el blog de giltesa

*   Menú con submenus para Shield LCD en Arduino - el blog de giltesa

* License:  CC BY-NC-SA 3.0

* Date:     2016/12/10

*

* Arduino Uno, Pinout:

*               ____________________

*              |          USB       |

*              |13                12|

*              |3V3               11|

*              |AREF              10|

*   APAD |A0                       9| LCD

*              |A1                 8| LCD

*              |A2                 7| LCD

*              |A3                 6| LCD

*              |A4                 5| LCD

*              |A5                 4| LCD

*              |               3/SCL|

*              |               2/SDA|

*              |5V               GND|

*              |RST              RST|

*              |GND        1/INT2/RX|

*              |VIN        0/INT3/TX|

*              |MISO              SS|

*              |SCK             MOSI|

*              |____________________|

*

*/


/* LIBRERIAS Y OBJETOS */

#include <LiquidCrystal.h>

#include <EEPROM.h>


// Evitamos programar con números los pines del Arduino.

// Asi que los declaramos con el nombre IOPINxx con el que

//  seran usados. Si es necesario los definimos e inicializamos


#define IOPIN4 4         // #define  solo declara el nombre

#define IOPIN5 5         //  con una constante para el compilador

#define IOPIN6 6

#define IOPIN7 7

#define IOPIN8 8

#define IOPIN9 9


// Inicializamos las constante usadas por la libraria LiquidCrystal

//   associando los pines de LCD-keypad shield, con los pines que

//   estan conectados al Arduino-Uno.


#define rowsLCD         2        // Número de filas del LCD

#define columnsLCD     16        // Número de columnas del LCD


const int rs = IOPIN8, en = IOPIN9, d4 = IOPIN4,

          d5 = IOPIN5, d6 = IOPIN6, d7 = IOPIN7;


//   y declaramos el LCD del shield

LiquidCrystal lcd(rs, en, d4, d5, d6, d7);


//   Constantes que usa el shield LCD-Keypad

#define AKeyPad        A0        // Entrada del teclado

#define IOPIN13        13        // También declaramos el pin del LED interno

int led13 = IOPIN13;             //   y lo definimos.

                                 // Teclas del KeyPad

boolean btnLeft   = false;       // Atras    / Salir

boolean btnRight  = false;       // Adelante / Entrar menu/submenu

boolean btnUp     = false;       // Opción anterior

boolean btnDown   = false;       // Opción siguiente

boolean btnSelect = false;       // Opción siguiente


//   Las siguientes son utilizadas para definir el tipo de menú.

//     Type_S          es uno donde el regreso del menú es una cadena de caracteres

//     Type_BOOL  es uno donde el regreso del menú es una variable de tipo Boleana

//     Type_INT      es uno donde el regreso del menú es una variable de tipo Entero


#define TYPE_SMENU1       1

#define TYPE_SMENU2       2

#define TYPE_BOOL         3

#define TYPE_INT          4

boolean exitMenu      = false;    // para evaluar si salimos de los menus

byte menuPosition     = 0;        // apuntador para la Posición del ítem del menú principal

                                  // apuntando al 1er ítem.


//  Variables para control del tiempo

unsigned long tNow      = 0;

unsigned long tPrevious = 0;


// caracteres de la memoria ROM del LCD

#define iconArrowR   0x7E         // LCD (icon Flecha Derecha)

#define iconArrowL   0x7F         // LCD (icon Flecha Izquierda)

#define iconGrados   0:LOL:F         // LCD (icon Grados)

#define iconDiv      0xFD         // LCD (icon Division)

/* MENUS */

const byte iMENU = 10;            // número de ítems en el menú principla

const char *txMENU[] = {

    "Ver tiempo     ",            // Ocupan un máximo de 16 columnas del LCD

    "Unid. tiempo   ",

    "Eje X tiempo   ",

    "Eje Y tiempo   ",

    "Ver temperatura",

    "Unid. temp.    ",

    "Eje X temp.    ",

    "Eje Y temp.    ",

    "Guardar y salir",

    "Salir          "

};


/* TEXTOS SUB MENU 1 */

const byte  iMENU1 = 3;           // número de ítems en el submenú

const char *txSMENU1[] = {

    " Milisegundos ",             // el valor de la selección ocupa un máximo de 2 columnas del LCD

    "   Segundos   ",

    "   Minutos    "

};


/* TEXTOS SUB MENU 2 */

const byte  iMENU2 = 2;           // número de ítems en el submenú

const char *txSMENU2[] = {

    "   Grados C   ",             // el valor de la selección ocupa un máximo de 2 columnas del LCD

    "   Grados F   "

};



/* ESTRUCTURAS CONFIGURACION

    The General syntax for a structure is:

    struct structureTag {

                           StructureMemberList;

                        };

   structureTag is a data declaration—no memory has been allocated yet for a single MemberList variable.

   Obviously, you need to define a variable using this type of structure definition for the structure to be useful

   in a program. The syntax is:

                          struct structureTag structureVariable;

  Ej:

          struct servicePeople {

                                   int ID;

                                   char Name[20];

                                   char PW[10];

                                   long Phone;

                               };


           struct servicePeople myServicePeople;      // myServicePeople es miembro de la estructura servicePeople

           struct servicePeople yourServicePeople;

*/


//  Se usa esta estructura STRUCT para almacenar los parámetros del programa en la EEPROM

struct MYDATA{

    int initialized;

    int time_show;

    int time_unit;

    int time_x;

    int time_y;

    int temp_show;

    int temp_unit;

    int temp_x;

    int temp_y;

  };


//  y definimos una estructura UNION para ligar la STRUCT previamente definida

// para usarla como un objeto, casi un pseudo-class donde memory es miembro de ella y con ello

//  facilitar la lectura-escritura a la EEPROM

union MEMORY{

     MYDATA d;                    // proceso  .d  regresa la estructura del tipo MYDATA

     byte b[sizeof(MYDATA)];      // proceso  .b regresa el tamaño de la estructura MYDATA

   }  memory;                     //  hacemos elemento memory miembro de esta unión



/**   readConfiguration()

* Lee (y configura la primera vez) la memoria EEPROM con la configuración del usuario

*/

void readConfiguration()

   {

      for( int i=0 ; i < sizeof(memory.d) ; i++  )   //  la pseudo-class memory

             memory.b = EEPROM.read(i);

            

      if( !memory.d.initialized ) {                  // Guarda los valores predeterminados            {

             memory.d.initialized   = true;

             memory.d.time_show     = 1;          // muestra en LCD el tiempo

             memory.d.time_unit     = 1;          //  Unidad = segundos

             memory.d.time_x        = 0;

             memory.d.time_y        = 0;

             memory.d.temp_show     = 1;          // muestra en LCD la temperatura

             memory.d.temp_unit     = 0;          //  Unidad = ºC

             memory.d.temp_x        = 0;

             memory.d.temp_y        = 1;

           }

     }         // cerramos función readConfiguration



/**

* Lee las teclas del KeyPad

*/

void readButtons( )

     {

      /* Entrada Analogica A0 del arduino lee de mi shield LCD-Keypad (0,100,259.409,638)

       *  Asi que somos generosos al definir los valores de comparación. Ajusta de acuerdo

       *  a lo que lea tu teclado.

       */

         int val  = analogRead(AKeyPad);

         btnLeft     = false;

         btnRight    = false;

         btnUp       = false;

         btnDown     = false;

         btnSelect   = false;


         if( val < 30 )

             btnRight = true;

         else if( val < 150 )

             btnUp    = true;

         else if( val < 360 )

             btnDown  = true;

         else if( val < 535 )

             btnLeft  = true;

         else if( val < 760 )

             btnSelect  = true;


      //  Revisa el teclado hasta que alla una tecla apretada

      while( (btnLeft || btnRight || btnUp || btnDown || btnSelect) && analogRead(AKeyPad) < 1000 );

   }           // cerramos función readButtons



/** openSubMenu

* Muestra el SubMenu en el LCD.

*

* @param nameID    Indice del array que contiene el titulo del submenu.

* @param typeMenu  Segun el tipo, se representara el submenu de una forma u otra.

* @param value     Puntero a la variable que almacena el dato, y que se modificara.

* @param minValue  Valor minimo que puede tener la variable.

* @param maxValue  Valor maximo que puede tener la variable.

*/

void openSubMenu( byte nameID, byte typeMenu, int *value, int minValue, int maxValue )

{

    lcd.clear();


    while( !exitMenu )

    {

        readButtons();


        if( btnRight || btnLeft )

        {

            exitMenu = true;

        }

        else if( btnUp  && (*value)-1 >= minValue )

        {

            (*value)--;

        }

        else if( btnDown && (*value)+1 <= maxValue )

        {

            (*value)++;

        }



        if( !exitMenu && millis()%250 == 0 )

        {

            lcd.setCursor(0, 0);

            lcd.print(txMENU[nameID]);


            lcd.setCursor(0, 1);

            lcd.print("<");

            lcd.setCursor(columnsLCD - 1, 1);

            lcd.print(">");


            if( typeMenu == TYPE_SMENU1 )

            {

                lcd.setCursor(1,1);

                lcd.print(txSMENU1[*value]);

            }

            else if( typeMenu == TYPE_SMENU2 )

            {

                lcd.setCursor(1 , 1);

                lcd.print(txSMENU2[*value]);

            }

            else if( typeMenu == TYPE_BOOL )

            {

                lcd.setCursor(columnsLCD/2-1, 1);

                lcd.print(*value == 0 ? "NO" : "SI");

            }

            else if( typeMenu == TYPE_INT )

            {

                lcd.setCursor(columnsLCD/2 - 1, 1);

                lcd.print(*value);

                lcd.print(" ");

            }


        }   // cerramos if !exitMenu

    }       // cerramos while


    exitMenu     = false;

    lcd.clear();                        // Position del LCD.cursor= 0,0

}           // cerramos función openSubMenu



/**

* Para la EEprom

*/

void writeConfiguration()

   {

       for( int i=0 ; i<sizeof(memory.d) ; i++  )

        EEPROM.write( i, memory.b );

   }



/**

* Devuelve la temperatura del sensor interno del microcontrolador

*/

double getTemp()

{

    ADMUX = (_BV(REFS1) | _BV(REFS0) | _BV(MUX3));

    ADCSRA |= _BV(ADEN);

    delay(20);

    ADCSRA |= _BV(ADSC);

    while( bit_is_set(ADCSRA,ADSC) );

    return (ADCW - 324.31 ) / 1.22;

}



/**    openMenu()

* Muestra el Menu principal en el LCD.

*/

void openMenu()

     {

         lcd.clear();                                            //  lcd.cursor = 0,0

         while( !exitMenu )

                  {

                     readButtons();


                    //  revisa si fue la tecla LEFT – Si es asi,  sal de la revisión de teclas

                     if( btnLeft )  { exitMenu = true; }


                    //  revisa si fue la tecla UP Y no hemos llegado al principio del menú

                    //    si es asi: decrementa apuntador para la Posición del ítem del menú principal

                     else if( btnUp && menuPosition-1 >= 0 )   { menuPosition--; }


                    //  revisa si fue la tecla Down Y no hemos llegado al final del menú

                    //    si es asi: incrementa apuntador para la Posición del ítem del menú principal

                     else if( btnDown && menuPosition+1 < iMENU )  { menuPosition++; }


                     //  busca si fue la tecla Right

                    else if( btnRight )

                        {

                      //  esta es una tecla multifunción así que revisamos que función hacemos

                     //            basados en el apuntador para la Posición del ítem del menú principal

                           switch( menuPosition )

                                {

                                   case 0:                               // 1er. ítem del menú

                                           openSubMenu( 0, TYPE_BOOL,   &memory.d.time_show, 0, 1  );

                                           break;

                                   case 1:                               // 2do. ítem del menú

                                            openSubMenu( 1, TYPE_SMENU1, &memory.d.time_unit, 0, 2  );

                                            break;

                                   case 2:                                // 3er. ítem del menú

                                            openSubMenu( 2, TYPE_INT, &memory.d.time_x, 0, 15 );

                                            break;

                                   case 3:                               // 4to. ítem del menú

                                           openSubMenu( 3, TYPE_INT, &memory.d.time_y, 0, 1 );

                                           break;

                                   case 4:                               // 5to. ítem del menú

                                           openSubMenu( 4, TYPE_BOOL, &memory.d.temp_show, 0, 1 );

                                           break;

                                  

                                   case 5:                               // 6to. ítem del menú

                                           openSubMenu( 5, TYPE_SMENU2, &memory.d.temp_unit, 0, 1  );

                                           break;

                                   case 6:                               // 7to. ítem del menú

                                           openSubMenu( 6, TYPE_INT, &memory.d.temp_x, 0, 15 );

                                           break;

                                   case 7:                               // 8vo. ítem del menú

                                           openSubMenu( 7, TYPE_INT, &memory.d.temp_y, 0, 1  );

                                           break;

                                   case 8:                               // 9no. ítem del menú

                                           writeConfiguration();         //  Guardar y Salir

                                           exitMenu = true;

                                           break;

                                   case 9:                               // 10mo. ítem del menú -  Cancelar cambios y Salir

                                          readConfiguration();

                                          exitMenu = true;

                                          break;

                                }                                        // cerramos selección de función (switch) de tecla Right

                             }                                           // cerramos busqueda de tecla Right


                      if( !exitMenu && millis()%500 == 0 )

                      // cada 500 ms lo hacemos en caso de que queramos salir del menú

                        {

                            if(  menuPosition % rowsLCD == 0 )         //  Si LCD.cursor esta en la última posición

                              {

                                 for( int i=menuPosition ; i<(menuPosition+rowsLCD) ; i++ )

                                      {

                                          lcd.setCursor(1, i % rowsLCD);


   //  The conditional operator ? : is the only ternary operator in C.

   // Sintaxis: expression1 ? expression2 : expression3

        /*

       Expression1 is evaluated first.

         If its value is true,  then expression2 is evaluated and expression3 is ignored.

         If its value is false, then expression3 evaluates and expression2 is ignored.

       The result will be a value of either expression2 or expression3 depending upon which of them evaluates as True.

       Conditional operator associates from right to left.

        */

                                          lcd.print( i<iMENU ? txMENU : "                " );

                                       }

                               }

                            if( (menuPosition-(rowsLCD-1)) % rowsLCD == 0 )

                            {

                            for( int i=(menuPosition-(rowsLCD-1)) ; i<((menuPosition-(rowsLCD-1))+rowsLCD) ; i++ )

                                 {

                                    lcd.setCursor(1, i % rowsLCD);

                                    lcd.print( i<iMENU ? txMENU : "                " );

                                 }

                            }

                            for( int i=0 ; i<rowsLCD ; i++ )

                                 {

                                    lcd.setCursor(0 , i );

                                    lcd.print(" ");

                                 }

                            lcd.setCursor(0, menuPosition % rowsLCD );

                            lcd.write(iconArrowR);

                         }        // cerramos if (  !exitMenu && millis()%500 )

                     }            // cerramos else if( btnRight )

                    exitMenu     = false;                                         //  restablecemos valor predeterminado

                   menuPosition = 0;                                             //   y también la posición

                   lcd.clear();

    }    // cerramos openmenu()



/**

* Inicializamos Arduino y shields

*/

void setup()

{

    // Carga la configuracion desde la EEPROM, y la configura la primera vez:

    readConfiguration();


    // Inicia el LCD:

    lcd.begin(columnsLCD, rowsLCD);


    //  Crea caracteres de usuario en LCD-RAM

     //  lcd.createChar(nombre, matriz);


    // Imprime la informacion del proyecto:

    lcd.setCursor(0,0); lcd.print("  Menu Shield   ");

    lcd.setCursor(0,1); lcd.print("  LCD-KeyPad    ");

    delay(2000);

    lcd.clear();

  }     // cerramos setup



/**

*  Aquí el programa principal

*/


void loop() {

    tNow = millis();

    readButtons();


     if( btnRight ) { openMenu(); }


// Despliega el valor del tiempo transcurrido en la primera hilera cada segundo:

//    si es que fué habilitado en el submenú iMENU1 de la opción

//   Ver tiempo del menú principal iMENU  , usando la unidad seleccionada en

//  la opción Unid. tiempo  del menú principal


    if( tNow - tPrevious >= 1000 )

      {

        tPrevious = tNow;


       //  class = memory,  proceso = estructura myData,  elemento de la estructura  = time_show

       //  limpiamos display si vamos a mostrar el valor del tiempo o de la temperatura

        if( memory.d.time_show == 1 || memory.d.temp_show == 1 )  { lcd.clear(); }

      

        if( memory.d.time_show == 1 )                           // Si mostramos el tiempo:

          {                                                     // ajustamos la posición del  LCD.cursor

            lcd.setCursor(memory.d.time_x, memory.d.time_y);

            switch( memory.d.time_unit )                        // mostramos el tiempo actual:

              {                                                 // de acuerdo a la unidad de tiempo seleccionada

                case 0:                                         //   unidad de tiempo Milecimas  seleccionada

                    lcd.print(tNow);

                    lcd.print(" Mil");

                    break;

                case 1:                                         //   unidad de tiempo Segundos seleccionada

                    lcd.print(tNow/1000);                       //    convertimos mils a segundos y mostramos

                    lcd.print(" Seg");

                    break;

                case 2:                                         //   unidad de tiempo Min seleccionada

                    lcd.print(tNow/1000/60);                    //    convertimos mils a Minutos y mostramos

                    lcd.print(" Min");

                    break;

              }                                                  // cerramos switch para mostrar tiempo actual

        }                                                        // cerramos If  si mostramos el tiempo

  

// Despliega el valor de la temperatura con la unidad seleccionada (ºF ó ºC)

//   en el submenú  iMENU2,  si es que fué habilitado en el submenú de la opción

//   Ver temperatura del menú principal iMENU   

        if( memory.d.temp_show == 1 )                            // Si mostramos la temperatura:

          {                                                      // ajustamos la posición del  LCD.cursor

            lcd.setCursor(memory.d.temp_x, memory.d.temp_y);

            switch( memory.d.temp_unit )                         // mostramos la temperatura actual:

               {                                                 // de acuerdo a la unidad de tiempo seleccionada

                 case 0:                                         //   unidad de temperatura = C

                    lcd.print(getTemp());

                    lcd.print(" C");

                    break;

                 case 1:                                         //   unidad de temperatura = F

                    lcd.print(1.8 * getTemp() + 32);             //  convierte a Grados Farenheith y muestralo

                    lcd.print(" F");                                                       

                    break;

               }                                                 // cerramos switch para mostrar temperatura actual

          }                                                      // cerramos If Si mostramos la temperatura

    }                                                            // cerramos If tNow - tPrevious

  }    // cerramos loop
 
Última edición por un moderador:
Atrás
Arriba