domingo, 8 de dezembro de 2019

Medidor do fator de potência com o PIC 18F4550 - Wattímetro e frequêncímetro.

Olá pessoal!

    Neste exemplo os conceitos abordados serão:
  • Uso do conversor analógico digital
  • Implementação da comunicação em 4 bits utilizando LCD(funciona para 16X2 ou 4X20).
  • Leitura de tensão AC e DC .
  • Medir o fator de potência.
     A intenção é criar um voltímetro,amperímetro,wattímetro,frequencímetro e medidor do fato de potência que por consequência deve ler tensão e corrente DC E AC.O programa se torna extenso mas se for entendido por partes ficará mais fácil o que cada bloco de código faz.
      Para o amperímetro é usado um resistor de 1 R ligado a uma carga(neste caso foi usado uma associação indutor e resistor para ter defasagem das senoides, para o voltímetro foi usado um divisor de tensão que mede até 30V porém não fica tão preciso devido ao numero de bits do conversor AD interno(10 bits).Para o Wattímetro é usado apenas os dados de tensão e corrente para fornecer a potencia instantânea, já para o caso de tensão DC a potencia media é feita ao longo do tempo(pois pode haver variações de tensão e corrente).O circuito montado para testes e simulação é mostrado na figura a seguir:
    Figura 1 - Esquemático geral(elaboração própria)

        Para medir o fator de potencia poderia ter sido feito internamente, com o microcontrolador, porém montar um simples circuito detector de zero com uma porta XOR é uma solução interessante, pois então só é preciso pegar a forma de onda que é proporcional a defasagem do sinal na saida da porta XOR. Porém durante os testes foi observado que esta porta XOR gera dois pulsos, e que o segundo é do momento que as ondas descem, o que gera imprecisão devido ao tempo de decaimento da tensão na saída da porta, na figura 2,em amarelo sinal de saída de XOR, em azul a tensão em vermelho a corrente do circuito.Com este circuito interno foi utilizado apenas uma porta do microcontrolador para medir a largura do sinal(período) e do pulso(fator de potência), por meio das interrupções do ccp1.

                                   Figura 2 - Sinais lidos no circuito de detecção de zero(elaboração própria)
   
      Outra observação é que para simular no proteus, ora se usa a fonte DC, ora se usa a fonte AC em simulações separadas, para alternar entre o modo AC e DC basta apertar o botão de seleção,na figura a seguir é mostrado as simulações  AC e DC.No modo AC a tensão e corrente medida são as de pico, para converter para RMS(que é a mostrada nos multímetros), basta dividir por raiz de 2, conforme figura AC ,figura 4.
          Figura 3 - Modo DC(elaboração própria)

Figura 4 - Modo AC (elaboração própria)

O circuito funcionando é visto na figura 5, veja que ele tem 2 botões sendo um para reset do microcontrolador e outro para alternar os modo de funcionamento. 
                                         Figura 5 - foto do protótipo implementado(elaboração própria)

O código fonte:

/* 
 * Author: Aguivone Moretti Fógia
 * descrição: Multimetro que mede fator de potencia
 * freq.de clock: 20MHZ
 * data:08/12/2019 
 * testado fisicamente.
 */
#include "MCC18_18F4550.h"
#include "tempo_20mhz.h"
#include "LCD_4bits.h"
unsigned char Buffer_lcd[16]; // variavel usada pelo LCD
//variaveia globais para cpp1////////////////////////////////
unsigned int tempo_pulso=0;
unsigned int periodo=0;
unsigned char estado_amostras=0;
unsigned int tempo_ccp1=0;
//variaveis globais para ADC
unsigned char modo_tensao=0;//padrão CC
unsigned char descarta_FP=0;
float PM[5];//pega os ultimos 5 valores para potencia media DC
//defines usados
#define sin1_FP PORTCbits.RC4
#define sin2_FP PORTCbits.RC5
#define BOT_SEL PORTEbits.RE2
#define LED_STATUS PORTEbits.RE1
#define LED_MODO   PORTEbits.RE0 //sinaliza modo AC e pisca a cada pulso de clock
//equações usadas
#define EQ_TENSAO (float)((lido_adc*35.2)/1023) //para divisor de 10K - 1K - Máx: 33V
#define EQ_CORRENTE (float)((lido_adc*5.0)/1023)
#define EQ_FP (float)((90.0*tempo_pulso)/periodo) //fator de potencia
#define EQ_FREQ  (float)((1/(0.0000002*periodo*2.0))/8)//para 20Mhz - divide por 8 pois o T1CON está assim//frequencia
unsigned char sinal_FP= ' ';
////prototipos de funções ///////////////////////
void Inic_Regs (void);
void Read_ADC_DC(void);
void Read_ADC_CA(void);
void high_isr (void);
void Tela_inicial(unsigned char modo);
void verifica_botao(void);
//void low_isr (void);
//vetor de interrupção de alta prioridade
#pragma code high_vector=0x08
void interrupt_at_high_vector(void)
{
    _asm GOTO high_isr _endasm        //desvia programa para rotina de tratamento da interrupção
}
#pragma code 
//*****************************
//Rotina de tratamento de interrupção (ISR)
#pragma interrupt high_isr 
void high_isr (void)
{
   if(PIR1bits.CCP1IF)
    {
        if(estado_amostras==0)
        {//prepara contagem
           TMR1L =0;//Zera timer 1
           TMR1H =0;//Zera timer 1           
           if((sin2_FP)&&(sin1_FP))//se os dois estiver em um indica que está na descida do sinal e isso gera imprecisão
           {//descarta
               estado_amostras=0;               
           }
           else
           {//sinal subindo               
               if(sin1_FP)
                   {sinal_FP= '-';}
               else{sinal_FP= '+';}
               CCP1CON = 0X04;//captura a cada borda de descida
               estado_amostras=1;
           }            
           
        }
        else
        {
            if(estado_amostras==1)
            {//borda de descida - pega valor do pulso
                tempo_pulso =((((unsigned int)CCPR1H)<<8)|CCPR1L);
                estado_amostras = 2;
                CCP1CON = 0X05;//captura a cada borda de subida 
                _delay_ms(1);//
                if((sin2_FP==0)||(sin1_FP==0))//se os dois estiver em zero indica que está na descida do sinal e isso gera imprecisão
                           {//sinal invalido  
                             estado_amostras = 0;
                           }
            }
            else
            {//fim de coleta
                if(estado_amostras == 2)
                {   
                 periodo =((((unsigned int)CCPR1H)<<8)|CCPR1L);
                 estado_amostras=3;//pra sinalizar fim de amostragem  
                 PIE1bits.CCP1IE = 0;//não precisa mais das interrupçõe do CCP1
                 PORTDbits.RD3 = 0;
                }
            }
        }        
        PIR1bits.CCP1IF = 0;//limpa flag   
    }
    
}
/////rotina principal/////////////////////////////
void main() 
{
  Inic_Regs();
  inicializa_LCD();
  Tela_inicial('D');
  modo_tensao=0;
  while(1)
   { 
     verifica_botao();
     if(modo_tensao)//modo AC
     {
        Read_ADC_CA();
        tempo_ccp1=0; 
        estado_amostras=0;//libera nova coleta     
        PIE1bits.CCP1IE = 1;//liga interrupcão do ccp1
        while(tempo_ccp1<100)//demora em media 100ms
         { 
           if(estado_amostras==3)
               {//tem coleta mostra o valor 
                   float temp;
                   unsigned int parte_inteira;                //declaração de variável local
                   unsigned int parte_decimal;                //declaração de variável local 
                   if(descarta_FP == 0)
                   {
                    temp=EQ_FP;//pois divide por (2*semicilo)
                   }
                   else
                   {
                    temp=0;//tensão ou corrente sem leitura   
                   }
                    parte_inteira = (int)temp;            //pega parte inteira do valor
                    parte_decimal =(unsigned int)((temp - parte_inteira) * 10);    //resgata parte fracionária do valor ,2 casa decimais
                    sprintf(Buffer_lcd,"%c%u.%u",sinal_FP,parte_inteira,parte_decimal);        //converte valor em string e armazena no vetor buf    
                    impri_lcd(4,'3');
                    if(descarta_FP == 0)
                   {
                     temp=(EQ_FREQ);//para 20Mhz - divide por 8 pois o T1CON está assim
                     
                   }
                   else
                   {
                    temp=(EQ_FREQ*2);//tensão ou corrente sem leitura -> onda fica igual a corrente ou a tensão  
                   }                   
                   parte_inteira = (int)temp;            //pega parte inteira do valor
                   parte_decimal =(unsigned int)((temp - parte_inteira) * 10);    //resgata parte fracionária do valor ,2 casa decimais
                   sprintf(Buffer_lcd,"%u.%u",parte_inteira,parte_decimal);        //converte valor em string e armazena no vetor buf    
                   impri_lcd(3,'4'); //frequencia
                   _delay_ms(250);//lê a cada 0.5s
                   PIE1bits.CCP1IE = 1;//liga interrupcão do ccp1
                   _delay_us(10);//espera tempo para CCP1 se preparar
                   estado_amostras=0;//libera nova coleta
               }
               if(estado_amostras==2)
               {
                    if((sin2_FP==0)||(sin1_FP==0))//se os dois estiver em um indica que está na descida do sinal
                        //(do segundo sinal) e isso gera imprecisão 
                           {//sinal invalido  
                             estado_amostras = 0;
                           }

               }
           _delay_ms(1);
           tempo_ccp1++;
       }//fim do laçõ ccp1
       PIE1bits.CCP1IE = 0;//desliga interrupcão do ccp1 pra garantir
     }
     else//modo DC
     {
         Read_ADC_DC(); 
     }     
    _delay_ms(500);
    LED_STATUS=~LED_STATUS;
  }//fim do while 
  }//fim do laço main

/////////////////////////////////////////////////////////////////////////////////

void verifica_botao(void)
{
     if(!BOT_SEL)//testa botão de seleção -> AC/DC
     {
            _delay_ms(200);//debouce
            if(!BOT_SEL)
            {
               if(modo_tensao)
               {//modo DC
                   Tela_inicial('D');
                   modo_tensao=0;
                   LED_MODO =0;   
                   PIE1bits.CCP1IE = 0;//desliga interrupcão do ccp1
               }
               else
               {//modo AC
                   Tela_inicial('A');
                   LED_MODO =1;
                   modo_tensao=1;
                   estado_amostras=0;//libera nova coleta     
                   PIE1bits.CCP1IE = 1;//liga interrupcão do ccp1
               }
               _delay_ms(200);//debouce
            }
     }
}
 void Inic_Regs(void)
{
    TRISA = 0x03;            //PORTA saida, exceto RA0(tensão) e RA1(corrente) será usado para entradas analogicas
    TRISB = 0x00;            //PORTB saida
    TRISC = 0x34;            //PORTC saida e pino c2 entrada ccp1,pino C4 e C5 informa sinal do fator de potencia 
    TRISD = 0x00;            //PORTD saida
    TRISE = 0x04;            //PORTE saida,botão no RE2
    PORTA = 0x00;            //limpa PORTA
    PORTB = 0x00;            //limpa PORTB
    PORTC = 0x00;            //limpa PORTC
    PORTD = 0x00;            //limpa PORTD:
    PORTE = 0x00;            //limpa PORTE
//********************************************************************
    ADCON0 = 0X01;          //liga modulo analogico digital e configura entrada de sinal para o pino RA0(AN0)
    ADCON1=0b00001011;          // Vref- seta ao Vss e  Vref+ ao Vdd , somente Ra0 ate Ra4 analogico
    //ADCON2 = 0b101101010;    // justificado a direita     // clk Fosc/32
       ADCON2=0b10101001;
    // tempo de aquisição 12TAD(assim funciona pra qualquer cristal, porem fica mais lento, se precisar deve ser ajustado)
    INTCON=0b11000000;//liga interrupção de periferico e geral
    T3CON = 0;//mantem o ccp1 e ccp2 ligado ao timer1
    T1CON = 0b10110001;//modo de 16bits - fosc/dividido por 8 - timer ligado (lé no maximo500khz para clock de 8MHZ))
    CCP1CON = 0b00000101;//captura a cada borda de subida
    IPR1= 0X05;//coloca estouro de timer  e ccp1 como alta prioridade
    PIR1bits.CCP1IF = 0;//limpa flag do ccp1
    PIE1bits.CCP1IE = 1;//liga interrupcão do ccp1
    PIE1bits.TMR1IE = 0;//deixa interrupção de timer desligado(não vai prescisar))
 }
 //configura tela inicial
 void Tela_inicial(unsigned char modo)
{    
    if(modo == 'A') 
    {
        sprintf(Buffer_lcd,"Vrms=      V  -> AC ");
        impri_lcd(0,'1');
        sprintf(Buffer_lcd,"Vp=     V  Ip=     A");
    }else
    {
        sprintf(Buffer_lcd,"Multimetro    -> DC ");
        impri_lcd(0,'1');
        sprintf(Buffer_lcd,"Vdc=     V Idc=    A");  
    }
  impri_lcd(0,'2'); 
  sprintf(Buffer_lcd,"Fp=        Pi=     W");
  impri_lcd(0,'3');
  sprintf(Buffer_lcd,"F=      hz Pm=     W");
  impri_lcd(0,'4');  
} 
 //faz leitura de amostras em corrente continua
void Read_ADC_CA(void)
{       
       unsigned int lido_adc;
       float temp,tensao;
       unsigned int parte_inteira;                //declaração de variável local
       unsigned int parte_decimal;                //declaração de variável local
       unsigned int hist_leitura[5]={0,0,0,0,0};//pega 5 amostras para não estourar a variavel
       unsigned char fim_captura=245;//esse valor pede pra encontrar quando a onda está subindo
       unsigned int num_de_tentativas = 0;
       descarta_FP=0;//libera leitura do fator de potencia
       //////////Lê tensão////////////////////
        ADCON0 = 0X01; //RA0
        _delay_us(40);//espera tensão estabilizar 
        hist_leitura[0] = 0XFFFF;//coloca valor maximo para garantir 
        while((fim_captura == 245)&&(modo_tensao==1))
        {
            ADCON0bits.GO = 1; //RA1 habilita conversão         
             while (ADCON0bits.DONE == 1); //espera finalizar leitura
            hist_leitura[1] =  (((int)ADRESH)<<8)|(ADRESL);
             if((hist_leitura[1]>hist_leitura[0])&&(hist_leitura[1]<10))//pra garantir que está no começo da onda 
                {
                   fim_captura = 0;//finalizou processo onda está subindo
                }
            hist_leitura[0]= hist_leitura[1];//recebe valor atual
            verifica_botao();
            num_de_tentativas++;
            if(num_de_tentativas>1000)//anti travamento
            {
                fim_captura = 253;//indica que não leu tensão
            }
        }
        while((fim_captura < 250)&&(modo_tensao==1))
        {
            ADCON0bits.GO = 1; //RA1 habilita conversão         
            while (ADCON0bits.DONE == 1); //espera finalizar leitura
            hist_leitura[fim_captura] =  (((int)ADRESH)<<8)|(ADRESL);
            if(fim_captura == 0)
            {
                if(hist_leitura[0]<hist_leitura[4])
                {
                   fim_captura = 252;//finalizou processo 
                   lido_adc = ((hist_leitura[4]+hist_leitura[3]+hist_leitura[2])/3);//tira uma média das 3 ultimas amostras
                }
            }
            else
            {
                if(hist_leitura[fim_captura]<hist_leitura[fim_captura-1])
                {
                    unsigned char conta = 0;
                     lido_adc =0;
                    while(conta<5)//0 a 4
                    {
                        if(conta != fim_captura)//ignora valor atual
                        {
                            lido_adc =  lido_adc + hist_leitura[conta];
                        }
                        conta++;
                    }
                     lido_adc = lido_adc/4;//tira media
                    fim_captura = 252;//finalizou processo 
                }
            }            
            if(fim_captura<250)
            {
                fim_captura++;
                if(fim_captura > 4)
                {
                    fim_captura = 0; 
                }
            }
           verifica_botao(); 
        }
        if(fim_captura == 253)
        {
          temp = 0;
          descarta_FP=1;
        }else
        {
         temp = EQ_TENSAO ;         
        }
    //*******************************************************************
        if(temp < 0)                    //valor é menor que 0?
        { temp = temp * (-1);        //inverte o sinal de float
        }
    //*******************************************************************
        parte_inteira = (int)temp;            //pega parte inteira do valor
        tensao =temp;
    //*******************************************************************
        parte_decimal =(unsigned int)((temp - parte_inteira) * 100);    //resgata parte fracionária do valor ,2 casa decimais
        Tela_inicial('A');//limpa campos
        sprintf(Buffer_lcd,"%u.%u",parte_inteira,parte_decimal);        //converte valor em string e armazena no vetor buf    
        impri_lcd(3,'2'); 
        temp=(tensao/1.4142);
        parte_inteira = (int)temp;            //pega parte inteira do valor        
    //*******************************************************************
        parte_decimal =(unsigned int)((temp - parte_inteira) * 100);    //resgata parte fracionária do valor ,2 casa decimais
        sprintf(Buffer_lcd,"%u.%u",parte_inteira,parte_decimal);        //converte valor em string e armazena no vetor buf    
        impri_lcd(5,'1'); 
        
        //////////Lê corrente////////////////////        
        hist_leitura[0]=0;
        hist_leitura[1]=0;
        hist_leitura[2]=0;
        hist_leitura[3]=0;
        hist_leitura[4]=0;
        ADCON0 = 0X05; ///liga RA1  
        _delay_us(40);//espera tensão estabilizar 
        fim_captura = 245;
        hist_leitura[0] = 0XFFFF;//coloca valor maximo para garantir 
        num_de_tentativas=0;
         while((fim_captura == 245)&&(modo_tensao==1))
        {
            ADCON0bits.GO = 1; //RA1 habilita conversão         
            while (ADCON0bits.DONE == 1); //espera finalizar leitura
            hist_leitura[1] =  (((unsigned int)ADRESH)<<8)|(ADRESL);
             if((hist_leitura[1]>hist_leitura[0])&&(hist_leitura[1]<10))//pra garantir que está no começo da onda
                {
                   fim_captura = 0;//finalizou processo onda está subindo
                }
            hist_leitura[0]= hist_leitura[1];//recebe valor atual
            verifica_botao();
            num_de_tentativas++;
            if(num_de_tentativas>1000)//anti travamento
            {
                fim_captura = 253;//indica que não leu tensão
            }            
        }
        while((fim_captura < 250)&&(modo_tensao==1))
        {
            ADCON0bits.GO = 1; //RA0 habilita conversão         
            while (ADCON0bits.DONE == 1); //espera finalizar leitura
            hist_leitura[fim_captura] =  (((int)ADRESH)<<8)|(ADRESL);
            if(fim_captura == 0)
            {
                if(hist_leitura[0]<hist_leitura[4])
                {
                   fim_captura = 252;//finalizou processo 
                   lido_adc = ((hist_leitura[4]+hist_leitura[3]+hist_leitura[2])/3);//tira uma média das 3 ultimas amostras
                }
            }
            else
            {
                if(hist_leitura[fim_captura]<hist_leitura[fim_captura-1])
                {
                    unsigned char conta = 0;
                     lido_adc =0;
                    while(conta<5)//0 a 4
                    {
                        if(conta != fim_captura)//ignora valor atual
                        {
                            lido_adc =  lido_adc + hist_leitura[conta];
                        }
                        conta++;
                    }
                     lido_adc = lido_adc/4;//tira media
                    fim_captura = 252;//finalizou processo 
                }
            }            
            if(fim_captura<250)
            {
                fim_captura++;
                if(fim_captura > 4)
                {
                    fim_captura = 0; 
                }
            }
            verifica_botao();
        }
        if(fim_captura == 253)
        {
          temp = 0;
          descarta_FP=1;
        }else
        {
         temp = EQ_CORRENTE;  
        }               
    //*******************************************************************
        if(temp < 0)                    //valor é menor que 0?
        { temp = temp * (-1);        //inverte o sinal de float
        }
    //*******************************************************************
        parte_inteira = (int)temp;            //pega parte inteira do valor        
    //*******************************************************************
        parte_decimal =(unsigned int)((temp - parte_inteira) * 100);    //resgata parte fracionária do valor ,2 casa decimais
        sprintf(Buffer_lcd,"%u.%u",parte_inteira,parte_decimal);        //converte valor em string e armazena no vetor buf    
        impri_lcd(14,'2');
        temp = tensao*temp;
        parte_inteira = (int)temp;            //pega parte inteira do valor 
        parte_decimal =(unsigned int)((temp - parte_inteira) * 10);//somente uma casa decimal
        sprintf(Buffer_lcd,"%u.%u",parte_inteira,parte_decimal);        //converte valor em string e armazena no vetor buf    
        impri_lcd(14,'3'); 
        //potencia media ac
        temp = temp/2;
        parte_inteira = (int)temp;            //pega parte inteira do valor 
        parte_decimal =(unsigned int)((temp - parte_inteira) * 10);//somente uma casa decimal
        sprintf(Buffer_lcd,"%u.%u",parte_inteira,parte_decimal);        //converte valor em string e armazena no vetor buf    
        impri_lcd(14,'4');

}
//faz leitura de amostras em corrente continua
void Read_ADC_DC(void)
{
       unsigned int lido_adc;
       float temp,tensao;
       unsigned int parte_inteira;                //declaração de variável local
       unsigned int parte_decimal;                //declaração de variável local
       unsigned int amostras[5];
       //////////Lê tensão////////////////////
        ADCON0 = 0X01;//RA0 
        _delay_us(40);//espera tensão estabilizar
        lido_adc = 0;
        while(lido_adc<5)
        {
        ADCON0bits.GO = 1; //RA0
        while (ADCON0bits.DONE == 1); //espera finalizar leitura
        amostras[lido_adc] =  (((unsigned int)ADRESH)<<8)|(ADRESL);
        lido_adc++;
        }
        temp = amostras[0] +amostras[1] +amostras[2] +amostras[3] +amostras[4];
        lido_adc =(unsigned int)temp/5;
        temp = EQ_TENSAO;  
        Tela_inicial('D');//limpa campos
    //*******************************************************************
        if(temp < 0)                    //valor é menor que 0?
        { temp = temp * (-1);        //inverte o sinal de float
        }
    //*******************************************************************
        parte_inteira = (int)temp;            //pega parte inteira do valor
        tensao =temp;
    //*******************************************************************
        parte_decimal =(unsigned int)((temp - parte_inteira) * 100);    //resgata parte fracionária do valor ,2 casa decimais
        sprintf(Buffer_lcd,"%u.%u",parte_inteira,parte_decimal);        //converte valor em string e armazena no vetor buf    
        impri_lcd(4,'2');
        
        //////////Lê corrente ////////////////////
       ADCON0 = 0X05; ///liga RA1        
        _delay_us(40);//espera tensão estabilizar
       lido_adc = 0;
        while(lido_adc<5)
        {
        ADCON0bits.GO = 1; //RA0
        while (ADCON0bits.DONE == 1); //espera finalizar leitura
        amostras[lido_adc] =  (((unsigned int)ADRESH)<<8)|(ADRESL);
        lido_adc++;
        }
        temp = amostras[0] +amostras[1] +amostras[2] +amostras[3] +amostras[4];
        lido_adc =(unsigned int)temp/5;
        temp = EQ_CORRENTE;       
    //*******************************************************************
        if(temp < 0)                    //valor é menor que 0?
        { temp = temp * (-1);        //inverte o sinal de float
        }
    //*******************************************************************
        parte_inteira = (int)temp;            //pega parte inteira do valor        
    //*******************************************************************
        parte_decimal =(unsigned int)((temp - parte_inteira) * 100);    //resgata parte fracionária do valor ,2 casa decimais
        sprintf(Buffer_lcd,"%u.%u",parte_inteira,parte_decimal);        //converte valor em string e armazena no vetor buf    
        impri_lcd(15,'2');
        temp = tensao*temp;
        parte_inteira = (int)temp;            //pega parte inteira do valor 
        parte_decimal =(unsigned int)((temp - parte_inteira) * 10);//somente uma casa decimal
        sprintf(Buffer_lcd,"%u.%u",parte_inteira,parte_decimal);        //converte valor em string e armazena no vetor buf    
        impri_lcd(14,'3');        
        PM[3]= PM[4];
        PM[2]= PM[3];
        PM[1]= PM[2];
        PM[0]= PM[1];
        PM[4]= temp+ PM[0]+PM[1]+ PM[2]+ PM[3];
        tensao =temp;
        temp= PM[4]/5;//potencia media reaproveitando variavel
        PM[4]=tensao;
        parte_inteira = (int)temp;            //pega parte inteira do valor 
        parte_decimal =(unsigned int)((temp - parte_inteira) * 10);//somente uma casa decimal
        sprintf(Buffer_lcd,"%u.%u",parte_inteira,parte_decimal);        //converte valor em string e armazena no vetor buf    
        impri_lcd(14,'4');
}

Arquivo MCC18_18F4550_H.h


/* 
 * File:   main_lcd.c
 * Author: aguivone
 * descrição: como usar display lcd no modo 4 bits
 * freq.de clock: 20MHZ
 * data:31/10/2019 
 * não esquecer de coloca o arquivo MCC18_18F4550.h
 */
#include <stdio.h>
#include <p18f4550.h>    
#include <string.h>
#include <delays.h>

#ifndef __MCC18_18F4550___H
#define    __MCC18_18F4550___H

// CONFIG1L
#pragma config PLLDIV = 1       // PLL Prescaler Selection bits (No prescale (4 MHz oscillator input drives PLL directly))
#pragma config CPUDIV = OSC1_PLL2// System Clock Postscaler Selection bits ([Primary Oscillator Src: /1][96 MHz PLL Src: /2])
#pragma config USBDIV = 1       // USB Clock Selection bit (used in Full-Speed USB mode only; UCFG:FSEN = 1) (USB clock source comes directly from the primary oscillator block with no postscale)

// CONFIG1H
#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator (HS))
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor disabled)
#pragma config IESO = OFF       // Internal/External Oscillator Switchover bit (Oscillator Switchover mode disabled)

// CONFIG2L
#pragma config PWRT = OFF       // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOR = OFF        // Brown-out Reset Enable bits (Brown-out Reset disabled in hardware and software)
#pragma config BORV = 3         // Brown-out Reset Voltage bits (Minimum setting 2.05V)
#pragma config VREGEN = OFF     // USB Voltage Regulator Enable bit (USB voltage regulator disabled)

// CONFIG2H
#pragma config WDT = OFF        // Watchdog Timer Enable bit (WDT disabled (control is placed on the SWDTEN bit))
#pragma config WDTPS = 32768    // Watchdog Timer Postscale Select bits (1:32768)

// CONFIG3H
#pragma config CCP2MX = ON      // CCP2 MUX bit (CCP2 input/output is multiplexed with RC1)
#pragma config PBADEN = ON      // PORTB A/D Enable bit (PORTB<4:0> pins are configured as analog input channels on Reset)
#pragma config LPT1OSC = OFF    // Low-Power Timer 1 Oscillator Enable bit (Timer1 configured for higher power operation)
#pragma config MCLRE = ON       // MCLR Pin Enable bit (MCLR pin enabled; RE3 input pin disabled)

// CONFIG4L
#pragma config STVREN = ON      // Stack Full/Underflow Reset Enable bit (Stack full/underflow will cause Reset)
#pragma config LVP = OFF        // Single-Supply ICSP Enable bit (Single-Supply ICSP disabled)
#pragma config ICPRT = OFF      // Dedicated In-Circuit Debug/Programming Port (ICPORT) Enable bit (ICPORT disabled)
#pragma config XINST = OFF      // Extended Instruction Set Enable bit (Instruction set extension and Indexed Addressing mode disabled (Legacy mode))

