/* V1.0 Transceptor para 70 MHZ "7 metros" Diseñado y programado por Mermelado en el 2020, en mis días de confinamiento obligado por el gobierno socialcomunista. Se basa en el chip SI5351 y puede copiarse tal cual, o servir de base para otros proyectos. Que lo disfrutéis :) */ #include "si5351.h" #include #include #include const int int0 = 2; // Pin A2 para interrupción 0 "Uno, Nano, etc." (VA A CLK ROTARY ENCODER) const int int1 = 3; // Pin A3 para interrupción 1 "Uno, Nano, etc." (VA A DT ROTARY ENCODER) const byte pulsador = 4; // pin para el pulsador del rotary encoder const byte TX_RX = 5; // pin para la conmutación TX/RX (entrada) const byte parar = 6; // pin para la parada del transceptor const byte TX_RX_out = 7; // pin para la conmutación TX/RX (salida) const byte scan = 8; // pin para la detección de nivel bajo, que activará la función Scan() const int scanpreciso = A0; // pin para la determinación del máximo nivel de señal y así precisar la frecuencia correcta const bool TX_RXmodo = 1; // Podemos decidir si se activa el TX con flanco ascendente o descendente, útil según el micrófono que usemos. 0 para activar por ascendente "pulsador abierto" y 1 para descendente "pulsador cerrado" const int32_t cal_factor_TX = 360000; // FACTOR CALIBRACIÓN EN TX, NÚMERO NEGATIVO AUMENTA FRECUENCIA, MAYOR NÚMERO POSITIVO, DISMINUYE FRECUENCIA const int32_t cal_factor_RX = 10; // FACTOR CALIBRACIÓN EN RX, NÚMERO NEGATIVO AUMENTA FRECUENCIA, MAYOR NÚMERO POSITIVO, DISMINUYE FRECUENCIA const uint32_t target_oscTX = 5120000000ULL; // Frecuencia del oscilador fijo en transmisión const uint32_t target_oscRX = 1070000000ULL; // Frecuencia intermedia, se usa en recepción y se resta a la frecuencia fundamental const int32_t frecuenciamin = 65000; // Determina la frecuencia inferior admisible const int32_t frecuenciamax = 75000; // Determina la frecuencia superior admisible const int pasos[] = {1, 5, 10, 25, 50, 100, 250, 500, 1000}; // Array con los pasos de frecuencia const byte reescan = 5; // Tiempo en segundos para el reescaneado despues de detenerse y si no hay interacción humana previa. NOTA: Valor "0" significa que no habrá reescaneado const int tiempomem = 200; // Velocidad en milisegundos del escaneado de memorias const byte tiempoparada = 3; //Establece el tiempo de parada del equipo en segundos, si no lo cancelamos etretanto, en caso de cancelarlo, también establece el tiempo del mensaje de cancelación, antes de volver al loop() const bool superar = 0; // En modo frecuencias, permite superar ligermanete los márgenes superior e inferior establecidos, al escanear una estación que se encuentra ligeramente fuera de máregenes. 0= No, 1= Sí int offset = 0; // Se usa para programar frecuencias de emisión superiores o inferiores a la de recepción, útil para futuros repetidores byte cualpaso = 5; // determina la posición del paso en el array, con cinco, el paso inicial es de 100khz int32_t frecuencia = 70000; //Frecuencia inicial uint32_t frecuenciaTXcalc; // Contendrá el resultado de la ecuación necesaria para establecer la frecuencia de oscilación del SI5351 en transmisión uint64_t frecuenciaRXcalc; // Contendrá el resultado de la ecuación necesaria para establecer la frecuencia de oscilación del SI5351 en recepción volatile int encoder0Pos = 0; // Determina el valor que devuelven las ISR. 1 para incrementar, 2 para decrementar. NOTA: 0 se establece por defecto y en la función en uso, despues de ejecutar la orden del ISR unsigned long tiempo = 0; // Para determinar el tiempo transcurrido desde que se pulsó el pulsador del rotary encoder, hasta que se liberó, así como cualquier otro requerimiento de temporización del sketch unsigned long tiempolcd; // Necesario para controlar el tiempo que permanecerá encendido el lcd, sin actividad, no es posible utilizar la variable anterior, ya que pueden coincidir ambos conteos en el tiempo byte segundoslcd = 30; // Establece el tiempo en segundos, antes de que se apague el lcd, mientras no se esté en TX o se manipule algún mando. Nota: Poner a 0 para inhibir auto-apagado, inactivo en modo menú bool FrecMem = 0; // Corresponde al modo de uso de las frecuencias. 0=frecuencias y 1= memorias const byte cantidadmem = 20; // Determina el número de memorias. Caben más, pero a mí con 20 me parece suficiente. int32_t presets[cantidadmem]; //Contendrá las memorias almacenadas en la EEPROM int cualpreset = 0; // Determina el preset seleccionado. OJO, la memoria 1 es el preset 0; bool salir = 0; // Se usa para saber que venimos del último menú y así poder salir de todos los menús bool recuerda; // Lo necesitamos para que sólo se ejecute una vez ciertas labores de la función Frecuencia, como por ejemplo la siguiente variable int32_t frecuencianterior; // Recordará la frecuencia anterior al cambio de modo a memorias // Caracteres personalizados byte CaracterVolver[] = { B00100, B01110, B11111, B00100, B00100, B00101, B00010, B00000 }; byte CaracterSubrrayar[] = { B11111, B00000, B00000, B00000, B00000, B00000, B00000, B00000 }; byte aconacento[] = { B00010, B00100, B01110, B00001, B01111, B10001, B01111, B00000 }; byte iconacento[] = { B00010, B00100, B00000, B01100, B00100, B00100, B01110, B00000 }; byte interrogacion[] = { B00100, B00000, B00100, B01000, B10000, B10001, B01110, B00000 }; // Instancia Al SI5351 "oscilador" Si5351 si5351; // Instancia LCD LiquidCrystal_I2C lcd(0x27, 16, 2); void setup() { attachInterrupt(digitalPinToInterrupt(int0), ISR_pinA, CHANGE); // interrupción 0, Corresponde al pin 2 attachInterrupt(digitalPinToInterrupt(int1), ISR_pinB, CHANGE); // interrupción 1, Corresponde al pin 3 pinMode(pulsador, INPUT_PULLUP); // Pulsador del rotary encoder - Equivale a poner una resistencia de pullup pinMode(TX_RX, INPUT_PULLUP); // Conmutador TX/RX - Equivale a poner una resistencia de pullup pinMode(TX_RX_out , OUTPUT); // Salida para controlar la conmutación Tx-Rx pinMode(parar, OUTPUT); // Se irá a la función parar, para iniciar las tareas de parado del equipo a estado alto pinMode(scan, INPUT_PULLUP); // Detectará si se pone a nivel bajo para ir a la función Scan, Siempre que estemos en modo scan si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0); lcd.init(); lcd.backlight(); si5351.set_correction(cal_factor_RX, SI5351_PLL_INPUT_XO); // Aplicamos factor de corrección frecuenciaTXcalc = ((frecuencia+offset)*100000)-target_oscTX; // Cálculo de la frecuencia real del TX // En recepción es imposible ponerlo todo en una sola ecuación, arduino se hace la picha un lío y no tengo idea de la razón, quien lo sepa, que me lo explique frecuenciaRXcalc = frecuencia; frecuenciaRXcalc = frecuenciaRXcalc * 100000 - target_oscRX; si5351.set_ms_source(SI5351_CLK0, SI5351_PLLB); si5351.set_ms_source(SI5351_CLK1, SI5351_PLLB); si5351.set_freq(frecuenciaRXcalc, SI5351_CLK0); si5351.set_freq(frecuenciaTXcalc, SI5351_CLK1); si5351.output_enable(SI5351_CLK0, 1); // Habilita el oscilador de RX si5351.output_enable(SI5351_CLK1, 0); // Deshabilita el oscilador de TX si5351.update_status(); lcd.createChar(0, CaracterVolver); lcd.createChar(1, CaracterSubrrayar); lcd.createChar(2, aconacento); lcd.createChar(3, iconacento); lcd.createChar(4, interrogacion); // EEPROM // Lo primero que haremos es asignar a las direcciones 1022 y 1023, la comprobación de que ha sido inicializada, // para eso leeremos sus respectivos valores que deberan coincidir con 6 para la 1022 y 9 para la 1023, // son números al azar :). Si no coinciden se inicializa, es decir, escribimos esos valores en ellas y ponemos todo lo demás, como se detallará a continuación. // Esto sólo ocurre la primera vez que ejecutamos el código o si cambiamos la eeprom, o la modificamos con otro software byte test1 = EEPROM.read(1022); byte test2 = EEPROM.read(1023); // Forzar inicialización de memoria "hard reset". Se logra manteniendo pulasado el botón del rotary encoder, es tan simple como cambiar el valor de una de las variables del test if (digitalRead(pulsador) == LOW) {test1 = 0;} // Se inicializa de ser necesario, los test "direcciones 1022 y 1023" a 6 y 9, la 0 guarda cantidad de memorias almacenadas y por tanto se inicializa a 0, la uno guarda modo y se inicializa a 0 "Modo frecuencia", // la 2 recuerda el último preset seleccionado y se inicializa a 0, la 3 y 4 "porque es un int", guarda el offset en modo frecuencia y se inicializan a 0, la dirección 1013, se destina a recordar los pasos // de frecuencia y se inicializará con el valor 5, que corresponde con pasos de 100khz, las direcciones 1014, 1015, 1016 y 1017, ya que es un int32_t y ocupa 4 bytes, // contendrán la frecuencia anterior, necesaria para volver del modo memorias y se inicializará con la misma frecuencia que la inicial , lo mismo ocurre con las direcciones 1018, 1019, 1020 y 1021, // que contendrán la frecuencia inicial y el resto a 255 y podrán usarse para el almacenamiento de memorias if(test1 != 6 || test2 != 9) {EEPROM.update(0, 0);EEPROM.update(1, 0); EEPROM.update(2, 0); EEPROM.update(3, 0); EEPROM.update(4, 0); EEPROM.update(1013, 5); EEPROM.put(1014, frecuencia); EEPROM.put(1018, frecuencia); EEPROM.update(1022, 6); EEPROM.update(1023, 9); for (int i = 5; i < 1012; i++) {EEPROM.update(i, 255);} lcd.clear(); display(15); while(digitalRead(pulsador) == LOW); display(255);} // Fin del if // EMPIEZA EL PROCESO DE CARGAR ESTADOS DESDE LA EEPROM //Leemos las memorias y las asignamos a un array int32_t valor; byte cuantasmem = EEPROM.read(0); if (cuantasmem > 0) { // Solamente se ejecuta si hay alguna memoria // Las memorias estarán a partir de la dirección 5 y separadas 6 bytes entre si, 4 bytes para frecuencia y 2 para el offset byte hastadonde = (cuantasmem * 6) -1; // Esta sencilla operación determina la siguiente posición de memoria a escribir en el array. ejemplo: hay dos memorias, luego cuantasmem vale 2, (2*6)-1=11, por tanto la siguiente dirección a escribir es la 11 byte incremento = 0; // Leemos sus valores y los almacenamos en el array presets for (int i = 5; i <= hastadonde; i=i+6) { EEPROM.get(i, valor); presets[incremento] = valor; incremento++; } // Fin del for } // Fin del if int oft; FrecMem = EEPROM.read(1); // Recuperamos el modo if (cuantasmem > 0) {cualpreset = EEPROM.read(2);} // Recuperamos el último preset que se usó, esto sólo es necesario si hay alguna memoria almacenada offset = EEPROM.get(3, oft); // Recuperamos el offset en modo frecuencias frecuencia = EEPROM.get(1018, valor); // Recuperamos la última frecuencia frecuencianterior = EEPROM.get(1014, valor); // Recuperamos la frecuencia anterior cualpaso = EEPROM.read(1013); // Recuperamos el índice array pasos que se guardó // Presentación tiempo = millis(); const String b[] = {"e", "l", "e", "c", "t", "r", "o", "n", "i", "c", "s"}; const String a = "Mermelado "; byte c = 0; for(byte i=15; i>=3; i--) { lcd.setCursor(i,0); lcd.print(a); if(c<11) {lcd.setCursor(c+2,1); lcd.print(b[c]);} c++; while(millis() < tiempo + 100 && encoder0Pos==0 && digitalRead(pulsador) == HIGH && digitalRead(TX_RX) == TX_RXmodo); tiempo = millis(); } while(millis() < tiempo + 3000 && encoder0Pos==0 && digitalRead(pulsador) == HIGH && digitalRead(TX_RX) == TX_RXmodo); tiempo = millis(); display(255); Frecuencia(1); tiempolcd = millis(); // inicializamos el contador de apagado del lcd } // Fin del setup bool estado = 0; // recuerda si estamos en TX o RX byte control = 0; // necesario para determinar la función del pulsador void loop() { cli(); byte isr = encoder0Pos; sei(); tiempo = millis(); // Auto apagado del display if(millis() >= tiempolcd+(segundoslcd*1000) && isr == 0 && digitalRead(pulsador) == HIGH && estado == 0 && segundoslcd >0){lcd.noBacklight();} else if(isr != 0 || digitalRead(pulsador) == LOW || estado == 1) {lcd.backlight(); tiempolcd = millis();} // Si pulsamos brevemente el pulsador, pasamos al siguiente paso de frecuencia, si lo pulsamos entre 500 y 1499 milisegundos, nos vamos al menú Offset y si lo apretamos por 1500 milisegundos o más, nos vamos al menú principal // Mientras mantenemos pulsado, contamos el tiempo que transcurre y otorgamos un valor a "control" en función de ese tiempo. También mostramos en el lcd lo que ocurrirá si lo soltamos // Nota: Si está el PTT pulsado, el while no se ejecutará. Esto sirve para poder parar el equipo sin correr un paso while(digitalRead(pulsador) == LOW && estado == 0){lcd.backlight(); tiempolcd = millis(); isr =0; encoder0Pos=0; int t; if(FrecMem == 1) {t = 500;} else {t = 1500;} // En modo memorias se produce una excepción y es que se ignora el menú de offset y se acorta el tiempo del menú principal de 1500 a 500 milisegundos if(millis() == tiempo+499){lcd.clear();} // Un milisegundo antes del primer mensaje en pantalla, la borramos else if(millis() >= tiempo+t){control = 2; display(2);} // Iremos al menú principal else if(millis() >= tiempo+500 && FrecMem == 0) {control = 3; display(11);} // Iremos al menú de Offset, siempre y cuando estemos en frecuencias "no procede este menú en modo memorias" else {control = 1;} // Se entenderá que se pulsó brevemente y aumentará un paso } // Fin del while if(control == 2) {menu(); control = 0; display(1);} // Como se determinó en el while, nos vamos al menú principal else if(control == 3) {Offset(); control = 0; display(255); display(1);} // Como se determinó en el while, nos vamos al menú de Offset if(control == 1){control = 0; cualpaso++; if (cualpaso >8) {cualpaso = 0;} display(1); // Como se determinó en el while, sumamos un paso de frecuencia "step" } // Fin del pulsador // Si se pulsa el PTT y simultaneamente el pulsador del rotay encoder, nos vamos a Parar(). Nota: Es recomendable pulsar el PTT antes que el pulsador del rotary, porque de lo contrario se avanzará un paso y quedará memorizado así. if(digitalRead(pulsador) == LOW && estado != 0){Parar(); display(1);} // Cambio frecuencia if(Comprobador(isr)) {tiempo = millis(); Frecuencia(1); isr = 0;} // Transmisión/Recepción if(TX_RXmodo == 1 && digitalRead(TX_RX) == LOW && estado == 0) {tx_rx();} // Transmite en estado bajo, en mi caso al pulsar else if(TX_RXmodo == 0 && digitalRead(TX_RX) == HIGH && estado == 0) {tx_rx();} // Transmite en estado alto, en mi caso al no pulsar if(TX_RXmodo == 1 && digitalRead(TX_RX) == HIGH && estado == 1) {tx_rx();} // Recibe en estado bajo, en mi caso al pulsar else if(TX_RXmodo == 0 && digitalRead(TX_RX) == LOW && estado == 1) {tx_rx();} // Recibe en estado alto, en mi caso al no pulsar } // Fin del loop void Scan() { byte c = 0; // Se usa para restar uno la primera vez, porque al llamar a Frecuencia, se incrementa o decrementa 1 y no queremos que eso ocurra la primera vez, por la misma razón, al llegar a la última mem, la variable se establece en -1 en lugar de 0. También otros usos menores. byte z = 0; lcd.clear(); // Si tenemos el squelsh demasiado abierto, o sea, que se escucha soplido por el altavoz, nos avisará para bajarlo. También podremos salir del modo scan pulsando PTT while(digitalRead(scan) == LOW && digitalRead(TX_RX) == TX_RXmodo) {encoder0Pos=0; display(36);} lcd.clear(); display(37); // Saldremos del modo Scan, cuando se pulse el PTT while(digitalRead(TX_RX) == TX_RXmodo){ // El truco de que en el while esté la condición de que encoder0Pos sea diferente a 1 ó 2, sirve para cambiar el sentido de la búsqueda en pleno escaneo // Si hemos rotado en sentido horario if(encoder0Pos==1) { while(digitalRead(scan) == HIGH && encoder0Pos!=2 && digitalRead(TX_RX) == TX_RXmodo) { display(38); // Imprime el signo + if(digitalRead(pulsador) == LOW && !FrecMem) {control = 0; Pasos();} // Si es modo frecuencia y ascendemos "rotary encoder en sentido horario" if(!FrecMem) { if(frecuencia+pasos[cualpaso] > frecuenciamax) {frecuencia = frecuenciamin;} // Sencilla Manera de hacer que al llegar al final, vuelva a empezar en búsqueda progresiva frecuencia=frecuencia+pasos[cualpaso]; Frecuencia(1); c = 1; } // Fin de si es modo frecuencia // Si es modo memorias y ascendemos "rotary encoder en sentido horario" else { if(c == 0){cualpreset--; c = 3;} // Frecuencia() nos subirá un paso cada vez que la llamamos, por eso la primera vez que la llamamamos, bajamos un paso, porque la primera vez debe mostrar sólo la memoria actual encoder0Pos = 1; if(cualpreset+1 >= EEPROM.read(0)) {cualpreset = -1;} Frecuencia(1); tiempo = millis(); while(digitalRead(scan) == HIGH && millis() < tiempo+tiempomem && encoder0Pos != 2 && digitalRead(TX_RX) == TX_RXmodo); tiempo = millis(); } // Fin de si es modo memorias } // Fin del while } // Fin de si ascendemos "if(encoder0Pos==1)" // Si hemos rotado en sentido antihorario else if(encoder0Pos==2) {while(digitalRead(scan) == HIGH && encoder0Pos!=1 && digitalRead(TX_RX) == TX_RXmodo) { display(39); // Imprime el signo - if(digitalRead(pulsador) == LOW && !FrecMem) {control = 0; Pasos();} if(!FrecMem) { if(frecuencia-pasos[cualpaso] < frecuenciamin) {frecuencia = frecuenciamax;} // Sencilla Manera de hacer que al llegar al final, vuelva a empezar en búsqueda regresiva frecuencia=frecuencia-pasos[cualpaso]; Frecuencia(1); c = 2; } // Fin de si es modo memorias // Si es modo memorias y descendemos "rotary encoder en sentido antihorario" else { if(c == 0){cualpreset++; c = 4;} // Frecuencia() nos bajará un paso cada vez que la llamamos, por eso la primera vez que la llamamamos, subimos un paso, porque la primera vez debe mostrar sólo la memoria actual encoder0Pos = 2; if(cualpreset <= 0) {cualpreset = EEPROM.read(0);} Frecuencia(1); tiempo = millis(); while(digitalRead(scan) == HIGH && millis() < tiempo+tiempomem && encoder0Pos != 1 && digitalRead(TX_RX) == TX_RXmodo); tiempo = millis(); } // Fin de si es modo memorias } // Fin del while } // Fin de si descendemos "if(encoder0Pos == 2)" // Llegados a este punto, si se encontró una señal en modo memoria, no hay más que hacer, excepto el tiempo de espera sin actividad o la actividad manual, pero si fue en modo frecuencias, el problema es que se detuvo al superar el umbral del squelsh, // lo que casi asegura que no llegó a la frecuencia objetivo, para ello lo que haremos, es valernos de una referencia analógica, mientras vamos aumentando o disminuyendo la frecuencia "según el caso" los KHZ de uno en uno, hasta encontrar el nivel de señal máximo. int32_t frecref = 0; int maximo = 0; // Mientras esté superado el umbral del squelsh y estemos explorando en uno u otro sentido. NOTA: esto se ignora en modo memorias, porque se estableció c a 3 ó 4, según sentido de la búsqueda while(digitalRead(scan) == LOW && (c == 1 || c == 2)) { if (analogRead(scanpreciso) > maximo){maximo = analogRead(scanpreciso); frecref = frecuencia;} // Se almacena en la variabe "frecref" la frecuencia que coincidió con el umbral de señal más alto if(c == 1) {frecuencia++;} // Se incrementa la frecuencia mientras el squelsh esté superado, mientras la instrucción anterior determina el punto álgido else {frecuencia--;} // Se decrementa la frecuencia mientras el squelsh esté superado, mientras la instrucción anterior determina el punto álgido Frecuencia(0); // Se ejecuta el incremento o decremento // Justo antes de salir del while, actualizamos tiempo, que después usaremos tiempo = millis(); } // Fin del while // Si todo fue bien y frecref almacenó algún valor, Se lo aplicamos a la variable frecuencia y actualizamos frecuencia. También cambiamos el valor de c. Esto sirve para que encoder0Pos, sólo se ejecute posteriormente si este if se cumplió, de lo contrario, el comportamiento sería errárico // En modo memorias c=3 ó c=4, por tanto esto también es ignorado if(frecref > 0){if(c == 1) {c = 10;} else if(c == 2) {c = 11;} // Cuidamos de no superar los límites y si se superaron, se sintonizará en el límite y así almenos sabremos que hay una emisora ahí, aunque llegue algo desplazada. NOTA: puede igorarse esta restricción, colocando la constante "superar" en 1 if(superar) {frecuencia = frecref;} // Fin del if de superar // Si no se permite superar else { if(frecref > frecuenciamax) {frecuencia = frecuenciamax;} else if(frecref < frecuenciamin) {frecuencia = frecuenciamin;} else {frecuencia = frecref;} } // Fin del else de si no se permite superar frecref = 0; Frecuencia(1); } // Fin del if de frecref // Qué sucede si no hacemos nada después de encontrar una estación? // Pondremos una constante en el principio del sketch, que determinará en segundos el tiempo de espera para seguir escaneando a menos que haya interacción humana "encoder0Pos > 0" NOTA: constante a 0= no hay reescaneado if(millis() > tiempo + reescan*1000 && reescan > 0 || encoder0Pos > 0){ // Aquí ocurre lo mismo que en el if de frecref "dos if más arriba", sólo que esta vez es en modo manual, es decir, si rotamos en un sentido o en otro, antes de que acabe el tiempo preestablecido, sólo en modo frecuencias if(encoder0Pos==1 && !FrecMem){c = 10;} else if(encoder0Pos==2 && !FrecMem){c = 11;} // Un error común en muchos receptores, es que al reactivarse el escaneado y sobre todo con señales potentes, se detiene una y otra vez antes de pasar al siguiente tramo vacío de espectro. la línea siguiente // soluciona este problema, pues situa la frecuencia en el siguiente KHZ posterior o anterior "según sentido de la búsqueda" a la última frecuencia que saltó el squelsh. De nuevo, sólo en modo frecuencia y siempre respetando los límites de la banda if(!FrecMem) {while(digitalRead(scan) == LOW) {if(c == 10) {frecuencia++;} else {frecuencia--;} Frecuencia(0);}} // Con las memorias ocurre algo parecido y es que hay un retardo en el squelsh y a menos que se escanee a paso de tortuga, fallará. Una solución es la siguiente y es parecida a la anterior, sólo que en lugar de avanzar 1 KHZ, avanzamos o retrocedemos una memoria else{ // Caso ascendente. Nota: Hay que tener previsto la posibilidad de estar en la última memorias, para eso es la comprobación if(encoder0Pos==1) {if(cualpreset+1 >= EEPROM.read(0)) {cualpreset = -1;} encoder0Pos=1; Frecuencia(1); encoder0Pos=1; c= 3;} // Caso descendente. Nota: Hay que tener previsto la posibilidad de estar en la primera memorias, para eso es la comprobación else if(encoder0Pos==2) {if(cualpreset <= 0) {cualpreset = EEPROM.read(0);} encoder0Pos=2; Frecuencia(1); encoder0Pos=2; c= 4;} } // Fin del else de si es modo memorias // Esto reactiva la búsqueda en el sentido que estaba, o a uno distinto si rotamos el rotary encoder mientras se espera el tiempo establecido. El cambio anterior del valor de c, es debido a que sólo debe ejecutarse el encoder0Pos, si transcurrió el tiempo de espera if(c > 9) {encoder0Pos = c -9; c = 0;} // Lo mismo en modo memorias if(c==3){encoder0Pos = 1;} if(c==4){encoder0Pos = 2;} } // Fin del if de espera de evento temporal o manual } // Fin del while que sale al pulsar el PTT encoder0Pos = 0; tiempolcd = millis(); // No queremos que al salir estemos en penumbra :) display(255); } // Fin de la función Scan // Es bueno poder modificar los pasos en pleno escaneo. NOTA: Sólo la usa la función Scan void Pasos(){ if(millis() > tiempo+500){tiempo = millis(); control = 0;} else {control = 1;} // No queremos rebotes if(control == 0 && !Comprobador(encoder0Pos)) {cualpaso++; if (cualpaso >8) {cualpaso = 0;} } // Fin del if de si control es 0 } // Fin de la función Pasos byte Comprobador(byte isr) { // Cada vez que isr vale 1 ó 2, hay que llamar a la función Frecuencia(1). // El problema es que los pulsadores chinos son imperfectos y a veces al pulsarlos, // unos instantes antes, cambian su estado de conteo, lo que puede producir un falso incremento, decremento de frecuencia. // Eso obliga a darle unos instantes a la instrucción antes de cumplirse, para verificar que no se ha pulsado el botón instantes después del falso positivo // En mi caso fue suficiente con 30 milisegundos. Ese tiempo puede requerir reajustes según el rotary encoder usado, icluso puede que no sea necesario, o que no se logre una solución total. if(digitalRead(pulsador) == LOW){isr = 0; return false;} // Primero nos aseguramos de que no ocurra lo contraio, o sea, de que isr esté siempre a 0, mientras el pulsador esté apretado if(isr > 0){ while(millis() < tiempo+30){ // Durante el tiempo indicado vamos comprobando que no se haya pulsado el botón y de hacerse, ponemos isr a 0 y salimos del bucle if(digitalRead(pulsador) == LOW){isr = 0; break;} } if(isr > 0 && digitalRead(pulsador) == HIGH){return true;} // Si no hay percances, devolvemos TRUE } return false; } // Fin del comprobador isr void Frecuencia(bool x) { cli(); byte isr = encoder0Pos; sei(); // Hay un pequeño problema con el rotary encoder, si no se rota, no se actualizará la frecuencia al cambiar de modo, por ende, en modo frecuencia es imposible recordarla, si no se almacena antes de cambiar a modo memorias if(FrecMem && recuerda){frecuencianterior = frecuencia; frecuencia = presets[cualpreset]; recuerda = 0;} // Se ejecuta una vez, cada vez que entramos en modo memorias else if(!FrecMem && recuerda && EEPROM.read(0) > 0) {frecuencia = frecuencianterior; recuerda = 0;} // Se ejecuta una vez, cada vez que entramos en modo frecuencias. NOTA: Si no hay memorias almacenadas, no se ejecuta // Comprobamos una vez más que todo está en orden y procedemos si es frecuencia if(Comprobador(isr) && !FrecMem) { // Aumentamos frecuencia o bajamos, según sentido de rotación, pero respetamos los límites inferior y superior if(isr==1 && frecuencia+pasos[cualpaso] <= frecuenciamax) {frecuencia=frecuencia+pasos[cualpaso];} else if(isr==2 && frecuencia-pasos[cualpaso] >= frecuenciamin) {frecuencia=frecuencia-pasos[cualpaso];} isr, encoder0Pos = 0; } // Fin de la comprobación para frecuencias // Comprobamos una vez más que todo está en orden y procedemos si es presets else if(FrecMem) { if(isr==1 && cualpreset+1 < EEPROM.read(0)) {cualpreset++;} else if(isr==2 && cualpreset > 0) {cualpreset--;} frecuencia = presets[cualpreset]; isr, encoder0Pos = 0; } // Fin de la comprobación para memorias frecuenciaTXcalc = ((frecuencia+offset)*100000)-target_oscTX; // Cálculo de la frecuencia real del TX frecuenciaRXcalc = frecuencia; frecuenciaRXcalc = frecuenciaRXcalc * 100000 - target_oscRX; si5351.set_freq(frecuenciaTXcalc, SI5351_CLK1); si5351.set_freq(frecuenciaRXcalc, SI5351_CLK0); if(x) {display(1);} // Solo se actualiza el display si x es TRUE } // Fin función Frecuencia void tx_rx() { tiempo = millis(); if(digitalRead(TX_RX) == TX_RXmodo) {estado = 0; Frecuencia(1); si5351.output_enable(SI5351_CLK1, 0); si5351.output_enable(SI5351_CLK0, 1);si5351.set_correction(cal_factor_RX, SI5351_PLL_INPUT_XO); digitalWrite(TX_RX_out , LOW);} // Activamos RX else if(digitalRead(TX_RX) != TX_RXmodo){ // ¿ Qué ocurre si al transmitir rebasamos los márgenes establecidos al sumar o restar offset? if(frecuencia+offset > frecuenciamax){lcd.clear(); while(millis() < tiempo+2000){display(12); lcd.setCursor(0, 1); display(13);} tiempo = millis(); display(255); display(1); return;} else if(frecuencia+offset < frecuenciamin){lcd.clear(); while(millis() < tiempo+2000){display(12); lcd.setCursor(0, 1); display(14);} tiempo = millis(); display(255); display(1); return;} estado = 1; Frecuencia(1); si5351.output_enable(SI5351_CLK1, 1);si5351.output_enable(SI5351_CLK0, 0);si5351.set_correction(cal_factor_TX, SI5351_PLL_INPUT_XO); digitalWrite(TX_RX_out , HIGH);} // Activamos TX } //Fin de la función tx_rx // Menú principal void menu() { tiempo = millis(); display(2); while(digitalRead(pulsador) == LOW); // Hay que esperar a que se suelte el botón "estado HIGH" o se producirá un bonito rebote y saldrá del menú byte caso = 0; bool primero = 0; encoder0Pos = 0; while(caso != 255) { cli(); byte isr = encoder0Pos; sei(); int velocidad = 250; // Determina la velocidad de parpadeo del cursor // Sólo se cumple si no hay pulsación if(Comprobador(isr)) { if(isr == 1 && caso < 2) {caso++;} // El 2 representa el número de elementos del menú -1, porque empezamos a contar desde 0, modificar si se cambia dicho número de elementos else if(isr == 2 && caso > 0) {caso--;} encoder0Pos = 0; } else{isr =0; encoder0Pos = 0;} //lcd.clear();lcd.print(caso); switch (caso) { case 0: if(digitalRead(pulsador) == LOW) {encoder0Pos = 0; caso = 255; display(255); break;} while(millis() <= tiempo+velocidad && encoder0Pos == 0) {display(3);} // Salimos del menú break; case 1: if(digitalRead(pulsador) == LOW) {encoder0Pos = 0; if(FrecMem == 1) {lcd.clear(); while(millis() <= tiempo+600 && encoder0Pos == 0); FrecMem = 0; display(33); display(35); while(millis() <= tiempo+1500 && encoder0Pos == 0); salir = 1; recuerda = 1; Frecuencia(1); display(255); caso = 255; break;} // Cambiamos a modo frecuencia else {menu_frec_mem(); display(255); if(salir) {salir = 0; Frecuencia(1); tiempolcd = millis(); return;} display(2);} } // Corresponde a ir al menú frec/mem while(millis() <= tiempo+velocidad && encoder0Pos == 0) {display(4);} // Corresponde a Frec/Mem o a pasar a modo frecuencias si estamos en modo memorias break; case 2: if(digitalRead(pulsador) == LOW) {encoder0Pos = 0; display(255); Scan(); caso = 255; break;} while(millis() <= tiempo+velocidad && encoder0Pos == 0) {display(5);} // Corresponde a Scan break; } // Fin del swith tiempo = millis(); while(millis() <= tiempo+velocidad && digitalRead(pulsador) == HIGH && encoder0Pos == 0) {lcd.setCursor(0, 1); display(10);} // Borramos segunda fila tiempo = millis(); } // Fin del while encoder0Pos = 0; lcd.clear(); while(digitalRead(pulsador) == LOW); // Hay que esperar a que se suelte el botóm "estado HIGH o se producirá un bonito rebote y alterará valores de otro menú o de contador de pasos de frecuencia en caso de salir tiempolcd = millis(); } // Fin del menú principal // menú Memoria/Frecuencia Memorizar void menu_frec_mem() { display(255); while(digitalRead(pulsador) == LOW); // Hay que esperar a que se suelte el botóm "estado HIGH o se producirá un bonito rebote y saldrá del menú display(6); byte caso = 0; bool primero = 0; encoder0Pos = 0; while(caso != 255) { cli(); byte isr = encoder0Pos; sei(); tiempo = millis(); int velocidad = 250; // Determina la velocidad de parpadeo del cursor // Sólo se cumple si no hay pulsación if(Comprobador(isr)) { if(isr == 1 && caso < 2) {caso++;} // El 3 representa el número de elementos del menú -1, porque empezamos a contar desde 0, modificar si se cambia dicho número de elementos else if(isr == 2 && caso > 0) {caso--;} encoder0Pos = 0; } else{isr =0; encoder0Pos = 0;} switch (caso) { case 0: if(digitalRead(pulsador) == LOW) {caso = 255; break;} while(millis() <= tiempo+velocidad && encoder0Pos == 0) {display(3);} // Corresponde a Salir break; case 1: if(digitalRead(pulsador) == LOW) {lcd.clear(); while(millis() <= tiempo+600 && encoder0Pos == 0); if(FrecMem == 0 && EEPROM.read(0) == 0) {display(32);} // No hay memorias guardadas else if(FrecMem == 0 && EEPROM.read(0) > 0) {FrecMem = 1; display(34); display(35);} // Cambiamos a modo memoria else {FrecMem = 0; display(33); display(35);} // Cambiamos a modo frecuencia while(millis() <= tiempo+1500 && encoder0Pos == 0); lcd.clear(); caso = 255; salir = 1; recuerda = 1; break;} while(millis() <= tiempo+velocidad && encoder0Pos == 0) {display(4);} break; case 2: if(digitalRead(pulsador) == LOW) {menu_memo(); caso = 255; salir = 1; recuerda = 0 ;break;} while(millis() <= tiempo+velocidad && encoder0Pos == 0) {display(9);} // Corresponde a Memo, al ser último menú recursivo, al volver saldrá de todos los menús break; } tiempo = millis(); while(millis() <= tiempo+velocidad && digitalRead(pulsador) == HIGH && encoder0Pos == 0) {lcd.setCursor(0, 1); display(10);} // Borramos segunda fila tiempo = millis(); } // Fin del while encoder0Pos = 0; while(digitalRead(pulsador) == LOW); // Hay que esperar a que se suelte el botóm "estado HIGH o se producirá un bonito rebote y alterará valores de otro menú o de contador de pasos de frecuencia en caso de salir } // Fin del menú Memoria/Frecuencia Memorizar // menú memo void menu_memo() { display(255); while(digitalRead(pulsador) == LOW); // Hay que esperar a que se suelte el botóm "estado HIGH o se producirá un bonito rebote y saldrá del menú int offset_eeprom; int direccioneeprom; volver: // En caso de arrepentirnos antes de grabar, aquí es donde volverá el flujo byte caso = EEPROM.read(0)+1; // definimos caso con la dirección del contenido de la primera memoria vacía y así podemos memorizar la memoria después de la última guardada, sin rotar, aunque también podremos rotar para sobreescribir memorias existentes bool primero = 0; encoder0Pos = 0; while(caso != 255) { cli(); byte isr = encoder0Pos; sei(); tiempo = millis(); // Ponemos los límites inferior y superior que serán desde la primera memoria hasta la inmediatamente siguiente a la última ocupada if(Comprobador(isr)) { if(isr == 1 && caso < EEPROM.read(0)+1 && EEPROM.read(0) < cantidadmem) {caso++; encoder0Pos = 0;} // El +1 es para contar una más de la última y así llegar a la nueva vacía. cantidadmem determina el número máximo de memorias permitidas else if(isr == 2 && caso > 1) {caso--; encoder0Pos = 0; } // Una manera de salir de todos los menús, por si nos arrepentimos. girando a la izquierda desde la memoria uno, sale este menú y si luego giramos en cualquier sentido, volvemos a la memoria 1 else if(isr == 2 && caso == 1) {lcd.clear(); display(20); encoder0Pos = 0; while(encoder0Pos == 0) {if(digitalRead(pulsador) == LOW) {return;}} while(encoder0Pos !=0) {caso=1; encoder0Pos = 0;} lcd.clear(); display(16);} } // Fin del if comprobador else{isr =0; encoder0Pos = 0;} // Mostramos el resultado lcd.setCursor(0, 0); display(16); lcd.print(frecuencia); display(17); lcd.setCursor(0, 1); lcd.print(caso); // Mostramos la frecuencia actual de la memoria o "Está vacía" en caso de ser memoria nueva. Nota: El 1000 es un número cualquiera. simplemente tiene que ser inferior a la frecuencia mínima if(presets[caso-1] < 1000) {display(22);} // Como no está vacía, la mostramos junto al offset si lo hubiere else { if(caso > 9) {display(19);} else {display(18);} // Si la memoria contiene dos dígitos o más, se mostrará "cont." en lugar de "contiene", sencillamente porque no cabe lcd.print(presets[caso-1]); // ¿Qué offset tenemos? EEPROM.get(((caso*6)-1)+4, offset_eeprom); // Si hay offset, hacemos un scroll para mostrarlo if(offset_eeprom != 0) { bool u = 0; byte x; byte y; byte z; int i; // Determina el número de dígitos del offset, útil para el scroll if(offset_eeprom >= 1000) {y = 1;} else if(offset_eeprom >= 100) {y = 2;} else {y = 3;} // Arrays necesarios para mostrar en el scroll String oft[] = {" ", ": ", "t: ", "et: ", "set: ", "fset: ", "ffset: ", "offset: "}; String esp[] = {" ", " ", " "}; // Mientras no se rote o se pulse el encoder rotatório while(encoder0Pos == 0 && digitalRead(pulsador) == HIGH){ tiempo = millis(); if( u == 0) {while(millis() < tiempo + 3000 && encoder0Pos == 0 && digitalRead(pulsador) == HIGH); tiempo = millis();} oft[0] = " "; esp[0] = " "; // Empieza el scroll de izquierda a derecha for(i=0; i<8+y; i++) { while(millis() < tiempo + 170 && encoder0Pos == 0 && u == 0 && digitalRead(pulsador) == HIGH) { // tienen que transcurrir 170 miisegundos antes de que avance el siguiente caracter if(i > 7) {z =7; x = i -8;} else {z = i; x = 0;} lcd.setCursor(0, 1); lcd.print(oft[z]); lcd.print(offset_eeprom); display(21); lcd.print(esp[x]); lcd.print(caso); if (caso < 10) {display(18);} else {display(19);} lcd.print(presets[caso-1]); } // Fin del while temporizador tiempo = millis(); } //Fin del for u = 1; // Cuando está a 1, se ejecuta el segundo scroll while(millis() < tiempo + 3000 && encoder0Pos == 0 && digitalRead(pulsador) == HIGH); // Esperamos 3 segundos antes del segundo scroll tiempo = millis(); // Empieza el scroll de derecha a izquierda for(i=8+y; i>=-1; i--) { while(millis() < tiempo + 170 && encoder0Pos == 0 && u == 1 && digitalRead(pulsador) == HIGH) { // tienen que transcurrir 170 miisegundos antes de que avance el siguiente caracter if(i >7){z=7; x= i -9;} else{z = i; x=0; esp[0] = "";} if(i<0){z = 0; oft[0] = "";} if(x < 1 || x > 2){x = 0;} lcd.setCursor(0, 1); lcd.print(oft[z]); lcd.print(esp[x]); lcd.print(caso); if (caso < 10) {display(18);} else {display(19);} lcd.print(presets[caso-1]); } // Fin del while temporizador tiempo = millis(); } //Fin del for u = 0; // Cuando está a 1, se ejecuta el primer scroll } //Fin del while de mientras no se rote el encoder rotatório } // Fin de si hay offset } // Fin del else if(digitalRead(pulsador) == LOW){ // Hay que comprobar que esa frecuencia no se encuentre ya almacenada en otra memoria. NOTA: Si tiene un offset diferente, no se entenderá como repetida y se permitirá su escritura for (int i=0; i < cantidadmem; i++) { // La comprobación del offset la hago con un segundo if, para no tener que leer la eeprom más que una vez. Por cierto, de momento el offset no se guarda en array y se lee directamente de la eeprom, esto es para no ocupar mucho sketch if(presets[i] == frecuencia) { // Para hallar la dirección correcta se añade uno, ya que el array empieza por 0 y se multiplica por 6 y se resta 1, porque la primera memoria es 5 "1 menos que el factor de multiplicación", //luego se incrementa en pasos de 6 bytes "6 direcciones de memoria" y al final se suma 4 para acceder al byte del offset. Ejemplo: hay dos memorias. i vale 1, 1+1=2, luego 2*6 = 12, -1 = 11, + 4 y estamos en el inicio del offset. // Son 4 bytes para la frecuencia y 2 para el offset, en el ejemplo, la segunda memoria ocuparía las direcciones 11, 12, 13, 14 y el offset las direcciones 15 y 16. // Buscamos el valor del offset justo en la frecuencia que ha coincidido, así que reciclamos "offset_eeprom", que ya no necesitamos EEPROM.get(((i*6)-1)+4, offset_eeprom); if(offset == offset_eeprom){ // Imprime el mensaje de error por lcd, además de especificar en que memoria está ya memorizada lcd.clear(); display(23); while(millis() < tiempo +1000); tiempo = millis(); display(255); display(24); lcd.print(i+1); while(millis() < tiempo +3000); lcd.clear(); tiempo = millis(); return; } // Fin del if de coincidencia de offset } // Fin del if de coincidencia de frecuencia } // Fin del for // Así se determina la dirección correcta a grabar direccioneeprom = (caso * 6) -1; // Esto determina la dirección correcta de la eeprom a grabar la frecuencia // Vamos al menú de confirmación y cancelamos o grabamos display(255); confirmar(); if(salir) {salir = 0; display(255); goto volver;} // No soy amante de los gotos, pero en este caso es limpio y la manera más simple de volver sin salir del menú // Escritura en eeprom EEPROM.put(direccioneeprom, frecuencia); // Actualizamos frecuencia EEPROM.put(direccioneeprom + 4, offset); // Guardamos el offset en las 2 "un int en 16 bits ocupa 2 bytes" direcciones siguentes a la frecuencia + 4. Si el valor almacenado es 0, no hay offset if(presets[caso-1] < 1000) {EEPROM.put(0, EEPROM.read(0)+1);} // Actualizamos cantidad de memorias almacenadas, se suma uno si está vacía o se queda igual si sobreescribimos una existente. 1000 es un número cualquiera inferior a cualquier posible frecuencia. // Actualizamos el array presets[caso-1] = frecuencia; // Restamos uno porque los arrays empiezan por 0 display(255); // Mostramos el mensaje de grabado correcto lcd.clear(); display(25); lcd.print(caso); display(26); // Mantenemos el mensaje de grabado correcto durante 3 segundos, a menos que se rote el encoder tiempo = millis(); encoder0Pos = 0; while(millis() < tiempo + 3000 && encoder0Pos == 0); lcd.clear(); break; } // Fin de si el pulsador está pulsado } // Fin del while encoder0Pos = 0; while(digitalRead(pulsador) == LOW); // Hay que esperar a que se suelte el botóm "estado HIGH o se producirá un bonito rebote y alterará valores de otro menú o de contador de pasos de frecuencia en caso de salir tiempo = millis(); } // Fin del menu_memo // Menú confirmar void confirmar() { tiempo = millis(); display(27); while(digitalRead(pulsador) == LOW); // Hay que esperar a que se suelte el botón "estado HIGH" o se producirá un bonito rebote y saldrá del menú byte caso = 0; bool primero = 0; encoder0Pos = 0; while(caso != 255) { cli(); byte isr = encoder0Pos; sei(); int velocidad = 250; // Determina la velocidad de parpadeo del cursor // Sólo se cumple si no hay pulsación if(Comprobador(isr)) { if(isr == 1 && caso < 1) {caso++; encoder0Pos = 0;} // El 2 representa el número de elementos del menú -1, porque empezamos a contar desde 0, modificar si se cambia dicho número de elementos else if(isr == 2 && caso > 0) {caso--; encoder0Pos = 0; } } else{isr =0; encoder0Pos = 0;} switch (caso) { case 0: if(digitalRead(pulsador) == LOW) {encoder0Pos = 0; caso = 255; salir = 1; break;} display(29); while(millis() <= tiempo+velocidad && encoder0Pos == 0) {display(28);} // Corresponde al no break; case 1: if(digitalRead(pulsador) == LOW) {encoder0Pos = 0; caso = 255; break;} display(28); while(millis() <= tiempo+velocidad && encoder0Pos == 0) {display(29);} // Corresponde al sí break; } tiempo = millis(); while(millis() <= tiempo+velocidad && digitalRead(pulsador) == HIGH && encoder0Pos == 0) { if(caso == 1) {display(30);} else {display(31);} //display(10); } // Borramos segunda fila tiempo = millis(); } // Fin del while encoder0Pos = 0; lcd.clear(); while(digitalRead(pulsador) == LOW); // Hay que esperar a que se suelte el botóm "estado HIGH o se producirá un bonito rebote y alterará valores de otro menú o de contador de pasos de frecuencia en caso de salir } // Fin del menú confirmar // Menú offset, para programar diferencia de frecuencia en emisión, útil para futuros repes void Offset() { lcd.setCursor(0, 0); display(11); lcd.setCursor(0, 1); display(10); while(digitalRead(pulsador) == LOW){} // Hay que esperar a que se suelte el botón y si se tarda más de un segundo, volvemos con false, para que el loop sepa que tiene que ir a por el menú principal while(1==1){ cli(); byte isr = encoder0Pos; sei(); if(Comprobador(isr)) { if(isr == 1 && offset < 1000) {offset = offset +10; isr = 0; encoder0Pos = 0;} else if(isr == 2 && offset > -1000) {offset = offset -10; isr = 0; encoder0Pos = 0;} } else if (digitalRead(pulsador) == LOW) {break;} else{isr =0; encoder0Pos = 0;} lcd.setCursor(0, 1); lcd.print(offset);lcd.print(F("khz ")); } // Fin del while encoder0Pos = 0; while(digitalRead(pulsador) == LOW){} // Hay que esperar a que se suelte el botóm "estado HIGH o se producirá un bonito rebote y alterará valores de otro menú o de contador de pasos de frecuencia en caso de salir tiempolcd = millis(); // No queremos estar a oscuras al volver } // Fin de función Offset // Inicio del ciclo de parado. primero se guardan los estados que hubiren cambiado y luego se para void Parar() { lcd.backlight(); tiempolcd = millis(); // Activamos LCD, por si estaba apagado display(255); display(40); // Presentamos el mensaje de parada // No se ejectutará nada, hasta que no soltemos el botón y el PTT while(estado == 1 || digitalRead(pulsador) == LOW){ // Reconstruimos la variable estado if(digitalRead(TX_RX) == LOW && TX_RXmodo == 1 || digitalRead(TX_RX) == HIGH && TX_RXmodo == 0) {estado = 1;} else {estado = 0;} }; // Fin del while tiempolcd = millis(); // Nota: reutilizaremos "tiempolcd" y así ahorramos algo de espacio // el mensaje permanecerá en pantalla el tiempo establecido a menos que en ese periodo de tiempo, se pulse o rote el rotary encoder o se apriete el PTT, en cuyo caso se cancelará el apagado. while(encoder0Pos == 0 && estado == 0 && digitalRead(pulsador) == HIGH && millis() < tiempolcd + tiempoparada*1000){ // Reconstruimos la variable estado if(digitalRead(TX_RX) == LOW && TX_RXmodo == 1 || digitalRead(TX_RX) == HIGH && TX_RXmodo == 0) {estado = 1;} else {estado = 0;} }; // Fin del while // Si se pulsó o rotó el rotary encoder o se pulsó el PTT, durante el tiempo establecido en el anterior while, se iniciará el proceso de cancelación if (encoder0Pos != 0 || estado != 0 || digitalRead(pulsador) == LOW) { display(255); display(41); // Presentamos el mensaje de parada cancelada tiempolcd = millis(); while(millis() < tiempolcd + tiempoparada*1000); // Para evitar solapamientos, es mejor esperar a que no haya nada pulsado antes de volver while(encoder0Pos != 0 || estado != 0 || digitalRead(pulsador) == LOW) { // Reconstruimos la variable estado if(digitalRead(TX_RX) == LOW && TX_RXmodo == 1 || digitalRead(TX_RX) == HIGH && TX_RXmodo == 0) {estado = 1;} else {estado = 0;} encoder0Pos = 0; } // Fin del whie display(255); tiempolcd = millis(); encoder0Pos, estado = 0; return; } // Fin del if // De lo contrario, grabamos y paramos el chiringuito else { //Empezamos por mirar si estamos en modo memorias o frecuencia y a actualizar la eeprom si procede if(FrecMem == 1) {EEPROM.update(1, 1);} else {EEPROM.update(1, 0);} // Se guarda el modo en la dirección 1 EEPROM.update(2, cualpreset); // Se guarda si procede, el número de memoria que hay actualmente, en la dirección 2 // Para valores superiores a 1 byte, usamos EEPROM.put ya que maneja objetos de tamaño personalizado. Se basa en EEPROM.update y por tanto tampoco sobreescribe valores iguales ya existentes EEPROM.put(3, offset); // Se guarda si procede el offset del modo frecuencias. Al ser un int, ya que puede superar 255 bits, ocupará 2 bytes y serán las direcciones 3 y 4 EEPROM.update(1013, cualpaso); // Se guardarán, si procede, los pasos de frecuencia en la dirección 1013 EEPROM.put(1014, frecuencianterior); // Se guarda si procede la frecuencia anterior del modo frecuencias. Al ser un int32_t, ya que puede superar 32000 bits, ocupará 4 bytes y serán las direcciones 1014, 1015, 1016 y 1017 EEPROM.put(1018, frecuencia); // Se guarda si procede la frecuencia actual del modo frecuencias. Al ser un int32_t, ya que puede superar 32000 bits, ocupará 4 bytes y serán las direcciones 1018, 1019, 1020 y 1021 digitalWrite(parar, HIGH); } // Fin del else }// Fin de la función Parar // ISR para el decremento "giro a la izquierda" void ISR_pinA() { if(digitalRead(2)==digitalRead(3)){encoder0Pos=2;} // Con el if evitamos conteos erroneos en las transiciones } // ISR para el incremento "giro a la derecha" void ISR_pinB() { if(digitalRead(2)==digitalRead(3)){encoder0Pos=1;} // Con el if evitamos conteos erroneos en las transiciones } void display(byte z) { int32_t freclcd; unsigned long tiempo2 = millis(); // Menú principal if(z == 2) {lcd.setCursor(0, 0); lcd.write(0); if(FrecMem == 1) {lcd.print(F(" ModoFrec "));} else {lcd.print(F(" Frec/Mem "));} lcd.print(F("Scan"));} // Primera fila else if(z == 3) {lcd.setCursor(0, 1);lcd.write(1); lcd.print(F(" "));} // Segunda fila, opción salir seleccionada NOTA: TAMBIEN USADA EN LOS DEMÁS MENÚS else if(z == 4) {lcd.setCursor(0, 1); lcd.print(F(" ")); lcd.write(1);lcd.write(1);lcd.write(1);lcd.write(1);lcd.write(1);lcd.write(1);lcd.write(1);lcd.write(1);} // Segunda fila, opción Frec/Mem else if(z == 5) {lcd.setCursor(0, 1); lcd.print(F(" ")); lcd.write(1); lcd.write(1); lcd.write(1); lcd.write(1);} // Segunda fila, opción Scan // Menú Frec/Mem else if(z == 6) {lcd.setCursor(0, 0); lcd.write(0); if(FrecMem == 1) {lcd.print(F(" ModoFrec "));} else {lcd.print(F(" ModoMemo "));} lcd.print(F("Memo"));} // Primera fila. muestra salir, Fre o Mem "según selección" y Memo else if(z == 7) {lcd.setCursor(0, 1); lcd.print(F(" ")) ;lcd.write(1);lcd.write(1);lcd.write(1);lcd.write(1);lcd.write(1);lcd.write(1);lcd.write(1);lcd.write(1);} // Segunda fila, opción Frec else if(z == 8) {lcd.setCursor(0, 1); lcd.print(F(" ")); lcd.write(1); lcd.write(1); lcd.write(1);} // Segunda fila, opción Mem else if(z == 9) {lcd.setCursor(0, 1); lcd.print(F(" ")); lcd.write(1); lcd.write(1); lcd.write(1); lcd.write(1);} // Segunda fila, opción Memo else if(z == 32) {lcd.print(F("No hay memorias")); lcd.setCursor(0,1); lcd.print(F("disponibles"));} else if(z == 33) {lcd.print(F("Modo frecuencia"));} // Fila primera al pasar a modo frecuencia else if(z == 34) {lcd.print(F("Modo memorias"));} // Fila primera al pasar a modo memorias else if(z == 35) {lcd.setCursor(0, 1); lcd.print(F("seleccionado"));} // Fila segunda, común para los dos anteriores // Menú Scan else if(z == 36) {lcd.setCursor(0, 0); display(23); lcd.setCursor(0, 1); lcd.print(F("squelsh abierto"));} // Se muestra al hacer la garrulada de escanear con el squelsh abierto else if(z == 37) {lcd.print(F("Scan <- +>")); lcd.setCursor(0, 1); lcd.print(F("PTT para detener"));} // Se muestra al principio del escaneado else if(z == 38) {lcd.setCursor(12, 0); lcd.print(F("+"));} // Muestra el sentido ascendente del escaneo, 4 espacios a la derecha de la frecuencia "fila uno" else if(z == 39) {lcd.setCursor(12, 0); lcd.print(F("-"));} // Muestra el sentido descendente del escaneo, 4 espacios a la derecha de la frecuencia "fila uno" // Función Parar else if(z == 40) {lcd.print(F("Parando..."));} // Presentamos el mensaje de parada else if(z == 41) {lcd.print(F("Parada cancelada"));} // Presentamos el mensaje de parada cancelada // Menú memo else if(z == 16) {lcd.print(F("Guardar "));} // En fila superior presenta el mensaje de guardar en memo else if(z == 17) {lcd.print(F(" en"));} // En fila superior presenta el mensaje de guardar en memo else if(z == 18) {lcd.print(F(" contiene "));} // Se muestra si el número de memorias almacenadas es inferior a 10 else if(z == 19) {lcd.print(F(" cont. "));} // Se muestra si el número de memorias almacenadas es igual o superior a 10, simplemente porque no cabe la palabra entera en el lcd else if(z == 20) {lcd.print(F("Pulsar para")); lcd.setCursor(0, 1); lcd.print(F("salir"));} // Sale del menú memo y de todos los demás. else if(z == 21) {lcd.print(F("khz"));} else if(z == 22) {lcd.print(F(" est")); lcd.write(2); lcd.print(F(" vac")); lcd.write(3); lcd.print(F("a "));} // Escribe en lcd la frase " está vacía " else if(z == 23) {lcd.print(F("ERROR!"));} // Se muestra en la primera fila, cuando se intenta memorizar un contenido ya existente en otra memoria else if(z == 24) {lcd.print(F("frecuencia ya")); lcd.setCursor(0, 1); lcd.print(F("memorizada en "));} // como la anterior, pero en la segunda fila else if(z == 25) {lcd.print(F("Memoria "));} // Primera fila. Informa de que se guardó la memoria else if(z == 26) {lcd.setCursor(0, 1); lcd.print(F("guardada OK"));} //Segunda fila. Informa de que se guardó la memoria correctamente // Menú confirmar else if(z == 27) {lcd.write(4); lcd.print(F("Est")); lcd.write(2); lcd.print(F("s seguro?"));} // Primera fila, pregunta de confirmación else if(z == 28) {lcd.setCursor(0, 1); lcd.print(F("No"));} // Segunda fila, respuesta de confirmación negativa else if(z == 29) {lcd.setCursor(14, 1); lcd.print(F("S"));lcd.write(3);} // Segunda fila, respuesta de confirmación positiva else if(z == 30) {lcd.setCursor(0, 1); lcd.print(F("No "));} // Segunda fila, respuesta de confirmación positiva else if(z == 31) {lcd.setCursor(0, 1); lcd.print(F(" S")); lcd.write(3);} // Segunda fila, respuesta de confirmación positiva // varios else if(z == 10) {lcd.print(F(" "));} // Limpia fila seleccionada else if(z == 11) {lcd.setCursor(0, 0); lcd.print(F("Offset TX "));} // Imprime el mensaje Offset TX de la primera fila else if(z == 12) {lcd.print(F("ERROR frecuencia"));} // Imprime primera fila del mensaje de frecuencia rebasada else if(z == 13) {lcd.print(F("m")); lcd.write(2); lcd.print(F("xima rebasada "));} // Imprime segunda fila del mensaje de frecuencia máxima rebasada else if(z == 14) {lcd.print(F("m")); lcd.write(3);lcd.print(F("nima rebasada "));} // Imprime segunda fila del mensaje de frecuencia mínima rebasada else if(z == 15) {lcd.print(F("RESET MEM"));} // Imprime el reseteo de la EEPROM else if(z == 255) {lcd.clear(); while(millis() <= tiempo2 + 800) {}; tiempo2 = millis();} // Borra pantalla por el tiempo indicado else { if(digitalRead(TX_RX) != TX_RXmodo) {freclcd = frecuencia + offset;} else {freclcd = frecuencia;} String x, y, z; int mhz = freclcd/1000; int khz = freclcd - (mhz*1000); if(khz < 10) {x = "00";} else if(khz < 100) {x = "0";} else {x = "";} if(pasos[cualpaso] < 10) {y = " ";} else if(pasos[cualpaso] < 100) {y = " ";} else if(pasos[cualpaso] < 1000) {y = " ";} else {y = "";} lcd.setCursor(0, 0);lcd.print(mhz);lcd.print(F(".")); lcd.print(x); lcd.print(khz); lcd.print(F("mhz")); lcd.setCursor(0, 1); // Lo que muestra la segunda fila en modo frecuencia if(FrecMem == 0) {lcd.print(F("F ")); lcd.print(F("pasos: ")); lcd.print(pasos[cualpaso]); lcd.print(F("khz")); lcd.print(y);} // Lo que muestra la segunda fila en modo memorias else { int offset_eeprom; EEPROM.get((((cualpreset+1)*6)-1)+4, offset_eeprom) ; if(cualpreset+1 < 10) {z = " ";} else if(cualpreset+1 < 100) {z = " ";} else if(cualpreset+1 < 1000) {z = " ";} else {z = "";} lcd.print(F("M")); lcd.print(cualpreset+1); lcd.print(F(" oft:")); if(offset_eeprom >= 0) {lcd.print(F(" "));} if(offset_eeprom == 0) { lcd.print(F("no hay")); lcd.print(z);} else {lcd.print(offset_eeprom); display(21); lcd.print(z);} } // Fin delelse de lo que muestra la segunda fila en modo memorias if(estado == 0) {lcd.setCursor(14, 0); lcd.print(F("RX"));} else {lcd.setCursor(14, 0); lcd.print(F("TX"));} // Impriminos + o - para offset, solamente si estamos en modo frecuencias lcd.setCursor(13, 0); if(offset > 0 && FrecMem == 0) {lcd.print(F("+"));} else if(offset < 0 && FrecMem == 0) {lcd.print(F("-"));} else {lcd.print(F(" "));} } // Fin del primer else }