Medición de Tensión Alterna TrueRMS v2

Basándonos en la versión 1 de esta nota: http://electgpl.blogspot.com.ar/2016/04/medicion-de-tension-alterna-truerms.html
Vamos a realizar algunas mejoras en el firmware que van a hacer el código mas preciso y luego seguiremos con otro nuevo post sobre la medición de corriente para realizar el medidor de energía eléctrica.
Las mejoras son las siguientes:

1) Eliminamos el renglón del LCD donde mostramos el falso valor Pico (falso porque estábamos tomando el valor TrueRMS calculado y multiplicándolo por Raiz de 2, cosa que es falso ya que Raiz de 2 es un valor correspondiente al RMS para una señal seno, no aplica a otra forma de onda.

2) Cambio el valor del delay en la iteración del lazo for, que en el post anterior lo tenia en 10ms pero en la practica tras pruebas lo deje en 60us, esto es porque tenemos que medir tanto el hemiciclo positivo como el negativo que luego pasa a ser positivo por el rectificador de onda completa, entonces la medición debe ser de los 100Hz de señal. Para ello calculamos 300 iteraciónes por 60us, nos dará unos 18ms de delay (esto si no tomamos en cuenta la demora que tiene el resto de las instrucciones que si bien son pocos ciclos, existe), de esta forma podremos recorrer las 300 iteraciónes por los dos hemiciclos.

3) El cruce por cero, el método que tenemos para saber cuando la señal es cero y una vez que es así comenzamos la medición para que sea mas exacta y sincronizada, para ello usamos un Do While que lee el ADC y permanece dentro del mismo lazo hasta que pase por Cero y luego sale del lazo Do While dando lugar a entrar al lazo for de medición.

4) Por ultimo podremos ver en renglón donde se muestra el valor de tensión donde se multiplica por 75 y se suma a 44, esto es porque se ha realizado una regresión lineal de valores para llegar a la ecuación que responde a nuestra curva que por suerte es bastante lineal.
El procedimiento es medir la tensión RMS que nos da el ADC (entre 0 y 5V) y mediante un Variac vamos variando la tensión de entrada en el transformador de medición entre 150 y 240Vac (ese es el rango que he utilizado), se toman mediciones en distintos valores de tensión dentro de ese rango, Ej: 150, 160, 170, ... ,230 y 240Vac, a cada valor le corresponde un valor que sera el que entrega el transformador reductor, rectificado y atenuado en el ADC. A cada valor de tensión alterna variada entre 150 y 240Vac le corresponde un valor de ADC, entonces tomamos esas dos columnas de valores, las ingresamos en la planilla de calculo y luego se aplica un gráfico de dispersión xy donde una vez graficado se aplica la regresión estadística y nos muestra la ecuación. 
Esta ecuación es útil para mi sistema, es decir, el transformador que use de 220 a 12V el puente rectificador, atenuador, etc... Es un proceso que tendremos que hacer según el hardware que estemos configurando (es parte de la calibración, ya que luego se puede poner un preset en el atenuador resistivo del ADC para ajustar de forma fina).

El programa:


  1. #include <16F883.h>
  2. #device adc=10
  3. #use delay(int=4000000)
  4. #include <LCD.C>
  5. #include <math.h>
  6. void main(){
  7.    setup_adc_ports(sAN0|VSS_VDD);
  8.    setup_adc(ADC_CLOCK_DIV_2);
  9.    int16 i;
  10.    float adc1, valorMax=0;
  11.    lcd_init();
  12.    while(true){
  13.       do{
  14.          set_adc_channel(0);
  15.          adc1=read_adc()*5.0/1023.0;
  16.          delay_us(20);
  17.       }while(adc1!=0);
  18.       for(i=0;i<300;i++){
  19.          set_adc_channel(0);
  20.          adc1=read_adc()*5.0/1023.0;
  21.          delay_us(60);
  22.          valorMax=adc1*adc1+valorMax;
  23.       }  
  24.       lcd_gotoxy(1,1);
  25.       printf(lcd_putc,"Voltimetro Trms");
  26.       lcd_gotoxy(1,2);
  27.       printf(lcd_putc,"Val RMS: %3.0f ",(sqrt(valorMax/300)*75)+44);
  28.       valorMax=0;
  29.    }
  30. }

