Skip to content

Instantly share code, notes, and snippets.

@giltesa
Last active January 8, 2018 01:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save giltesa/839f8ac6fb21d2cfd21e to your computer and use it in GitHub Desktop.
Save giltesa/839f8ac6fb21d2cfd21e to your computer and use it in GitHub Desktop.
Arduino - Control Emisoras.c
/**
* Name: Arduino - Control de emisoras de radioaficionado
* Autor: Alberto Gil Tesa
* Web: https://giltesa.com
* License: CC BY-NC-SA 3.0
* Version: 2.0.3
* Date: 2018/01/08
*
* Arduino Micro, Pinout:
* _______________
* | USB |
* RELE A1 |13 12| RELE A2
* |3V3 11| RELE A3
* |AREF 10| RELE A4
* RELE B1 |A0 9| RELE A5
* RELE B2 |A1 8| RELE MSG
* RELE B3 |A2 7| RELE AUX
* RELE B4 |A3 6| RELE PEDAL
* PEDAL |A4 5| ENCODER IZQUIERDA
* ENCODER CENTRO |A5 4| ENCODER DERECHA
* | 3/SCL| LCD
* | 2/SDA| LCD
* |5V GND|
* |RST RST|
* |GND 1/INT2/RX|
* |VIN 0/INT3/TX|
* |MISO SS|
* |SCK MOSI|
* |_______________|
*
*/
/**
* LIBRERIAS NECESARIAS PARA EL FUNCIONAMIENTO DEL CODIGO
*/
#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
/**
* OBJETOS DE LAS LIBRERIAS
*/
#ifdef __AVR_ATmega328P__
LiquidCrystal_I2C lcd(0x3F, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
#else
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
#endif
/**
* MACROS, CONSTANTES, ENUMERADORES, ESTRUCTURAS Y VARIABLES GLOBALES
*/
#define DEBUG true // Activa el modo depuracion para mostrar informacion por la consola Serial, comentar con // al inicio para desactivar la depuracion.
#define COUNT(x) sizeof(x)/sizeof(*x) // Macro para contar el numero de elementos de un array
const byte pRELE_MSG = 8; // Rele para el mensaje temporizado
const byte pRELE_AUX = 7; // Rele de los equipos auxiliares
const byte pRELE_PEDAL = 6; // Rele del microfono del pedal
const byte pBTN_LEFT = 5; // Encoder izquierda
const byte pBTN_RIGHT = 4; // Encoder derecha
const byte rowsLCD = 4; // Numero de filas del LCD
const byte colsLCD = 20; // Numero de columnas del LCD
#ifdef __AVR_ATmega328P__
const byte pBTN_OK = 3; // Encoder centro //REVISAR
const byte pBTN_PEDAL = 2; // Pedal
#else
const byte pBTN_OK = A5; // Encoder centro //REVISAR
const byte pBTN_PEDAL = A4; // Pedal
#endif
enum Button{ Unknown, Ok, Left, Right, Pedal }; // Enumerador con los diferentes botones disponibles
enum Screen{ Menu1, Menu2, Flag, Number }; // Enumerador con los distintos tipos de submenus disponibles
// Generador de iconos: http://mikeyancey.com/hamcalc/lcd_characters.php
const byte bEMISORA[] = { B00000, B00010, B00010, B00010, B11111, B11111, B11111, B00000 }; // Emisora
const byte bMICROFONO[] = { B00000, B01110, B01010, B01110, B00100, B00100, B00100, B00000 }; // Microfono
const byte bDIGITAL[] = { B00001, B00011, B00101, B00001, B11101, B10100, B10100, B11100 }; // Digital
const byte bGRABADORA[] = { B00000, B00000, B11100, B11111, B10001, B11111, B11111, B00000 }; // Grabadora
const byte bLLAMADOR[] = { B00000, B00010, B00110, B11110, B11110, B00110, B00010, B00000 }; // Llamador
const byte bMENSAJE[] = { B00000, B00000, B11111, B11111, B10101, B10001, B11111, B00000 }; // Mensaje
const byte bPEDAL[] = { B00000, B00000, B10000, B11000, B11110, B11111, B00000, B00000 }; // Pedal
const byte bFLECHA[] = { B00000, B00100, B00110, B11111, B00110, B00100, B00000, B00000 }; // Flecha
const byte icoEMISORA=0, icoMICROFONO=1, icoDIGITAL=2, icoGRABADORA=3, icoLLAMADOR=4, icoMENSAJE=5, icoPEDAL=6, icoFLECHA=7;
/* TEXTOS MENUS */
const char *txMENU[] = {
"Rele Grupo A ",
"Rele Grupo B ",
"Rele Msg Estado ",
"Rele Msg T. reposo ",
"Rele Msg T. func. ",
"Rele Aux ",
"Rele Pedal T. apag ",
"R.Pedal Msg T. apag",
"Guardar y salir ",
"No guardar y salir ",
"Salir "
};
const byte iMENU = COUNT(txMENU);
/* RELES A */
const byte pRELE_A[] = { 13, 12, 11, 10, 9 }; // Pines de los reles del GrupoA
const char *txRELE_A[] = { // Nombre de cada rele del GrupoA, maximo 18 caracteres por nombre.
"1. KENWOOD TS-850 ",
"2. YAESU FT-897 ",
"3. KENWOOD TM-742 ",
"4. KENWOOD TR-50 ",
"5. AUXILIAR "
};
const byte iRELE_A = COUNT(txRELE_A); // Numero de reles del GrupoA
const byte icoRELE_A[] = {
icoEMISORA,
icoEMISORA,
icoEMISORA,
icoEMISORA,
icoEMISORA
};
/* RELES B */
const byte pRELE_B[] = { A0, A1, A2, A3 }; // Pines de los reles del GrupoB
const char *txRELE_B[] = { // Nombre de cada rele del GrupoB, maximo 18 caracteres por nombre.
"1. MICROFONO ",
"2. MODOS DIGITALES",
"3. GRABADORA ",
"4. LLAMADOR CQ "
};
const byte iRELE_B = COUNT(txRELE_B); // Numero de reles del GrupoB
const char icoRELE_B[] = {
icoMICROFONO,
icoDIGITAL,
icoGRABADORA,
icoLLAMADOR
};
/**
* ESTRUCTURAS CONFIGURACION
*/
struct MYDATA{
int initialized;
int sGroupA; // Estado rele grupo A -1(OFF), 0(R1), 1(R2), 2(R3), 3(R4), 4(R5)
int sGroupB; // Estado rele grupo B -1(OFF), 0(R1), 1(R2), 2(R3), 3(R4)
int sMsg; // Estado rele mensaje 0, 1
int tMsgSleep; // Tiempo pausa rele mensaje 0~32767 segundos
int tMsgWork; // Tiempo trabajo rele mensaje 0~32767 segundos
int sAux; // Estado rele auxiliar 0, 1
int tPedalWork; // Tiempo trabajo rele pedal 0~32767 segundos
int tPedalMsgWork; // Tiempo trabajo rele pedal forzado por Msg 0~32767 segundos
};
union MEMORY{
MYDATA d;
byte b[sizeof(MYDATA)];
}
memory;
/**
* INICIO Y CONFIGURACION DEL PROGRAMA
*/
void setup()
{
// Configura los pines de salida y entrada:
pinMode( pBTN_OK, INPUT );
pinMode( pBTN_LEFT, INPUT );
pinMode( pBTN_RIGHT, INPUT );
pinMode( pBTN_PEDAL, INPUT );
for( int i=0 ; i < iRELE_A ; i++ )
pinMode( pRELE_A[i], OUTPUT );
for( int i=0 ; i < iRELE_B ; i++ )
pinMode( pRELE_B[i], OUTPUT );
pinMode( pRELE_MSG, OUTPUT );
pinMode( pRELE_AUX, OUTPUT );
pinMode( pRELE_PEDAL, OUTPUT );
// Carga la configuracion de la EEPROM, y la configura la primera vez:
readConfiguration();
// Inicia el LCD:
lcd.begin(colsLCD, rowsLCD);
lcd.backlight();
lcd.createChar( icoEMISORA, bEMISORA );
lcd.createChar( icoMICROFONO, bMICROFONO );
lcd.createChar( icoDIGITAL, bDIGITAL );
lcd.createChar( icoGRABADORA, bGRABADORA );
lcd.createChar( icoLLAMADOR, bLLAMADOR );
lcd.createChar( icoMENSAJE, bMENSAJE );
lcd.createChar( icoPEDAL, bPEDAL );
lcd.createChar( icoFLECHA, bFLECHA );
// Imprime la informacion del proyecto:
lcd.setCursor(0,0); lcd.print("Control de emisoras ");
lcd.setCursor(0,1); lcd.print(" radioaficionado ");
lcd.setCursor(0,2); lcd.print("giltesa.com - v2.0.3");
lcd.setCursor(0,3);
for( int i=0 ; i<colsLCD ; i++ )
{
lcd.print(".");
delay(120);
}
lcd.clear();
#ifdef DEBUG
Serial.begin(9600);
while(!Serial);
Serial.println("Modo depuracion activado\nRecuerde desactivarlo una vez terminado de usar\n");
#endif
}
/**
* PROGRAMA PRINCIPAL
*/
void loop()
{
static unsigned long tNow = 0;
static unsigned long tNowSeg = 0;
static unsigned long tTaskMainProgram = 0;
static unsigned long tPedalBefore = 0;
static unsigned long tMsgSleepBefore = 0;
static unsigned long tMsgWorkBefore = 0;
static boolean sPedal = false;
static boolean sPedalMsg = false;
static boolean sPedalRst = false;
static boolean sUpdateIO = true;
static Button btnPressed = Button::Unknown;
tNow = millis();
tNowSeg = tNow/1000;
btnPressed = readButtons();
if( btnPressed == Button::Ok )
{
openMenu();
sUpdateIO = true;
}
if( btnPressed == Button::Pedal )
{
if( !sPedal && !sPedalMsg ){
sPedal = true;
tPedalBefore = tNowSeg;
}else{
sPedal = false;
sPedalMsg = false;
sPedalRst = true;
}
}
// Tareas que se ejecutan solo cada X tiempo:
if( tNow - tTaskMainProgram > 250 )
{
tTaskMainProgram = tNow;
// Actualiza LCD linea 1 y 2: Con estado de los reles del grupo A y B:
lcd.setCursor(0,0);
if( memory.d.sGroupA == -1 ){
lcd.print(" OFF ");
}else{
lcd.write(icoRELE_A[memory.d.sGroupA]);
lcd.print(txRELE_A[memory.d.sGroupA]);
}
lcd.setCursor(0,1);
if( memory.d.sGroupB == -1 ){
lcd.print(" OFF ");
}else{
lcd.write(icoRELE_B[memory.d.sGroupB]);
lcd.print(txRELE_B[memory.d.sGroupB]);
}
// Actualiza LCD linea 3: Con tiempo restante para emitir el mensaje:
if( memory.d.sMsg && memory.d.tMsgSleep > 0 && memory.d.tMsgWork > 0 )
{
if( tMsgSleepBefore + memory.d.tMsgSleep >= tNowSeg )
{
digitalWrite( pRELE_MSG, LOW );
lcd.setCursor(0,2);
lcd.write(icoMENSAJE);
lcd.print("Msg conexi: ");
lcd.setCursor(13,2);
lcd.print( tMsgSleepBefore + memory.d.tMsgSleep - tNowSeg );
lcd.print("s");
tMsgWorkBefore = tNowSeg;
}
else if( tMsgWorkBefore + memory.d.tMsgWork >= tNowSeg )
{
if( !sPedalMsg && memory.d.tPedalMsgWork > 0 )
{
sPedalMsg = true;
tPedalBefore = tNowSeg;
}
digitalWrite( pRELE_MSG, HIGH );
lcd.setCursor(0,2);
lcd.write(icoMENSAJE);
lcd.print("Msg descon: ");
lcd.setCursor(13,2);
lcd.print( tMsgWorkBefore + memory.d.tMsgWork - tNowSeg );
lcd.print("s");
}
else
{
tMsgSleepBefore = tNowSeg;
}
}
// Actualiza LCD linea 4: Con tiempo para la desconexion del pedal:
if( (sPedal && tPedalBefore + memory.d.tPedalWork > tNowSeg)
|| (sPedalMsg && memory.d.tPedalMsgWork > 0 && tPedalBefore + memory.d.tPedalMsgWork > tNowSeg) )
{
lcd.setCursor(0,3);
lcd.write(icoPEDAL);
lcd.print("Pedal desc: ");
lcd.setCursor(13,3);
lcd.print( tPedalBefore + (sPedal ? memory.d.tPedalWork : memory.d.tPedalMsgWork) - tNowSeg );
lcd.print("s");
digitalWrite( pRELE_PEDAL, HIGH );
}
else if( sPedal || sPedalMsg || sPedalRst )
{
sPedal = false;
sPedalMsg = false;
sPedalRst = false;
lcd.setCursor(0,3);
lcd.print(" ");
digitalWrite( pRELE_PEDAL, LOW );
}
}// Fin tareas
// Establece los nuevos estados I/O
if( sUpdateIO )
{
sUpdateIO = false;
tNow = millis();
tNowSeg = tNow/1000;
tMsgSleepBefore = tNowSeg;
tMsgWorkBefore = tNowSeg;
for( int i=0 ; i < iRELE_A ; i++ )
digitalWrite( pRELE_A[i], LOW );
if( memory.d.sGroupA > -1 )
digitalWrite( pRELE_A[memory.d.sGroupA], HIGH );
for( int i=0 ; i < iRELE_B ; i++ )
digitalWrite( pRELE_B[i], LOW );
if( memory.d.sGroupB > -1 )
digitalWrite( pRELE_B[memory.d.sGroupB], HIGH );
digitalWrite( pRELE_AUX, memory.d.sAux );
}
}
/**
* MUESTRA EL MENU PRINCIPAL EN EL LCD.
*/
void openMenu()
{
byte idxMenu = 0;
boolean exitMenu = false;
boolean forcePrint = true;
Button btnPressed = Button::Unknown;
lcd.clear();
while( !exitMenu )
{
btnPressed = readButtons();
if( btnPressed == Button::Left && idxMenu-1 >= 0 )
{
idxMenu--;
}
else if( btnPressed == Button::Right && idxMenu+1 < iMENU )
{
idxMenu++;
}
else if( btnPressed == Button::Ok )
{
switch( idxMenu )
{
case 0: openSubMenu( idxMenu, Screen::Menu1, &memory.d.sGroupA, -1, 4 ); break; // Rele Grupo A
case 1: openSubMenu( idxMenu, Screen::Menu2, &memory.d.sGroupB, -1, 3 ); break; // Rele Grupo B
case 2: openSubMenu( idxMenu, Screen::Flag, &memory.d.sMsg, 0, 1 ); break; // Rele Msg Estado
case 3: openSubMenu( idxMenu, Screen::Number, &memory.d.tMsgSleep, 0, 32767 ); break; // Rele Msg T. reposo
case 4: openSubMenu( idxMenu, Screen::Number, &memory.d.tMsgWork, 0, 32767 ); break; // Rele Msg T. func.
case 5: openSubMenu( idxMenu, Screen::Flag, &memory.d.sAux, 0, 1 ); break; // Rele Aux
case 6: openSubMenu( idxMenu, Screen::Number, &memory.d.tPedalWork, 0, 32767 ); break; // Rele Pedal T. apag
case 7: openSubMenu( idxMenu, Screen::Number, &memory.d.tPedalMsgWork, 0, 32767 ); break; // Rele Pedal T. apag
case 8: writeConfiguration(); exitMenu = true; break; // Guarda y sale
case 9: readConfiguration(); exitMenu = true; break; // Cancela los cambios y sale
case 10: exitMenu = true; break; // Sale, pero mantiene los cambios temporalmente
}
forcePrint = true;
}
if( !exitMenu && (forcePrint || btnPressed != Button::Unknown) )
{
forcePrint = false;
static const byte endFor1 = (iMENU+rowsLCD-1)/rowsLCD;
int graphMenu = 0;
for( int i=1 ; i<=endFor1 ; i++ )
{
if( idxMenu < i*rowsLCD )
{
graphMenu = (i-1) * rowsLCD;
break;
}
}
byte endFor2 = graphMenu+rowsLCD;
for( int i=graphMenu, j=0; i< endFor2 ; i++, j++ )
{
lcd.setCursor(1, j);
lcd.print( (i<iMENU) ? txMENU[i] : " " );
}
for( int i=0 ; i<rowsLCD ; i++ )
{
lcd.setCursor(0, i);
lcd.print(" ");
}
lcd.setCursor(0, idxMenu % rowsLCD );
lcd.write(icoFLECHA);
}
}
lcd.clear();
}
/**
* MUESTRA EL SUBMENU EN EL LCD.
*
* @param menuID ID del menu principal para usarlo como titulo del submenu
* @param screen 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 menuID, Screen screen, int *value, int minValue, int maxValue )
{
boolean exitSubMenu = false;
boolean forcePrint = true;
Button btnPressed = Button::Unknown;
lcd.clear();
while( !exitSubMenu )
{
btnPressed = readButtons();
if( btnPressed == Button::Ok )
{
exitSubMenu = true;
}
else if( btnPressed == Button::Left && (*value)-1 >= minValue )
{
(*value)--;
}
else if( btnPressed == Button::Right && (*value)+1 <= maxValue )
{
(*value)++;
}
if( !exitSubMenu && (forcePrint || btnPressed != Button::Unknown) )
{
forcePrint = false;
lcd.setCursor(0,0);
lcd.print(txMENU[menuID]);
lcd.setCursor(0,1);
lcd.print("<");
lcd.setCursor(colsLCD-1,1);
lcd.print(">");
if( screen == Screen::Menu1 )
{
lcd.setCursor(1,1);
lcd.print(*value > minValue ? txRELE_A[*value] : "OFF " );
}
else if( screen == Screen::Menu2 )
{
lcd.setCursor(1,1);
lcd.print(*value > minValue ? txRELE_B[*value] : "OFF " );
}
else if( screen == Screen::Flag )
{
lcd.setCursor(colsLCD/2-1, 1);
lcd.print(*value == 0 ? "OFF" : "ON ");
}
else if( screen == Screen::Number )
{
lcd.setCursor(colsLCD/2-1, 1);
lcd.print(*value);
lcd.print(" seg ");
}
}
}
lcd.clear();
}
/**
* LEE (Y CONFIGURA LA PRIMERA VEZ) LA MEMORIA EEPROM CON LA CONFIGURACION DE USUARIO
*/
void readConfiguration()
{
for( int i=0 ; i < sizeof(memory.d) ; i++ )
memory.b[i] = EEPROM.read(i);
if( !memory.d.initialized )
{
#ifdef DEBUG
Serial.println("Memoria EEPROM reseteada");
#endif
memory.d.initialized = true;
memory.d.sGroupA = -1;
memory.d.sGroupB = -1;
memory.d.sMsg = false;
memory.d.tMsgSleep = 0;
memory.d.tMsgWork = 0;
memory.d.sAux = false;
memory.d.tPedalWork = 0;
memory.d.tPedalMsgWork = 0;
}
}
/**
* ESCRIBE LA MEMORIA EEPROM CON AL CONFIGURACION DE USUARIO
*/
void writeConfiguration()
{
for( int i=0 ; i<sizeof(memory.d) ; i++ )
EEPROM.write( i, memory.b[i] );
}
/**
* LEE LOS DISTINTOS BOTONES DISPONIBLES Y DEVUELVE EL QUE HAYA SIDO PULSADO
*/
Button readButtons()
{
static unsigned long tNow = 0;
static unsigned long tLastbtnPressed = 0;
const unsigned long tDebounceWait = 1000; //Tiempo de espera entre pulsaciones, pulsaciones antes de tiempo se ignoran.
Button btnPressed = Button::Unknown;
tNow = millis();
if( digitalRead(pBTN_OK) ){
btnPressed = Button::Ok;
}
else if( digitalRead(pBTN_LEFT) ){
btnPressed = Button::Left;
}
else if( digitalRead(pBTN_RIGHT) ){
btnPressed = Button::Right;
}
else if( digitalRead(pBTN_PEDAL) ){
btnPressed = Button::Pedal;
}
if( btnPressed != Button::Unknown )
{
if( tNow - tLastbtnPressed < tDebounceWait )
btnPressed = Button::Unknown;
else
{
tLastbtnPressed = tNow;
while( digitalRead(pBTN_LEFT) || digitalRead(pBTN_RIGHT) || digitalRead(pBTN_OK) || digitalRead(pBTN_PEDAL) );
delay(100);
#ifdef DEBUG
static unsigned long counter = 0;
Serial.print("Pulsacion numero ");
Serial.print(++counter);
Serial.print(" = ");
Serial.print(btnPressed == Button::Ok ? "OK" : "");
Serial.print(btnPressed == Button::Left ? "IZQUIERDA" : "");
Serial.print(btnPressed == Button::Right ? "DERECHA" : "");
Serial.print(btnPressed == Button::Pedal ? "PEDAL" : "");
Serial.print(" (Tiempo ");
Serial.print(millis()/1000);
Serial.println(")");
#endif
}
}
return btnPressed;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment