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.

Gracias, Surbyte, he estado haciendo múltiples pruebas con tu código. Compila perfecto y funciona, pero me surgieron algunas dudas:
Me he centrado en el for para comparar tus resultados con los míos (Me interesó esto a pesar de que mi interés principal en la consulta iba por ponerle un timing a la condición para que se cumpla al final de cierto lapso)

  1. La mediana que arroja el medianFilter me resulta menos útil para estabilizar los picos que el promedio, al menos aparentemente. He igualado algunas variables análogas: De ventana le puse 11 para que no tenga que promediar las dos cifras centrales). En mi código que usa promedio, usé también 11 como número para la iteración.

Por otra parte, multipliqué por 10 el rawMeasure y la mediana para emparejar con los datos de mi código que también multiplicaba por 10 el analogRead y la SUMA con el objetivo de tener números más cómodos.
Comparé los resulatdos en el Serial Plotter y salió esta diferencia:


Copio el for de los dos códigos que ajusté para comparar:
Mediana:
image

Promedio:
image
He analizado en excel segmentos de resultados de 10 segundos tomados al azar tanto para mediana como para promedio y pude ver que los resultados de la mediana tienen un comportamiento cambiante en diferentes momentos mientras que en esos mismos momentos los resultados de el promedio no mostraban diferencias de comprtamiento.
Esos cambios me llevan a no poder definir acertadamente el nivel al que debe bajar la señal del sensor UV para accionar el buzzer.
Es posible que algo haya hecho mal. Quizá tengas alguna pista.
Gracias de nuevo por tu aporte, me ha permitido sumergirme en ámbitos totalmente desconocidos para mi.

Tienes razón, me he desviado de tu objetivo que es informar cuando la lámpara
emita por debajo del nivel crítico durante, por ej. 10 minutos seguidos (600,000 ms) tal como lo dicen el post#1.
De todos modos saber que estas leyendo valores aceptablemente buenos es importante.
No entiendo porque tienes esos saltos, pense que eran algunos pero no que todo es asi.

Porque no haces algo como loguear información y si detectas la situacion mencionada la comentas. Con esos datos podemos evaluar como filtrar mejor la información UV y proveerte de una solucion mas adecuada.

Me pareció buenísimo entrar en el tema de los promedios versus la media a pesar de que no haya sido esa la consulta, pues me conduce un entrenamiento más profundo. Bien, loguearé información y te cuento. Voy a registrar el comportamiento en ciertos lapsos y los consignaré en un excel (supongo que a eso te refieres). Las tomas de información de 10 segundos que he realizado comparando tu for (para mediana) con el mío (para promedio) dan una idea del problema pero puede haber un sesgo. Pero lapsos adecuados de varios minutos u horas son inviables dada que la cantidad de datos no entrarían en una planilla de excel. Pienso que si aplico delays largos entre lecturas puedo tener una muestra del comportamiento en un lapso más grande y así minimizar sesgos. me serviría si me aconsejas dónde poner los delay en el código. Pero si piensas que poner delays no funcionará, te pido me avises. Gracias de nuevo.

No uses excel. Sube datos crudos tiempo vs data y si son texto (mejor que excel) lo comprimes .zip y lo subes aqui.
Sobre estos datos trabajemos mancomunadamente y luego con esto resuelto apuntemos a tu problema, de nuevo, si me aparto (esta es mi mirada) me lo haces saber, pero creo que el momento de la decisión pasa por aca o me equivoco?

Lecturas 30 min. Como Promedio y como MedianaMEDIANAYPROMEDIO.zip (667,2 KB)
El promedio me dio decimales, pero se puede trabajar.

pero donde estan los valores RAW o crudos? ya los procesaste, yo quería hacer mi propio procesamiento para ver a que te enfrentas.

22:44:21 17.27
22:44:21 0,27.27
22:44:21 0,17.27
22:44:21 40,21.82
22:44:21 20,11.82
22:44:21 0,20.00
22:44:21 0,23.64
22:44:21 0,15.45
22:44:21 0,24.55
22:44:21 0,22.73
22:44:21 0,17.27
22:44:21 10,13.64

Esto que veo tiene por ejemplo al final 10, 13.64 debo suponer que 10 es el valor y 13.64 el promedio? O sea lo que esta antes de la coma son los valores crudos?

Exacto, tal como lo propusiste en tu código, el primer valor es RAW y el segundo es el valor (después de la coma) procesado. He incluido esa misma distinción en mi código. Pero igual ten en cuenta que los valores los he multiplicado x 10 tanto en tu código como en el mío. Si no sirve así, puedo tomar nuevos datos sin multiplicación.