Linea 13 a 17, aquí podremos ver el Do While, recordemos que el Do While es igual al While pero con la diferencia que la primera vez ejecutara su código sin importar la condición que tenga y luego de la primera vez ejecutara según su condición.
En este caso hacemos una lectura del ADC la pasamos a un nivel entre 0 y 5V, la condición del Do While sera que el valor del adc sea distinto de cero, entonces mientras que el valor sea distinto de cero el Do While sera verdadero y el programa quedara loopeado dentro de ese lazo, una vez que el valor sea igual a cero, la condición sera falsa y saldrá del Do While, entonces seguirá con la instrucción siguiente que es el for, de esa forma el Do While cumple la función de esperar que la señal sea 0 o pase por 0.

Linea 18 a 23, Este sera el for que genera las 300 iteraciónes donde realizaremos la sumatoria de los cuadrados de cada valor cada 60us como hemos mencionado anteriormente.

Linea 27, Aquí tal vez podríamos realizar un paso anterior y pre-calcular los valores para mostrarlos en una variable nueva, pero el funcionamiento es el mismo y lo he dejado asi, pero es sencillo realizar el calculo antes de mostrar el valor en el LCD y luego enviarlo a una variable. El calculo que se realiza en la misma linea del LCD es tomar el valor sumado de los cuadrados y dividirlo por las 300 muestras para sacar el promedio de la suma y luego aplicar la raíz cuadrada. Por ultimo he agregado la multiplicación por 75 y la suma de 44 porque es el valor de la regresión que me ha dado el gráfico de dispersión de la planilla de calculo.

En resumen, hemos desarrollado un muy simple medidor de tensión TrueRMS con 16.7ksps. 



Medición de Tensión Alterna TrueRMS v1

Como hemos visto en otros post donde medimos tensión o corriente con el micrcontrolador, siempre lo hacemos en continua, donde no tenemos problemas con la forma de onda, valores pico, frecuencia y demás.
Pero cuando intentamos medir una señal alterna nos encontramos con algunos factores que complican nuestra medición, ya que si medimos una continua pulsante o una alterna, tendremos valores en el ADC que estarán cambiando constantemente, esto podríamos medirlo calculando un máximo, pero la idea nuestra es aplicar un poco de electrónica y calculo.
Si bien este proyecto mide el verdadero valor eficaz (TrueRMS) no pretende ser el reemplazo de instrumentos profesionales ni mucho menos. Es un proyecto que puede servir de modo educativo y también para realizar algunos proyectos hogareños donde no se requiere precisión ni certificaciones. 

Para comprender la teoría de nuestro proyecto debemos principalmente saber que es el valor RMS y porque existe la variante "Verdadero valor RMS" en un instrumento de medición.

Recordemos la forma de onda alterna que nos provee el suministro eléctrico domiciliario.



Como podemos ver en la imagen de la forma de onda, responde a la función seno con los máximos entre 1 y -1, con un periodo de 360° o 2¶. Donde también podremos ver el cruce por cero en 0°, 180° y 360°, al tratarse de una señal periódica este ciclo se repetirá indefinidamente.

El valor Vp o Valor Pico, es el que se encuentra entre el 0 de la amplitud y el valor máximo, el valor Vpp sera el valor entre los dos picos (el positivo y el negativo, que en este caso sera 2 veces Vp), por ultimo el valor eficaz o RMS (Root Mean Square, o Raíz Media Cuadrática).
El valor RMS como lo indica su nombre sera el valor de √2/2 lo que es igual a 0.707, como podremos ver en el gráfico la cota de Vef sale a 0.707.

Usualmente cuando uno busca el valor pico de una tensión alterna del suministro eléctrico, lo que se hace es tomar el valor eficaz medido por el multimetro (220V) y lo multiplicamos por √2 (1.4142) este valor nos dará un Vp=311V y un Vpp de 622V. 

El valor RMS conocido como √2/2, no es una constante física, sino que es el resultado de un calculo mas complejo en base a la formula del valor eficaz con su integral.
Donde debemos integrar en el rango deseado el valor del cuadrado de la función, en este caso, seno y luego debemos calcular su raíz.



