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

WhatsBee blog

Midiendo la energía: 22 resuelto el camino de ida

 

Releyendo el artículo anterior veo que es bastante espeso, no es mi intención aburrir, pero el artículo es fiel reflejo de lo aburrida que es esta fase.

He mejorado mucho el código de la PDU, que ha quedado bastante reducido, basicamente lo que he hecho es meter las variables en matrices y recorrer el código n veces (donde n es el número de puertos) dentro de un bucle for.

He añadido la librería de Xbee y he migrado a la versión 21 del entorno Arduino, que ya es capaz de concatenar dos cadenas sin utilizar librerías adicionales. Estaba trabajando con la versión 17 y dedicado durante más de una hora a no ser capaz de construir una cadena que sea la concatenación de dos variables.

Desde la librería Xbee genero un paquete, toda la info la meto en el payload, con el siguiente formato: «PT=1;VRMS=220.0V;IRMS=0.0A….» cada valor es extraido del algoritmo de la PDU y se mete en el payload del paquete.

Gateways de Digi

La dirección de destino de los paquetes es la 0x00000000 0x00000000, que envía directamente al coordinador de la red. El coordinador es el gateway de Digi, he creado un driver para manejar los paquetes específicos de nuestro driver, que lo que hace es recoger los valores y asignarlos a propiedades. Los valores que se calculan a partir de los existentes se calculan en el driver y también se asignan a propiedades. Todo funciona correctamente, cada segundo la PDU envía un paquete correspondiente a los datos de un puerto que es interpretado por el gateway, representado en la web del equipo, etc.

El siguiente paso ya es el final, además de recibir lo que manda la PDU e interpretarlo debemos de ser capaces de enviar info del X4 a la PDU, para ser capaces de obtener el dato en el momento que queramos, activar y descativar los relés, etc.

Existe un problema importante (que no tengo claro en este momento como voy a resolver), la PDU tarda aproximadamente un segundo en tomar 3000 valorores de tensión y corriente y en cálcular las potencias. Durante este segundo no se pueden atender peticiones de la red Zigbee, porque el timing es importante, por lo que corremos el riesgo de que si intentamos cambiar el estado de un relé mientras mide la energía la PDU no hará la acción.

Las posibles soluciones para resolver este tema son:

  • Si la UART tiene cache, analizar el paquete pasado un segundo y apagar en ese momento el relé (con ese retraso)
  • Si podemos disparar una interrupción en el momento en el que llegue el paquete podemos descartar esa medida que está tomando, atender la petición de la red y, si es necesario, tomar la medida posteriormente.

Os mantendré informados….

La nueva versión del firmware de la PDU es la siguiente:

//**********************************************************************************
//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
//**********************************************************************************
//******************************* X B E E **************************
#include <XBee.h> //Librería Xbeee
XBee xbee = XBee(); //Crea el objeto de Xbee
uint8_t payload[70]; //Crea el puntero para el payload
XBeeAddress64 addr64 = XBeeAddress64(0x00000000, 0x00000000);
ZBTxRequest zbTx = ZBTxRequest(addr64, payload, sizeof(payload));
ZBTxStatusResponse txStatus = ZBTxStatusResponse();
//******************************************************************
//********************Variables de configuración********************
const int NumeroDeMuestras = 3000; //Número de muestras para las medidas de energía, 
const int NumPorts =4; //Define el número de puertos de la PDU
//Pines de entrada de voltaje y corriente
int PinV = 1; //Pin de medida de tensión
int PinI[NumPorts+1] = {0, 2, 3, 4, 5}; //Pines de medida de corriente (1 a 4)
//********************Coeficientes de calibración********************
double CalV = 1.62; //Coeficiente de calibración de tensión
double CalI [NumPorts+1] = {0, 0.043, 0.043, 0.043, 0.043}; //Coeficientes de calibración de corriente
double CalFase[NumPorts+1] = {0, 0.7, 0.7, 0.7, 0.7}; //Coeficientes de calibración de fase
//********************Variables para las muestras********************
int UlMuestraV, UlMuestraI, MuestraV, MuestraI;
//********************Variables filtradas********************
double UlFiltradoV, UlFiltradoI, FiltradoV, FiltradoI, 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 PotenciaReal[5];
double PotenciaAparente[5];
double FactorPotencia[5];
double Vrms[5];
double Irms[5];
       
