Cami Can Calders, 8 2º-2ª | 08173 Sant Cugat del Valles info@bmotes.com 932504996

WhatsBee blog

Midiendo la energía: 19, el filtro pasa bajos y primera versión del firmware.

En el capítulo anterior……

Si recordais habíamos obtenido un medidor que comprarado con uno comercial nos daba valores bastante parecidos (del orden de un 1%), pero que tenía un problema en el valor devuelto sin carga provocado por el ruido eléctrico del sensor.

Decía que el problema era estético porque por el tipo de producto no se van a medir potencias de 3 ó 4 W, pero cuando no estás utilizando una salida esperas que las lecturas de consumo sean cero y no tengan un comportamiento errático. De hecho creo que es mejor que para unos pocos Watios marque cero que no que por el ruido vaya dando lecturas de potencias negarivas ni cosas por el estilo.

Los sensores de corriente que hemos utilizado tienen dos patillas a las que se les pone un condensador de filtro (FILTER en el esquema), internamente antes del último amplificador operacional del chip se inserta este condensador, eso permite poner el filtro con un condensador más pequeño y sin utilizar la resistencia en serie, porque utilizaremos la resistencia de salida de un operacional. A continuación el esquema interno del sensor:

Esquema interno del sensor de corriente
En el esquema original el condensador de filtro que habíamos puesto era de 1 nF, viendo el ruido es claramente insuficiente, el valor óptimo que hemos encontrado es de 100nF, aún así seguimos teniendo un cierto nivel de ruido que nos provoca el problema mencionado (probablemente el ruido no salga ya del sensor, sino que lo adquiramos por el camino) .
 
La opción que nos queda es añadir un filtro pasa bajos a la salida del sensor. Un filtro pasa bajos es un filtro que, como indica su nombre, permite el paso a las frecuencias más bajas y lo atenua para las más altas, la corriente que queremos medir es de la red eléctrica, por lo tanto de 50Hz, el ruido es de una frecuencia mucho mayor.
 
Hay dos formas de hacer un filtro pasa bajo pasivo, una inductancia en serie y una resistencia en paralelo o una resistencia en serie y un condensador en paralelo. Normalmente se utiliza la segunda porque loc condensadores suelen tener un comportamiento mucho más puro, son más econnómicos, más fáciles de conseguir, etc.
 
He hecho una prueba con un filtro pasa bajo con una resietencia de 200 ohms y un condensador electrolítico de 10 uF y me elimina el ruido, pero me atenua demasiado la señal, al final lo dejo sin el filtro pasa bajo analógico y decido implementar un filtrop pasa bajo en el firmware.
 
Lo de implementar el filtro pasa bajos en el firmware suena sofisticado, pero no lo es, imaginaros que tenemos unas muestras de una tensión con una frecuencia de 50 Hz y encima el ruido, con una frecuencia mucho mayor y una intensidad mucho menor. Simplemente promediando cada valor con el anterior, con los dos anteriores, con los tres anteriores, etc. eliminamos el ruido, que es de una frecuencia mucho más alta. Podeis hacer la prueba con un Excel de varios valores que cuando haceis un gráfico os da muchos picos, es como añadirle una línea de tendencia de «media móvil», en función del órden del filtro vereis como os va suavizando los picos.
 He implementado el filtro el el firmware, pero tampoco me acaba de gustar la solución, sigue habiendo un poco de ruido, me adelanta un poco la señal (porque promedio con valores anteriores porque los posteriores no existen) y me sigue afectando en el momento en el que no hay consumo con las lecturas erráticas (aunque mucho menos).
 
Analizando el problema de una forma intuitiva, veo que en la entrada analógica cuando no hay consumo el valor central debería de ser de 607 y el problema es que me oscila entre 605 y 609, este cambio mínimo en 3000 muestras se suma. Como en la potencia se suman los cuadrados (siempre positivos) y en la potencia aparente no, provoca una desviación entre ambas. Asi que finalmente mi implementación del filtro es un «if», en el firmware: le pongo que si el valor está entre 605 y 609 que considere siempre que es un 607.  Soy consciente de que esto induce un pequeño error en los cálculos, pero ahora el comportamiendo sin carga es absolutamente estable.
 
Adicionalmente redondeo los valores a un decimal, resto 4VA (de forma completamente empírica al resultado de la potencia aparente) y pongo algunos filtros adicionales al resultado: si la potencia es menor que 3W que devuelva cero, lo mismo con la potencia aparente. Todo esto son «chapucillas» que me inducen un pequeño error en las condiciones de carga más bajas, pero que me estabilizan las lecturas.
 
