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

(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