Aumentar precisión en lectura de Vrms

Buenas tardes a todos y gracias de antemano por las posibles respuestas. Estoy realizando un sensor de voltaje alterno,con Arduino Uno el cual recitifico con diodo a media onda para introducir en arduino. El algoritmo que utilizo se basa en tomar cuantas más muestras posibles en el periodo T de la señal, e ir integrando, utilizando la definición de Vrms, que es la siguiente:

Por tanto, voy tomando los valores instantáneos de V y multiplicando por el diferencial de t, el cual calculo usando micros. Una vez ha transcurrido el tiempo T, calculo el valor de Vrms, haciendo la raiz y multiplicando por 1/T, o f.

Repito la operación 50 veces y extraigo la media.
Bien, el valor que obtengo tiene un error relativo del 7% aprox respecto a dos múltimetros de alta calidad, algo que es demasiado, ya que tengo que medir voltajes del orden de 230 V y este error es demasiado.

Teniendo en cuenta que la frecuencia con la que estoy midiendo es de 4,32 KHz, no debería ser mucho más preciso el valor? Podría optimizar el código sin necesidad de tocar la velocidad de lectura del A/D y que fuera más preciso?

El número de muestras que me toma por periodo de la señal es de unas 86-87.

long tiempo;

double valor, f=50,V_rms = 0,V_rms_medio=0, matrix,t_muestreo = 0;
double endit=0, prvTime = 0;

void setup() {

  Serial.begin(9600);
}

void loop() {
  
  double sum = 0;//Variable Integrador
  int i=0;
  for(int k=0;k<50;k++)//Bucle que repite 50 veces el proceso obtención de Vrms para sacar una media
  {   
      i=0;
      double init = micros();//Inicio la cuenta
      endit = init;
      while((endit-init) < 20000)//Mientras no se alcanza el periodo de la señal en microsegundos(20000 us, equivalente a una frecuencia de 50 Hz) se van tomando muestras
      { 
        prvTime = micros();  //Toma el tiempo antes de leer la señal 
        valor = analogRead(0);//Lee el voltaje
        endit = micros();//Toma el tiempo justo despues de leer la señal
        matrix=valor*5/(1024);//Convierte el valor del ACD en voltios
        sum = (matrix*matrix)*((endit-prvTime)/1000000)+sum; //Integrado de la señal. El voltaje al cuadrado por el diferencial de t, en segundos más el valor acumulado
        i++;
        
      }
      V_rms = sqrt(f*sum)+V_rms; //voltaje VRMS es igual a la raiz de la frecuencia por la integral de V2*dt, calculada previamente en sum, más el valor de voltaje calculado
      t_muestreo = endit-init;
      //en la iteración previa de k.
      sum=0;
      //Reinicio el valor sum para la siguiente iteración del for loop.
    
     
   }
   V_rms_medio = V_rms/50;//Calculo la media de los valores finales de Vrms calculados
   V_rms=0;//Reinicio V_rms para el siguiente void loop.
   Serial.print("Vrms: ");
   Serial.println(V_rms_medio,DEC);
   Serial.print("Numero de Itereaciones: ");
   Serial.println(i);
   Serial.print("Tiempo de muestreo us: ");
   Serial.println(t_muestreo);
   Serial.print("Frecuencia de muestreo KHz: ");
   Serial.println(i/(t_muestreo*1000/1000000));//Frecuencia de muestreo es igual al número de muestras por segundo, dividido entre 1000 para pasar a KHz
   Serial.println("");

    
   
}

Hay que tener en cuenta varios factores:

  • La resolución del ADC del arduino es de 10 bits, no se le puede pedir mucha precisión.
  • Los cálculos matemáticos, sobretodo multiplicar y dividir, son lentos y además poco precisos.
  • Si rectificas las señal en media onda, durante el semiciclo positivo tendrás valores positivos, pero durante el semiciclo negativo, tendrás muchos valores oscilantes sobre 0 y ¡ojo! que no será 0, tendrás valores negativos
  • Si usas un divisor de tensión, has de conocer el valor exacto de las resistencias y ten en cuenta que la temperatura varia su valor... poco pero suficiente.
  • Siempre hay ruido eléctrico.
    [/list]

Deberías darte una vuelta por la página de OpenEnergyMonitor y verás unos buenos consejos y usos.

Por ejemplo yo lo que he hecho hasta ahora es lo siguiente:

for (i=0; i<numMuestras; i++)
{
    muestra = analogRead(a);
    vinst = muestra * 5.0 / 1024.0;
    vinst = vinst*vinst;
}
vrms = sqrt(vinst/numMuestras);

Si quieres una precisión muy exacta lo suyo sería un hardware especifico para ello como un ADE7753 o similar.

Gracias Victor por la respuesta. Si había leído la web de Openmonitor, y la librería que tienen creada. En su caso la tienen hecha para una onda sinusoidal con offset. Si no le encuentro posibilidad de mejora no la rectificaré y le pondré un offset, otra opción es rectificarla, pero no sé hasta que punto conseguiría una señal en contínua que tenga un rizado decente para que no se me vuelva loco Arduino al leer.

Lo que dices de valores negativos no lo he entendido. Con la rectificación de media onda tengo unos pequeños picos negativos, pero son del orden de 100 mV máximo, con lo que Arduino los acepta, según el DataSheet del ATMega el mínimo son GND -0,5 V, pero aunque el valor que entre sea negativo, el ADC te dá un valor de cero.