// CONFIG5L
#pragma config CP0 = OFF        // Code Protection bit (Block 0 (000800-001FFFh) is not code-protected)
#pragma config CP1 = OFF        // Code Protection bit (Block 1 (002000-003FFFh) is not code-protected)
#pragma config CP2 = OFF        // Code Protection bit (Block 2 (004000-005FFFh) is not code-protected)
#pragma config CP3 = OFF        // Code Protection bit (Block 3 (006000-007FFFh) is not code-protected)

// CONFIG5H
#pragma config CPB = OFF        // Boot Block Code Protection bit (Boot block (000000-0007FFh) is not code-protected)
#pragma config CPD = OFF        // Data EEPROM Code Protection bit (Data EEPROM is not code-protected)

// CONFIG6L
#pragma config WRT0 = OFF       // Write Protection bit (Block 0 (000800-001FFFh) is not write-protected)
#pragma config WRT1 = OFF       // Write Protection bit (Block 1 (002000-003FFFh) is not write-protected)
#pragma config WRT2 = OFF       // Write Protection bit (Block 2 (004000-005FFFh) is not write-protected)
#pragma config WRT3 = OFF       // Write Protection bit (Block 3 (006000-007FFFh) is not write-protected)

// CONFIG6H
#pragma config WRTC = OFF       // Configuration Register Write Protection bit (Configuration registers (300000-3000FFh) are not write-protected)
#pragma config WRTB = OFF       // Boot Block Write Protection bit (Boot block (000000-0007FFh) is not write-protected)
#pragma config WRTD = OFF       // Data EEPROM Write Protection bit (Data EEPROM is not write-protected)

