sexta-feira, 23 de setembro de 2022

Voltímetro para corrente alternada - Voltimetro AC com PIC

 Olá, hoje postarei um voltímetro para ler sinais AC usando o microcontrolador PIC16F88, no qual analisa um sinal de corrente alternada no pino RA0 e retorna o valor lido em milivolts na porta serial, atente que na simulação e no protótipo real o valor na função de configuração da serial é diferente.

    Este projeto é útil quando se precisa ler o valor de pico da tensão, com alguma pequenas modificações é possível ter uma tensão média(tipo do multímetro) ou o valor de pico (tensão RMS). Para simulação foi usado apenas um gerador de sinal, um osciloscopio e um terminal serial configurado para (19200 de baud rate), no pic a frequência de clock é de 8 mhz (pois está usando o oscilador interno). Este projeto funcionou bem na protoboard e na simulação. Favor clicar na imagem para ver melhor os detalhes da tensão.


O código fonte :
/*
 * Empresa cliente: exemplo_blog
 * Compilador : MPlabXC8
 * Microcontroladores compativeis : 16F88A
 * Autor: Aguivone Moretti Fógia
 * Versão: 1
 * Data :  23_09_2022   -> ler tensao AC
 */

#include <stdio.h>
#include <string.h> //para usar funçoes de string deve se adicionar este header
#include <stdlib.h>
#define _XTAL_FREQ 8000000
#include <xc.h>

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

#pragma config FOSC  = INTOSCIO  // Oscillator Selection bits (INTRC oscillator; port I/O function on both RA6/OSC2/CLKO pin and RA7/OSC1/CLKI pin)
//#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator) -- cristal de 20mhz
#pragma config WDTE  = OFF       // Watchdog Timer Enable bit (WDT habilitado)
#pragma config PWRTE = OFF       // Power-up Timer Enable bit (PWRT enabled)
#pragma config MCLRE = OFF      // RA5/MCLR/VPP Pin Function Select bit (RA5/MCLR/VPP pin function is digital I/O, MCLR internally tied to VDD)
#pragma config BOREN = OFF      // Brown-out Detect Enable bit (BOD disabled)
#pragma config LVP   = OFF        // Low-Voltage Programming Enable bit (RB4/PGM pin has digital I/O function, HV on MCLR must be used for programming)
#pragma config CPD   = OFF         // Data EE Memory Code Protection bit (proteção da eeprom)
#pragma config CP    = OFF         // Flash Program Memory Code Protection bit (proteção do codigo)


#define  TAM_MAX   10//tamanho maximo do vetor de amostragem(numero de pontos)
#define  RANGE_ADC 10//variação maxima permitida (filtro de ruido)

#define LED_STATUS   RB0
#define  TEMPO_ENVIO  5000//(aprox. 0,5s)tempo para enviar os dados(em tempo de rotina))

int vetor[TAM_MAX+1];  //armazena as amostra temporaria
char flag_libera=0;
char tempo_media = 0;
int tempo_de_envio = 0;
int tensao_pico=0;


/////////////////////////////////funçoes usadas pela uart /////////////////////////////////////////////////////////////////

/*datasheet pag 101 - baudrate para 8mhz  - high speed
 baud rate desejado    -  baud rate real - faixa de erro - SPBRG
 2400  2404   0,16  207
 9600  9615   0,16  51
19200  19231  0,16  25
28800  29412  2,12  16
38400  38462  0,16  12
57600  55556  3,55  8
 */
