alarma temporizada en un mismo led al presionar un botón

hola a todos, soy tecnologo mecatronico y estoy entrando a la programación en arduino, aun me falta mucho pero estoy haciendo lo que puedo.

necesito encender un led en el momento de dejar presionado un botón, este permanecera encendido por 5 minutos y al cabo de este tiempo si no se a soltado el botón que empiece a parpadear indicando que lleva mas de 5 minutos presionado, y al soltarlo vuelva y se apague y quede en estado low hasta que vuelvan y presionen el botón.

agradezco su colaboración, de verdad lo necesito urgente

Tu ejemplo lo desarrollamos casi todas las semanas. Ahora hay 2 en software de características similares.
Te aconsejo que mires y revises para ver si se ajustan a tu necesidad o al menos sirven de puntapié inicial.

Hola nano2400,

por si no encuentras algo que te sirva, he aquí una forma de hacerlo:

const int boton = 13;
const int led = 12;
int botVal;  
int x;  //esta variable se encargará de almacenar los segundos que pasan 
         //desde que se empieza a apretar el botón.

void setup(){
     pinMode(led, OUTPUT);
     pinMode(boton, INPUT); //se conecta a 5V la entrada de un botón, y la 
                                      //salida (con una resistencia) al pin13.
}

void loop(){
     botVal = digitalRead(boton); //botVal toma el valor leído en boton
     if (botVal == 1){                //si el botón está apretado
          digitalWrite(led, HIGH);  //el led se enciende
          delay(1000);                 //la comprobación se hace cada vez que pasa un segundo 
                                            //(si se suelta, al segundo se apaga el led).
          x++;                           //la variable x aumenta en uno
          if(x > 299){                  //cuando x es mayor de 499 (han pasado 300 seg = 5 min)
               digitalWrite(led, LOW);//el led se apaga
               delay(1000);            //espera 1 seg y vuelve a hacer el bucle. Así se consigue que,
                                            //mientras el botón esté pulsado, el led parpadee a razón de
                                            //1 segundo por estado (encendido/apagado), y si se suelta
                                            //se apaga
   }

     else{                                //si el botón no está apretado
          digitalWrite(led, LOW);   //el led se apaga (o sigue apagado)
          x = 0;                         //la variable x se resetea ya que, si hemos apretado el botón antes,
                                           //sin esta sentencia la x seguiría teniendo el valor anterior.
   }
}

Espero que te sirva!!

1 Like

muchas gracias esta muy bueno el programa, estaba perdido por razones laborales, lo estuve mirando y el if(x > 299){ //cuando x es mayor de 499 (han pasado 300 seg = 5 min) no empieza a parpadear.

que podria ser o ese tiempo se lo puedo aplicar a otro led para que parpadee al cabo de 5 minutos y haga todo el resto del programa

A ver, ¿Donde está la duda? ¿O lo que quieres es código hecho? Porque te han dicho donde conseguirlo, te han dado uno (que a mi juicio es complicarse la vida) y acabas preguntando algo que da a entender que no entiendes ni el if.

Plantea tu duda exacta no el ejercicio que te han mandado por favor... y lo de urgente... bueno es tu tarea no la nuestra.

Te comento que mi primer año de programación todo aquel que se dedicó a hacer copy and paste de internet acabó suspendiendo los exámenes uno tras otro.
En el segundo año no eramos ni la mitad y abandonaba la gente por la complejidad y al final de los estudios nos podían contar con los dedos de las manos lo que en su inicio eran aulas sin espacio de todos los que éramos.
Tú sabrás que eres quien estudia si las casas se empiezan desde los cimientos o desde los tejados.
¿Aprendes realmente copiando? En el trabajo no te van a pedir que copies sino que desarrolles por tu cuenta.

Dime la duda en concreto no el ejercicio y te doy código en pocas líneas.

hola arduinito muchas gracias por tus recomendaciones de programacion y consejos a seguir. no estoy copiando ni pegando nada, estoy investigando mucho a pesar de que la programación sea para ustedes muy sencilla de desarrollar.
dije que era nuevo en este tema y que estoy investigando para lograr afianzar mas este tema y poder ayudar a otras personas con todas sus dudas como las tengo ahora.

si entiendo que es la condición "if" pero al aplicarlo a el programa

x++; //la variable x aumenta en uno
if(x > 299){ //cuando x es mayor de 499 (han pasado 300 seg = 5 min)
digitalWrite(led, LOW); //el led se apaga
delay(1000);

no logro que el mismo led me parpadee y estoy intentando que active otro led si es este caso el msmo no da para lograr que parpadee después de los 5 minutos.

Por partes:

El código de juli29piano no es correcto.

Ese contador de tiempo es incorrecto, un incremental en el loop "x++" no te va a dar el tiempo exacto.
El loop vuelve a empezar tras ejecutar cada línea.

Es decir si tienes X número de líneas que tardan Z tiempo en ejecutarse (por poco que sea) pasará esto:
1 línea -> 1ms
2 línea -> 3ms
3 línea -> 0.5 ms
4 línea -> 1ms

Esto haría que el loop tarde 5.5 milisegundos

Y ahora para rematar ponen un delay() de un segundo que detiene todo el código.
La primera vuelta tardaría 1 seg y 5.5
Ya la siguiente sería el doble de error... y así a cada vuelta.

¿Disparate verdad?
Especialmente porque delay() interrumpe todo y arduino no sigue en el resto de líneas con lo que si quisieras que otros leds o cualquier otro componente necesitase un tiempo no te iba a funcionar.

Bueno aparte ese código que te han pasado tiene más fallos, por eso reitero que no se hagan copy and paste. Entiendan y razonen los códigos porque si hay algo mal no van a saber resolverlo.

Antes de hacer copy and paste como hiciste con el que te dieron y estaba mal, "moléstate" en ir línea a línea entendiendo el código y si tienes dudas pregunta.

No soy mucho de poner código por esto mismo, porque la gente copia y no sabe que está haciendo, luego si está mal algo o no es exacto a lo que quieren no saben salir de ahí ¿Entonces cual es el sentido de meramente copiar? ¿Quedarse atrás del resto? ¿Nunca hacer nada por ti mismo? ¿Tener el código mal?

A muchos usuarios sé que les molesta que le digan que no sólo copien, pero se les dice por esto mismo, porque que copies algo no significa que esté bien aún cuando lo parezca.

Este ejemplo hace lo que quieres pero en vez de 5 minutos lo puse a 5 segundos que ya bastante tiempo me parece tener pulsado un botón :grin: . Cambia el tiempo de la variable segun conveniencia.


Circuito:


Código (Es cortito):

const byte
led   = 3, //pin led
boton = 4; //pin pulsador

unsigned long 
tiempo;     //tiempo total en milisegundos

unsigned int
intervalo_alarma   = 5000,// 5 seg
intervalo_parpadeo = 250; // 1/4 seg

bool 
estado_led  = false, //encendido/apagado
esta_activo = false; //se pulsó o no el botón (usado para asignar el tiempo 1 sola vez)

void setup() {
  pinMode(boton, INPUT);
  pinMode(led,   OUTPUT);
}

void loop() {

  if(digitalRead(boton)){
    if(!esta_activo){
      esta_activo = true;
      tiempo = millis();
    }
    else if(millis() >= tiempo + intervalo_alarma){
      if(millis() >= tiempo + intervalo_alarma + intervalo_parpadeo){
        tiempo += intervalo_parpadeo;
        estado_led = !estado_led;
      }
      //Cuando entre en este if pero no por el siguiente que está anidado el valor 
      //de estado_led estará en false, luego no añado el valor de nuevo a low porque
      //es redundante ;)
    }
    else{
      estado_led = true;
    }
  }else{
    esta_activo = estado_led = false;
  }
  
  digitalWrite(led, estado_led); //Encender o apagar el led
}

arduinito, si que sabe programar y lo bien que lo explica dice mucho de usted. antes de ponerlo a trabajar voy a investigar a fondo cada condición y para que es cada cosa.

entiendo varias cosas, ensaye el programa y corre perfectamente.

le voy a trabajar en como modificarlo, cualquier duda estaré escribiendo de nuevo.

de verdad muchas gracias por ofrecer sus conocimientos en estos foros y así yo también poder ayudar a otros.

buenas tardes, estuve modifican dolo o duplicando las funciones para que me trabajaran tres pulsadores independientes y cada uno con su led, pero no me quiere dar. le doy dos entradas mas y dos salidas y el resto del programa muy similar pero me sale error o no me corre bien el programa. como podria hacerlo o que idea me dan. gracias

Ayudaría a que te ayuden el que pongas el código de lo que tienes hecho. Y ten en cuenta el usar la opción </> para insertar el código en el mensaje (la que está junto a la B de poner en negritas).

el codigo original me ayudo arduinito y de verdad su que fue de gran ayuda:

</>

const byte
led   = 3, //pin led
boton = 4; //pin pulsador

unsigned long 
tiempo;     //tiempo total en milisegundos

unsigned int
intervalo_alarma   = 5000,// 5 seg
intervalo_parpadeo = 250; // 1/4 seg

bool 
estado_led  = false, //encendido/apagado
esta_activo = false; //se pulsó o no el botón (usado para asignar el tiempo 1 sola vez)

void setup() {
  pinMode(boton, INPUT);
  pinMode(led,   OUTPUT);
}

void loop() {

  if(digitalRead(boton)){
    if(!esta_activo){
      esta_activo = true;
      tiempo = millis();
    }
    else if(millis() >= tiempo + intervalo_alarma){
      if(millis() >= tiempo + intervalo_alarma + intervalo_parpadeo){
        tiempo += intervalo_parpadeo;
        estado_led = !estado_led;
      }
      //Cuando entre en este if pero no por el siguiente que está anidado el valor 
      //de estado_led estará en false, luego no añado el valor de nuevo a low porque
      //es redundante ;)
    }
    else{
      estado_led = true;
    }
  }else{
    esta_activo = estado_led = false;
  }
  
  digitalWrite(led, estado_led); //Encender o apagar el led
}

(El primer paso de varios)

Voy a aprovechar y no sólo voy dar un pez. Voy a tratar de enseñar a pescar para que puedan comer todos los días. Más o menos voy a tratar de explicar, "pasos a pasos", cómo llegar hasta la solución que aplicaría yo. Supongo que no es la ideal ni la más óptima, pero es la que yo, más o menos, aplico para "mis cosillas".

Primero advertir de que la comparación if(millis() >= tiempo + intervalo_alarma){ tiene un problema un poco "sutil" (que está ampliamente comentado y documentado en muchos sitios). El desbordamiento de los millis(). Si buscan seguro que encontrarán por ahí explicación del porqué y cómo evitar el problema que sucede cada 49.7 días (bueno, que si no van a tenerlo tanto tiempo encendido tampoco les va a dar problemas). Pero la solución es sencilla, así que vale la pena asegurarnos de "hacerlo bien".

Para evitar el problema, en vez de ver si el momento actual es mayor o igual al momento de inicio más el tiempo que se quiere esperar que pase, lo que hacemos es ver si el tiempo transcurrido desde el inicio es mayor o igual al tiempo que estamos esperando que transcurra. Reconozco que parece un trabalenguas, así que mejor si ponemos el código:

if ( millis() >= (tiempo + intervalo_alarma) ) { // Esto nos puede dar problemas cada 49.7 días, y esperar menos de lo esperado

if ( (millis() - tiempo) >= intervalo_alarma ) { // Esto nos funcionará perfectamente y sin problemas, incluso cada 49.7 días

¿La explicación del porqué? Puede resultar un poco exotérica si no se tiene claro cómo funciona el C++ cuando se desbordan los enteros al operar con ellos. Pero si están interesados en saber el porqué, ya tienen una idea de qué han de buscar en San Google.

Aparte de los millis(), he cambiado el programa para aplicar una solución tipo máquina de estados. Para ello me valgo de un tipo enumerado (lo que los anglosajones llaman un enum) por los motivos que explico en esta respuesta del foro Para qué sirve un enum? - #3 by IgnoranteAbsoluto - Software - Arduino Forum

También me permito la licencia de quitar todo lo que hay en el loop() y ponerlo en una función que he llamado alarma_loop() a la que se llama desde el loop(). Más adelante se verá porqué.

Otro pequeño cambio ha sido cambiar un poco el nombre de las constantes y ponerlas todas en mayúsculas. Soy algo de la vieja escuela y me gustan que las constantes resalten en el código.

Así que tras esos cambios la cosa me ha quedado tal que así:

const byte PIN_LED = 3;
const byte PIN_PULSADOR = 4;

const unsigned long INTERVALO_ALARMA   = 5000; // 5 seg
const unsigned long INTERVALO_PARPADEO = 250; // 1/4 seg

enum estado_t { // Definimos este tipo de variable, para controlar la máquina de estados
  ESTADO_NO_PULSADO_LED_APAGADO,  // No está pulsado y el LED ha de estar apagado
  ESTADO_PULSADO_LED_ENCENDIDO,   // Está pulsado y el LED ha de estar encendido porque no ha "saltado" la alarma
  ESTADO_ALARMA_LED_ENCENDIDO,    // Está pulsado y el LED ha de estar encendido habiendo "saltado" la alarma
  ESTADO_ALARMA_LED_APAGADO       // Está pulsado y el LED ha de estar apagado habiendo "saltado" la alarma
};

estado_t estado = ESTADO_NO_PULSADO_LED_APAGADO;  // Inicialmente se considera que no está pulsado

unsigned long instanteUltimoCambioEstado = 0; // Cuándo se ha realizado el último cambio de estado

void alarma_loop() {
  if (digitalRead(PIN_PULSADOR)) {
    // Está pulsado
    if (estado == ESTADO_NO_PULSADO_LED_APAGADO) {
      // La máquina de estado dice que hasta ahora no estaba pulsado, así que cambiamos de estado y encendemos el LED
      estado = ESTADO_PULSADO_LED_ENCENDIDO;
      digitalWrite(PIN_LED, HIGH);
      instanteUltimoCambioEstado = millis(); // Guardamos el momento en que hemos cambiado de estado
    }
    else if (estado == ESTADO_PULSADO_LED_ENCENDIDO) {
      // La máquina de estado dice que hasta ahora estaba pulsado, pero no ha pasado el tiempo como para que salte la alarma (hasta ahora)
      if ( (millis() - instanteUltimoCambioEstado) >= INTERVALO_ALARMA ) {
        // Ha igualado o superado el tiempo definido para que "salte" la alarma, así que cambiamos de estado y apagamos el LED
        estado = ESTADO_ALARMA_LED_APAGADO;
        digitalWrite(PIN_LED, LOW);
        instanteUltimoCambioEstado = millis(); // Guardamos el momento en que hemos cambiado de estado
      }
    }
    else {
      // Si ha llegado hasta aquí es porque estal en ESTADO_ALARMA_LED_ENCENDIDO o ESTADO_ALARMA_LED_APAGADO
      if ( (millis() - instanteUltimoCambioEstado) >= INTERVALO_PARPADEO ) {
        // Ha igualado o superado el tiempo definido de parpadeo, así que hay que cambiar de estado
        if (estado == ESTADO_ALARMA_LED_APAGADO) {
          // El LED estaba apagado, así que toca encenderlo
          estado = ESTADO_ALARMA_LED_ENCENDIDO;
          digitalWrite(PIN_LED, HIGH);
        }
        else {
          // El LED estaba encendido, así que toca apagarlo
          estado = ESTADO_ALARMA_LED_APAGADO;
          digitalWrite(PIN_LED, LOW);
        }
        instanteUltimoCambioEstado = millis(); // Guardamos el momento en que hemos cambiado de estado
      }
    }
  }
  else {
    // No está pulsado
    if (estado != ESTADO_NO_PULSADO_LED_APAGADO) {
      // La máqina de estado a de cambiar de estado y asegurarse de que el LED está apagado
      estado = ESTADO_NO_PULSADO_LED_APAGADO;
      digitalWrite(PIN_LED, LOW);
      // Nota, en este caso no nos hace falta recordar en qué momento ha cambiado de estado porque para salir de este estado no depende del tiempo
    }
  }
}

void setup() {
  pinMode(PIN_PULSADOR, INPUT);
  pinMode(PIN_LED, OUTPUT);
  digitalWrite(PIN_LED, LOW); // Inicialmente nos aseguramos de que el LED esté apagado
}

void loop() {
  alarma_loop();
}

La idea es que la máquina de estado controle los cuatro posibles estados y la transición entre ellos. Se verifica cuando se dan las condiciones para el cambio de un estado a otro y, cuando esto sucede, se realizan las acciones oportunas acorde al cambio de estado. Así como actualizar las variables que controlan el estado y los tiempos. Creo que el código y sus comentarios son lo suficientemente claros... pero si hay dudas no duden en preguntar en el foro, que para eso está.

1 Like

(El segundo paso)

Sí, muy bonito todo hasta ahora, pero eso sólo maneja un pulsador y un LED. ¿Si queremos tres cómo lo hacemos?

Bueno, en principio no nos libramos de tener tres variables de cada, ya que cada LED ha de tener sus propias variables que controle su estado y demás necesidades. Pero ¿y el código?

No es plan de hacer tres veces el mismo código y tenerlo por triplicado (¿o sí? hay una cosa en C++ que se llaman plantillas, templates para los anglosajones, pero eso es "otro mundo" y tiene sus desventajas). Así que lo que voy a hacer es pasar como parámetros a la función alarma_loop() las variables de cada pareja pulsador/LED y llamar a la función tantas veces como sea necesario.

No sólo tengo que "triplicar" las variables estado e instanteUltimoCambioEstado, también de de "triplicar" las constantes PIN_LED y PIN_PULSADOR. En el código se ven que tienen añadidas los sufijos _1, _2 y _3. Y me he inventado unos valores para los pines nuevos.

Por supuesto, he de adaptar la función alarma_loop() de tal manera que las constantes que usaba hasta ahora pasan a ser variables que les paso por parámetro (pinPulsador y pinLed). Como ya no son constantes no las dejo todas en mayúsculas. El otro cambio es un poco más sutil. Las variables estado e instanteUltimoCambioEstado no han cambiado de nombre, pero ahora no son las dos variables globales que eran antes. Ahora son dos variables locales a la función y paso sus valores por referencia (observe que hay un & junto a cada una de ellas en la declaración como parámetro de la función):

void alarma_loop(byte pinPulsador, byte pinLed, estado_t &estado, unsigned long &instanteUltimoCambioEstado) {

¿Porqué el & para estas dos y no para las dos anteriores? Pues porque necesito que los cambios de los valores de estas dos variables "se vean" reflejados en las variables "originales". Los cambios de las variables estado e instanteUltimoCambioEstado son como si se cambiasen las variables estado_1 e instanteUltimoCambioEstado_1 cuando se usan éstas en la llamada de la función. Si no tuvieran el & nunca cambiarían de valor las variables estado_1 e instanteUltimoCambioEstado_1, por que se estarían pasando por copia. Como siempre, San Google tiene más información al respecto.

Lógicamente, en el setup() se han de inicializar los nuevos pines y asegurarse que todos los LEDs están apagados.

En el loop() se llama ahora tres veces a la función alarma_loop() con los parámetros correspondientes para manejar los tres conjuntos de pulsador/LED.

El código con los cambios:

const byte PIN_LED_1 = 3;
const byte PIN_PULSADOR_1 = 4;

const byte PIN_LED_2 = 5;
const byte PIN_PULSADOR_2 = 6;

const byte PIN_LED_3 = 7;
const byte PIN_PULSADOR_3 = 8;

const unsigned long INTERVALO_ALARMA   = 5000; // 5 seg
const unsigned long INTERVALO_PARPADEO = 250; // 1/4 seg

enum estado_t { // Definimos este tipo de variable, para controlar la máquina de estados
  ESTADO_NO_PULSADO_LED_APAGADO,  // No está pulsado y el LED ha de estar apagado
  ESTADO_PULSADO_LED_ENCENDIDO,   // Está pulsado y el LED ha de estar encendido porque no ha "saltado" la alarma
  ESTADO_ALARMA_LED_ENCENDIDO,    // Está pulsado y el LED ha de estar encendido habiendo "saltado" la alarma
  ESTADO_ALARMA_LED_APAGADO       // Está pulsado y el LED ha de estar apagado habiendo "saltado" la alarma
};

estado_t estado_1 = ESTADO_NO_PULSADO_LED_APAGADO;  // Inicialmente se considera que no está pulsado
estado_t estado_2 = ESTADO_NO_PULSADO_LED_APAGADO;  // Inicialmente se considera que no está pulsado
estado_t estado_3 = ESTADO_NO_PULSADO_LED_APAGADO;  // Inicialmente se considera que no está pulsado

unsigned long instanteUltimoCambioEstado_1 = 0; // Cuándo se ha realizado el último cambio de estado
unsigned long instanteUltimoCambioEstado_2 = 0; // Cuándo se ha realizado el último cambio de estado
unsigned long instanteUltimoCambioEstado_3 = 0; // Cuándo se ha realizado el último cambio de estado

void alarma_loop(byte pinPulsador, byte pinLed, estado_t &estado, unsigned long &instanteUltimoCambioEstado) {
  if (digitalRead(pinPulsador)) {
    // Está pulsado
    if (estado == ESTADO_NO_PULSADO_LED_APAGADO) {
      // La máquina de estado dice que hasta ahora no estaba pulsado, así que cambiamos de estado y encendemos el LED
      estado = ESTADO_PULSADO_LED_ENCENDIDO;
      digitalWrite(pinLed, HIGH);
      instanteUltimoCambioEstado = millis(); // Guardamos el momento en que hemos cambiado de estado
    }
    else if (estado == ESTADO_PULSADO_LED_ENCENDIDO) {
      // La máquina de estado dice que hasta ahora estaba pulsado, pero no ha pasado el tiempo como para que salte la alarma (hasta ahora)
      if ( (millis() - instanteUltimoCambioEstado) >= INTERVALO_ALARMA ) {
        // Ha igualado o superado el tiempo definido para que "salte" la alarma, así que cambiamos de estado y apagamos el LED
        estado = ESTADO_ALARMA_LED_APAGADO;
        digitalWrite(pinLed, LOW);
        instanteUltimoCambioEstado = millis(); // Guardamos el momento en que hemos cambiado de estado
      }
    }
    else {
      // Si ha llegado hasta aquí es porque estal en ESTADO_ALARMA_LED_ENCENDIDO o ESTADO_ALARMA_LED_APAGADO
      if ( (millis() - instanteUltimoCambioEstado) >= INTERVALO_PARPADEO ) {
        // Ha igualado o superado el tiempo definido de parpadeo, así que hay que cambiar de estado
        if (estado == ESTADO_ALARMA_LED_APAGADO) {
          // El LED estaba apagado, así que toca encenderlo
          estado = ESTADO_ALARMA_LED_ENCENDIDO;
          digitalWrite(pinLed, HIGH);
        }
        else {
          // El LED estaba encendido, así que toca apagarlo
          estado = ESTADO_ALARMA_LED_APAGADO;
          digitalWrite(pinLed, LOW);
        }
        instanteUltimoCambioEstado = millis(); // Guardamos el momento en que hemos cambiado de estado
      }
    }
  }
  else {
    // No está pulsado
    if (estado != ESTADO_NO_PULSADO_LED_APAGADO) {
      // La máqina de estado a de cambiar de estado y asegurarse de que el LED está apagado
      estado = ESTADO_NO_PULSADO_LED_APAGADO;
      digitalWrite(pinLed, LOW);
      // Nota, en este caso no nos hace falta recordar en qué momento ha cambiado de estado porque para salir de este estado no depende del tiempo
    }
  }
}

void setup() {
  pinMode(PIN_PULSADOR_1, INPUT);
  pinMode(PIN_PULSADOR_2, INPUT);
  pinMode(PIN_PULSADOR_3, INPUT);
  pinMode(PIN_LED_1, OUTPUT);
  pinMode(PIN_LED_2, OUTPUT);
  pinMode(PIN_LED_3, OUTPUT);
  digitalWrite(PIN_LED_1, LOW); // Inicialmente nos aseguramos de que el LED esté apagado
  digitalWrite(PIN_LED_2, LOW); // Inicialmente nos aseguramos de que el LED esté apagado
  digitalWrite(PIN_LED_3, LOW); // Inicialmente nos aseguramos de que el LED esté apagado
}

void loop() {
  alarma_loop(PIN_PULSADOR_1, PIN_LED_1, estado_1, instanteUltimoCambioEstado_1);
  alarma_loop(PIN_PULSADOR_2, PIN_LED_2, estado_2, instanteUltimoCambioEstado_2);
  alarma_loop(PIN_PULSADOR_3, PIN_LED_3, estado_3, instanteUltimoCambioEstado_3);
}

Obviamente, si se quieren más "alarmas" se duplican todas las líneas que hay que duplicar.

1 Like

(El tercer paso)

Tenemos dos constantes y dos variables por cada "alarma". Si el programa evolucionara de tal manera que necesitamos más variables por cada "alarma", habría que revisar todo el programa para ver dónde necesitamos lo nuevo y pasar por parámetro a las funciones que lo necesiten. Aparte de no estar "agrupadas" de alguna manera.

Esto lo podemos "solucionar" con una estructura (struct para los anglosajones). En lugar de tener varios pinPulsador_X, pinLed_X, estado_X e instanteUltimoCambioEstado_X por cada "alarma", lo que hago es crear una estructura que llamaré Alarma que tendrá las variables pinPulsador, pinLed, estado e instanteUltimoCambioEstado. Esto se hace declarando la estructura tal que así:

struct Alarma {
  byte pinPulsador;
  byte pinLed;
  estado_t estado;
  unsigned long instanteUltimoCambioEstado;
};

Y ahora sólo hay que declarar tres variables del tipo de la estructura Alarma que he creado:

struct Alarma alarma_1;
struct Alarma alarma_2;
struct Alarma alarma_3;

Y ya tenemos a nuestra disposición las variables:

alarma_1.pinPulsador
alarma_1.pinLed
alarma_1.estado
alarma_1.instanteUltimoCambioEstado
alarma_2.pinPulsador
alarma_2.pinLed
alarma_2.estado
alarma_2.instanteUltimoCambioEstado
alarma_3.pinPulsador
alarma_3.pinLed
alarma_3.estado
alarma_3.instanteUltimoCambioEstado

El punto que une el nombre de la variable tipo struct y su variable "interna" se usa si estamos trabajando con la estructura "directamente" o si la pasamos a una función por referencia (esto último en C no se puede, pero en C++ sí. Y Arduino es C++). Pero si lo que pasamos a una función es un puntero a la estructura, deberíamos de "obtener" primero la estructura apuntada, y eso se hace con el operador * (asterisco). Lo que pasa es que el punto tiene mayor precedencia que el asterisco por lo que habría que usar los paréntesis y si suponemos que el puntero a la estructura se llama punteroAlarma para acceder al pinLed del puntero "la cosa" quedaría así:

(*punteroAlarma).pinLed

"Por suerte" hay una forma "abreviada" de escribir lo mismo, y es con el operador -> (menos, mayor que) y "la cosa" se puede escribir asi:

punteroAlarma->pinLed

Como siempre, San Google para tener más información sobre las estructuras y punteros a estructuras.

Como se han de poder modificar algunos campos de las estructuras, cuando se pasan a las funciones, las paso por puntero (Para ello se ha de anteponer un & en la llamada). Más adelante se verá porqué las paso por punteros y no por referencia.

En lugar de hacer una por una la inicialización de cada campo de la estructura en el setup(), lo que hago es crear la función void alarma_setup(struct Alarma *alarma, byte pinPulsador, byte pinLed), a la que le paso un puntero a la estructura y las constantes con que quiero inicializar algunos de sus campos cuando la llamo en el setup() varias veces por cada estructura que quiero inicializar. Y ya de paso la función inicializa los pines y se asegura de apagar los LEDs.

He cambiado la función alarma_loop() para que reciba sólo un parámetro en lugar de cuatro. Este parámetro es el puntero a la estructura que contiene las cuatro variables. Para poder "usar" las variables de la estructura lo que he hecho es anteponer alarma-> a las "variables sueltas" que tenía ya que les puse los mismos nombres.

Obviamente, hay que cambiar también los parámetros de las tres llamadas a alarma_loop() que se hacen en el loop().

El programa queda así:

const byte PIN_LED_1 = 3;
const byte PIN_PULSADOR_1 = 4;

const byte PIN_LED_2 = 5;
const byte PIN_PULSADOR_2 = 6;

const byte PIN_LED_3 = 7;
const byte PIN_PULSADOR_3 = 8;

const unsigned long INTERVALO_ALARMA   = 5000; // 5 seg
const unsigned long INTERVALO_PARPADEO = 250; // 1/4 seg

enum estado_t { // Definimos este tipo de variable, para controlar la máquina de estados
  ESTADO_NO_PULSADO_LED_APAGADO,  // No está pulsado y el LED ha de estar apagado
  ESTADO_PULSADO_LED_ENCENDIDO,   // Está pulsado y el LED ha de estar encendido porque no ha "saltado" la alarma
  ESTADO_ALARMA_LED_ENCENDIDO,    // Está pulsado y el LED ha de estar encendido habiendo "saltado" la alarma
  ESTADO_ALARMA_LED_APAGADO       // Está pulsado y el LED ha de estar apagado habiendo "saltado" la alarma
};

struct Alarma {
  byte pinPulsador;
  byte pinLed;
  estado_t estado;
  unsigned long instanteUltimoCambioEstado;
};

struct Alarma alarma_1;
struct Alarma alarma_2;
struct Alarma alarma_3;

void alarma_setup(struct Alarma *alarma, byte pinPulsador, byte pinLed) {
  alarma->pinPulsador = pinPulsador;
  alarma->pinLed = pinLed;
  alarma->estado = ESTADO_NO_PULSADO_LED_APAGADO;  // Inicialmente se considera que no está pulsado
  alarma->instanteUltimoCambioEstado = 0; // Cuándo se ha realizado el último cambio de estado
  pinMode(alarma->pinPulsador, INPUT);
  pinMode(alarma->pinLed, OUTPUT);
  digitalWrite(alarma->pinLed, LOW); // Inicialmente nos aseguramos de que el LED esté apagado
}

void alarma_loop(struct Alarma *alarma) {
  if (digitalRead(alarma->pinPulsador)) {
    // Está pulsado
    if (alarma->estado == ESTADO_NO_PULSADO_LED_APAGADO) {
      // La máquina de estado dice que hasta ahora no estaba pulsado, así que cambiamos de estado y encendemos el LED
      alarma->estado = ESTADO_PULSADO_LED_ENCENDIDO;
      digitalWrite(alarma->pinLed, HIGH);
      alarma->instanteUltimoCambioEstado = millis(); // Guardamos el momento en que hemos cambiado de estado
    }
    else if (alarma->estado == ESTADO_PULSADO_LED_ENCENDIDO) {
      // La máquina de estado dice que hasta ahora estaba pulsado, pero no ha pasado el tiempo como para que salte la alarma (hasta ahora)
      if ( (millis() - alarma->instanteUltimoCambioEstado) >= INTERVALO_ALARMA ) {
        // Ha igualado o superado el tiempo definido para que "salte" la alarma, así que cambiamos de estado y apagamos el LED
        alarma->estado = ESTADO_ALARMA_LED_APAGADO;
        digitalWrite(alarma->pinLed, LOW);
        alarma->instanteUltimoCambioEstado = millis(); // Guardamos el momento en que hemos cambiado de estado
      }
    }
    else {
      // Si ha llegado hasta aquí es porque estal en ESTADO_ALARMA_LED_ENCENDIDO o ESTADO_ALARMA_LED_APAGADO
      if ( (millis() - alarma->instanteUltimoCambioEstado) >= INTERVALO_PARPADEO ) {
        // Ha igualado o superado el tiempo definido de parpadeo, así que hay que cambiar de estado
        if (alarma->estado == ESTADO_ALARMA_LED_APAGADO) {
          // El LED estaba apagado, así que toca encenderlo
          alarma->estado = ESTADO_ALARMA_LED_ENCENDIDO;
          digitalWrite(alarma->pinLed, HIGH);
        }
        else {
          // El LED estaba encendido, así que toca apagarlo
          alarma->estado = ESTADO_ALARMA_LED_APAGADO;
          digitalWrite(alarma->pinLed, LOW);
        }
        alarma->instanteUltimoCambioEstado = millis(); // Guardamos el momento en que hemos cambiado de estado
      }
    }
  }
  else {
    // No está pulsado
    if (alarma->estado != ESTADO_NO_PULSADO_LED_APAGADO) {
      // La máqina de estado a de cambiar de estado y asegurarse de que el LED está apagado
      alarma->estado = ESTADO_NO_PULSADO_LED_APAGADO;
      digitalWrite(alarma->pinLed, LOW);
      // Nota, en este caso no nos hace falta recordar en qué momento ha cambiado de estado porque para salir de este estado no depende del tiempo
    }
  }
}

void setup() {
  alarma_setup(&alarma_1, PIN_PULSADOR_1, PIN_LED_1);
  alarma_setup(&alarma_2, PIN_PULSADOR_2, PIN_LED_2);
  alarma_setup(&alarma_3, PIN_PULSADOR_3, PIN_LED_3);
}

void loop() {
  alarma_loop(&alarma_1);
  alarma_loop(&alarma_2);
  alarma_loop(&alarma_3);
}

Pero por desgracia esta solución, en este caso, no es gratis. Las constantes pinPulsador_X y pinLed_X no "gastaban" memoria RAM. Pero al formar parte de una estructura las variables pinPulsador y pinLed sí que "gastan" RAM. Es un mal menor si estamos dispuestos a "sacrificar" un poco de la escasa RAM que tiene los microcontroladores en aras de "simplificar" la implementación. Y no, no estoy de coña, cuando llegue a dónde quiero ir verán cómo queda todo.

1 Like

(El cuarto paso)

Hasta ahora sólo he usado las herramientas disponibles en C, pero esto es C++, así que vamos a usar algo de C++.

En C++ las estructuras no sólo pueden estar formada por variables (y otras estructuras anidadas), sino que también pueden tener funciones que pertenecen a la estructura. Así que, aprovechando esto, voy a "meter dentro de la estructura" las funciones alarma_setup() y alarma_loop.

Primero las añadimos en la declaración de la estructura quedando así:

struct Alarma {
  void alarma_setup(byte pinPulsador, byte pinLed);
  void alarma_loop();
  byte pinPulsador;
  byte pinLed;
  estado_t estado;
  unsigned long instanteUltimoCambioEstado;
};

Como la implementación de las funciones están fuera de la declaración, hay que indicar que esas funciones pertenecen a la estructura Alarma para ello se les antepone al nombre Alarma:: y quedan algo así:

void Alarma::alarma_setup(byte pinPulsador, byte pinLed) {
void Alarma::alarma_loop() {

No sé si se han dado cuenta de que ha desaparecido el parámetro struct Alarma *alarma. Pues sí, ha desaparecido. Ya no hace falta, ya que ahora las funciones forman parte de la estructura Alarma, así que estructura está accesible dentro de cada una de sus funciones... Pero está accesible a través de un puntero "especial" que siempre se llama this (en C++ this es una palabra reservada que sólo se puede utilizar como puntero al objeto al cual pertenece la función, y una estructura es un objeto).

Así que no hace falta pasar el puntero a la estructura porque automáticamente tenemos el puntero a la estructura cuyo nombre siempre es this. Entonces, en lugar de usar el puntero alarma, usamos el puntero this con lo que en todo lugar donde aparezca alarma-> debería de aparecer this->. Los que ya conocen el C++ han de saber que no es imprescindible en todos los casos y simplemente se podría quitar el alarma->, pero no lo recomiendo. A la larga interesa "ver" que estamos tratando con variables del objeto y no con otras variables "que pasaban por ahí". De hecho en la función alarma_setup() es imprescindible el this-> para poder diferenciar las variables de la estructura, de los parámetros de la función, ya que les he puesto adrede el mismo nombre (esto es peligroso si no pones el this-> correctamente, pero así no tengo que estar inventándome nombres nuevos y de paso lo pongo de ejmplo de lo que se puede hacer).

this->pinPulsador = pinPulsador;

En la línea anterior se le está asignando a la variable de la estructura (this->pinPulsador) el valor pasado como parámetro a la función (pinPulsador).

Pero, si ahora no hay que pasarle como parámetro la estructura a la función, ¿cómo sabe la función de qué estructura (objeto) se trata (alarma_1, alarma_2 o alarma_3)? Pues llamando a la función del objeto, no a la función "suelta":

  alarma_1.alarma_loop();
  alarma_2.alarma_loop();
  alarma_3.alarma_loop();

El programa ahora sería:

const byte PIN_LED_1 = 3;
const byte PIN_PULSADOR_1 = 4;

const byte PIN_LED_2 = 5;
const byte PIN_PULSADOR_2 = 6;

const byte PIN_LED_3 = 7;
const byte PIN_PULSADOR_3 = 8;

const unsigned long INTERVALO_ALARMA   = 5000; // 5 seg
const unsigned long INTERVALO_PARPADEO = 250; // 1/4 seg

enum estado_t { // Definimos este tipo de variable, para controlar la máquina de estados
  ESTADO_NO_PULSADO_LED_APAGADO,  // No está pulsado y el LED ha de estar apagado
  ESTADO_PULSADO_LED_ENCENDIDO,   // Está pulsado y el LED ha de estar encendido porque no ha "saltado" la alarma
  ESTADO_ALARMA_LED_ENCENDIDO,    // Está pulsado y el LED ha de estar encendido habiendo "saltado" la alarma
  ESTADO_ALARMA_LED_APAGADO       // Está pulsado y el LED ha de estar apagado habiendo "saltado" la alarma
};

struct Alarma {
  void alarma_setup(byte pinPulsador, byte pinLed);
  void alarma_loop();
  byte pinPulsador;
  byte pinLed;
  estado_t estado;
  unsigned long instanteUltimoCambioEstado;
};

struct Alarma alarma_1;
struct Alarma alarma_2;
struct Alarma alarma_3;

void Alarma::alarma_setup(byte pinPulsador, byte pinLed) {
  this->pinPulsador = pinPulsador;
  this->pinLed = pinLed;
  this->estado = ESTADO_NO_PULSADO_LED_APAGADO;  // Inicialmente se considera que no está pulsado
  this->instanteUltimoCambioEstado = 0; // Cuándo se ha realizado el último cambio de estado
  pinMode(this->pinPulsador, INPUT);
  pinMode(this->pinLed, OUTPUT);
  digitalWrite(this->pinLed, LOW); // Inicialmente nos aseguramos de que el LED esté apagado
}

void Alarma::alarma_loop() {
  if (digitalRead(this->pinPulsador)) {
    // Está pulsado
    if (this->estado == ESTADO_NO_PULSADO_LED_APAGADO) {
      // La máquina de estado dice que hasta ahora no estaba pulsado, así que cambiamos de estado y encendemos el LED
      this->estado = ESTADO_PULSADO_LED_ENCENDIDO;
      digitalWrite(this->pinLed, HIGH);
      this->instanteUltimoCambioEstado = millis(); // Guardamos el momento en que hemos cambiado de estado
    }
    else if (this->estado == ESTADO_PULSADO_LED_ENCENDIDO) {
      // La máquina de estado dice que hasta ahora estaba pulsado, pero no ha pasado el tiempo como para que salte la alarma (hasta ahora)
      if ( (millis() - this->instanteUltimoCambioEstado) >= INTERVALO_ALARMA ) {
        // Ha igualado o superado el tiempo definido para que "salte" la alarma, así que cambiamos de estado y apagamos el LED
        this->estado = ESTADO_ALARMA_LED_APAGADO;
        digitalWrite(this->pinLed, LOW);
        this->instanteUltimoCambioEstado = millis(); // Guardamos el momento en que hemos cambiado de estado
      }
    }
    else {
      // Si ha llegado hasta aquí es porque estal en ESTADO_ALARMA_LED_ENCENDIDO o ESTADO_ALARMA_LED_APAGADO
      if ( (millis() - this->instanteUltimoCambioEstado) >= INTERVALO_PARPADEO ) {
        // Ha igualado o superado el tiempo definido de parpadeo, así que hay que cambiar de estado
        if (this->estado == ESTADO_ALARMA_LED_APAGADO) {
          // El LED estaba apagado, así que toca encenderlo
          this->estado = ESTADO_ALARMA_LED_ENCENDIDO;
          digitalWrite(this->pinLed, HIGH);
        }
        else {
          // El LED estaba encendido, así que toca apagarlo
          this->estado = ESTADO_ALARMA_LED_APAGADO;
          digitalWrite(this->pinLed, LOW);
        }
        this->instanteUltimoCambioEstado = millis(); // Guardamos el momento en que hemos cambiado de estado
      }
    }
  }
  else {
    // No está pulsado
    if (this->estado != ESTADO_NO_PULSADO_LED_APAGADO) {
      // La máqina de estado a de cambiar de estado y asegurarse de que el LED está apagado
      this->estado = ESTADO_NO_PULSADO_LED_APAGADO;
      digitalWrite(this->pinLed, LOW);
      // Nota, en este caso no nos hace falta recordar en qué momento ha cambiado de estado porque para salir de este estado no depende del tiempo
    }
  }
}

void setup() {
  alarma_1.alarma_setup(PIN_PULSADOR_1, PIN_LED_1);
  alarma_2.alarma_setup(PIN_PULSADOR_2, PIN_LED_2);
  alarma_3.alarma_setup(PIN_PULSADOR_3, PIN_LED_3);
}

void loop() {
  alarma_1.alarma_loop();
  alarma_2.alarma_loop();
  alarma_3.alarma_loop();
}

Esto es parte del paradigma de la programación orientada a objetos: las variables y funciones pueden ser "propias" del objeto. Repito, en C++ una estructura es un objeto (bueno es una clase que por defecto todas sus variables y funciones son públicas).

1 Like

(El quinto paso)

Llegado a este punto resulta que tenemos que las funciones son propias de la estructura, al igual que las variables que la compone. Y así como se podía usar los mismos nombres para otras variables que no tenían nada que ver con la estructura, igualmente se puede usar el mismo nombre para una función interna de la estructura que para otra estructura o una función global. Dicho esto, ya no tiene sentido que las funciones se llamen alarma_setup y alarma_loop. El prefijo de alarma_ para mí está de más. Así que las puedo llamar setup y loop, igual que nuestras dos famosas funciones de Arduino, que no entrarán en conflicto ya que, por ejemplo, no sería lo mismo:

loop();
alarma_1.loop();

La primera llamará al loop() de toda la vida, mientras que la segunda llamada lo hará al de la estructura Alarma... Bueno, no es del todo cierto, si dentro de una función de la estructura se llama a la función loop(), se estará llamando a su fución loop(), no a la de Arduino. Para llamar al a función de Arduino, que es "ajena" a la estructura, tendría que llamar a la funcion así:

::loop();

Para más información... exacto, San Google.

Pero mira por dónde, aún no estoy contento del todo. Hay una cosa que tiene el C++ que son los llamados constructores. ¿Qué es eso? San Google, como siempre. Porque por ahora sólo me limitaré a explicar lo básico, muy básico.

A grandes rasgos un constructor es una función que se llama justo en el momento de instanciar el objeto ("instanciar un objeto" es la forma elegante de decir "crear una variable"). Y sirve para inicializar los valores internos a nuestro gusto en el momento de crearse.

¿Y cuándo se crea (instancia) el objeto? Pues cuando la ejecución del programa llega a la definición de la variable (objeto), que si se trata de uno global, se crea antes de ejecutar... Bueno, la función main() a la cual no tenemos acceso en Arduino, y es esta la que llama a setup() y loop(). Así que en el caso de los objetos globales sus constructores se ejecutan antes que el main().

Bueno, después de esta escueta introducción a los constructores (en realidad poco he explicado, así que a Google que vas), decir que los constructores (puede haber más de uno por estructura/clase) se han de llamar igual que la estructura (o clase) y admite parámetros como cualquier otra función. Pero no tienen tipo... y cuando digo que no tienen es que no tienen, ni tan siquiera son de tipo void. Así que como quiero hacer que la función hasta ahora conocida como alarma_setup pase a ser el constructor de la estructura, he de cambiar la declaración:

 void alarma_setup(byte pinPulsador, byte pinLed);

Por:

 Alarma(byte pinPulsador, byte pinLed);

El void ha desaparecido (repito que nunca el constructor ha de tener tipo).

¿Pero cómo se llama al constructor? Pues el constructor se llama cuando se instancia el objeto, y en este caso (puede haber otros casos) es cuando declaramos el objeto:

struct Alarma alarma_1;
struct Alarma alarma_2;
struct Alarma alarma_3;

Ahí es dónde se declaran las tres estructuras, así que ahí es donde "se llama" al constructor. Si no tuviera parámetros, o vamos a usar valores por defecto para todos sus parámetros, no habría que hacer nada. Pero este no es el caso, así que pongamos los parámetros para el constructor:

struct Alarma alarma_1(PIN_PULSADOR_1, PIN_LED_1);
struct Alarma alarma_2(PIN_PULSADOR_2, PIN_LED_2);
struct Alarma alarma_3(PIN_PULSADOR_3, PIN_LED_3);

Por supuesto, las llamada a alarma_setup ya no hace falta en la función setup() de Arduino, así que queda vacía.

El otro cambio que quería hacer es sencillo: cambiar toda aparición de alarma_loop por loop.

El código ahora queda así:

const byte PIN_LED_1 = 3;
const byte PIN_PULSADOR_1 = 4;

const byte PIN_LED_2 = 5;
const byte PIN_PULSADOR_2 = 6;

const byte PIN_LED_3 = 7;
const byte PIN_PULSADOR_3 = 8;

const unsigned long INTERVALO_ALARMA   = 5000; // 5 seg
const unsigned long INTERVALO_PARPADEO = 250; // 1/4 seg

enum estado_t { // Definimos este tipo de variable, para controlar la máquina de estados
  ESTADO_NO_PULSADO_LED_APAGADO,  // No está pulsado y el LED ha de estar apagado
  ESTADO_PULSADO_LED_ENCENDIDO,   // Está pulsado y el LED ha de estar encendido porque no ha "saltado" la alarma
  ESTADO_ALARMA_LED_ENCENDIDO,    // Está pulsado y el LED ha de estar encendido habiendo "saltado" la alarma
  ESTADO_ALARMA_LED_APAGADO       // Está pulsado y el LED ha de estar apagado habiendo "saltado" la alarma
};

struct Alarma {
  Alarma(byte pinPulsador, byte pinLed);
  void loop();
  byte pinPulsador;
  byte pinLed;
  estado_t estado;
  unsigned long instanteUltimoCambioEstado;
};

struct Alarma alarma_1(PIN_PULSADOR_1, PIN_LED_1);
struct Alarma alarma_2(PIN_PULSADOR_2, PIN_LED_2);
struct Alarma alarma_3(PIN_PULSADOR_3, PIN_LED_3);

Alarma::Alarma(byte pinPulsador, byte pinLed) {
  this->pinPulsador = pinPulsador;
  this->pinLed = pinLed;
  this->estado = ESTADO_NO_PULSADO_LED_APAGADO;  // Inicialmente se considera que no está pulsado
  this->instanteUltimoCambioEstado = 0; // Cuándo se ha realizado el último cambio de estado
  pinMode(this->pinPulsador, INPUT);
  pinMode(this->pinLed, OUTPUT);
  digitalWrite(this->pinLed, LOW); // Inicialmente nos aseguramos de que el LED esté apagado
}

void Alarma::loop() {
  if (digitalRead(this->pinPulsador)) {
    // Está pulsado
    if (this->estado == ESTADO_NO_PULSADO_LED_APAGADO) {
      // La máquina de estado dice que hasta ahora no estaba pulsado, así que cambiamos de estado y encendemos el LED
      this->estado = ESTADO_PULSADO_LED_ENCENDIDO;
      digitalWrite(this->pinLed, HIGH);
      this->instanteUltimoCambioEstado = millis(); // Guardamos el momento en que hemos cambiado de estado
    }
    else if (this->estado == ESTADO_PULSADO_LED_ENCENDIDO) {
      // La máquina de estado dice que hasta ahora estaba pulsado, pero no ha pasado el tiempo como para que salte la alarma (hasta ahora)
      if ( (millis() - this->instanteUltimoCambioEstado) >= INTERVALO_ALARMA ) {
        // Ha igualado o superado el tiempo definido para que "salte" la alarma, así que cambiamos de estado y apagamos el LED
        this->estado = ESTADO_ALARMA_LED_APAGADO;
        digitalWrite(this->pinLed, LOW);
        this->instanteUltimoCambioEstado = millis(); // Guardamos el momento en que hemos cambiado de estado
      }
    }
    else {
      // Si ha llegado hasta aquí es porque estal en ESTADO_ALARMA_LED_ENCENDIDO o ESTADO_ALARMA_LED_APAGADO
      if ( (millis() - this->instanteUltimoCambioEstado) >= INTERVALO_PARPADEO ) {
        // Ha igualado o superado el tiempo definido de parpadeo, así que hay que cambiar de estado
        if (this->estado == ESTADO_ALARMA_LED_APAGADO) {
          // El LED estaba apagado, así que toca encenderlo
          this->estado = ESTADO_ALARMA_LED_ENCENDIDO;
          digitalWrite(this->pinLed, HIGH);
        }
        else {
          // El LED estaba encendido, así que toca apagarlo
          this->estado = ESTADO_ALARMA_LED_APAGADO;
          digitalWrite(this->pinLed, LOW);
        }
        this->instanteUltimoCambioEstado = millis(); // Guardamos el momento en que hemos cambiado de estado
      }
    }
  }
  else {
    // No está pulsado
    if (this->estado != ESTADO_NO_PULSADO_LED_APAGADO) {
      // La máqina de estado a de cambiar de estado y asegurarse de que el LED está apagado
      this->estado = ESTADO_NO_PULSADO_LED_APAGADO;
      digitalWrite(this->pinLed, LOW);
      // Nota, en este caso no nos hace falta recordar en qué momento ha cambiado de estado porque para salir de este estado no depende del tiempo
    }
  }
}

void setup() {
}

void loop() {
  alarma_1.loop();
  alarma_2.loop();
  alarma_3.loop();
}

Recomiendo buscar más información sobre los constructores. Porque hay muchascosas que aquí no he contado que son "interesantes". Una de ellas es construir los elementos del la estructura/clase invocando directamente a sus respectivos constructores, sin necesidad de usarlos en el cuerpo del constructor (lo que va entre las llaves { }).

Una última cosa (que nunca está de más saber):

int a = 10;

es lo mismo que:

int a(10);

Pero no es lo mismo que:

a = 10;

Y las tres no tienen absolutamente nada que ver con:

a(10);

Ahí lo dejo...

2 Likes

(El sexto paso)

¡Qué pesaíto que soy! ¿Es que no me voy a cansar de dar la tabarra? Pues no. Así que si quieren me banean... o dejan de leer, que nadie les obliga.

Ahora voy ha hacer un cambio "de verdad". No veo muy práctico que todas las alarmas tarden el mismo tiempo en "saltar". No sé si es el caso de nano2400, pero por si algún día necesita que "salten" en intervalos diferentes cada alarma, e incluso si quiere que parpadeen los LEDs a diferentes frecuencias los unos de los otros, voy a añadir la parametrización de ambos tiempos independientes para cada alarma.

Primeramente definiré las constantes con el tiempo que quiero para cada una de ellas (esto, en particular, no gasta memoria de ningún tipo, ni de programa ni RAM, ya que el compilador al final sólo incluirá los números en el programa, el resto de información es para "tener claro", el compilador y nosotros, de qué se trata):

const unsigned long INTERVALO_ALARMA_1   = 5000; // 5 seg
const unsigned long INTERVALO_PARPADEO_1 = 250; // 1/4 seg

const unsigned long INTERVALO_ALARMA_2   = 4000; // 4 seg
const unsigned long INTERVALO_PARPADEO_2 = 100; // 1/10 seg

const unsigned long INTERVALO_ALARMA_3   = 6000; // 6 seg
const unsigned long INTERVALO_PARPADEO_3 = 500; // 1/2 seg

Añado al final de la estructura dos nuevos campos (esto sí que consume memoria RAM por cada objeto instanciado):

  unsigned long intervaloAlarma;
  unsigned long intervaloParpadeo;

Como necesito asignar e inicializar estas variables en algún momento, y me viene bien hacerlo en el constructor, añado al constructor el tratamiento de estas variables. La declaración del constructor pasa a ser:

  Alarma(byte pinPulsador, byte pinLed, unsigned long intervaloAlarma, unsigned long intervaloParpadeo);

Por supuesto, hay que cambiar la implementación del constructor (ver el código completo).

Y por último cambiar la función loop() de la estructura alarma y sustituir INTERVALO_ALARMA por this->intervaloAlarma e INTERVALO_PARPADEO por this->intervaloParpadeo.

Ya está, no hace falta más. Aquí el código:

const byte PIN_LED_1 = 3;
const byte PIN_PULSADOR_1 = 4;

const byte PIN_LED_2 = 5;
const byte PIN_PULSADOR_2 = 6;

const byte PIN_LED_3 = 7;
const byte PIN_PULSADOR_3 = 8;

const unsigned long INTERVALO_ALARMA_1   = 5000; // 5 seg
const unsigned long INTERVALO_PARPADEO_1 = 250; // 1/4 seg

const unsigned long INTERVALO_ALARMA_2   = 4000; // 4 seg
const unsigned long INTERVALO_PARPADEO_2 = 100; // 1/10 seg

const unsigned long INTERVALO_ALARMA_3   = 6000; // 6 seg
const unsigned long INTERVALO_PARPADEO_3 = 500; // 1/2 seg

enum estado_t { // Definimos este tipo de variable, para controlar la máquina de estados
  ESTADO_NO_PULSADO_LED_APAGADO,  // No está pulsado y el LED ha de estar apagado
  ESTADO_PULSADO_LED_ENCENDIDO,   // Está pulsado y el LED ha de estar encendido porque no ha "saltado" la alarma
  ESTADO_ALARMA_LED_ENCENDIDO,    // Está pulsado y el LED ha de estar encendido habiendo "saltado" la alarma
  ESTADO_ALARMA_LED_APAGADO       // Está pulsado y el LED ha de estar apagado habiendo "saltado" la alarma
};

struct Alarma {
  Alarma(byte pinPulsador, byte pinLed, unsigned long intervaloAlarma, unsigned long intervaloParpadeo);
  void loop();
  byte pinPulsador;
  byte pinLed;
  estado_t estado;
  unsigned long instanteUltimoCambioEstado;
  unsigned long intervaloAlarma;
  unsigned long intervaloParpadeo;
};

Alarma alarma_1(PIN_PULSADOR_1, PIN_LED_1, INTERVALO_ALARMA_1, INTERVALO_PARPADEO_1);
Alarma alarma_2(PIN_PULSADOR_2, PIN_LED_2, INTERVALO_ALARMA_2, INTERVALO_PARPADEO_2);
Alarma alarma_3(PIN_PULSADOR_3, PIN_LED_3, INTERVALO_ALARMA_3, INTERVALO_PARPADEO_3);

Alarma::Alarma(byte pinPulsador, byte pinLed, unsigned long intervaloAlarma, unsigned long intervaloParpadeo) {
  this->pinPulsador = pinPulsador;
  this->pinLed = pinLed;
  this->estado = ESTADO_NO_PULSADO_LED_APAGADO;  // Inicialmente se considera que no está pulsado
  this->instanteUltimoCambioEstado = 0; // Cuándo se ha realizado el último cambio de estado
  this->intervaloAlarma = intervaloAlarma;
  this->intervaloParpadeo = intervaloParpadeo;
  pinMode(this->pinPulsador, INPUT);
  pinMode(this->pinLed, OUTPUT);
  digitalWrite(this->pinLed, LOW); // Inicialmente nos aseguramos de que el LED esté apagado
}

void Alarma::loop() {
  if (digitalRead(this->pinPulsador)) {
    // Está pulsado
    if (this->estado == ESTADO_NO_PULSADO_LED_APAGADO) {
      // La máquina de estado dice que hasta ahora no estaba pulsado, así que cambiamos de estado y encendemos el LED
      this->estado = ESTADO_PULSADO_LED_ENCENDIDO;
      digitalWrite(this->pinLed, HIGH);
      this->instanteUltimoCambioEstado = millis(); // Guardamos el momento en que hemos cambiado de estado
    }
    else if (this->estado == ESTADO_PULSADO_LED_ENCENDIDO) {
      // La máquina de estado dice que hasta ahora estaba pulsado, pero no ha pasado el tiempo como para que salte la alarma (hasta ahora)
      if ( (millis() - this->instanteUltimoCambioEstado) >= this->intervaloAlarma ) {
        // Ha igualado o superado el tiempo definido para que "salte" la alarma, así que cambiamos de estado y apagamos el LED
        this->estado = ESTADO_ALARMA_LED_APAGADO;
        digitalWrite(this->pinLed, LOW);
        this->instanteUltimoCambioEstado = millis(); // Guardamos el momento en que hemos cambiado de estado
      }
    }
    else {
      // Si ha llegado hasta aquí es porque estal en ESTADO_ALARMA_LED_ENCENDIDO o ESTADO_ALARMA_LED_APAGADO
      if ( (millis() - this->instanteUltimoCambioEstado) >= this->intervaloParpadeo ) {
        // Ha igualado o superado el tiempo definido de parpadeo, así que hay que cambiar de estado
        if (this->estado == ESTADO_ALARMA_LED_APAGADO) {
          // El LED estaba apagado, así que toca encenderlo
          this->estado = ESTADO_ALARMA_LED_ENCENDIDO;
          digitalWrite(this->pinLed, HIGH);
        }
        else {
          // El LED estaba encendido, así que toca apagarlo
          this->estado = ESTADO_ALARMA_LED_APAGADO;
          digitalWrite(this->pinLed, LOW);
        }
        this->instanteUltimoCambioEstado = millis(); // Guardamos el momento en que hemos cambiado de estado
      }
    }
  }
  else {
    // No está pulsado
    if (this->estado != ESTADO_NO_PULSADO_LED_APAGADO) {
      // La máqina de estado a de cambiar de estado y asegurarse de que el LED está apagado
      this->estado = ESTADO_NO_PULSADO_LED_APAGADO;
      digitalWrite(this->pinLed, LOW);
      // Nota, en este caso no nos hace falta recordar en qué momento ha cambiado de estado porque para salir de este estado no depende del tiempo
    }
  }
}

void setup() {
}

void loop() {
  alarma_1.loop();
  alarma_2.loop();
  alarma_3.loop();
}

Un pequeño detalle, no hace falta poner struct al declarar el objeto, así que:

struct Alarma alarma_1(PIN_PULSADOR_1, PIN_LED_1, INTERVALO_ALARMA_1, INTERVALO_PARPADEO_1);

Ha quedado así:

Alarma alarma_1(PIN_PULSADOR_1, PIN_LED_1, INTERVALO_ALARMA_1, INTERVALO_PARPADEO_1);
2 Likes

(El séptimo y último paso. ¡Por fin!)

Vamos a crear nuestra pequeña librería. Para ello vamos a dividir el programa en tres ficheros. Uno seguirá el .ino que teníamos hasta ahora. Y los otros dos serán un .h y un .cpp con la "librería". (He de confesar que no sé cómo se crean estos ficheros con el IDE de Arduino ya que utilizo otras herramientas).

Básicamente se trata de poner las diferentes partes que declaran e implementan Alarma en otros ficheros. Para empezar creamos un fichero alarma.h con la declaración del enumerado estado_t y de la estructura clase Alarma. ¿Clase? Sí, clase. Me he cansado de la estructura, la he cambiado y la he convertido en una clase.

¿Qué diferencia hay entre una estructura y una clase en C++? Pues, que yo sepa, la única diferencia es que las funciones y las variables en las estructuras son por defecto públicas y en las clases son por defecto privadas. ¿Qué es eso de público y privado? Son otra característica de la programación orientada a objeto, la encapsulación. Lo que se declare como público puede ser accesible directamente "por otros", mientras que lo privado no. Desde una función ajena a Alarma podíamos, hasta ahora, llamar a las funciones de Alarma, así como también podíamos cambiar o leer directamente alguna de sus variables. Pero si las hacemos privadas sólo se podrá hacer desde las funciones propias de Alarma. Esto se hace para evitar que accidentalmente hagamos cosas que nos perjudiquen. Con sólo ver qué se ha declarado como público ya sabemos qué podemos usar de Alarma. Lo que sea privado no debemos usarlo desde "fuera". Para ello ponemos las etiquetas public: o private:, según convenga, para indicar que las funciones o variables que hay a continuación son públicas o privadas.

Así que para convertirla en clase, hemos de cambiar donde pone struct por class y poner las etiquetas public: y private: y nada más.

A alarma.h también hay que añadirle unas cuantas directivas para el preprocesador por "cuestiones técnicas". Son esas líneas que empiezan con un #.

El código de alarma.h quedaría:

#ifndef ALARMA_H
#define ALARMA_H

#if (ARDUINO >= 100)
 #include <Arduino.h>
#else
 #include <WProgram.h>
 #include <pins_arduino.h>
#endif

enum estado_t { // Definimos este tipo de variable, para controlar la máquina de estados
  ESTADO_NO_PULSADO_LED_APAGADO,  // No está pulsado y el LED ha de estar apagado
  ESTADO_PULSADO_LED_ENCENDIDO,   // Está pulsado y el LED ha de estar encendido porque no ha "saltado" la alarma
  ESTADO_ALARMA_LED_ENCENDIDO,    // Está pulsado y el LED ha de estar encendido habiendo "saltado" la alarma
  ESTADO_ALARMA_LED_APAGADO       // Está pulsado y el LED ha de estar apagado habiendo "saltado" la alarma
};

class Alarma {
  public:
    Alarma(byte pinPulsador, byte pinLed, unsigned long intervaloAlarma, unsigned long intervaloParpadeo); // Constructor
    void loop();
  private:
    byte pinPulsador;
    byte pinLed;
    estado_t estado;
    unsigned long instanteUltimoCambioEstado;
    unsigned long intervaloAlarma;
    unsigned long intervaloParpadeo;
};

#endif // ALARMA_H

En otro fichero (alarma.cpp) ponemos la implementación de la clase Alarma. Lo primero sería poner la directiva del preprocesador que incluye a alarma.h (#include "alarma.h"). El contenido de alarma.cpp sería:

#include "alarma.h"

Alarma::Alarma(byte pinPulsador, byte pinLed, unsigned long intervaloAlarma, unsigned long intervaloParpadeo) {
  this->pinPulsador = pinPulsador;
  this->pinLed = pinLed;
  this->estado = ESTADO_NO_PULSADO_LED_APAGADO;  // Inicialmente se considera que no está pulsado
  this->instanteUltimoCambioEstado = 0; // Cuándo se ha realizado el último cambio de estado
  this->intervaloAlarma = intervaloAlarma;
  this->intervaloParpadeo = intervaloParpadeo;
  pinMode(this->pinPulsador, INPUT);
  pinMode(this->pinLed, OUTPUT);
  digitalWrite(this->pinLed, LOW); // Inicialmente nos aseguramos de que el LED esté apagado
}

void Alarma::loop() {
  if (digitalRead(this->pinPulsador)) {
    // Está pulsado
    if (this->estado == ESTADO_NO_PULSADO_LED_APAGADO) {
      // La máquina de estado dice que hasta ahora no estaba pulsado, así que cambiamos de estado y encendemos el LED
      this->estado = ESTADO_PULSADO_LED_ENCENDIDO;
      digitalWrite(this->pinLed, HIGH);
      this->instanteUltimoCambioEstado = millis(); // Guardamos el momento en que hemos cambiado de estado
    }
    else if (this->estado == ESTADO_PULSADO_LED_ENCENDIDO) {
      // La máquina de estado dice que hasta ahora estaba pulsado, pero no ha pasado el tiempo como para que salte la alarma (hasta ahora)
      if ( (millis() - this->instanteUltimoCambioEstado) >= this->intervaloAlarma ) {
        // Ha igualado o superado el tiempo definido para que "salte" la alarma, así que cambiamos de estado y apagamos el LED
        this->estado = ESTADO_ALARMA_LED_APAGADO;
        digitalWrite(this->pinLed, LOW);
        this->instanteUltimoCambioEstado = millis(); // Guardamos el momento en que hemos cambiado de estado
      }
    }
    else {
      // Si ha llegado hasta aquí es porque estal en ESTADO_ALARMA_LED_ENCENDIDO o ESTADO_ALARMA_LED_APAGADO
      if ( (millis() - this->instanteUltimoCambioEstado) >= this->intervaloParpadeo ) {
        // Ha igualado o superado el tiempo definido de parpadeo, así que hay que cambiar de estado
        if (this->estado == ESTADO_ALARMA_LED_APAGADO) {
          // El LED estaba apagado, así que toca encenderlo
          this->estado = ESTADO_ALARMA_LED_ENCENDIDO;
          digitalWrite(this->pinLed, HIGH);
        }
        else {
          // El LED estaba encendido, así que toca apagarlo
          this->estado = ESTADO_ALARMA_LED_APAGADO;
          digitalWrite(this->pinLed, LOW);
        }
        this->instanteUltimoCambioEstado = millis(); // Guardamos el momento en que hemos cambiado de estado
      }
    }
  }
  else {
    // No está pulsado
    if (this->estado != ESTADO_NO_PULSADO_LED_APAGADO) {
      // La máqina de estado a de cambiar de estado y asegurarse de que el LED está apagado
      this->estado = ESTADO_NO_PULSADO_LED_APAGADO;
      digitalWrite(this->pinLed, LOW);
      // Nota, en este caso no nos hace falta recordar en qué momento ha cambiado de estado porque para salir de este estado no depende del tiempo
    }
  }
}

Nos quedaría en el fichero .ino todo aquello que no hemos puesto en los dos ficheros anteriores. Con un #include "alarma.h" al principio.

Tanta constante hace "que los arboles no dejen ver el bosque" y esta vez, aunque no es muy recomendable, voy a quitar las constantes y poner las declaraciones de las alarmas directamente con los valores que queremos y un comentario al lado que nos indica qué es cada parámetro. El .ino quedaría así:

#include "alarma.h"

Alarma alarma_1(4, 3, 5000, 250); // (pinPulsador, pinLed, intervaloAlarma, intervaloParpadeo)
Alarma alarma_2(6, 5, 4000, 100); // (pinPulsador, pinLed, intervaloAlarma, intervaloParpadeo)
Alarma alarma_3(8, 7, 6000, 500); // (pinPulsador, pinLed, intervaloAlarma, intervaloParpadeo)

void setup() {
}

void loop() {
  alarma_1.loop();
  alarma_2.loop();
  alarma_3.loop();
}

En una clase se pueden declarar variables de los "tipos básicos" así como de otras clases. Por ejemplo la clase Alarma podría tener un objeto de la clase Servo si fuera necesario (sí, Servo es una clase que tiene variables y funciones propias).

Y así es cómo lo haría yo... Bueno no tal cual lo he descrito, sino que desde el principio empiezo a definir una o varias clases con lo que creo que voy a necesitar y añadiendo según necesito.

El .ino han quedado relativamente limpio. Esta técnica nos permite continuar añadiendo "fácilmente" cosas a nuestro programa. ¿De verdad que vale la pena todo esto? Eso lo tendrá que decidir cada cual. Bjarne Stroustrup cree que sí (esto último es para hacerme el enterao).

Perdón por el tostón. Espero que le sirva de ayuda y orientación a alguien. Si quieren corregir, aclarar, complementar, preguntar o consultar dudas, si es posible, que sea por el foro y no por privado para así todos podamos ser partícipes.

alarma.h (1.01 KB)

alarma.cpp (2.95 KB)

pulsadores.ino (398 Bytes)

2 Likes

leo cada parte de tu respuesta y me sorprendo con todo lo que sabes, voy a estudiar mas para comprender mejor cada linea de tu código, y les contare como me fue. me parece super programar pero se que me falta es mucho y mas cuando estas en un pais que programar no esta en una de tus materias de escuela o colegio y lo que ves en la universidad es poco. agradezco por esta colaboración y desearía seguir cada uno de sus pasos para aprender mas.

donde puede obtener mas información de como aprender a programar mas rápido porque de verdad que esto me encanta