// CONFIG7L
#pragma config EBTR0 = OFF      // Table Read Protection bit (Block 0 (000800-001FFFh) is not protected from table reads executed in other blocks)
#pragma config EBTR1 = OFF      // Table Read Protection bit (Block 1 (002000-003FFFh) is not protected from table reads executed in other blocks)
#pragma config EBTR2 = OFF      // Table Read Protection bit (Block 2 (004000-005FFFh) is not protected from table reads executed in other blocks)
#pragma config EBTR3 = OFF      // Table Read Protection bit (Block 3 (006000-007FFFh) is not protected from table reads executed in other blocks)

// CONFIG7H
#pragma config EBTRB = OFF      // Boot Block Table Read Protection bit (Boot block (000000-0007FFh) is not protected from table reads executed in other blocks)


#endif    /* __MCC18_18F4550___H */

Arquivo tempo.h
/* 
 * File:   tempo.h
 * Author: aguiv
 *
 * Created on 8 de Novembro de 2019, 15:54
 */
#include <delays.h>
#ifndef TEMPO_H
#define    TEMPO_H

 void _delay_us(int valor);
 void _delay_ms(int valor);
 #include "tempo_20MHz.c"

#endif    /* TEMPO_H */

Aquivo tempo_20MHz.c
/* 
 * File:   tempo.c
 * Author: aguivone
 *
 * Created on 8 de Novembro de 2019, 16:02
 */