Como podremos ver tendremos que cambiar v por la función seno y llegaremos al valor de Raíz de 2 sobre 2 "√2/2". 
Hay que tener en cuenta que el resultado de √2/2 solo servirá para una función seno, si la forma de onda que estamos midiendo no es senoidal (por ejemplo: una señal con ruido, la señal de una carga inductiva de potencia, la señal de un variador, de un dimmer, etc...) no servirá el valor √2/2. Esto quiere decir que deberíamos realizar un análisis de la función y una vez que llegamos a la función podremos aplicar la integral y demás, pero la realidad es que esto no es algo útil para un instrumento de medición, debemos encontrar la manera para que nuestro instrumento de medición pueda medir cualquier forma de onda y saber el valor eficaz de la misma para que sea un instrumento TrueRMS. 
Los instrumentos que no son TrueRMS solo dan el valor eficaz en señales senoidales ya que usan la formula √2/2.
Por suerte para nosotros existe otra forma de hacerlo mas discreta donde aplicaremos la formula de la raíz de la sumatoria de los cuadrados de la media.



Esta formula nos dará el mismo resultado que la formula de la integral, pero para utilizar la formula debemos tomar varias mediciones a la señal "Discretizar" tomando muchas mediciones a la misma y cada medición sera elevada al cuadrado, sumada con la siguiente y al finalizar todas las mediciones "Muestreo" dividiremos esa suma por la cantidad de muestras y aplicaremos la raíz cuadrada.
La precisión de la misma sera función de la cantidad de muestras que tomemos.



Como podemos ver en la imagen, estamos tomando valores instantáneos de tensión a determinado tiempo, V1, V2, V3, ... Vn, en esta imagen solo tomamos 7 valores pero mientras mas muestras tomemos mas cerca de la realidad estará nuestra señal. 
En nuestro caso tomamos 300 muestras, cada muestra se eleva al cuadrado (se multiplica por si misma) y se suma con la siguiente (aquí pondremos el concepto de acumulador), luego dividimos el valor final en 300 ya que son la cantidad de muestras realizadas y luego aplicaremos la raíz cuadrada, la cantidad de muestras es a gusto, y mientras mas muestras tomemos mas tardara nuestro programa en realizar el calculo, pero mas eficiente sera el valor que nos muestra.
Hay que tener en cuenta también el valor de la frecuencia de la forma de onda, como nuestro programa esta pensado para medir la tensión RMS en la red eléctrica solamente tendremos 50Hz que luego de rectificarla tendremos 100Hz lo que sera igual a 10ms.

En este caso la señal es senoidal, pero como nuestro sistema toma muestras y las procesa, dará igual el tipo de función o la forma de onda que se presente, lo cual lo convierte en un voltímetro AC TrueRMS.

Una vez que tenemos resuelto el procesamiento de la señal, tenemos el segundo problema, que será tratar la señal de 220Vac para ingresarla al ADC que soporta solo 5Vdc.
Para esto utilizamos un transformador de 220Vac a 9 o 12Vac, que aparte de reducir la tensión cumple la función de aislación galvánica del sistema para mayor seguridad, luego como estamos hablando de 12Vac podremos poner un puente rectificador de onda completa con 4 diodos ya que la caída de tensión de los diodos 1V para onda completa (ya que se usan dos diodos simultáneos por cada hemiciclo 500mV+500mV) y en 12Vac 1V no representa mucha caída en nuestra medición.
Luego de tener una señal pulsante (sin filtrarla) utilizaremos un divisor de tensión resistivo e ingresaremos esa señal en el ADC del microcontrolador.

Aquí podremos ver una simulación del mismo con el osciloscopio virtual.


En la practica hemos obtenido resultados similares



El circuito implementado es simple ya que la interfaz esta realizada con el transformador, el puente rectificador de onda completa y el divisor resistivo, el resto es solo el microcontrolador y el LCD.


