/**
* 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