// usado para criar funções de tempo

 void _delay_us(int valor)
 {
     while(valor>0)
     {
      Nop();Nop();Nop();Nop();Nop();//Nop();Nop();Nop();      
      //para 32-> 8 ciclos ,20MHZ -> 5 ciclos dão 1us , para 8mhz -> 2ciclo, para 4mhz ->>1ciclo 
      valor--;
     }
 }
 void _delay_ms(int valor)
 {
     while(valor>0)
     {
      Delay1KTCYx(5);
      //para 32MHZ -> coloque 8
      //para 20MHZ -> 5000 ciclos dão 1ms -> coloque 5
      // 8mhz -> 2000 -> coloque 2
      // 4mhz -> 1000 -> coloque 1
      valor--;
     }
 }
 //delay de segundos
 void _delay_s(int valor)
 {
     while(valor>0)
     {
      _delay_ms(1000); 
      valor--;
     }
 }



Aquivo da biblioteca do LCD - LCD_4bits.h

/* 
 * File:   LCD_4bits.h
 * Author: aguivone
 *
 * Created on 8 de Novembro de 2019, 16:07
 */
//biblioteca para acessar o display de LCD no modo 4 bits
//o pino r/w é conectado ao terra pois não é feita nenhuma leitura no display
//configuração:
//
// pino display  / pino da porta selecionada
//
// D4 = bit 0 da porta
// D5 = bit 1 da porta
// D6 = bit 2 da porta
// D7 = bit 3 da porta
//
// RS = bit 4 da porta
// EN = bit 5 da porta
//

