Medir tiempo de duración de una condición

Buenos días a todos. Estoy instalando un sensor UV junto a una lámpara UVC para detectar el momento en que ésta ha perdido intensidad de emisión de UV (final de su vida útil) y usando un buzzer como alerta.
Con el código que copio abajo he logrado que el buzzer se active cuando la emisión de UV baja hasta cierto nivel crítico, sin embargo hay situaciones en que la lámpara emitirá radiación dentro de este nivel crítico sin estar estropeada (por ej. los primeros minutos después de encenderla o, poco antes de llegar al fin de su vida útil). En estos casos, se encenderá el buzzer por un lapso muy corto o intermitentemente y me dará una falsa alarma.
Lo que busco es que el buzzer solamente se encienda si la condición de emisión de UV se encuentra bajo el nivel crítico durante, por ej. 10 minutos seguidos (600,000 ms)
Entiendo que se debería de usar la función millis()
El código que he hecho es este:

int SENSOR;
float SUMA;

void setup() {
  Serial.begin(9600);
  pinMode(2,OUTPUT);
}

void loop() {
  // read the input on analog pin 0:
  SUMA=0;
  for(int i=0;i<5000;i++){
    SENSOR = analogRead(A0);
     SUMA= (SENSOR+SUMA);}
      delay(1);
// print out the value you read:
  Serial.println(SUMA*10/5000);
      
   if((SUMA*10/5000)<20){   //Nivel debajo de 20, enciende el buzzer
    digitalWrite(2,HIGH);}
    else{digitalWrite(2,LOW);} // y si es mayor, se mantiene apagado.

Creo que la modificación (o la ampliación del código) debería de estar principalmente en el if.

Gracias desde ya por su valiosa colaboración. Saludos.

Su publicación se trasladó a su ubicación actual, ya que es más adecuado.

¿Podría también tomar unos momentos para aprender a usar el foro?

Otra ayuda general y consejos de solución de problemas se pueden encontrar aquí.
Te ayudará a obtener lo mejor del Foro en el futuro.

Ve documentación y lee los tutoriales de millis().
Este es uno pero hay 4 mas

1 Like

Gracias, buen dato. Está muy bueno!

Lo que buscas se llama retraso al encendido.

Se trata de conseguir que cuando la condición sea falsa el temporizador no cuente, para ello tenemos varias opciones, pero quizás la mas sencilla es que la variable donde se guarda el valor del temporizador sea 0.

Cuando la condición se cumple, comprobamos si la variable de nuestro temporizador es cero, significando que la condición hasta ahora era falsa. En este caso hemos de guardar en la variable del temporizador el tiempo que indica millis.

Igualmente puede ocurrir que la condición se cumpla y la variable del temporizador no sea cero. Esto significa que anteriormente la condición se cumplió y ahora debemos comparar con el tiempo que queremos que dure.

Como es una operación muy útil, decidí en su momento crear una función de retraso al encendido que pudiera utilizar en mis programas muy a menudo.

Te la dejo aquí:

unsigned int onDelayTimer(unsigned int condicion, unsigned long &timer, unsigned long duracion) {
  if (condicion == 0) {
    timer = 0;
    return 0;                    
  }
  else {
    if (timer == 0) {               
      timer = millis();             
      return   0;                 
    }
    else {             
      if (millis() - timer > duracion ) return 1;  
      else return 0;
    }
  }                                                                            
}

Ahora tan solo falta definir una variable de temporizado y usarla en el programa:

unsigned long t; // Variable de temporizador.

void loop() {
  if ( onDelayTimer( valorUV < valorCritico, t, 600000) ) {
    encenderBuzzer();
  }
 

Si hay que añadir algún "pero" a la función es que no recuerda, si la condición es cierta inicia la cuenta, pero si antes de acabar el tiempo la condición es falsa, cuando se vuelva a cumplir empezará de 0.

Muchas gracias por tu explicación!! Soy muy novato aún y me es difícil extrapolar tu función de retraso a mi código. Su puedes tú o alguien más, que me ayude a ver cómo y en qué parte de mi código pongo la función y la variable y qué debo eliminar de mi código también. Gracias!!

Tu código quedaría mas o menos así:

int   sensor;
float suma;

// Variable para el temporizador.
unsigned long timer;

void setup() {
  Serial.begin(9600);
  pinMode(2, OUTPUT);
}

void loop() {
  suma = 0;
  for (int i=0; i<5000; i++) {
    sensor = analogRead(A0);
    suma = sensor + suma;
  }
  delay(1);
  Serial.println(suma*10/5000);
  if ( onDelayTimer( suma*10/5000 < 20, timer, 60000) ) 
    digitalWrite(2, HIGH);
  else
    digitalWrite(2, LOW);
}

Aunque hay cosas que se deben de mejorar:

  • Cuando lees el sensor tomas 5000 muestras y haces la media. Me parecen muchas lecturas y va a hacer lento el código.
  • El delay sobra. Huye de delay.
  • Si haces un calculo muchas veces, quizas sea conveniente usar una variable para guardar el cálculo. Por ejemplo, en tu caso multiplicas por 10 y divides por 5000 la variable suma en un par de sitios. Crea una variable llamada "media" y guarda el cálculo en ella para usar esa variable.

Ya ves que la función no presenta mucho problema.

1 Like

Comencé pensando que esto estaba mal

int   sensor;
float suma;

y que debería ser en su lugar

int   sensor;
unsigned long suma;

Para mi no tiene sentido usar float para tener un sumador de números enteros.
La mejor opción es long o unsigned long (entero largo sin signo).

Cuando comencé esta observación creí que con 5000 valores habría diferencias entre float y long o ulong pero veo que la falla ocurre para 20000 valores.
Te dejo el código de pruebas para que observes de qué hablo

#include <Arduino.h>

int   sensor;
float suma;
unsigned long suma2;
// Variable para el temporizador.
unsigned long timer;
void setup() {
  unsigned int i;
 
  Serial.begin(9600);
  suma = 0.0;
  suma2 = 0;
  for (i=0; i<50000; i++) {
    sensor = 1023.0;
    suma  = sensor + suma;
    suma2 = sensor + suma2;
    if (i%10000 == 0) {
      Serial.println("                i = : "+ String(i));
      Serial.println("Resultado con float : "+ String(suma));
      Serial.println("Resultado con ulong : "+ String(suma2));
    }
  }
  Serial.println("                i = : "+ String(i));
  Serial.println("Resultado con float : "+ String(suma));
  Serial.println("Resultado con ulong : "+ String(suma2));
}
void loop() {
}

Como dije es un temor infundado, al menos funciona bien hasta 20000 valores. Veras que para 10000 ambos coinciden pero para 20k no. Obviamente la representación flotante falla. suma2 en cambio tiene el valor correcto 1023x20000= 20460000
image
Y solo por curiosidad a los 16401 valores es que float y unsigned long dan valores diferentes, qué sorpresa para mi!!! Hubiera jurado que era antes, pero punto para float.
image

Ahora si, olvida tomar 5000 valores porque es francamente es despropósito.
Si algo requiere 5000 muestras es porque esta muy lleno de ruido y espero no sea tu caso.

Usa esto, la mediana y el filtro Mediana de Luis Llamas es muy bueno.
Si necesitas ayuda para implementarlo no habrá problemas.

Absolutas gracias, Víctor. Anduve en otros temas y recién me conecto de nuevo con el foro. Lo de las 5,000 muestras, era pensando en promediar 5 segundos pero, si bajo el número de muestras no debería haber demasiada diferencia. Igual, la fuente de UV que debo medir es una lámpara UVC china genérica y es ultra ruidosa. Quizá con 2000 funcione bien, voy a probar.
Sobre el delay no estaba seguro, la idea era estandarizar al milisegundo y la lectura sea más pareja, porque, libres, los tiempos corren un poco irregulares, me parece.
Voy a ver lo de la variable "media" para economizar, simplificar y hacer el código más pulcro. Una pregunta: Dónde y cómo debería declarar "onDelayTimer"? No lo veo declarado en el código. Gracias de nuevo por los comentarios. Te contaré cómo me fue con tu aporte.

Muchas gracias, Surbyte, por los comentarios. Puse float porque los valores que me entrega el sensor analógico vienen con decimales y luego los multiplico x 10 para trabajar más cómodamente. Voy a analizar lo de usar unsigned long versus float a ver si lo alcanzo a entender mejor.
Lo de 5,000 es porque la fuente que debo leer es muy ruidosa, es una lampara UVC genérica. pero, como dije en mi repuesta a VictorJam, voy a probar con 2000 porque, en principio, no debería de haber una diferencia sustancial. Voy a ver cómo me va con el filtro mediana de Luis Llamas, Gracias de nuevo y vemos cómo me va y, seguro te molestaré de nuevo por aclaraciones. Es que recién estoy empezando con Arduino y muchas coas son todavía "chino" para mi,

Ya te he comentado que el ruido no se elimina con promedio tan pesado como el que usas, mejora con filtros pensados estadísticamente para eleminarlo.
La opcion de la mediana no media (promedio), es mucho mejor que el promedio.
Ver el post#8 al final.

1 Like

Gracias Surbyte por la información. Es cierto una mediana corrige mejor los picos que un promedio. Me descargué la librería MedianFilter de Llamas que me recomendaste, pero parece que no estoy en el nivel de conocimiento y experiencia para implementarlo en mi pequeño código. Si me das algún alcance, bienvenido.

Esto compila y tiene incorporado el fitro mediana, a ver si funciona como se espera.

#include <MedianFilterLib.h>

int   sensor;
float suma;
int values[20];
size_t valuesLength = 20;
int median;

// Variable para el temporizador.
unsigned long timer;

unsigned int onDelayTimer(unsigned int condicion, unsigned long &timer, unsigned long duracion) {
  if (condicion == 0) {
    timer = 0;
    return 0;                    
  }
  else {
    if (timer == 0) {               
      timer = millis();             
      return   0;                 
    }
    else {             
      if (millis() - timer > duracion ) return 1;  
      else return 0;
    }
  }                                                                            
}

MedianFilter<int> medianFilter(10);  // planteada una ventana de 10 elementos

void setup() {
  Serial.begin(9600);
  pinMode(2, OUTPUT);
}

void loop() {
  suma = 0;
  float timeMean = 0;
	for (size_t iCount = 0; iCount < valuesLength; iCount++) {
      int rawMeasure = analogRead(A0);
      unsigned long timeCount = micros();
      median = medianFilter.AddValue(rawMeasure);
      Serial.print(rawMeasure);
		  Serial.print(",");
		  Serial.println(median);
  }
  if ( onDelayTimer( median < 20, timer, 60000) ) 
      digitalWrite(2, HIGH);
  else
      digitalWrite(2, LOW);
}

He planteado una ventana de 10 elementos sobre 20 posibles, pero eso puede variar, lee la nota de Luis Llamas y ajustalo a tu necesidad.