En la primera versión del firmware me limito a mostrar los valores por el puerto serie, de momento no las envío por el Zigbee, a continuación el sketch (que seguramente mejoraremos):
 
//**********************************************************************************
//PDU gestionable por carlos Rodríguez
//http://www.zigbe.net
//El Sketch mide voltaje y corriente y calcula los valores de consumo de potencia
//Basado en los algoritmos del la app note de Atmel AVR465 con inspiración en el código de openenergymonitor
//**********************************************************************************

//********************Variables de configuración********************
int NumeroDeMuestras = 3000;

//Pines de entrada de voltaje y corriente
int PinV = 1;

int PinI1 = 2;
int PinI2 = 3;
int PinI3 = 4;
int PinI4 = 5;
int PinI;

//********************Coeficientes de calibración********************
double CalV = 1.62;

double CalI1 = 0.043;
double CalI2 = 0.043;
double CalI3 = 0.043;
double CalI4 = 0.043;
double CalI;

double CalFase1 = 0.7;
double CalFase2 = 0.7;
double CalFase3 = 0.7;
double CalFase4 = 0.7;
double CalFase;

//********************Variables para las muestras********************
int UlMuestraV;
int UlMuestraI;
int MuestraV;
int MuestraI;
int MuestraI0;
int MuestraI1;
int MuestraI2;
int MuestraI3;

//********************Variables filtradas********************
double UlFiltradoV;
double UlFiltradoI;
double FiltradoV;
double FiltradoI;
double filterTemp;

//********************Almacena el voltahe instantaneo con la fase calibrada********************
double calibratedV;

//********************Variables de cálculo de potencia********************
double sqI,sqV,instP,sumI,sumV,sumP;

//********************Variables de salida********************
double PotenciaReal1, PotenciaAparente1, FactorPotencia1, Vrms1, Irms1;
double PotenciaReal2, PotenciaAparente2, FactorPotencia2, Vrms2, Irms2;
double PotenciaReal3, PotenciaAparente3, FactorPotencia3, Vrms3, Irms3;
double PotenciaReal4, PotenciaAparente4, FactorPotencia4, Vrms4, Irms4;
double PotenciaReal, PotenciaAparente, FactorPotencia, Vrms, Irms;

//********************Variables de medida de la energía********************
    //Calculation of kwh
    //time taken since last measurment timems = tmillis - ltmillis;
    unsigned long ltmillis, tmillis, timems;
    //time when arduino is switched on... is it 0?
    unsigned long startmillis;
    //kwhTotal is cumulative kwh today, the other 3 are historCalIX over the last 3 days for comparison.
    double kwhTotal =0.0;

//********************Variables de medida de la frecuencia********************
//Tiempo en microsegundos desde el último paso del voltaje por cero
unsigned long vLastZeroMsec;
unsigned long vPeriod;
//Suma de vPeriod para obtener el promedio
unsigned long vPeriodSum;
//Número de periodos
unsigned long vPeriodCount;
//Frequencia
float freq;
//Se utiliza para filtrar lecturas erróneas de vPeriod
//50Hz -> 20000, 60 Hz -> 16666
unsigned long expPeriod = 20000;
unsigned long filterWidth = 2000;

void setup()
{
Serial.begin(115200);
//*******************Setup de la medida de energía*******************
tmillis = millis();
startmillis=tmillis;

pinMode (13, OUTPUT);
//Para aproximar el primer valor
//CalcularEnergia();
}