#include <stdio.h>
#include <p18f4550.h>    //se for necessario troque este
#include <string.h>

#ifndef LCD_4BITS_H
#define    LCD_4BITS_H
//variaveis locais
extern unsigned char Buffer_lcd[16]; //lembre de criar esta variavel no codigo principal
/////////////////////////////////////pinos do microcontrolador usado no lcd ///////////////////////////////////////////
//define qual porta vai usar neste caso port D

///acepic////////////
/*
  #define BIT_0  PORTDbits.RD4
 #define BIT_1  PORTDbits.RD5
 #define BIT_2  PORTDbits.RD6
 #define BIT_3  PORTDbits.RD7

 #define RS     PORTEbits.RE0
 #define EN     PORTEbits.RE1
*/
//prototipo montado na protoboard/////////
 #define BIT_0  PORTDbits.RD4
 #define BIT_1  PORTDbits.RD5
 #define BIT_2  PORTDbits.RD6
 #define BIT_3  PORTDbits.RD7

 #define RS     PORTDbits.RD0
 #define EN     PORTDbits.RD1


union{
    unsigned char VALOR;
    struct{
        unsigned b0:1;
        unsigned b1:1; 
        unsigned b2:1;
        unsigned b3:1;
        unsigned b4:1;
        unsigned b5:1;
        unsigned b6:1;
        unsigned b7:1;
    };    
}LCD;
void porta_lcd(unsigned char ucDado); 
void escreve_LCD(unsigned char ucDado,unsigned char ucVlr); 
void inicializa_LCD(void);
void impri_lcd(int iPos_inicial ,unsigned char ucLinha);
void impri_caracter_lcd(unsigned char ucCaracter,int iPos_inicial ,unsigned char ucLinha);
 #include "LCD_4bits.c"
#endif    /* LCD_4BITS_H */



Até a próxima! obrigado!

Usando lcd no modo 4 bits, Wattímetro,voltímetro e amperímetro.


     Olá pessoal que acompanha o blog!
   Neste exemplo os conceitos abordados, no uso do compilador C18, serão o uso do conversor analógico digital e a implementação da comunicação em 4 bits utilizando LCD(funciona para 16X2 ou 4X20). A intenção é criar wattímetro que por consequência deve ler tensão e corrente, lembre-se que este circuito serve apenas para corrente continua. 

O código fonte :


/* 
 * File:   main_lcd.c
 * Author: aguivone
 * descrição: como usar display lcd no modo 4 bits no compilador C18
 * freq.de clock: 20MHZ
 * data:08/12/2019 
 */
#include <stdio.h>
#include <p18f4550.h>    
#include <string.h>
#include <delays.h>

//variaveis locais
unsigned char Buffer_lcd[16];
/////////////////////////////////////pinos do microcontrolador usado no lcd ///////////////////////////////////////////
//o pino r/w é conectado ao terra pois não é feita nenhuma leitura no display
//configuração:
//
// pino display  / pino da porta selecionada
//
// D4 = bit 0 da porta
// D5 = bit 1 da porta
// D6 = bit 2 da porta
// D7 = bit 3 da porta
//
// RS = bit 4 da porta
// EN = bit 5 da porta
//
//define qual porta vai usar neste caso port D
 #define BIT_0  PORTDbits.RD0
 #define BIT_1  PORTDbits.RD1
 #define BIT_2  PORTDbits.RD2
 #define BIT_3  PORTDbits.RD3

 #define RS     PORTDbits.RD4
 #define EN     PORTDbits.RD5
