Pues sí….
Tal como comenté en el último artículo nuestro producto ya envía de forma periódica al gateway los valores de corriente rms, tensión rms, potencia aparente, potencia activa, potencia reactiva, frecuencia, etc. Además se identifica, envía el número de serie, el identificador del HW y el del SW. Como primer prototipo alfa ya nos vale, aunque hay que pulir algunas cosas.
He hecho una nueva versión del firmware (la 10) que incluye un par de novedades, las comento y aprovecho para publicarla.
Para compensar desviaciones de los componentes el sketch tiene una serie de parámetros de calibración, de tensión, de corriente para cada puerto y de fase para cada puerto. Los parámetros de calibración se definen como variables al principio del sketch. Pero ¿cómo los puede cambiar el usuario si necesidad de reescribir el firmware y cargarlo?, tenemos que darle opción.
No tiene ninguna lógica preguntarlos en un interfaz de usuario cada vez que se enchufa la PDU, deberíamos de establecer una calibración por defecto y dar al usuario la posibilidad de cambiarla. Evidentemente no podemos permitirnos que se carguen los parámetros por defecto cada vez que se vaya la corriente, deberíamos de almacenarlos en un área de memoria que no se borre aunque la PDU no esté alimentada, esta memoria es la EEPROM.
El ATMEGA328 contiene 4K de memoria EEPROM, los valores que contenía el sketch ahora son los valores por defecto, la primera vez que se arranca el producto se comprueba si están escritos en la EEPROM, y si no lo están se escriben, esto solo sucede la primera vez que se enchufa el producto a la corriente.
Para poder cambiar estos valores he puesto una ventana de comando en el driver, ahora se pueden enviar comandos a la PDU, para encender y apagar relés, para cambiar los valores de calibración y para cambiar los datos de la PDU: número de serie, versión del HW, etc. Simplemente en esta inputbox tenemos que poner XXX:YYY=ZZZZZZ. Para modificar el estado de los relés:
CMD:PX=Y -> dónde X es el puerto e Y es 0 ó 1 en función de si queremos encender o apagar el relé.
Para cambiar un parámetro de configuración: CNF:XXX=YYYYYYYYY, dónde XXX puede tomar los valores CV0 (calibración de V), CIX (Calibración de I para el puertoX), CFX (Calibración de fase para el puertoX), PID (Identificador de producto), SNX(Número de serie), HWV (Versión del HW), SWV (Versión del SW), TLE (Segundos entre envíos de lecturas) o TIN (Segundos entre envíos de la identificación del equipo). Lo que hay despues del signo igual es el valor que queremos asignar. Recuerdo que estos valores se almacenan de forma permanente.
En próximas versiones añadiremos nuevos parámetros para definir el estado por defecto de los relés en el arranque de la PDU, para definir en que momento se quiere cortar la corriente, etc. También utilizaremos la EEPROM para almacenar el consumo acumulado cada hora.
Otra novedad es que ahora la PDU acepta el comando GET del gateway y envía de forma inmediata toda la info, de esta forma podemos alargar los tiempos en los que el gateway envía y controlarlo desde el driver pidiendo la info con un scheduler.
Hay un montón de características que podemos añadir, pero de momento lo dejaremos en este punto, seguiremos trabajando en otros elementos del entorno y al final le daremos un repaso.
Por si a alguien le interesa él fuente de la versión 10 del código es el siguiente (Mis disculpas por el aburrimiento ;-)):
//********************************************************************************** //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 //********************************************************************************** //____________________LIBRERIA DE EEPROM PARA ALMACENAR LOS DATOS___________________ #include <EEPROM.h> //____________________NEWSOFTSERIAL (SOLO PARA DEPURAR)_____________________________ #include <NewSoftSerial.h> uint8_t ssRX = 8; // Connect Arduino pin 8 to TX of usb-serial device uint8_t ssTX = 7;// Connect Arduino pin 7 to RX of usb-serial device NewSoftSerial nss(ssRX, ssTX); int cuentas =0; int cuentasZB =0; //__________________________________XBEE____________________________________________ #include <XBee.h> //Librería Xbeee #define MAX_PAYLOAD_SIZE 72 XBee xbee = XBee(); //Crea el objeto de Xbee uint8_t payload[MAX_PAYLOAD_SIZE]; //Crea el puntero para el payload XBeeAddress64 addr64 = XBeeAddress64(0x00000000, 0x00000000); ZBTxRequest zbTx = ZBTxRequest(addr64, payload, sizeof(payload)); ZBTxStatusResponse txStatus = ZBTxStatusResponse(); // create reusable response objects for responses we expect to handle ZBRxResponse rx = ZBRxResponse(); ModemStatusResponse msr = ModemStatusResponse(); String pzb; //Contiene la cadena que se va a enviar en el payload //*************************Tiempo Entre Lecturas**************************
struct Config { char OK[3]; char ProductId[10]; char SerialNumber[10]; char HardwareVersion[10]; char SoftwareVersion[10]; double tLecturas; //Tiempo en segundos para leer cada puerto, lo lee de la EEPROM double tInfo; //Tiempo en segundos para mandar la info del equipo, lo lee de la EEPROM };
Config defConf = {"OK", "bMotesPDU", "000000001", " A01.03", " V07.10", 5, 100}; Config cf;
double mlecturas=0; //Contador del tiempo de lectura double tUlLectura=0; //Tiempo transcurrido desde la última lectura double tUlLecInfo=0; //Contador del tiempo de info
//________________________VARIABLES DE CONFIGURACIÓN DEL HARDWARE________________________ 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 int Port=1; //define el puerto incial 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) int PinRele[NumPorts+1] = {0, 6, 3, 4, 5}; //Pines en los que están conectados los relés int EstadoRele[NumPorts+1] = {0, 1, 1, 1, 1}; //Por defecto todos los relés están en on TODO: Esto debería de poder ser configurable???? //________________________COEFICIENTES DE CALIBRACIÓN____________________________________
struct Calib { char OK[3]; double CalV; //Coeficiente de calibración de tensión double CalI [NumPorts+1]; //Coeficientes de calibración de corriente double CalFase[NumPorts+1]; //Coeficientes de calibración de fase double CalOfset; double DifOfset; }; Calib defCal = {"OK", 1.62, {0, 0.043, 0.043, 0.043, 0.043}, {0, 0.7, 0.7, 0.7, 0.7}, 607, 3}; Calib cl;
/*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 Y ACUMULADORES_____________________ int UlMuestraV, UlMuestraI, MuestraV, MuestraI; double UlFiltradoV, UlFiltradoI, FiltradoV, FiltradoI, filterTemp, calibratedV; double sumI,sumV,sumP; //_________________________VARIABLES PARA ALMACENAR LOS RESULTADOS DE SALIDA_____________ double PotenciaReal[NumPorts+1]; double PotenciaAparente[NumPorts+1]; double FactorPotencia[NumPorts+1]; double Vrms[NumPorts+1]; double Irms[NumPorts+1]; //********************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******************** unsigned long vLastZeroMsec;//Tiempo en microsegundos desde el último paso del voltaje por cero unsigned long vPeriod; unsigned long vPeriodSum;//Suma de vPeriod para obtener el promedio unsigned long vPeriodCount;//Número de periodos float freq;//Frequencia unsigned long expPeriod = 20000;//Se utiliza para filtrar lecturas erróneas de vPeriod, 50Hz -> 20000, 60 Hz -> 16666 unsigned long filterWidth = 2000;
int statusLed = 13; int errorLed=13;
//___________________________TEMPLATES PARA LA GRABACIÓN EN LA EEPROM_______________________________________ template <class T> int EEPROM_writeAnything(int ee, const T& value) { const byte* p = (const byte*)(const void*)&value; int i; for (i = 0; i < sizeof(value); i++) EEPROM.write(ee++, *p++); return i; }
template <class T> int EEPROM_readAnything(int ee, T& value) { byte* p = (byte*)(void*)&value; int i; for (i = 0; i < sizeof(value); i++) *p++ = EEPROM.read(ee++); return i; }
void setup() { xbee.begin(9600); //*******************Setup de la medida de energía******************* tmillis = millis(); startmillis=tmillis; for (int n=1; n<=NumPorts; n++){pinMode (PinRele[n], OUTPUT);} //Inicializa todos los puertos de los relés como salidas pinMode (13, OUTPUT); //Inicializa el puerto del LED como salida EEPROM_readAnything (0, cf); //carga las variables de la EEPROM en cf if (defConf.OK == cf.OK){ //suponemos que la EEPROM tiene algo grabado //No hace nada } else{EEPROM_writeAnything (0 , defConf); delay(50); //se espera porque en la EEPROM se tarda en escribir EEPROM_readAnything (0, cf); //Carga los datos de la EEPROM en la variable cf }
EEPROM_readAnything (100, cl); //carga las variables de la EEPROM en cf if (defCal.OK == cl.OK){ //suponemos que la EEPROM tiene algo grabado //No hace nada } else{EEPROM_writeAnything (100 , defCal); delay(50); //se espera porque en la EEPROM se tarda en escribir EEPROM_readAnything (100, cl); //Carga los datos de la EEPROM en la variable cf }
// Solo depuración nss.begin(9600); nss.println("Se ha reiniciado el micro!!"); nss.println (cf.ProductId);
//TODO: Definir el valor de arranque de los puertos por defecto //TODO: Hacer una función para escribir directamente en la EEPROM desde la red Zigbee }
//_________________________________BUCLE PRINCIPAL DEL PROGRAMA________________________________________ void loop() { if (millis()-tUlLectura >cf.tLecturas*1000){ //Si ha pasado un tiempo superior al de la última lectura tUlLectura=millis();//Asigna el tiempo de la última lectura if (Port>NumPorts){Port=1;}//si el número de puerto es mayor que el número de puertos vuelve al uno flashLed(13, Port, 100); //Activa el Led un número de veces igual al del puerto que está escaneando // debug nss.println (" "); nss.print ("Calculando energia, Puerto->"); nss.println (Port); if (CalcularEnergia(Port)){ //Calcula la energía devuelve true si no se cancela por una entrada serie nss.println ("ha vuelto de la funcion de calcular FINALIZADA"); enviarPaquetePort (Port); Port += 1; //Incrementa el número de puerto } else { nss.println ("ha vuelto de la funcion de calcular CANCELADA"); } } if (millis()-tUlLecInfo >cf.tInfo*1000){ //Si ha pasado un tiempo superior al de la última lectura nss.println ("Enviando datos del HW"); tUlLecInfo=millis();//Asigna el tiempo de la última lectura enviarPaqueteHW(); } leerZigbee(); } //Fin del loop
//_______________________________________LEE LOS PAQUETES DE LA RED ZIGBEE________________________________________ void leerZigbee() { xbee.readPacket(); //lee el paquete cuentasZB += 1; if (cuentasZB>200){ cuentasZB=0; nss.print ("#"); } if (xbee.getResponse().isAvailable()) {//Si se ha recibido..... nss.println ("Se ha recibido un paquete");
if (xbee.getResponse().getApiId() == ZB_RX_RESPONSE) { //Comprueba que se trate de un paquete ZB RX xbee.getResponse().getZBRxResponse(rx); //rellena la clase ZB RX
borrarPayload(); String comando; for (int n=0; n<=rx.getDataLength(); n++){ payload[n] = rx.getData()[n]; comando += rx.getData()[n]; }
if (comando.substring (0, 4)=="CMD:"){ //Se ha enviado un comando a los relés nss.println ("Se ha recibido un comando"); procesarRele (atoi(&comando[5]), atoi(&comando[7])); } else if(comando.substring (0, 4)=="CNF:"){ //Se ha enviado un cambio en la configuración nss.println ("Se ha recibido un comando de CONFIGURACION"); procesarConfig (comando.substring (4, 7), comando.substring (8, 17)); } else if(comando.substring (0, 3)=="get"){ //El gateway ha pedido que le manden la configuración nss.println ("El Gateway ha pedido información de estado"); for (int i=1; i<=NumPorts; i++){//Manda un paquete de cada puerto enviarPaquetePort(i);} } enviarPaqueteHW ();//Manda la info del HW } else if (xbee.getResponse().getApiId() == MODEM_STATUS_RESPONSE) { xbee.getResponse().getModemStatusResponse(msr); //El Xbee local envía estas respuestas ante ciertos eventos, por ejemplo asociación desasociación if (msr.getStatus() == ASSOCIATED) {flashLed(statusLed, 10, 10);} // Enciende el LED 10 veces para indicar que está asociado else if (msr.getStatus() == DISASSOCIATED) {flashLed(errorLed, 5, 10);}// Enciende el LED 5 veces para indicar que está desasociado else {flashLed(statusLed, 3, 10);}// Otro statos } else {flashLed(errorLed, 1, 25);} // Error inesperado } }
//_______________________PROCESAR COMANDO RELE____________________________________________________
void procesarRele (int Pto, int Est) { digitalWrite (PinRele[Pto], !Est); //Cambia el estado del puerto EstadoRele[Pto]=Est; //Cambia la variable que almacena el estado enviarPaquetePort (Pto); //Envia un paquete con la información del puerto actualizada //DEBUG nss.print ("Dentro de la función de los reles. Puerto->"); nss.print (Pto); nss.print (" / Estado->"); nss.println (Est); //DEBUG } //_______________________PROCESAR CONFIGURACION____________________________________________________ void procesarConfig (String cmd, String val){ if (cmd=="CV0"){
} else if (cmd=="CI1"){
} else if (cmd=="CI2"){
} else if (cmd=="CI3"){
} else if (cmd=="CI4"){
} else if (cmd=="CF1"){
} else if (cmd=="CF2"){
} else if (cmd=="CF3"){
} else if (cmd=="CF4"){
} else if (cmd=="PID"){ for (int i=0; i<10; i++){cf.ProductId[i]=val[i];} } else if (cmd=="SNX"){ for (int i=0; i<10; i++){cf.SerialNumber[i]=val[i];} } else if (cmd=="HWV"){ for (int i=0; i<10; i++){cf.HardwareVersion[i]=val[i];} } else if (cmd=="SWV"){ for (int i=0; i<10; i++){cf.SoftwareVersion[i]=val[i];} } else if (cmd=="TLE"){
} else if (cmd=="TIN"){ } //DEBUG nss.print ("Se ha recibido el comando "); nss.print (cmd); nss.print ("="); nss.println (val); //DEBUG EEPROM_writeAnything (0, cf); EEPROM_writeAnything (100, cl); delay(50); enviarPaqueteHW(); }
//______________________CALCULA LOS VALORES DE LA ENERGIA__________________________________________ boolean CalcularEnergia(int Port) { UlMuestraV=cl.CalOfset; UlMuestraI=cl.CalOfset; MuestraV=cl.CalOfset; MuestraI=cl.CalOfset; UlFiltradoV=2.5; UlFiltradoI=2.5; FiltradoV=2.5; FiltradoI=2.5; double MaxOfset=cl.CalOfset + cl.DifOfset; double MinOfset=cl.CalOfset - cl.DifOfset; for (int n=0; n<NumeroDeMuestras; n++) { if (digitalRead (0)==LOW) {return false;} //Si se ha recobodo algún dato por la UART cancela inmediatamente el bucle UlMuestraV=MuestraV; //Variables utilizadas para eliminar el ofset de tensión UlMuestraI=MuestraI; //Variables utilizadas para eliminar el ofset de corriente MuestraV = analogRead(PinV); //Lee las muestras de voltaje MuestraI = analogRead(PinI[Port]); //Lee las muestras de corriente if (MuestraI <=MaxOfset and MuestraI >=MinOfset){MuestraI=cl.CalOfset;} //Filtro de histéresis para eliminar el ruido en el sensor de corriente UlFiltradoV = FiltradoV; //Variables utilizadas para eliminar el ofset de tensión UlFiltradoI = FiltradoI; //Variables utilizadas para eliminar el ofset de corriente FiltradoV = 0.996*(UlFiltradoV+MuestraV-UlMuestraV); //Filtro digital de paso alto para eliminar el Ofset de 2,5V en la entrada de tensión FiltradoI = 0.996*(UlFiltradoI+MuestraI-UlMuestraI); //Filtro digital de paso alto para eliminar el Ofset de 2,5V en las entradas de corriente calibratedV = UlFiltradoV + cl.CalFase[Port] * (FiltradoV - UlFiltradoV); //Calibración de fase (para compensar el retraso en el conversor AD) sumV += calibratedV * calibratedV; //Añade al acumulador el cuadrado del voltaje sumI += FiltradoI * FiltradoI; //Eleva al cuadrado y añade al acumulador sumP +=calibratedV * FiltradoI; //Cálculo de la potencia instantanea Multiplica tensión por corriente y Añade al acumulador //Medición de la 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(); } } Vrms[Port] = cl.CalV*sqrt(sumV / NumeroDeMuestras); //Cálculo de la raiz cuadrada del promedio del voltaje elevado al cuadrado (RMS) / Aplica los coeficientes de calibración Irms[Port] = cl.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 PotenciaReal[Port] = cl.CalV*cl.CalI[Port]*sumP / NumeroDeMuestras;//calcula las potencias 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){PotenciaReal[Port]=0;}// No se detecta una potencia menor de 4W, para evitar los errores que quedan del filtro pasa bajos if (PotenciaAparente[Port]<4){PotenciaAparente[Port]=0;}// No se detecta una potenciao menor de 4VA, para evitar los errores que quedan del filtro pasa bajos FactorPotencia[Port] = PotenciaReal[Port] / PotenciaAparente[Port]; freq = (1000000.0 * vPeriodCount) / vPeriodSum; //divido por 1000 crc //Cálculo de la frecuencia vPeriodSum=0; //resetea los acumuladores vPeriodCount=0; //resetea los acumuladores sumV = 0;//resetea los acumuladores sumI = 0;//resetea los acumuladores sumP = 0;//resetea los acumuladores return true; }
//____________________________ENVIAR PAQUETE DE UN PUERTO_______________________________________ void enviarPaquetePort (int Port) { pzb= "PT="; pzb += floatToString(Port, 0); pzb += ";P="; pzb += floatToString(PotenciaReal[Port], 1); pzb += "W;S="; pzb += floatToString(PotenciaAparente[Port], 1); pzb += "VA;V="; pzb += floatToString(Vrms[Port], 1); pzb += "V;I="; pzb += floatToString(Irms[Port], 1); pzb += "A;F="; pzb += floatToString(freq, 1); pzb += "Hz;ST="; pzb += floatToString(EstadoRele[Port], 0); pzb += "st;"; borrarPayload(); for (int i=0; i<=pzb.length(); i++){payload[i] = pzb[i];} //Mete el valor de la string pzb en el payload xbee.send(zbTx); //Envía el paquete a la red Zigbee
//DEBUG nss.println ("enviarPaquetePort->ha enviado el paquete"); nss.println(pzb); // }
//____________________________ENVIAR PAQUETE DEL HARDWARE_______________________________________ void enviarPaqueteHW () { pzb= "PID="; pzb += cf.ProductId; pzb += ";HW="; pzb += cf.HardwareVersion; pzb += ";SW="; pzb += cf.SoftwareVersion; pzb += ";SN="; pzb += cf.SerialNumber; pzb += ";"; borrarPayload(); for (int i=0; i<=pzb.length(); i++){payload[i] = pzb[i];} //Copia la String en el Payload xbee.send(zbTx); //DEBUG nss.println ("enviarPaqueteHW->ha enviado el paquete"); nss.println(pzb); //
}
//____________________________BORRAR PAYLOAD_______________________________________ void borrarPayload() { for ( int n=0; n<=MAX_PAYLOAD_SIZE; n++){payload[n]= ' ';} //borra el contenido de la valiable del payload }
//_____________CONVERTIR LAS VARIABLES DOUBLE EN TEXTO______________________________ String floatToString(double number, uint8_t digits) { String resultString = ""; if (number < 0.0){ // Handle negative numbers resultString += "-"; number = -number; } double rounding = 0.5;// Redondea correctamente (1.999, 2) se imprimem como "2.00" for (uint8_t i=0; i<digits; ++i) rounding /= 10.0; number += rounding; unsigned long int_part = (unsigned long)number;// Extrae la parte entera del número y la imprime double remainder = number - (double)int_part; resultString += int_part; if (digits > 0) {resultString += ".";} // Imprime el punto digital si hay decimales while (digits-- > 0){ // Estrae los dígitos que quedan de uno en uno remainder *= 10.0; int toPrint = int(remainder); resultString += toPrint; remainder -= toPrint; } return resultString; }
//_________________MUESTRA LOS CÓDIGOS DE ERROR EN EL LED____________________________ 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);} } }
Hola Carlos, acabo de descubrir vuestra pagina gracias al trabajo del medidor de energía y esta muy currado, mi mas sincera enhorabuena.
Yo estoy intentando hacer algo igual con una placa tb ACS712 de 30A pero ya ensamblada para usarla en arduino.
http://img.alibaba.com/wsphoto/v0/498488806_2/F306-ACS712-module-30A-Hall-Current-Sensor-Module.jpg
Ahora mismo mis conociminetos acerca de este tema dejan mucho que desear por eso a ver si me puedes hechar una mano.
Partiendo del codigo del capitulo 19 que es mas resumido que el ultimo porque no lleva lo del xbee.
yo he conseguido que a 510 sea estable la medicion, tomando como partida ese valor como 0 osea 2,5v.
tus valores de los coeficientes de calibracion, salvo el CalFase que es 0.707 los demas no se como los has calculado.
y en el filtrado el 0.996 de donde sale??
Espero que me puedas hechar una mano, para lo que sea mi mail es riscking@hotmail.com
Espero tu respuesta.
UN saludo y aseguir asi.
Hola,
bueno, esta entrada ya tiene unos meses, no tengo muy buena memoria. De todas formas me has preguntado por una de las cosas que menos entendí. Solo leyendo el 0,996 de tu mensaje ya se a lo que te refieres.
Échale un ojo a este documento:
http://www.atmel.com/dyn/resources/prod_documents/doc2566.pdf
concretamente a la página 11:
When the ISR is started its first task is to read sampled data from the ADC and store
it in a location in SRAM where it cannot be overwritten by new data. The first task to
perform on the data is then to remove any DC offset. This is carried out using a digital
High-Pass Filter (HPF) of type Infinite Impulse Response (IIR). The transfer function
of the filter can be written as follows:
Equation 11. High-Pass Filter, Infinite Impulse Response Type.
y[n] = 0.996 × y[n −1]+ 0.996 × x[n]- 0.996 × x[n -1],
de aqui es de donde saqué la función para el filtro paso alto, entiendo que el número tiene que ver con la frecuencia de corte, pero no estoy seguro.
El resto de las calibraciones es puramente empírico, ajustando las desviaciones de fase con un medidor de potencia. Al final los resultados eran bastante satisfactorios, pero había problemas posiblemente con los redondeos de ahi los ajustes finales de no detectar potencias menores de 4W y los que están entre esas líneas.
En realidad muchas de las cosas que hay en el blog son experimentos inacabados, a los que falta darles un par de vueltas, espero ir retomando.
Aunque esto parece más un cuaderno de bitácora pretendemos hacer un espacio participativo, así que no te cortes y si haces algo interesante publícalo.
Saludos,