He probado tu código pero me da valores más alejados. Lo que en tu código no veo es que tengas en cuenta si estás midiendo una onda completa o dos, o tres y media, porque no controlas el tiempo.

Un saludo

Yo use para medir todo lo sugerido por OpenEnergy y los valores era mucho mejores que lo que dices.
Considera que puedes tener distorsión armónica tambien. Asi que tu integral dependera del número de muestras.

Porque usas y defines valor como double cuando lo que debes hacer para tus muestras es usar un unsigned long y solo sumar? Luego haces las cuentas correspondientes si en double.

También veo algunos errores y cosas para mejorar..
1024 no es, es 1023.
Son 1024 pasos o sea 10Bits pero entre 0 y 1023. error básico que introduce 0.1% no?
luego tu error de 50 sumas usando double de numeros enteros cuando puedes hacerlo como una suma de uint32_t unsigned long.
Luego 5/1023 y otra vez dividido 10000 tantas veces. porque no todo al final y mas iteracciones?
Busca optimizar todo.
Integra solo que necesitas y la constante sale fuera de la integral como corresponda.
5/1023 y el otro factor son constantes, para que acumularlos?

Me refiero al hecho de que si usas media onda, en un ciclo tendrás muchos valores positivos y luego muchos valores 0 (posiblemente negativos). No me he parado mucho a pensarlo, pero puede ser que esa suma de valores 0 (o negativos) que estas muestreando te afecte a la media.

Ahora mismo me estoy acordando, aunque no me acuerdo del sitio, utilizaban un rectificador onda completa, un puente de diodos, el divisor de tensión y la tensión a medir de este se filtraba con un filtro RC para obtener el menor valor posible de rizado. Es decir convertian la corriente alterna en continua pura. El circuito llegué a probarlo y funcionaba bien, solo hay que medir el valor de la continua y hacer los calculos hacia atras. Lo deseché porque también queria medir la frecuencia y muestreando también la obtenia.

Siempre se hace con onda completa.
Totalmente de acuerdo!!
Yo ademas use el esquema de OpenEnergy. ni siquiera rectifiqué, lo que hice fue agregar una componente DC, mover el 0 a 2.5v y luego leer todo sin rectificar.
El esquema esta sugerido en la web de OpenEnergy y usé su librería. Perfecto funciona, aunque no me acuerdo si leia 210 en mi tester y 208 en el arduino. Deberia leerlo de nuevo. El lunes lo hago.

Gracias Victor y Surbyte. La verdad es que lo de definir como double fue porque tenia algunos problemas de compatibilidad de tipos y por "comodidad" de compilar y mirar resultados puse todo double temporalmente, tienes razón si.

El esquema de OpenMonitor lo tengo visto, pero el tema era probar sin meter condensador, pero si no consigo optimizarlo tendré que tirar de ahí, que además tiene la biblioteca creada.

Realmente la frecuencia la leo con un detector de paso por cero, osea que si consigo buena precisión recitificando a contínua podría leer la frecuencia por otro lado y no sería problema.

La frecuencia leela con el cruce por cero ni se te ocurra de otro modo.

El esquema de OpenMonitor lo tengo visto, pero el tema era probar sin meter condensador, pero si no consigo optimizarlo tendré que tirar de ahí, que además tiene la biblioteca creada.

No pierdas tiempo esa librería funciona bien.

Hola de nuevo surbyte. Siguiendo vuestros consejos me he centrado en utilizar dicha libreria para calcular tanto el voltaje como la corriente y comparar la precisión. Ya he acondicionado el circuito, y ya tengo una onda completa con el Offset de 2.5. Adjunto imagen de la señal.

Bien, el problema es que no entiendo muy bien como funciona la librería. Para calcular corriente en el ejemplo hacen lo siguiente

 void setup()
{  
  Serial.begin(9600);
  
  emon1.current(1, 111.1);             // Current: input pin, calibration.
}

void loop()
{
  double Irms = emon1.calcIrms(1480);  // Calculate Irms only
  
  Serial.print(Irms*230.0);	       // Apparent power
  Serial.print(" ");
  Serial.println(Irms);		       // Irms
}

Lo de calibración no lo entiendo. He visto el .cpp y lo que hace la funcion calcIrms, pero no termino de entenderlo :o

for (unsigned int n = 0; n < Number_of_Samples; n++)
  {
    sampleI = analogRead(inPinI);

    // Digital low pass filter extracts the 2.5 V or 1.65 V dc offset, 
	//  then subtract this - signal is now centered on 0 counts.
    offsetI = (offsetI + (sampleI-offsetI)/1024);
	filteredI = sampleI - offsetI;

    // Root-mean-square method current
    // 1) square current values
    sqI = filteredI * filteredI;
    // 2) sum 
    sumI += sqI;
  }

  double I_RATIO = ICAL *((SupplyVoltage/1000.0) / (ADC_COUNTS));
  Irms = I_RATIO * sqrt(sumI / Number_of_Samples); 

  //Reset accumulators
  sumI = 0;
//--------------------------------------------------------------------------------------       
 
  return Irms;

¿Qué significa ese Valor de calibración?

Y en el ejemplo que calcula corriente y voltaje, para calcular voltaje introduce además de la calibración un Phase_shift, que yo entiendo que es el desfase entre corriente y voltaje, pero que no le veo mucho sentido que te lo pidan si no lo sabes, de ahí que quieras medir. En cuanto al Offset no entiendo muy bien como lo sacan