void loop()
{
//Primer puerto
digitalWrite(13,1); //Enciende el led de la placa
PinI=PinI1; //asigna el pin analógico de I del puerto1
CalI=CalI1; //asigna los valores de calibración del puerto1
CalFase=CalFase1; //Asigna los valores de calibración de fase del puerto1
//CalcularEnergia(); //Calcula la energía
CalcularEnergia(); //Calcula la energía
CalculoRMSPot(); //Calculos de potencia
Vrms1=Vrms; //Asigna resultados a variables
Irms1=Irms;
PotenciaReal1=PotenciaReal;
PotenciaAparente1=PotenciaAparente;
FactorPotencia1=FactorPotencia;
CalculoFrecuencia(); //Calcula la frecuencia
ResetearAcumuladores(); //resetea los acumuladores

//Segundo puerto
digitalWrite(13,0); //Documentado en el puerto1
PinI=PinI2;
CalI=CalI2;
CalFase=CalFase2;
CalcularEnergia();
CalculoRMSPot();
Vrms2=Vrms;
Irms2=Irms;
PotenciaReal2=PotenciaReal;
PotenciaAparente2=PotenciaAparente;
FactorPotencia2=FactorPotencia;
CalculoFrecuencia();
ResetearAcumuladores();

//tercer puerto
digitalWrite(13,1); //Documentado en el puerto1
PinI=PinI3;
CalI=CalI3;
CalFase=CalFase3;
CalcularEnergia();
CalculoRMSPot();
Vrms3=Vrms;
Irms3=Irms;
PotenciaReal3=PotenciaReal;
PotenciaAparente3=PotenciaAparente;
FactorPotencia3=FactorPotencia;
CalculoFrecuencia();
ResetearAcumuladores();

//Cuarto puerto
digitalWrite(13,0); //Documentado en el puerto1
PinI=PinI4;
CalI=CalI4;
CalFase=CalFase1;
CalcularEnergia();
CalculoRMSPot();
Vrms4=Vrms;
Irms4=Irms;
PotenciaReal4=PotenciaReal;
PotenciaAparente4=PotenciaAparente;
FactorPotencia4=FactorPotencia;
CalculoFrecuencia();
ResetearAcumuladores();

//--ENERGY MEASURMENT CALCULATION----------------    
//Calculate amount of time since last PotenciaReal measurment.
ltmillis = tmillis;
tmillis = millis();
timems = tmillis - ltmillis;

//Calculate number of kwh consumed.
// REVISAR____>//kwhTotal = kwhTotal + ((PotenciaReal/1000.0) * 1.0/3600.0 * (timems/1000.0));
//Reset accumulators

//Muestra la salida por el puerto serie
Serial.print ("Puerto 1 -> ");
Serial.print(PotenciaReal1);
Serial.print(" W / ");
Serial.print(PotenciaAparente1);
Serial.print(" VA / ");
Serial.print(FactorPotencia1);
Serial.print(" Factor P / ");
Serial.print(Vrms1);
Serial.print(" V rms / ");
Serial.print(Irms1);
Serial.print(" A rms / ");
Serial.print(kwhTotal);
Serial.print(" KWh / ");
Serial.print(freq);
Serial.println(" Hz");
Serial.print ("Puerto 2 -> ");
Serial.print(PotenciaReal2);
Serial.print(" W / ");
Serial.print(PotenciaAparente2);
Serial.print(" VA / ");
Serial.print(FactorPotencia2);
Serial.print(" Factor P / ");
Serial.print(Vrms2);
Serial.print(" V rms / ");
Serial.print(Irms2);
Serial.print(" A rms / ");
Serial.print(kwhTotal);
Serial.print(" KWh / ");
Serial.print(freq);
Serial.println(" Hz");
Serial.print ("Puerto 3 -> ");
Serial.print(PotenciaReal3);
Serial.print(" W / ");
Serial.print(PotenciaAparente3);
Serial.print(" VA / ");
Serial.print(FactorPotencia3);
Serial.print(" Factor P / ");
Serial.print(Vrms3);
Serial.print(" V rms / ");
Serial.print(Irms3);
Serial.print(" A rms / ");
Serial.print(kwhTotal);
Serial.print(" KWh / ");
Serial.print(freq);
Serial.println(" Hz");
Serial.print ("Puerto 4 -> ");
Serial.print(PotenciaReal4);
Serial.print(" W / ");
Serial.print(PotenciaAparente4);
Serial.print(" VA / ");
Serial.print(FactorPotencia4);
Serial.print(" Factor P / ");
Serial.print(Vrms4);
Serial.print(" V rms / ");
Serial.print(Irms4);
Serial.print(" A rms / ");
Serial.print(kwhTotal);
Serial.print(" KWh / ");
Serial.print(freq);
Serial.println(" Hz");
Serial.println("************************************************************************");

}
void CalcularEnergia()
{
  UlMuestraV=607;
  UlMuestraI=607;
  MuestraV=607;
  MuestraI=607;
  UlFiltradoV=2.5;
  UlFiltradoI=2.5;
  FiltradoV=2.5;
  FiltradoI=2.5;

for (int n=0; n<NumeroDeMuestras; n++)
{
   //Variables utilizadas para eliminar el ofset de tensión
//  MuestraI0=MuestraI1;
//  MuestraI1=MuestraI2;
//  MuestraI2=UlMuestraI;
   UlMuestraV=MuestraV;
   UlMuestraI=MuestraI;

   //Lee las muestras de corriente y voltaje
   MuestraV = analogRead(PinV);
   MuestraI = analogRead(PinI);

if (MuestraI <=610 and MuestraI >=604)
{MuestraI=607;
}
//Serial.println (MuestraI);
//  MuestraI3= (MuestraI0+MuestraI1+MuestraI2+UlMuestraI+MuestraI)/5;
 //Serial.print (MuestraI);
 //Serial.print (";");
 //Serial.println (MuestraI3);
//   MuestraI=MuestraI3;
   //Variables utilizadas para eliminar el ofset de tensión
   UlFiltradoV = FiltradoV;
   UlFiltradoI = FiltradoI;

   //Filtro digital de paso alto para eliminar el Ofset de 2,5V en las entradas
   FiltradoV = 0.996*(UlFiltradoV+MuestraV-UlMuestraV);
   FiltradoI = 0.996*(UlFiltradoI+MuestraI-UlMuestraI);

   //Calibración de fase (para compensar el retraso en el conversor AD)
   calibratedV = UlFiltradoV + CalFase * (FiltradoV - UlFiltradoV);

   //Calculo del voltaje RMS
   sqV= calibratedV * calibratedV;   //Eleva al cuadrado
   sumV += sqV; //Añade al acumulador

   //Calculo de la corriente RMS
   sqI = FiltradoI * FiltradoI; //Eleva al cuadrado
 //Serial.println (sqI);
   sumI += sqI; //Añade al acumulador

   //Cálculo de la potencia instantanea
   instP = calibratedV * FiltradoI; //Multiplica tensión por corriente
   sumP +=instP;  //Añade al acumulador

   //*******************MEDICIÓN DE FRECUENCIA**************************
   if (n==0) vLastZeroMsec = micros();

   if (UlFiltradoV < 0 && FiltradoV >= 0 && n>1)    //Chequea el paso por cero
   {
     vPeriod = micros() - vLastZeroMsec; //Periodo de la forma de onda del voltaje
     if (vPeriod > (expPeriod-filterWidth) && vPeriod<(expPeriod+filterWidth)) //Filtra las medidas de periodo erróneas
     {
        vPeriodSum += vPeriod;
        vPeriodCount++;
     }
     vLastZeroMsec = micros();
   }
   //********************************************************************
}
}
void ResetearAcumuladores()
{
sumV = 0;
sumI = 0;
sumP = 0;
}