//********************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;
String pzb; //Contiene la cadena que se va a enviar en el payload
int statusLed = 13;
       
void setup()
{
  xbee.begin(9600);
//*******************Setup de la medida de energía*******************
tmillis = millis();
startmillis=tmillis;
pinMode (13, OUTPUT);
}
void loop()
{
for (int Port=1; Port<=NumPorts; Port++) {
CalcularEnergia(Port); //Calcula la energía
//Parte del Zigbee
pzb= "PT=" + floatToString(Port, 0) +";P=" + floatToString(PotenciaReal[Port], 1) + "W;S=" + floatToString(PotenciaAparente[Port], 1)+ "VA;V=" + floatToString(Vrms[Port], 1)+ "V;I=" + floatToString(Irms[Port], 1) + "A;F=" + floatToString(freq, 1) + "Hz;";
for (int n=0; n<=pzb.length(); n++){
payload[n] = pzb[n];
}
xbee.send(zbTx);
}
void CalcularEnergia(int Port)
{
  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
   UlMuestraV=MuestraV;
   UlMuestraI=MuestraI;
 
   //Lee las muestras de corriente y voltaje
   MuestraV = analogRead(PinV);
   MuestraI = analogRead(PinI[Port]);
if (MuestraI <=610 and MuestraI >=604)
{MuestraI=607;
}
   //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[Port] * (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();
   }
   //********************************************************************
}
//Calcula tensiones y corrientes
Vrms[Port] = 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[Port]= double(int(Vrms[Port]*10))/10; //Reduce a uno el número de decimales
Irms[Port] = CalI[Port]*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[Port]= double(int(Irms[Port]*20))/20; //Dos decimales, pero la segunda cifra solo puede ser 0 ó 5
//calcula las potencias
PotenciaReal[Port] = CalV*CalI[Port]*sumP / NumeroDeMuestras;
PotenciaReal[Port] = double(int(PotenciaReal[Port]*10))/10; //Reduce el número de decimales a uno
PotenciaAparente[Port] = double(int((Vrms[Port] * Irms[Port] -4)*10))/10;//El cuatro es una calibración para compensar el algoritmo de reducción de ruido
if (PotenciaAparente[Port]<PotenciaReal[Port])
{PotenciaAparente[Port]=PotenciaReal[Port];} //En alguna ocasión "rara" Ha dado algún valor inferior que da factor de potencia negativo
if (PotenciaReal[Port]<4) // No se detecta una potencia menor de 4W, para evitar los errores que quedan del filtro pasa bajos
{PotenciaReal[Port]=0;}
if (PotenciaAparente[Port]<4)// No se detecta una potenciao menor de 4VA, para evitar los errores que quedan del filtro pasa bajos
{PotenciaAparente[Port]=0;}
FactorPotencia[Port] = PotenciaReal[Port] / PotenciaAparente[Port];
//Cálculo de la 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;
//resetea los acumuladores
sumV = 0;
sumI = 0;
sumP = 0;
}
//-----------------------------------------------------------------------------------------
//--------------Función para convertir las variables double en texto-----------------------
String floatToString(double number, uint8_t digits) 
{ 
  String resultString = "";
  // Handle negative numbers
  if (number < 0.0)
  {
     resultString += "-";
     number = -number;
  }
  // Redondea correctamente (1.999, 2) se imprimem como "2.00"
  double rounding = 0.5;
  for (uint8_t i=0; i<digits; ++i)
    rounding /= 10.0;
    number += rounding;
  // Extrae la parte entera del número y la imprime
  unsigned long int_part = (unsigned long)number;
  double remainder = number - (double)int_part;
  resultString += int_part;
  // Imprime el punto digital si hay decimales
  if (digits > 0)
    resultString += "."; 
  // Estrae los dígitos que quedan de uno en uno
  while (digits-- > 0)
  {
    remainder *= 10.0;
    int toPrint = int(remainder);
    resultString += toPrint;
    remainder -= toPrint; 
  } 
  return resultString;
}
//--------------------------------------------------------------------------------------
//------------Función para mostrar por el led los códigos de error----------------------
void flashLed(int pin, int times, int wait) {
    for (int i = 0; i < times; i++) {
      digitalWrite(pin, HIGH);
      delay(wait);
      digitalWrite(pin, LOW);
      if (i + 1 < times) {
        delay(wait);
      }
    }
}
//--------------------------------------------------------------------------------------

Dejar un comentario