void inicializa_RS232(long velocidade,int modo,char pega_ref)
{////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.
    long valor;
        if(modo == 1)
        {//modo = 1 ,modo alta velocidade
         TXSTA = 0X24;//8bits/transmissão habilitado/modo assincrono/highspeed
         valor =(((_XTAL_FREQ/velocidade)-16)/16);//calculo do valor do gerador de baud rate -> 10_06_2022
        }
        else
        {//modo = 0 ,modo baixa velocidade
         TXSTA = 0X20;////8bits/transmissão habilitado/modo assincrono/lowspeed
         valor =(((_XTAL_FREQ/velocidade)-64)/64);//calculo do valor do gerador de baud rate
        }
    if(pega_ref == 0)
    {//valor calculado
       SPBRG =(int)valor; // veja a pagina 101 do datasheet já tem calculado para 8MHZ
    }
    else
    {//valor colocado diretamente
      SPBRG =(int)velocidade; //coloca valor direto baseado no data sheet  
    }
    RCIE = 1;//habilita interrupção de recepção
    PEIE =1;
    TXEN = 1; // habilita o modulo de transmisã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)
}
///////////conversor adc //////////////////////////
// 0 - lê sensor de luz  1 - lê sensor magnetico  
int ler_adc_un(void)  //(int sele)
{
        int lido_adc;
        GIE = 0;//para não travar com outra interrupção     
        ADCON0 = 0XC5;//pede nova conversão //AN0
        while (ADCON0 == 0XC5); //espera finalizar leitura
        GIE = 1;
        lido_adc = (ADRESH << 8) | ADRESL; // XXXXXXXXLL   
        return(lido_adc);
}
void interrupt interrupcao(void)//vetor de interrupção
 {
    char teste;
    if(RCIF == 1)
    {//interrupção de recepção de dados
        //lembrar de por um timeout pra não ficar travado esperando dados
        RCIF = 0;//  limpa flag de interrupção de recepção
        teste = RCREG;
    }
}
void long_to_char(int valor)
{
    char valores[] = {'0','0','0','0'};
    while(valor>=1000)
       {
           valor = valor - 1000;
           valores[0]++;
       }
    while(valor>=100)
       {
           valor = valor - 100;
           valores[1]++;
       } 
    while(valor>=10)
       {
           valor = valor - 10;
           valores[2]++;
       } 
    while(valor>=1)
       {
           valor = valor - 1;
           valores[3]++;
       } 
    for(int indice = 0;indice < 4;indice++)
       {
           while(TRMT==0);//espera enviar caracter, esvaziando o shift register , usando o trmt é mais garantido a entrega 
           TXREG = valores[indice];//no datasheet diz que ao carregar o TXREG limpa o bit TXIF           
           while(TRMT==0);
       }
       TXREG ='\r';//retorna paar inicio de linha          
       while(TRMT==0);
       TXREG ='\n';//pula linha          
       while(TRMT==0);
}
void rotaciona_vetor()
{ // apenas rotciona e descarta o valor que estava em vetor[0]
    int aux=0;
    while(aux<TAM_MAX)
    {
        vetor[aux] = vetor[aux+1];
        aux++;
    }
}
void ordena_vetor()
{//coloca em ordem crescente onde o maior valor estará em vetor[tam_max]
    int aux=0;
    int aux2=0;
    int houve_troca = 1;
    while(houve_troca == 1)
    { ///faz isso enquanto houver troca de posição
        houve_troca = 0;
        aux=0;
        while(aux<TAM_MAX)
        {
            if(vetor[aux] > vetor[aux+1])
            {
               aux2 = vetor[aux+1];
               vetor[aux + 1] = vetor[aux];
               vetor[aux] = aux2;
               houve_troca = 1;
            }        
            aux++;
        }
    }
}
int analisa_sinal(void)
{ //analisa os 4 valores mais altos
    int valor_pico = 1050;//valor impossivel -> só pra indicar erro na leitura
    ordena_vetor();// coloca em ordem crescente
    ////verifica se há valores discrepantes//// erro ou ruido
    //veja que qualquer coisa dessas possibilidade é interpretado como zero
    
    if((vetor[TAM_MAX]-vetor[TAM_MAX-1])< RANGE_ADC) //se a diferença não for maior que o range
    {//senão houver valor discrepante está ok
        if((vetor[TAM_MAX-1]-vetor[TAM_MAX-2])< RANGE_ADC) //se a diferença não for maior que o range
            {//valida os 3 ultimos
              valor_pico = (vetor[TAM_MAX]+vetor[TAM_MAX-1]+vetor[TAM_MAX-2])/3;
            }
             
    }
    else
    {
        if((vetor[TAM_MAX-1]-vetor[TAM_MAX-2])< RANGE_ADC) //se a diferença não for maior que o range
            {//descarta o maior valor
               if((vetor[TAM_MAX-2]-vetor[TAM_MAX-3])< RANGE_ADC) //se a diferença não for maior que o range
                    {//valida 
                      valor_pico = (vetor[TAM_MAX-1]+vetor[TAM_MAX-2]+vetor[TAM_MAX-3])/3;  
                    }
            }     
    } 
    return(valor_pico);
}
//////////////////////////////////////////////////////Rotina principal///////////////////////////////////////////////////////////////çlj
void main(void)
{
    TRISB = 0X04;//configura portB como saida ,  RB2 é entrada pois é rx 232 e B4 não será usado
    TRISA = 0XFF;//configura portA como entrada .só RA4 e RA3 como saida 
    PORTA = 0;
    PORTB = 0;
    OPTION_REG = 0X80; // pull up desabilitados e prescaler para timer 0 - desabilita watchdog
    INTCON = 0;//desabilita todas as interrupções 
    CMCON = 0X07;//desliga comparadores 
    OSCTUNE = 0X1F;  // oscilador vel maxima
    OSCCON = 0XFE ; // oscilador interno com frequencia de 8mhz
    ///  OSCCON = 0X7C ; // oscilador cristal externo 20mhz
    /////ADC/////////////
    ANSEL = 0X01; //portas digitais e AN0 .
    ADCON0 = 0XC1;//ligado ao oscilador RC  - AN0 selecionado  e modulo de conversão ligado 
    ADCON1 = 0X80;//justificado a direita(assim os 2bits mais significativos fica no primeiro byte) , ref ligado ao vdd
   // ADCON1 = 0XC0;//justificado a direita(assim os 2bits mais significativos fica no primeiro byte) , ref ligado ao vdd   -> clock  dividido por 2
    /////////////////
    PIE1bits.ADIE = 0;// desliga interrupção do conversor ADC     
    //19200 -> modo de alta velocidade -> creio que o clock não está muito bem calibrado pois não está dando muito certo   
    //valores validos entre spbrg 27 e 30 -> para 19200 logo valores mais adequados 27 a 29 = mas 28 ficou melhor
    inicializa_RS232(29,1,1);//passa valor direto do registrador na placa fisica deve ser 28 ou 29
    inicializa_RS232(25,1,1);//passa valor direto do registrador para simular no proteus deve ser 25 
    GIE = 1;            
    for(;;) 
    {  //  atualiza os vetores 
         __delay_us(100); //para garantir uma leitura estavel 0,4ms
        vetor[TAM_MAX] = ler_adc_un();        
        if((flag_libera == 1))
        {
             if(vetor[TAM_MAX]<vetor[0])//localizou a regiao de pico  de tensão
            {
                if(analisa_sinal() < 1025)  //busca valor de pico valido
                { 
                    tensao_pico = analisa_sinal(); //valor instantaneo
                } 
              flag_libera=0;
            }   
        }
        else
        {//flag de liberação desligado
           if(vetor[TAM_MAX] > vetor[0])//localizou o inicio de subida
            {
               flag_libera=1;//vasculha o ponto de subida
            }
        }            
        rotaciona_vetor();
        if(tempo_de_envio >= TEMPO_ENVIO)
                 {
                    int aux=0;
                    aux = (tensao_pico*49)/10;
                    long_to_char(aux);   // valor medio 
                    LED_STATUS = ~LED_STATUS;//pisca a cada envio
                    tempo_de_envio = 0;
                 }        
        tempo_de_envio++;       
    }
}

Formatado no site http://hilite.me/ no dia 23/09/2022.