//////////////usado para escrever em cada bit da porta com o lcd.
union{
    unsigned char VALOR;
    struct{
        unsigned b0:1;
        unsigned b1:1; 
        unsigned b2:1;
        unsigned b3:1;
        unsigned b4:1;
        unsigned b5:1;
        unsigned b6:1;
        unsigned b7:1;
    };    
}LCD;
////prototipos de funções //////////////////////////////////////////////////////////////////
void Inic_Regs (void);
void _delay_us(int valor);
void _delay_ms(int valor);
unsigned char valor_bit(char cDa,char cBit);
void porta_lcd(unsigned char ucDado); 
void escreve_LCD(unsigned char ucDado,unsigned char ucVlr); 
void inicializa_LCD(void);
void impri_lcd(int iPos_inicial ,unsigned char ucLinha);
void impri_caracter_lcd(unsigned char ucCaracter,int iPos_inicial ,unsigned char ucLinha);
void Read_ADC (void);

////////função principal /////////////////////////////////////////////////////////////////////

void main() 
{
  Inic_Regs();
  inicializa_LCD();
  sprintf(Buffer_lcd,"Volts Corr. Pot.");
  impri_lcd(0,'1');
  sprintf(Buffer_lcd,"  0     0    0  ");
  impri_lcd(0,'2'); 
  while(1)
   { 
         Read_ADC();
         _delay_ms(300);//lê a cada 2s
   }//fim do laçõ while
 
  }//fim do laço main
/////////////inicializa as portas ////////////////////////////////////// 
void Inic_Regs(void)
{
    TRISA = 0x03;            //PORTA saida, exceto RA0(tensão) e RA1(corrente) será usado para entradas analogicas
    TRISB = 0x00;            //PORTB saida
    TRISC = 0x00;            //PORTC saida
    TRISD = 0x00;            //PORTD saida
    TRISE = 0x00;            //PORTE saida
    ADCON0 = 0X01;          //liga modulo analogico digital e configura entrada de sinal para o pino RA0(AN0)
    ADCON1=0b00001110;          // Vref- seta ao Vss e  Vref+ ao Vdd , somente Ra0 e Ra1 analogico
    //ADCON2 = 0b101101010;    // justificado a direita     // clk Fosc/32
       ADCON2=0b10101001;
    // tempo de aquisição 12TAD(assim funciona pra qualquer cristal, porem fica mais lento, se precisar deve ser ajustado)
    PORTA = 0x00;            //limpa PORTA
    PORTB = 0x00;            //limpa PORTB
    PORTC = 0x00;            //limpa PORTC
    PORTD = 0x00;            //limpa PORTD:
    PORTE = 0x00;            //limpa PORTE
//********************************************************************
}
//////////////funções de tempo /////////////////////////////////////////////////////////////// 
//===================================================================================
//Função:     _delay_us())
//Parâmetros: inteiro
//Retorno:   não tem retorno.
//Descrição: perde tempo em microsegundoas.
//===================================================================================
 void _delay_us(int valor)
 {
     while(valor>0)
     {
      Nop();Nop();Nop();Nop();Nop();//para 20MHZ -> 5 ciclos dão 1us//  para 8mhz -> 0,5 -> 2 ciclos
      valor--;
     }
 }
 //===================================================================================
//Função:     _delay_ms())
//Parâmetros: inteiro
//Retorno:   não tem retorno.
//Descrição: perde tempo em milisegundos.
//===================================================================================
 void _delay_ms(int valor)
 {
     while(valor>0)
     {
      Delay1KTCYx(5);//para 20MHZ -> 5000 ciclos dão 1ms -> [5]// 8mhz -> 2000 -> [2].
      valor--;
     }
 }

//===================================================================================
//Função:     _porta_lcd
//Parâmetros: char cDado
//Retorno:   não tem retorno.
//Descrição: envia um comando de configuração veja que os tempos devem ser maiores .
//===================================================================================
void porta_lcd(unsigned char ucDado)//byte a ser enviado
{
       LCD.VALOR = ucDado;
       //envia os bytes mais altos
       RS = 0; //habilita comandos
       BIT_0 = LCD.b4;
       BIT_1 = LCD.b5;
       BIT_2 = LCD.b6;
       BIT_3 = LCD.b7;
       EN = 1;
       _delay_ms(15);
       EN = 0;
       _delay_ms(15);
       //envia os bytes mais baixos
       BIT_0 = LCD.b0;
       BIT_1 = LCD.b1;
       BIT_2 = LCD.b2;
       BIT_3 = LCD.b3;
       EN = 1;
       _delay_ms(15);
       EN = 0;
       _delay_ms(15);
}
//===================================================================================
//Função:     _escreve_LCD
//Parâmetros: char cDado,
//            int iVlr
//Retorno:   não tem retorno.
//Descrição: escreve um caracter no display ou envia um comando.
//===================================================================================
void escreve_LCD(unsigned char ucDado,unsigned char ucVlr)//byte a ser enviado ,escreve caracter(0) ou comando(1)
{
    if(ucVlr == '0')
       {
         RS = 0; //habilita comandos
       }
       else
       {
        RS = 1; //habilita dados
       }
       LCD.VALOR = ucDado;
        _delay_us(5);//tempo para display atualizar
       //envia os bytes mais altos
       BIT_0 = LCD.b4;
       BIT_1 = LCD.b5;
       BIT_2 = LCD.b6;
       BIT_3 = LCD.b7;
       EN = 1;
       _delay_us(50);//tempo estabilizar o pino
       EN = 0;
       _delay_us(50);//tempo estabilizar o pino
       //envia os bytes mais baixos
       BIT_0 = LCD.b0;
       BIT_1 = LCD.b1;
       BIT_2 = LCD.b2;
       BIT_3 = LCD.b3;
       EN = 1;
       _delay_us(50);//tempo estabilizar o pino
       EN = 0;
       _delay_us(200);
}
//===================================================================================
//Função:     _inicializa_LCD
//Parâmetros: não tem
//Retorno:   não tem retorno.
//Descrição: faz a configuração inicial do display, verifique os datasheet dos fabricantes.
//===================================================================================
void inicializa_LCD(void)
{
       EN = 0;
       _delay_ms(15);//tempo para estabilizar a tensão e preparar o display
       porta_lcd(0X02);//cursor home - posiciona no inicio
       porta_lcd(0X28);//configura para usar modo 4bits,manda usar 2 linhas e matriza de caracter de 5X7.
       porta_lcd(0X01);//limpa display
       porta_lcd(0X06);//deslocamento do cursor para direita ,sem deslocamento de caracter automaticamente. 
       porta_lcd(0X0C);//liga display :0XC0 -> cursor desligado, 0X0F ->intermitente,0E -> fixo ,08 -> desligado.
       RS = 1; //habilita dados        

}
//===================================================================================
//Função:    _impri_caracter_lcd
//Parâmetros: char cCaracter, -> caracter a ser escrito
//            int iPos_inicial, indica em qual posição deve  colocar o caracter
//            int iLinha, indica qual o a linha do display vai usar
//Retorno:   não tem retorno.
//Descrição: usada para escrever um caracter em um display de lcd.
//===================================================================================
void impri_caracter_lcd(unsigned char ucCaracter,int iPos_inicial ,unsigned char ucLinha)
{
      switch(ucLinha)
           {
            case '1' :{escreve_LCD(0X80 + iPos_inicial,'0');}break;//linha1
            case '2' :{escreve_LCD(0XC0 + iPos_inicial,'0');}break;//linha2
            case '3' :{escreve_LCD(0X94 + iPos_inicial,'0');}break;//linha3
            case '4' :{escreve_LCD(0XD4 + iPos_inicial,'0');}//linha4
            }
       escreve_LCD(ucCaracter,'1');
}
 

