Ir al contenido

Temporizadores, interrupciones y código sin bloqueo: por qué delay() rompe el scan de un PLC

delay() es lo primero que enseña cualquier tutorial de Arduino y el primer hábito que un programa de PLC debe abandonar. En la estación de bombeo necesitas esperar antes de declarar un fallo y seguir leyendo la parada de emergencia mientras esperas. Esta lección te muestra cómo.

Qué aprenderás

  • Explicar por qué delay() deja sordo y mudo a un PLC.
  • Temporizar acciones con millis() sin bloquear.
  • Usar una interrupción en una entrada con capacidad de interrupción para una señal que no puedes permitirte perder, manteniendo el manejador corto.

Por qué delay() es un problema

delay() bloquea todo. Mientras se ejecuta, tu programa no puede leer una entrada nueva, reaccionar a un fallo ni responder a una petición Modbus. Un delay() de un segundo deja al PLC sordo y mudo durante un segundo completo en cada pasada, incluida la parada de emergencia.

Temporizar sin bloquear: millis()

millis() devuelve los milisegundos desde que arrancó la placa y no bloquea nada. Compararlo con una marca de tiempo guardada permite ejecutar código según un calendario mientras loop() sigue ejecutándose.

unsigned long lastToggle = 0;
const unsigned long interval = 1000;

void setup() {
  pinMode(Q0_1, OUTPUT);
}

void loop() {
  if (millis() - lastToggle >= interval) {
    lastToggle = millis();
    digitalWrite(Q0_1, !digitalRead(Q0_1));
  }

  // el resto de loop() se ejecuta en cada pasada, p. ej. la máquina de estados
}

Resultado esperado: el piloto de alarma de Q0_1 parpadea cada segundo mientras el resto del programa sigue funcionando. Usa este patrón para un piloto de fallo en el estado FAULT de la lección anterior.

Resta la marca guardada al tiempo actual, como arriba, y no al revés: esta forma sigue funcionando correctamente cuando el contador de millis() se reinicia tras unos 49 días.

Un timeout en la estación de bombeo

El mismo patrón implementa un timeout. Si la bomba funciona y el nivel no sube en un tiempo dado, la máquina pasa a FAULT. Guardas cuándo empezó el estado y lo comparas en cada pasada, sin delay().

unsigned long runningSince = 0;
const unsigned long maxRunTime = 30000;  // 30 s sin resultado

// al entrar en RUNNING: runningSince = millis();
// dentro de case RUNNING:
if (millis() - runningSince >= maxRunTime) {
  currentState = FAULT;
}

Interrupciones: reaccionar de inmediato a una señal

Una interrupción pausa loop() para ejecutar una función corta en el instante en que cambia una señal. Encaja en eventos que no pueden esperar a la siguiente pasada, como un pulso rápido de un encoder. No todas las entradas pueden generarla. En la familia M-Duino, dos entradas por zona admiten interrupción:

ZonaShield analógico/digitalShield de relés
BI0_5, I0_6I0_0, I0_1
CI1_5, I1_6I1_0, I1_1
DI2_5, I2_6I2_0, I2_1

Qué zonas existen depende de tu modelo, así que consulta la tabla de interrupciones de la guía de usuario del M-Duino (sección 5.4). En la familia Ardbox, solo I0_0 admite interrupción. Una entrada que no admita interrupción no la generará.

volatile bool stopRequested = false;

void onStopSignal() {
  stopRequested = true;
}

void setup() {
  // I0_6 admite interrupción en el shield analógico/digital (zona B)
  pinMode(I0_6, INPUT);
  attachInterrupt(digitalPinToInterrupt(I0_6), onStopSignal, RISING);
}

Mantén los manejadores de interrupción muy cortos y limita su trabajo a activar una bandera, como arriba. Haz el trabajo real en loop(), donde es seguro usar el resto del estado del programa.

¿millis() o una interrupción?

millis() encaja en todo lo que sigue un calendario: parpadeos, sondear un sensor, un timeout de estado. Una interrupción encaja en todo lo que debe capturarse en el instante en que ocurre. La mayoría de programas de PLC usan ambas con señales distintas.

Para profundizar

Siguiente lección: exponer el estado de la estación de bombeo por Modbus. Haz primero el cuestionario de abajo.

Calificación
0 0

No hay comentarios por ahora.

para ser el primero en comentar.