void CalculoRMSPot()
{
Vrms = CalV*sqrt(sumV / NumeroDeMuestras); //Cálculo de la raiz cuadrada del promedio del voltaje elevado al cuadrado (RMS) / Aplica los coeficientes de calibración
Vrms= double(int(Vrms*10))/10; //Reduce a uno el número de decimales
Irms = CalI*sqrt(sumI / NumeroDeMuestras); //Cálculo de la raiz cuadrada del promedio de la corriente elevada al cuadrado (RMS) / Aplica los coeficientes de calibración
Irms= double(int(Irms*20))/20; //Dos decimales, pero la segunda cifra solo puede ser 0 ó 5
//calcula los valores de potencia
PotenciaReal = CalV*CalI*sumP / NumeroDeMuestras;
PotenciaReal = double(int(PotenciaReal*10))/10; //Reduce el número de decimales a uno
PotenciaAparente = double(int((Vrms * Irms -4)*10))/10;//El cuatro es una calibración para compensar el algoritmo de reducción de ruido
if (PotenciaAparente<PotenciaReal)
{PotenciaAparente=PotenciaReal;} //En alguna ocasión "rara" Ha dado algún valor inferior que da factor de potencia negativo
if (PotenciaReal<4) // No se detecta una potencia menor de 4W, para evitar los errores que quedan del filtro pasa bajos
{PotenciaReal=0;}
if (PotenciaAparente<4)// No se detecta una potenciao menor de 4VA, para evitar los errores que quedan del filtro pasa bajos
{PotenciaAparente=0;}
FactorPotencia = PotenciaReal / PotenciaAparente;
}
void CalculoFrecuencia()
{
//******************Cáculos de frecuencia*****************
freq = (1000000.0 * vPeriodCount) / vPeriodSum; //divido por 1000 crc
freq= double(int(freq*20))/20; //El segundo decimal es cero o 5

vPeriodSum=0;
vPeriodCount=0;
}
 
 
 

Dejar un comentario