terça-feira, 12 de maio de 2015

Voltímetro RMS - para medir tensão alternada(AC) via RS-232.


           Este exemplo mostra como medir a tensão RMS senoidal(a mesma da rede elétrica), para isto o circuito deve ler o valor de pico e calcular o valor RMS, neste exemplo eu calculo o valor de pico a partir da tensão RMS, mas pode ser fazer o contrario também, basta lembra que  Vrms = VP/(1,41...) ou seja valor de pico dividido pela raiz quadrada de 2.
           Um dos maiores problemas é ler a tensão 220V ou 110V diretamente com o PIC dai tem se as opções de recorrer a um chip dedicado a isso(que custa caro e é difícil de encontrar) ou usar um transformador(mas dai a corrente de magnetização e as próprias características elétrica do mesmo não são padrão, o que gerar certo retrabalho), então a forma mais simples foi usar divisores resistivos,mas dai teria que montar outros circuitos externos para manter o sinal em níveis desejado pegando os pulsos negativos e positivos, dai dentro das possibilidades de simplificar o projeto montei o seguinte circuito:

            Caso não tenha experiência com eletrônica não monte o circuito físico("não me responsabilizo por eventuais choques ou danos materiais"), monte a seguinte tela de simulação no programa proteus:

            Veja que na simulação o circuito marcando exatamente a mesma tensão que o voltímetro AC. Como o circuito foi montado em uma protoboard pode ser que dê uma pequena variação na tensão medida no circuito física de vcs(mas mesmo assim estará próximo do valor real), se for conferir a tensão com um multímetro verá que se estiver na escala 200V será bem próximo ou igual se tiver na escala de 1000V dará uma diferença maior pois o próprio multímetro tem uma variação de -+5V (nesta escala), um detalhe interessante que reparei aqui nos teste de bancada foi que dependendo do lado que eu coloco o pino dá diferença no valor(ou seja a posição Fase e Neutro deve ser respeitada), coloquei um transformador para avaliar a situação e vi que os picos positivos da senoide eram maiores e talvez isso afeta o circuito de alguma forma, talvez pela própria fonte de alimentação do circuito, mas o fato é que mesmo assim o circuito funcionou de forma satisfatoria.
          Os resistores de 100K são para limitar a corrente e promover uma queda de tensão (isso evita que os diodos entrem em curto na sua região de tensão reversa), a tensão medida é o valor da queda de tensão no resistor de 3,3K(R3),após a retificação do sinal pelos diodos(1N4007) existe um segundo divisor de tensão que irá deixar passar uma pequena corrente suficiente para o microcontrolador ler.
         Com este circuito nas mãos o próximo passo era ler diferentes tensões alternadas e montar uma equação que convertesse as unidades lidas pelo modulo ADC do PIC para o valor RMS.Um detalhe importante deste circuito é a captura do maior valor lido durante o período de uma senoide(adotei 25ms pois assim abrange uma faixa maior e pode operar tanto em 50HZ quanto em 60HZ).
        No código você pode usar ele com o simulador ou com o circuito real(físico),basta  comentar uma das linhas :
        circuito_real(); // para usar o circuito fisico
        simulador(); //para usar o simulador
    Isso se deve pelo fato dos circuitos serem diferentes.

Segue abaixo o código fonte:

