Last active
January 8, 2018 01:18
-
-
Save giltesa/839f8ac6fb21d2cfd21e to your computer and use it in GitHub Desktop.
Arduino - Control Emisoras.c
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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