El programa es muy sencillo y corto, se ha realizado de la manera mas sencilla para ser base de futuros proyectos y para comprender mas fácil su funcionamiento.

  1. #include <16F883.h>
  2. #device adc=10
  3. #use delay(int=4000000)
  4. #include <LCD.C>
  5. #include <math.h>
  6. void main(){
  7.    setup_adc_ports(sAN0|VSS_VDD);
  8.    setup_adc(ADC_CLOCK_DIV_2);
  9.    int16 i;
  10.    float adc1, valorMax=0;
  11.    lcd_init();
  12.    while(true){
  13.       for(i=0;i<300;i++){
  14.          set_adc_channel(0);
  15.          adc1=read_adc()*5.0/1023.0;
  16.          delay_ms(10);
  17.          valorMax=adc1*adc1+valorMax;
  18.       }  
  19.       lcd_gotoxy(1,1);
  20.       printf(lcd_putc,"Val Pico: %.3f ",sqrt(valorMax/300)*1.4142);
  21.       lcd_gotoxy(1,2);
  22.       printf(lcd_putc,"Val RMS: %.3f ",sqrt(valorMax/300));
  23.       valorMax=0;
  24.    }
  25. }

El muestreo de los valores se realiza dentro del lazo for en la linea 13, donde podremos ver que posee un 300 iteraciones que serán las muestras a realizar, luego accedemos al ADC y convertimos el valor entre 0 y 1023 a un valor entre 0 y 5V (valor que habrá que modificar cuando tengamos que hacer la relación entre 0 y 5V en los 220V (en mi caso estoy contemplando que la representación sea entre 150 y 250Vac ya que es para medición de la red eléctrica).
Luego de un delay, realizamos la sumatoria del cuadrado de los valores en base a un simple acumulador y el cuadrado lo realizamos multiplicando el valor del ADC por si mismo, podríamos usar la función pow() que se incluye en la librería math.h pero esta hace que el código sea demasiado pesado y el resultado es el mismo.
Una vez realizadas las 300 muestras salimos del lazo for y mostramos los valores.
Solamente es valido el valor del segundo renglón "RMS" ya que ahí tomamos el valor medido lo dividimos por 300 y le aplicamos la raíz (como dice la formula del valor eficaz con la sumatoria), notase que en este caso la raíz usa la función sqrt() de la librería math.h, pero sera lo único que utilizamos para no sobrecargar la memoria del microcontrolador.
Digo que solamente es valido el segundo renglón ya que ese es TrueRMS, el primer renglón se ha puesto solo para ver el valor pico pero como se puede ver esta utilizando el valor medido TrueRMS multiplicado por raíz de 2, entonces estaríamos realizando un calculo híbrido, primero tomamos el valor TrueRMS con la formula sumatoria y luego le multiplicamos raíz de 2 como si se tratara de la formula integral, lo cual solo sirve para la función seno, entonces si la forma de onda no es seno, no debemos tomar en cuenta el valor Vp, de echo se podría eliminar pero lo he dejado para que se comprenda esto a modo ejemplo.




Ir a la versión 2

Control de 8 Dígitos con MAX7219

El integrado MAX7219 es un conversor serial paralelo con algunas funciones extra, como control PWM de brillo para los LEDs, tablas de verdad de dígitos de 7 segmentos, ya que es posible utilizar este integrado tanto para controlar una matriz de leds como para controlar display de 7 segmentos.
El MAX7219 posee una interfaz SPI (Control mediante Reloj, Dato y Carga).
Este dispositivo posee dos salidas de 8 bit conformando el control de 64 puntos (8x8), o bien 8 dígitos de 7 segmentos con su punto.

Las especificaciones son las siguientes:

o Interfaz serial de hasta 10MHz
o Control Individual de cada LED
o Decodificador de 7 segmentos o sin decodificador
o Modo bajo consumo de 150uA
o Control de Brillo
o Saludo inicial en los display para corroborar el funcionamiento
o Interfaz SPI, QSPI y Microwire
o Tensión de alimentación hasta 6V
o Corriente maxima de hasta 500mA
o Encapsulado SMD y THT
o Control de corriente externo mediante una resistencia.

La configuración típica que encontraremos en el datasheet sera la siguiente, aunque la esta mostrando con los 8 dígitos, podríamos utilizar una matriz de 8x8 puntos.:



En este caso se ha realizado el control SPI por software, aunque es posible hacerlo por hardware con el microcontrolador, la opción por software es sencilla para aquellos microcontroladores que no poseen la interfaz.

Como mencionaba anteriormente el MAX7219 posee algunas opciones especiales, no es solo un conversor serial paralelo.
Para ello debemos analizar los registros internos del MAX.



0xX0: En esta posición tendremos el No-Operation, es decir, no se realizara ninguna acción, pero no esta apagado el dispositivo sino que los leds en su salida están apagados.

0xX1 a 0xX8: Sera la elección de los dígitos o columnas, que podremos hacerlo de forma directa con estos valores.

0xX9: Es el registro que controlara el tipo de decodificación, si sera binaria o 7 segmentos.

0xXA: Con esta dirección controlaremos el brillo de los leds, que puede setearse al principio o variarse dinámicamente dando un efecto de tonalidad si quisiéramos dibujar en una matriz de 8x8 puntos.

0xXB: Esta dirección se utiliza si no estamos seguros de cuantos dígitos vamos a usar (por si queremos realizar una librería estándar).

0xXC: En esta dirección pasaremos al modo bajo consumo o lo despertaremos.

0xXF: Este sera un comando de test del Display.

El registro de tipo de decodificación nos indicara si decodificamos 7 segmentos los 8 dígitos, o para el primer dígito y el resto no, los primeros 4 dígitos y los otros 4 no y por ultimo todos en binario, para ello debemos decidir si al registro que posee el valor 0x09 le enviaremos 0x00, 0x01, 0x0F o 0xFF.



En esta otra tabla podremos ver la tabla de verdad interna donde tendremos la codificación para controlar los display de 7 segmentos si eligiéramos esa opción.



Ahora controlaremos el brillo de cada led, para ello debemos configurar el valor que nos demuestra la tabla y enviarlo al registro 0x0A, en esta tabla podremos ver que el valor va de 0 a 15 y en la primer columna veremos el ciclo de trabajo PWM para los leds.



La opción de elegir la cantidad de dígitos de forma dinámica requiere de la función ScanLimit, lo que realizara mas eficientemente la librería para utilizarla de forma universal.
Para ello debemos mover el valor de dígitos al registro 0x0B.



Por ultimo el registro encargado de apagar o poner en modo Sleep el circuito para que el consumo se reduzca a 150uA, también podremos despertarlo para ser utilizado, es muy útil en sistemas con baterías.


El circuito que he implementado es sumamente sencillo y no requiere de mucha explicación pero como podremos ver todo esta resuelto con el MAX7219, el microcontrolador solo utiliza 3 salidas digitales, lo que nos da la opción de usar un microcontrolador sencillo de 8 pines para ahorrar dinero y espacio en el circuito.


El programa que he realizado solo envía valores numéricos estáticos al circuito ya que para el ejemplo esta bien y luego uno puede modificar dinámicamente la función escribe7219() con los valores que quiera.

Lo primero que encontraremos serán las macros de definición para los pines de salida que solo hay que elegir cual sera el que quieren usar o queda cómodo en el diagrama impreso.

  1. //Pines a utilizar
  2. #define CLK  PIN_B6
  3. #define LOAD PIN_B5
  4. #define DATA PIN_B4

Luego tendremos las direcciones del registro mencionado anteriormente con cada cuadro de configuración que serán utilizados mas adelante en la inicialización del MAX.

  1. //Variables del MAX7219 segun Datasheet
  2. #define decodifica 0x09
  3. #define brillo 0x0A
  4. #define cantDigitos 0x0B
  5. #define bajoConsumo 0x0C

Luego se han realizado varias funciones en el código que podrían hacerse en menos pero esta forma permite la reutilización de código mas eficiente.

  1. //Funcion de Clock
  2. void clock7219(){

  1. //Funcion para cargar el valor
  2. void load7219(){

  1. //Funcion para enviar el dato de 16bit
  2. void envia7219(long dato){

  1. //Funcion para construir el dato a enviar
  2. void creaDato(byte modo, int datoEntrante){

  1. //Funcion para escibir, Digito y Valor (1xxx xxxx DP)
  2. void escribe7219(byte digito, int dato){

  1. //Modo bajo consumo 1=WakeUp, 0=Sleep
  2. void bajoConsumo7219(int modoOperacion){

  1. //Modo de codificacion Binario(15) o Decada
  2. void decodifica7219(int modoDecodifica){

  1. //Configuracion del nivel de brillo 1 a 15
  2. void brillo7219(int nivelBrillo){

  1. //Configuracion de la cantidad de digitos 1 a 8
  2. void cantDigitos7219(int totalDigitos){

De todas las funciones podríamos destacar la mas importante como la función creaDato() que sera llamada del resto de las funciones para tomar el valor de registro y de configuración como se menciono en los cuadros de configuración de registros anteriores.

Luego las funciones de manejo de bits como clock, dato y carga que son las necesarias para crear la comunicación con el integrado.

Por ultimo la función inicializa que se encarga de configurar los parámetros de registro en los cuadros principalmente antes de comenzar el ciclo repetitivo del programa.

La función que enviara el valor es escribe7219() donde enviaremos dígito y valor.

Ahora el programa completo:

  1. #include <16F883.h>
  2. #use delay(clock=4000000)
  3. //Pines a utilizar
  4. #define CLK  PIN_B6
  5. #define LOAD PIN_B5
  6. #define DATA PIN_B4
  7. //Variables del MAX7219 segun Datasheet
  8. #define decodifica 0x09
  9. #define brillo 0x0A
  10. #define cantDigitos 0x0B
  11. #define bajoConsumo 0x0C
  12. //Variable de formateo de salida
  13. long datoSerial=0;
  14. //Funcion de Clock
  15. void clock7219(){
  16.    output_low(CLK);
  17.    delay_ms(1);
  18.    output_high(CLK);
  19. }
  20. //Funcion para cargar el valor
  21. void load7219(){
  22.    output_low(LOAD);
  23.    delay_ms(1);
  24.    output_high(LOAD);
  25. }
  26. //Funcion para enviar el dato de 16bit
  27. void envia7219(long dato){
  28.    int cont;
  29.    for(cont=0;cont<16;++cont){
  30.       output_bit(DATA,shift_left(&dato,2,0));
  31.       clock7219();
  32.    }
  33.    load7219();
  34. }
  35. //Funcion para construir el dato a enviar
  36. void creaDato(byte modo, int datoEntrante){
  37.    datoSerial=0x00;
  38.    datoSerial<<=4;
  39.    datoSerial|=modo;
  40.    datoSerial<<=8;
  41.    datoSerial|=datoEntrante;
  42.    envia7219(datoSerial);
  43. }
  44. //Funcion para escibir, Digito y Valor (1xxx xxxx DP)
  45. void escribe7219(byte digito, int dato){
  46.    creaDato(digito, dato);
  47. }
  48. //Modo bajo consumo 1=WakeUp, 0=Sleep
  49. void bajoConsumo7219(int modoOperacion){
  50.    creaDato(bajoConsumo,modoOperacion);
  51. }
  52. //Modo de codificacion Binario(15) o Decada
  53. void decodifica7219(int modoDecodifica){
  54.    creaDato(decodifica, modoDecodifica);
  55. }
  56. //Configuracion del nivel de brillo 1 a 15
  57. void brillo7219(int nivelBrillo){
  58.    creaDato(brillo, nivelBrillo);
  59. }
  60. //Configuracion de la cantidad de digitos 1 a 8
  61. void cantDigitos7219(int totalDigitos){
  62.    creaDato(cantDigitos, totalDigitos);
  63. }
  64. void inicia7219(){
  65.    creaDato(bajoConsumo,1);
  66.    //Deco 7seg, 255 para 8dig, 15 para 4dig
  67.    creaDato(decodifica,255);
  68.    //Cant 7seg, 0 al 7
  69.    creaDato(cantDigitos,7);
  70.    //Brillo minimo 0 y maximo 15
  71.    creaDato(brillo,15);
  72. }
  73. void main(){
  74.    inicia7219();
  75.    while(true){
  76.       escribe7219(1,0);
  77.       escribe7219(2,0);
  78.       escribe7219(3,0);
  79.       escribe7219(4,1);
  80.       escribe7219(5,2);
  81.       escribe7219(6,1);
  82.       escribe7219(7,7);
  83.       escribe7219(8,0);
  84.    }
  85. }