/*
 *                             voltímetro AC usando RS-232 no MPlab XC8
 *
 * Compilador : MPlabXC8
 * Microcontrolador: 16F877A
 * Autor: aguivone
 * Versão: 1
 * Data :  12 de março de 2015
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <delays.h>
#define _XTAL_FREQ 20000000    // cristal de 20 Mhz
#include <xc.h>

/////////////////////////////////////////////////////////configuraçôes//////////////////////////////////////////////////

#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable bit (BOR disabled)
#pragma config LVP = OFF        // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off)
#pragma config WRT = OFF        // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)



////////////////////////////////////////////////variaveis globais///////////////////////////////////////////////////////////
#define MAXIMO  20 //tamanho máximo de amostra deve ser numero par(e maior que 4) para funcionar
// saiba que na verdade isso faz com que se tenha um impar(pois começa em 0)
char caracter;
bit flag_interrupcao = 0;
    
float variavel_aux=0;
unsigned long tensao = 0;

void delay_ms(long val)
   {
       while(val>0)
       {
           __delay_ms(1);//resolve bug do compilador
           val--;
       }
   }
///////////////////////////////////////////////////interrupção//////////////////////////////////////////////////////////////
void interrupt interrupcoes(void)//vetor de interrupção
 {
    if(RCIF)
    {//se interrupção de recepção da serial
        //aqui não será usado essa funcão
     caracter = RCREG;
     flag_interrupcao = 1;
     RCIF = 0;//  limpa flag de interrupção de recepção
    }
    if(ADIF)
    {//se interrupção do modulo analogico!
        ADIF = 0;//limpa flag
    }
 }

/////////////////////////////////funçoes usadas pela uart //////////////////////////////////////////////////////
void inicializa_RS232(long velocidade,int modo)
{////por padrão é usado o modo 8 bits e sem paridade, mas se necessario ajuste aqui a configuração desejada.
    //verifique datasheet para ver a porcentagem de erro e se a velocidade é possivel para o cristal utilizado.
    RCSTA = 0X90;//habilita porta serial,recepção de 8 bit em modo continuo,assincrono.
    int valor;
        if(modo == 1)
        {//modo = 1 ,modo alta velocidade
         TXSTA = 0X24;//modo assincrono,trasmissao 8 bits.
         valor =(int)(((_XTAL_FREQ/velocidade)-16)/16);//calculo do valor do gerador de baud rate
        }
        else
        {//modo = 0 ,modo baixa velocidade
         TXSTA = 0X20;//modo assincrono,trasmissao 8 bits.
         valor =(int)(((_XTAL_FREQ/velocidade)-64)/64);//calculo do valor do gerador de baud rate
        }
    SPBRG = valor;
    RCIE = 1;//habilita interrupção de recepção
    TXIE = 0;//deixa interrupção de transmissão desligado(pois corre se o risco de ter uma interrupção escrita e leitura ao mesmo tempo)
}
void escreve(char valor)
{
    TXIF = 0;//limpa flag que sinaliza envio completo.
    TXREG = valor;
    while(TXIF ==0);//espera enviar caracter
}
void imprime(const char frase[])
{
     char indice = 0;
     char tamanho = strlen(frase);
      while(indice < tamanho ) ///veja que o programa pode travar se aqui não tiver as duas aspas
       {
           escreve(frase[indice]);
           indice++;
       }
}
void long_to_char(long quant)
{
       char convert_char6='0';
       char convert_char7='0';
       char convert_char8='0';
       char convert_char9='0';
       while(quant>=1000)
        {
         quant=quant-1000;
         convert_char6++;
         }
       while(quant>=100)
        {
         quant=quant-100;
         convert_char7++;
         }
       while(quant>=10)
        {
         quant=quant-10;
        convert_char8++;
         }
       while(quant>=1)
        {
           quant=quant-1;
         convert_char9++;
         }
          escreve(convert_char6);
          escreve(convert_char7);
          escreve(convert_char8);
          escreve(',');
          escreve(convert_char9);

}

///////////////////////////////////////funçoes usadas pelo conversor AD//////////////////////////////////////////
void inicializa_AD()
{
    ADCON0 = 0X81;//freq. div. por 32(20mhz o que leva a 1.6us cada aquisição) ;habilita modulo de conversão'
    ADCON1 = 0X84;//leitura do valor justificado a direita,apenas RA0,RA1 e RA3 são portas analogicas.
    ADIE = 1;//não habilita interrupção de conversão analogica
    ADIF = 0;
}

// ===================================================================================
// Função:    _ler_adc_un
// Parâmetros: não tem.
// Retorno   : unsigned long ultensao.
// Descrição : retorna o valor lido em unidades de medidas.
// ===================================================================================

unsigned long ler_adc_un(void)
{
    ADCON0bits.GO = 1; //inicia conversão
    while (ADCON0bits.GO_DONE == 1); //espera finalizar leitura
    return((ADRESH << 8) | ADRESL);
}
// ===================================================================================
// Função:    _Valor_pico
// Parâmetros: não tem.
// Retorno   : unsigned long tensao.
// Descrição : retorna o valor lido em unidades de medidas.
// ===================================================================================
unsigned long Valor_pico()//não colocar mais que 60 amostras
{
    unsigned long tensao_temporaria; 
    unsigned int conta=0;
    unsigned long tensao_maxima = 0;
    //tempo de 1 senoide completa em 50hz - mas funciona tambem em 60hz
    //caso queira apenas 60hz use o valor 166
        while(conta < 2500)//tempo de amostra de 25ms(assim fica compativel com 50hz))
        {
            tensao_temporaria = ler_adc_un();
            if(tensao_temporaria > tensao_maxima)//pega sempre o maior valor valor de pico
            {
               tensao_maxima =  tensao_temporaria;
            }         
            __delay_us(10); 
            conta++;  
        }  
    return (tensao_maxima);
}
// ===================================================================================
// Função:    _simulador
// Parâmetros: não tem.
// Retorno   : não tem.
// Descrição : faz calculo e joga na interface serial . //usado somente na simulação do proteus
// ===================================================================================
void simulador()
{
    if(tensao != 0) //só trata se tiver lido alguma tensão
        {
            imprime("\n\r valor pico - valor RMS");
            imprime("\n\r ");
            variavel_aux = (489*tensao)/10000; //para ter mais precisão - valor de pico 
            long_to_char((unsigned long)variavel_aux);
            imprime(" - ");
            variavel_aux = (347*tensao)/10000; //para ter mais precisão - valor RMS
            long_to_char((unsigned long)variavel_aux);
        }
}
// ===================================================================================
// Função:    _circuito_real
// Parâmetros: não tem.
// Retorno   : não tem.
// Descrição : faz calculo e joga na interface serial . //usado para ler tensão de 0 a 350V(teorico pois só foi testado até 230V)
// ===================================================================================
void circuito_real()
{
    if(tensao != 0) //só trata se tiver lido alguma tensão
        {
            imprime("\n\r valor RMS - valor pico");
            imprime("\n\r ");
            variavel_aux = (((100*tensao) + 258)/278)*10; //para ter mais precisão - valor de pico 
            long_to_char((unsigned long)variavel_aux);
            imprime(" - ");
            variavel_aux = (variavel_aux*141)/100; //para ter mais precisão - valor RMS
            long_to_char((unsigned long)variavel_aux);
        }
}
void main(void)
{
    TRISC = 0X80;//configura portC  C7 (pino RX) como entrada
    PORTC = 0;  // limpar as portas que estão configuradas como saidas
    TRISA = 0XFF;//configura portA como entrada
    inicializa_RS232(9600,1);//modo de alta velocidade
    inicializa_AD();
    PEIE = 1;//habilita interrupção de perifericos do pic
    GIE = 1; //GIE: Global Interrupt Enable bit
    imprime("Voltimetro RMS usando a serial!");
    for(;;)
    {   
        tensao = Valor_pico();
        tensao = tensao + Valor_pico();
        tensao = tensao + Valor_pico();
        tensao = (tensao + Valor_pico())/4;//pega 4 amostras e tira uma media logo será 100ms
         //comente a linha que não for usar
       // circuito_real();
        simulador();
        delay_ms(500);//espera 0.5 segundo
    }//loop infinito

}









8 comentários :

  1. Olá Aguivone .
    Primeiro gostaria de parabenizar por escrever seus programas de maneira tão didática.
    Analisando o seu algoritmo percebi que como você calcula o valor RMS baseado na tensão de pico, é necessário que a forma de onda seja perfeitamente senoidal. Qualquer distorção na forma de onda do sinal acarretará em erros nos valores lidos.
    Uma sugestão seria implementar um algoritmo que calcule o valor RMS independente da forma de onda do sinal. Talvez usando uma interrupção cada que o sinal passar por zero e fazer várias amostras em um semiciclo. Depois basta apenas calcular o valor RMS através desse somatório ( equação 1): http://pt.wikipedia.org/wiki/Valor_eficaz.

    ResponderExcluir
  2. Olá Fernando, vocês está certo é que esqueci de informar que o circuito é para avaliar inclusive picos de tensão, pois há momentos que isso é relevante na aplicação, para reduzir os efeitos dos surtos de tensão(se for o caso) basta tira mais medias, e "sim" este circuito é somente para ondas senoidais de frequências entre uns 40 a 70 hz , mas com pequenas adaptações é possível usar para outros tipos de ondas. obrigado por contribuir com sua sugestão!

    ResponderExcluir
  3. Como vai, Aguivone? Obrigado por compartilhar com a gente o seu grande interesse e conhecimento em microcontroladores! Faço questão de dizer que blogs deste nível aqui no Brasil são raros.

    Estou estudando o código que você postou e apareceu uma dúvida: Onde você diz que o valor pode ser alterado para 166 (para medições em 60Hz), o correto não seria 1660? Pelo que entendi você está se referindo à variável conta, não é isso?

    Obrigado!

    ResponderExcluir
    Respostas
    1. acho que quando comentei o tempo estava diferente, talvez depois de testar posso não ter atualizado , como o tempo depende também das rotinas caba dando um pouco de divergência quanto ao valor teórico, mas creio que vc tenha razão deve ser 1660.

      Excluir
  4. Bom dia!
    Tem algum CI que faça esta mesma função com 220vca ou 380vca?
    Grato, Odair - osrtec@yahoo.com.br

    ResponderExcluir
    Respostas
    1. É possível fazer isso com qualquer microcontrolador PIC que tenha ADC (conversor analógico digital). O segredo é fazer o sinal passar por um divisor de tensão antes de chegar ao PIC, porque o ADC dele só aceita no máximo 5 volts de entrada.

      Excluir
  5. Olá, boa tarde!

    Parabéns pelo projeto.
    Estou implementando um parecido só que vai fazer medição entre fase (380V).

    Uma pergunta: Por acaso você chegou à testar na prática? Minha dúvida é referente a fuga de corrente pelo GND. Por acaso, você teve que conectar os dois GNDs (tanto do circuito a ser medido quanto o GND de alimentação do PIC)? E não apresentou nenhum problema?

    Abraço e obrigado!

    ResponderExcluir
  6. ola.

    aguem teria um programa dese jeito da seguinte forma.
    ler a tensão de rede 110 e deixar sair 110,quando ler a tensão 220 também deixa sair 110,alguém consegue pra mim por favor.

    agradecido desde já.
    mande no meu email por favor.
    lucianoolivera370@gmail.com

    ResponderExcluir

olá,digite aqui seu comentário!