//===================================================================================
//Função:    _impri_lcd
//Parâmetros: const char *ccFrase, mensagem que deve ser escrita usando "(aspas) para delimitar o tamanho
//            int iPos_inicial, indica em qual posição deve começar a colocar a mensagem
//            int iLinha, indica qual o a linha do display vai usar
//Retorno:   não tem retorno.
//Descrição: usada para escrever uma string(vetor de caracteres) em um display de lcd com parametro string.
//===================================================================================
//void impri_lcd(unsigned char frase[],int iPos_inicial ,unsigned char ucLinha)//  cuidado para não exceder a quantidade de caracter
void impri_lcd(int iPos_inicial ,unsigned char ucLinha)
{
      unsigned int indice=0;
      unsigned int tamanho = strlen(Buffer_lcd);
      switch(ucLinha)
           {
            case '1' :{escreve_LCD(0X80 + iPos_inicial,'0');}break;//linha1
            case '2' :{escreve_LCD(0XC0 + iPos_inicial,'0');}break;//linha2
            case '3' :{escreve_LCD(0X94 + iPos_inicial,'0');}break;//linha3
            case '4' :{escreve_LCD(0XD4 + iPos_inicial,'0');}//linha4
            }
      while(indice<tamanho)
       {
          if(Buffer_lcd[indice]!='\0')
          {           
            if((Buffer_lcd[indice]!='"') && (indice<21))//dados
                        {
                          escreve_LCD(Buffer_lcd[indice],'1');
                        }
          }
          else
          {
              indice = tamanho;//finaliza impressão
          }
       indice++;
       }
}

void Read_ADC (void)
{
       int lido_adc;
       float temp,tensao;
       unsigned int parte_inteira;                //declaração de variável local
       unsigned int parte_decimal;                //declaração de variável local
       //////////Lê tensão////////////////////
        ADCON0 = 0X05; 
        _delay_us(40);//espera tensão estabilizar
        ADCON0bits.GO = 1; //RA1
        while (ADCON0bits.DONE == 1); //espera finalizar leitura
       // lido_adc = ((((int)ADRESH <<8)|(ADRESL))&0x03FF); //Lê os valores em hexa
        lido_adc =  (((int)ADRESH)<<8)|(ADRESL);
        temp = (float)((lido_adc*5.0)/1023);       
    //*******************************************************************
        if(temp < 0)                    //valor é menor que 0?
        { temp = temp * (-1);        //inverte o sinal de float
        }
    //*******************************************************************
        parte_inteira = (int)temp;            //pega parte inteira do valor
        tensao =temp;
    //*******************************************************************
        parte_decimal =(unsigned int)((temp - parte_inteira) * 100);    //resgata parte fracionária do valor ,2 casa decimais
        sprintf(Buffer_lcd,"%u.%u",parte_inteira,parte_decimal);        //converte valor em string e armazena no vetor buf    
        impri_lcd(0,'2');
        
        //////////Lê corrente ////////////////////
       ADCON0 = 0X01; ///liga RA0        
        _delay_us(40);//espera tensão estabilizar
        ADCON0bits.GO = 1; //RA1
        while (ADCON0bits.DONE == 1); //espera finalizar leitura
       // lido_adc = ((((int)ADRESH <<8)|(ADRESL))&0x03FF); //Lê os valores em hexa
        lido_adc =  (((int)ADRESH)<<8)|(ADRESL);
        temp = (float)((lido_adc*5.0)/1023);       
    //*******************************************************************
        if(temp < 0)                    //valor é menor que 0?
        { temp = temp * (-1);        //inverte o sinal de float
        }
    //*******************************************************************
        parte_inteira = (int)temp;            //pega parte inteira do valor        
    //*******************************************************************
        parte_decimal =(unsigned int)((temp - parte_inteira) * 100);    //resgata parte fracionária do valor ,2 casa decimais
        sprintf(Buffer_lcd,"%u.%u",parte_inteira,parte_decimal);        //converte valor em string e armazena no vetor buf    
        impri_lcd(6,'2');
        temp = tensao*temp;
        parte_inteira = (int)temp;            //pega parte inteira do valor 
        parte_decimal =(unsigned int)((temp - parte_inteira) * 10);//somente uma casa decimal
        sprintf(Buffer_lcd,"%u.%u",parte_inteira,parte_decimal);        //converte valor em string e armazena no vetor buf    
        impri_lcd(12,'2');
}


A simulação do proteus:


     Muito obrigado,e até a próxima!