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

}









quinta-feira, 23 de abril de 2015

Controle remoto - parte 3 - a implementação do projeto.


       Olá, continuando o projeto do decodificador de controle remoto que pode ser usado para acionar relês (neste projeto será acionado 4 leds), com pequenas alterações é possível acionar mais relês ou cargas, mantive a parte de comunicação via serial para o caso de vocês quererem visualizar o código recebido num pc, outro detalhe foi a adição de 4 chaves de toque para fazer a gravação dos códigos além de 5 leds sendo 4 para os código do controle e um para indicar que está recebendo dados do controle remoto, o código fonte segue abaixo:

/*
 *                 Decodificando controle remoto MPlab XC8
 *
 * Compilador : MPlabXC8
 * Microcontrolador: 16F648A
 * Autor: aguivone
 * Versão: 1
 * Data :  23 de abril de 2015
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h> //para usar funçoes de string deve se adicionar este header
#define _XTAL_FREQ 4000000    // oscilador interno de 4 Mhz
#include <xc.h>
/////////////////////////////////////////////////////////configuraçôes//////////////////////////////////////////////////
#define LED_STATUS RB0
#define SINAL PORTAbits.RA0
#define CH1 PORTBbits.RB4
#define CH2 PORTBbits.RB5
#define CH3 PORTBbits.RB6
#define CH4 PORTBbits.RB7


#pragma config FOSC = INTOSCIO  // Oscillator Selection bits (INTOSC oscillator: I/O function on RA6/OSC2/CLKOUT pin, I/O function on RA7/OSC1/CLKIN)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = OFF      // RA5/MCLR/VPP Pin Function Select bit (RA5/MCLR/VPP pin function is digital input, MCLR internally tied to VDD)
#pragma config BOREN = ON       // Brown-out Detect Enable bit (BOD enabled)
#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 (Data memory code protection off)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

////////////////////////////////////////////////variaveis globais///////////////////////////////////////////////////////////
long  anterior = 0, atual = 0,largura_pulso = 0;
int   primeiro_pacote = 0,valor_pulso=0;
long  timeout = 0;
long  bit_0=0,bit_1=0,start_stop=0,range_bit=150;
char  comando;
long  byte_cod_baixo = 0;
long  byte_cod_alto = 0;
long  CH1_byte_cod_baixo = 1;
long  CH1_byte_cod_alto = 1;
long  CH2_byte_cod_baixo = 1;
long  CH2_byte_cod_alto = 1;
long  CH3_byte_cod_baixo = 1;
long  CH3_byte_cod_alto = 1;
long  CH4_byte_cod_baixo = 1;
long  CH4_byte_cod_alto = 1;
int   taman_byte =0;
int   stopbit_reconhecido = 0;
///////////////////////////////////////////////////interrupção//////////////////////////////////////////////////////////////
void interrupt CCP1(void)
{
 if(CCP1IF) //verifica se é interrupção de cpp
    {// cada incremtento do timer será a cada 1us ,pois estou usando o oscilador interno de 4mhz
        atual = CCPR1;
        CCP1IF = 0; //limpa interrupção de captura de sinal
                if (anterior > atual)
                {
                    largura_pulso = (0XFFFF - anterior) + atual;
                }
                else
                {
                    largura_pulso = atual - anterior;
                }
       anterior = atual;
   }
 }
/////////////////////////////////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 = 0;//não vou receber nenhum dado do pc
    //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 inicializa_CCP() {
    //habilita captura de pulso na borda de subida (prescaler sem divisão)
    CCP1CON = 0X05;//modo de captura sem divisão (borda de subida)
    CCP1IF = 0; //limpa flag de interrupção de captura
    CCP1IE = 1;//habilita modo de captura de pulsos*/
}

void inicializa_timer1() {
    TMR1IF = 0; //limpa flag de interrupção de timer
   // TMR1IE = 1; //habilita interrupção de estouro do timer
    TMR1IE = 0; //desabilita interrupção de estouro do timer
    T1CON = 0x81; //habilita leitura do timer 1 e o timer 1,sem prescaler
}
void limpa_pacote()
{
   comando =' ';
   byte_cod_baixo = 0;
   byte_cod_alto=0;
   taman_byte =0;
}
void rotina_tratamento(long byte_alto,long byte_baixo)
{
    int reconhecer = 1;
   if(CH1 == 0)
    {//grava codigo do botão 1
       CH1_byte_cod_alto = byte_alto;
       CH1_byte_cod_baixo = byte_baixo;
       imprime("\n\r CH1_gravado!");
       reconhecer = 0;
    }
   if(CH2 == 0)
    {//grava codigo do botão 2
       CH2_byte_cod_alto = byte_alto;
       CH2_byte_cod_baixo = byte_baixo;
       imprime("\n\r CH2_gravado!");
      reconhecer = 0;
    }
   if(CH3 == 0)
    {//grava codigo do botão 3
       CH3_byte_cod_alto = byte_alto;
       CH3_byte_cod_baixo = byte_baixo;
       imprime("\n\r CH3_gravado!");
      reconhecer = 0;
    }
   if(CH4 == 0)
    {//grava codigo do botão 4
       CH4_byte_cod_alto = byte_alto;
       CH4_byte_cod_baixo = byte_baixo;
       imprime("\n\r CH4_gravado!");
      reconhecer = 0;
    }
    if(reconhecer == 1)//indica que é pra reconhecer o codigo
    {
        TRISB = 0X08;//configura os pinos dos botoes como saidas
        PORTB = 0XFF;//desliga saidas
        if((byte_alto == CH1_byte_cod_alto )&&(byte_baixo == CH1_byte_cod_baixo))
        {
           imprime("\n\r CH1_ok");
           CH1 = 0;//liga pino
        }
        if((byte_alto == CH2_byte_cod_alto )&&(byte_baixo == CH2_byte_cod_baixo))
        {
           imprime("\n\r CH2_ok");
           CH2 = 0;//liga pino
        }
        if((byte_alto == CH3_byte_cod_alto )&&(byte_baixo == CH3_byte_cod_baixo))
        {
           imprime("\n\r CH3_ok");
           CH3 = 0;//liga pino
        }
       if((byte_alto == CH4_byte_cod_alto )&&(byte_baixo == CH4_byte_cod_baixo))
        {
           imprime("\n\r CH4_ok");
           CH4 =0;//liga pino
        }
        __delay_ms(500);//espera 0,5s
        PORTB = 0XF0;//desliga saidas
        TRISB = 0XF8;//os pinos voltam a ser entradas
    }
}
//////////////////////////////////////////////////////Rotina principal///////////////////////////////////////////////////////////////

void main(void) {
    TRISA = 0XE7;//todos são entrada exceto as saidas do comparador RA3 RA4
    TRISB = 0XF8;//configura portB  B3 (ccp1) e de rb4 a rb7 como entrada
    PORTB = 0;  // limpar as portas que estão configuradas como saidas
    CMCON = 0X06;//habilita somente um comparador interno
    inicializa_CCP();
    inicializa_timer1();
    inicializa_RS232(9600,1);//modo de alta velocidade
    imprime("controle remoto2");
    PEIE = 1;//habilita interrupção de perifericos do pic
    GIE = 1; //GIE: Global Interrupt Enable bit
    limpa_pacote();
    for(;;)
    {
          if(largura_pulso > 0)//leu pulsos
                     {
                      valor_pulso = largura_pulso;
                      if(primeiro_pacote == 0)
                          {
                           primeiro_pacote = 1;//descarta pois virá com lixo
                           limpa_pacote();
                          }
                      else
                      {
                      if(primeiro_pacote == 1)
                        {//tenta localizar o protocolo
                          primeiro_pacote = 2;
                             escreve('\n');//pula para proxima linha
                             escreve('\r');//coloca no inicio da linha
                           if((valor_pulso > 2900)&&(valor_pulso < 3200))
                            {//sony
                               escreve('G');
                               comando ='G';
                               bit_0 = 1200;
                               bit_1 = 1800;
                               start_stop=3000;
                            }
                           if(valor_pulso < 2900)
                            {//philips
                               escreve('P');
                               comando ='P';
                               bit_0 = 1800;
                               bit_1 = 2700;
                               start_stop=0;
                               if(valor_pulso > (bit_0 + range_bit))
                              {//bit 1
                                escreve('1');
                                byte_cod_baixo = 1;
                                taman_byte =1;
                              }
                              else
                              {//bit0
                                 escreve('0');
                                 taman_byte =1;
                              }
                            }
                            if((valor_pulso > 8000)&&(valor_pulso < 10000))
                            {//sansung
                               escreve('S');
                               comando ='S';
                               bit_0 = 1100;
                               bit_1 = 2250;
                               start_stop=9000;
                            }
                            if(valor_pulso > 10000)
                            {//lg
                               escreve('L');
                               comando ='L';
                               bit_0 = 1100;
                               bit_1 = 2250;
                               start_stop=13500;
                            }
                         LED_STATUS = 1;
                        }
                      else
                      {
                         if((valor_pulso > (start_stop - range_bit))&&(valor_pulso < (start_stop + range_bit)))
                              {//pulso de fim de pacote
                                escreve('F');//pra indicar que recebeu um bit de inicio ou fim de pacote 
                                if((comando == 'S')||(comando == 'L')||(comando == 'P')||(comando == 'G'))
                                  {//verifica se o pacote é valido para os protocolos, pode ser que outras marcas coicidam e funcione
                                    rotina_tratamento(byte_cod_alto,byte_cod_baixo);
                                  }
                                stopbit_reconhecido = 1;
                                // no caso do protocolo ca sony aqui pode se fazer a logica e separa os 3 codigos que são
                                //os mesmos
                              }
                         
                      if(valor_pulso < (bit_1 + range_bit))
                      {
                          if(valor_pulso > (bit_0 + range_bit))
                              {//bit 1
                                escreve('1');
                                if(taman_byte<32)
                                {
                                  long valor=1;
                                  byte_cod_baixo = byte_cod_baixo + (valor<<taman_byte);
                                }
                                else
                                {
                                  long valor=1,temp_byte;
                                  temp_byte = taman_byte-32;
                                  byte_cod_alto = byte_cod_alto + (valor<<temp_byte);
                                }
                                taman_byte++;
                              }
                              else
                              {//bit0
                                 escreve('0');
                                 taman_byte++;
                              }

                      }
                      }
                      }
                    largura_pulso=0;
                        timeout = 3000;//prepara contagem
                     }//fim do leu pulsos
     if(timeout>0)
     {
     timeout--;
     }
     if(timeout==1)
     {// tempo de estouro por esperar o pacote
          escreve('T');//timeout
          if(stopbit_reconhecido == 0)
          {
              if((comando == 'S')||(comando == 'L')||(comando == 'P')||(comando == 'G'))
              {//verifica se o pacote é valido para os protocolos, pode ser que outras marcas coicidam e funcione
                rotina_tratamento(byte_cod_alto,byte_cod_baixo);
              }
          }
          else
          {
                 stopbit_reconhecido = 0;
          }
          LED_STATUS = 0;          
      comando =' ';//limpa comando
      taman_byte = 0;
      primeiro_pacote = 0;

     }
    }//loop infinito

}

     Como eu tive que inverter o sinal de entrada pois usei um modulo de recepção infravermelho da sharp, neste caso o sinal ficou o oposto do que eu havia detectado antes,como me sobraram poucos pinos do microcontrolador tive que usar o recurso de o mesmo pino funcionar como entrada e saída ao mesmo tempo.Na simulação o circuito ficou assim a porta NOT é para simular o modulo receptor de IR


Site com um projeto legal usando esse tipo de modulo :  http://zorktronics.blogspot.com.br/2011/06/medindo-velocidade-angular-sem-um.html ,nestes links vendem uns modelos destes : 

Para demonstração do algoritmo funcionando, veja o vídeo a seguir:




sexta-feira, 27 de março de 2015

Controle remoto com PIC - Parte2

         Conforme demonstrado anterior o projeto é dividido em codificador e decodificador, agora vou mostrar como é o código do receptor de controle remoto.

O código fonte:

/*
 *                                               usando o modo CCP no MPlab XC8
 *
 * Compilador : MPlabXC8
 * Microcontrolador: 16F648A
 * Autor: aguivone
 * Versão: 1
 * Data :  01 de março de 2013
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h> //para usar funçoes de string deve se adicionar este header
#define _XTAL_FREQ 4000000    // oscilador interno de 4 Mhz
#include <xc.h>
/////////////////////////////////////////////////////////configuraçôes//////////////////////////////////////////////////
#define LED_STATUS RB0
#define SINAL PORTAbits.RA0

#pragma config FOSC = INTOSCIO  // Oscillator Selection bits (INTOSC oscillator: I/O function on RA6/OSC2/CLKOUT pin, I/O function on RA7/OSC1/CLKIN)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = OFF      // RA5/MCLR/VPP Pin Function Select bit (RA5/MCLR/VPP pin function is digital input, MCLR internally tied to VDD)
#pragma config BOREN = ON       // Brown-out Detect Enable bit (BOD enabled)
#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 (Data memory code protection off)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

////////////////////////////////////////////////variaveis globais///////////////////////////////////////////////////////////
long  anterior = 0, atual = 0,largura_pulso = 0;
int   primeiro_pacote = 0,valor_pulso=0;
long  timeout = 0;
long  bit_0=0,bit_1=0,start_stop=0,range_bit=150;

///////////////////////////////////////////////////interrupção//////////////////////////////////////////////////////////////
void interrupt CCP1(void)
{
 if(CCP1IF) //verifica se é interrupção de cpp
    {// cada incremtento do timer será a cada 1us ,pois estou usando o oscilador interno de 4mhz
        atual = CCPR1;
        CCP1IF = 0; //limpa interrupção de captura de sinal
                if (anterior > atual)
                {
                    largura_pulso = (0XFFFF - anterior) + atual;
                }
                else
                {
                    largura_pulso = atual - anterior;
                }
       anterior = atual;
   }
 }
/////////////////////////////////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 = 0;//não vou receber nenhum dado do pc
    //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 inicializa_CCP() {
    //habilita captura de pulso na borda de subida (prescaler sem divisão)
    CCP1CON = 0X05;//modo de captura sem divisão (borda de subida)
    CCP1IF = 0; //limpa flag de interrupção de captura
    CCP1IE = 1;//habilita modo de captura de pulsos*/
}

void inicializa_timer1() {
    TMR1IF = 0; //limpa flag de interrupção de timer
   // TMR1IE = 1; //habilita interrupção de estouro do timer
    TMR1IE = 0; //desabilita interrupção de estouro do timer
    T1CON = 0x81; //habilita leitura do timer 1 e o timer 1,sem prescaler
}


//////////////////////////////////////////////////////Rotina principal///////////////////////////////////////////////////////////////

void main(void) {
    TRISA = 0XFF;//como entrada
    TRISB = 0X08;//configura portB  B3 (ccp1) como entrada
    PORTB = 0;  // limpar as portas que estão configuradas como saidas
    inicializa_CCP();
    inicializa_timer1();
    inicializa_RS232(9600,1);//modo de alta velocidade
    imprime("controle remoto");
    PEIE = 1;//habilita interrupção de perifericos do pic
    GIE = 1; //GIE: Global Interrupt Enable bit
    for(;;)
    {
          if(largura_pulso > 0)//leu pulsos
                     {
                      valor_pulso = largura_pulso;
                      if(primeiro_pacote == 0)
                          {
                           primeiro_pacote = 1;//descarta pois virá com lixo
                          }
                      else
                      {
                      if(primeiro_pacote == 1)
                        {//tenta localizar o protocolo
                          primeiro_pacote = 2;
                             escreve('\n');//pula para proxima linha
                             escreve('\r');//coloca no inicio da linha
                           if((valor_pulso > 2900)&&(valor_pulso < 3200))
                            {
                               escreve('N');
                               bit_0 = 1200;
                               bit_1 = 1800;
                               start_stop=3000;
                            }
                           if(valor_pulso < 2900)
                            {
                               escreve('P');
                               bit_0 = 1800;
                               bit_1 = 2700;
                               start_stop=0;
                               if(valor_pulso > (bit_0 + range_bit))
                              {//bit 1
                                escreve('1');
                              }
                              else
                              {//bit0
                                 escreve('0');
                              }
                            }
                            if((valor_pulso > 8000)&&(valor_pulso < 10000))
                            {
                               escreve('S');
                               bit_0 = 1100;
                               bit_1 = 2250;
                               start_stop=9000;
                            }
                            if(valor_pulso > 10000)
                            {
                               escreve('L');
                               bit_0 = 1100;
                               bit_1 = 2250;
                               start_stop=13500;
                            }

                        }
                      else
                      {
                         if((valor_pulso > (start_stop - range_bit))&&(valor_pulso < (start_stop + range_bit)))
                              {//start bit
                                escreve('S');//pra indicar que recebeu um bit de inicio ou fim de pacote
                              }
                      if(valor_pulso < (bit_1 + range_bit))
                      {
                          if(valor_pulso > (bit_0 + range_bit))
                              {//bit 1
                                escreve('1');
                              }
                              else
                              {//bit0
                                 escreve('0');
                              }

                      }
                      }
                      }
                    largura_pulso=0;
                        timeout = 3000;//prepara contagem
                     }//fim do leu pulsos
     if(timeout>0)
     {
     timeout--;
     }
     if(timeout==1)
     {// tempo usado para resetar ou descartar pacotes com defeito
      primeiro_pacote = 0;
     }
    }//loop infinito

}
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Depois de montado os códigos vamos a simulação,na simulação o primeiro pacote dá um erro, mas logo em seguida tudo funciona normalmente.Veja a imagem da simulação e do esquemático no proteus :


O esquemático:
    
     Testando com um controle remoto e com o prototipo montado numa proto-o-board recebi os seguintes códigos do controle:

Padrão samsung, 33bits + start_stop_bit:
 
Power = 111000001110000001000000101111110
vol+  = 111000001110000011100000000111110
vol-  = 111000001110000011010000001011110
ch+   = 111000001110000001001000101101110
ch-   = 111000001110000000001000111101110

Padrão LG, 32bits + start_stop_bit:

Power = 01000001101111100010000111011110
vol+  = 01000001101111101000000101111110
vol-  = 01000001101111111000000001111110
ch+   = 01000001101111100000000111111110
ch-   = 01000001101111110000000011111110

padrão sony, repete 3 vezes um codigo de 11 bits + start_bit
com intervalos de 27ms 
         
Power = 10101001000
vol+  = 01001001000
vol-  = 11001001000
ch+   = 00001001000
ch-   = 10001001000

padrão philips envia 10 bits sem pulsos de inicio e fim

Power = 0100000101
vol+  = 0100001100
vol-  = 0000001101
ch+   = 0000011000
ch-   = 0100011001

Para pegar os dados eu montei na proto-o-board o esquema elétrico seguinte:



Em breve vou postar um artigo usado este projeto como base para acionar leds com diferentes controles, até a próxima!

Controle remoto com PIC - Parte 1

             Olá, hoje vou demonstrar como montar um controle remoto com o PIC e também como montar um receptor de controle remoto, a ideia inicial era montar apenas o decodificador(receptor do controle remoto) mas para isso eu precisava simular um controle remoto a medida que montava todo o firmware então acabei montando também o simulador de controle remoto(codificador), mas eu também queria que meu circuito reconhecesse pelo menos uns 4 tipos de marcas para que vocês pudessem testar em casa sem grandes dificuldades, então peguei 4 marcas mais vistas no mercado: LG, PHILIPS, SAMSUNG e SONY.Então comecei a ver a forma de ondas no osciloscópio e levantar informações(o que não é tão simples de encontrar), dai encontrei sobre a Sony e a Lg(protocolo NEC) embora o padrão da Samsung é o mesmo mas com diferença no tamanho do pulsos, o padrão Philips deu um pouco mais de trabalho pois não possuem um bit de "start".
               A característica do padrão NEC é a presença de um bit de inicio(start bit) e outro de fim(stop bit) e mais 1 ou 2 bits após o "stop bit", sendo que antes do "stop bit" existe um espaço de 46ms(padrão samsung) ou 44ms(LG), ao todo são 33bits, no padrão Sony existe um "start bit" e em seguida 12 bits(mas no circuito montado foi lido somente 11 mas deve ser devido ao fato de eu estar ignorando os espaços entre os bits mas isso não atrapalha o reconhecimento) que é enviado 3 vezes em intervalos de 27ms . Já o padrão Philips não tem "stop bit" e nem "start bit" ao todo dá 10bits. Veja como é a onda gerada pelo controle remoto do PIC:
               veja que não coloquei  a onda inteira do padrão NEC (LG e Samsung) para facilitar a visualização mas abaixo coloquei a onda completa do padrão samsung para dar uma idéia, a diferença entre Samsung e LG é o tamanho do "start bit":

           Os bits "0" e "1" se diferenciam pelo tempo em que dura o nível alto e o nível baixo, se tempo iguais vale 0 se os tempos forem diferentes nível alto,mas para facilitar a decodificação eu peguei o valor do período do sinal ou seja na borda de subida do sinal, o valor desses períodos não são padrões cada fabricante escolhe o seu, mas o da Samsung e LG são iguais os bits "0" e "1"(talvez por usar o mesmo protocolo e com diferença apenas no "start bit").
         Vamos agora ao código fonte do codificador de controle remoto, vale lembrar que colocando um led infravermelho ele deve acionar uma tv, bastando para isso fazer pequenas alterações neste código fonte(não testei na TV ainda pois gerei ele só pra testar o decodificador):

O código fonte:

/*
 *                               Emulando um controle remoto de TV no MPlab XC8
 *
 * Compilador : MPlabXC8
 * Microcontrolador: 16F628A
 * Autor: aguivone
 * Versão: 1
 * Data :  16 de março de 2015
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h> //para usar funçoes de string deve se adicionar este header
#define _XTAL_FREQ 4000000    // oscilador interno de 4 Mhz
#include <xc.h>
#include <delays.h>
/////////////////////////////////////////////////////////configuraçôes//////////////////////////////////////////////////
#define LED_STATUS RB0
#define LED_STATUS RB0


#pragma config FOSC = INTOSCIO  // Oscillator Selection bits (INTOSC oscillator: I/O function on RA6/OSC2/CLKOUT pin, I/O function on RA7/OSC1/CLKIN)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = OFF      // RA5/MCLR/VPP Pin Function Select bit (RA5/MCLR/VPP pin function is digital input, MCLR internally tied to VDD)
#pragma config BOREN = ON       // Brown-out Detect Enable bit (BOD enabled)
#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 (Data memory code protection off)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

long tempo_us1=0;

void interrupt interrupcao(void)//vetor de interrupção
 {
    if(T0IF)
    {//interrupção do timer0 - estoura quando atinge 255(0XFF)- estoura a cada 62.5us
        if(tempo_us1>0)
        {
            tempo_us1--;
        }
        TMR0 = 254;//reinicia timer com o valor calculado
        T0IF = 0;
        //62.5us + os delay da rotinas  = 75us
    }
 }

void inicializa_timer0()
{
    OPTION_REG = 0X03;//timer0 com prescaler dividido por 16
    T0IE = 1;//habilita interrupção do timer0
    TMR0 = 254;//zera registrador de contagem
}

void envia_bit(char protocolo,int val)
{
    switch(protocolo)
    {
        case 'G'://Samsung
        {
             if(val == 0)
            {//bit 0
             LED_STATUS = 1;
             __delay_us(560);
             LED_STATUS = 0;
             __delay_us(560);
            }
            else
            {//bit 1
             LED_STATUS = 1;
             __delay_us(560);
             LED_STATUS = 0;
             __delay_ms(1);
             __delay_us(680);
            }

        }break;
        case 'L'://LG 
        {
             if(val == 0)
            {//bit 0
             LED_STATUS = 1;
             __delay_us(560);
             LED_STATUS = 0;
             __delay_us(560);
            }
            else
            {//bit 1
             LED_STATUS = 1;
             __delay_us(560);
             LED_STATUS = 0;
             __delay_ms(1);
             __delay_us(680);
            }

        }break;
        case 'S'://sony
        {
                        if(val == 0)
            {//bit 0
             LED_STATUS = 1;
             __delay_us(600);
             LED_STATUS = 0;
             __delay_us(600);
            }
            else
            {//bit 1
             LED_STATUS = 1;
             __delay_ms(1);
             __delay_us(200);
             LED_STATUS = 0;
             __delay_us(600);
            }
        }break;
        case 'P'://
        {
           if(val == 0)
            {//bit 0
             LED_STATUS = 1;
             __delay_us(890);
             LED_STATUS = 0;
             __delay_us(890);
            }
            else
            {//bit 1
             LED_STATUS = 1;
             __delay_ms(1);
             __delay_us(800);
             LED_STATUS = 0;
             __delay_us(890);
            }

        }break;
    }
   
}

void envia_start_bit(char proto)
{
    switch(proto)
    {
    case 'L'://LG
    {
        LED_STATUS = 1;
        __delay_ms(9);
        LED_STATUS = 0;
        __delay_ms(4);
        __delay_us(500);
    }break;
   case 'G'://samsung
    {
        LED_STATUS = 1;
        __delay_ms(4);
        __delay_us(500);
        LED_STATUS = 0;
        __delay_ms(4);
        __delay_us(500);
    }break;
    case 'S'://sony
    {
        LED_STATUS = 1;
        __delay_ms(2);
        __delay_us(400);
        LED_STATUS = 0;
        __delay_us(600);
    }break;
    case 'P'://philips
    {
        LED_STATUS = 1;
        __delay_ms(1);
        __delay_us(800);
        LED_STATUS = 0;
        __delay_ms(1);
        __delay_us(800);
    }break;
}
}
void simula_pacote(char protocolo)
{
    switch(protocolo)
    {
        case 'L'://LG
        {
           envia_start_bit(protocolo);
           int temp = 0;
           while(temp<=10)//envia 10 bits 0
           {
              envia_bit(protocolo,0);
              temp++;
           }
           while(temp<=33)//envia 23 bit 1
           {
              envia_bit( protocolo,1);
              temp++;
           }
           __delay_ms(44);
           envia_start_bit(protocolo);
           envia_bit(protocolo,0);
           envia_bit(protocolo,1);
        }break;
       case 'G'://samsung
        {
           envia_start_bit(protocolo);
           int temp = 0;
           while(temp<=10)//envia 10 bits 0
           {
              envia_bit(protocolo,0);
              temp++;
           }
           while(temp<=33)//envia 23 bit 1
           {
              envia_bit( protocolo,1);
              temp++;
           }
           __delay_ms(46);
           envia_start_bit(protocolo);
           envia_bit(protocolo,0);
           envia_bit(protocolo,0);
        }break;
       case 'S'://sony
        {
           int envio = 0;
           while(envio<3)
           {
           envia_start_bit(protocolo);
           int temp = 0;
           while(temp<=6)//envia 6 bits 0
           {
              envia_bit(protocolo,0);
              temp++;
           }
           while(temp<=13)//envia 6 bit 1
           {
              envia_bit( protocolo,1);
              temp++;
           }
           __delay_ms(27);
           envio++;
           }
        }break;
        case 'P'://philips
        {
           int temp = 0;
           while(temp<3)//envia 2 bits 0
           {
              envia_bit(protocolo,0);
              temp++;
           }
           while(temp<7)//envia 4 bit 1
           {
              envia_bit( protocolo,1);
              temp++;
           }
           envia_start_bit(protocolo);
           while(temp<11)//envia 3 bit 1
           {
              envia_bit( protocolo,1);
              temp++;
           }
        }break;
    }
}
//////////////////////////////////////////////////////Rotina principal///////////////////////////////////////////////////////////////

void main(void) {
    TRISB = 0;//configura portB  como saida
    PORTB = 0;  // limpar as portas que estão configuradas como saidas
   // inicializa_timer0();
    PEIE = 1;//habilita interrupção de perifericos do pic
    GIE = 1; //GIE: Global Interrupt Enable bit
    for(;;)
    {
         simula_pacote('L');//simula um sinal de controle remoto LG
         __delay_ms(2000);//minimo de 100ms entre cada pacote mas para os teste vou colocar 5s
         simula_pacote('G');//simula um sinal de controle remoto Samsung
         __delay_ms(2000);//minimo de 100ms entre cada pacote mas para os teste vou colocar 5s
         simula_pacote('S');//simula um sinal de controle remoto Sony
         __delay_ms(2000);//minimo de 100ms entre cada pacote mas para os teste vou colocar 5s
         simula_pacote('P');//simula um sinal de controle remoto Philips
         __delay_ms(2000);//minimo de 100ms entre cada pacote mas para os teste vou colocar 5s

    }//loop infinito

}


Na segunda parte vou mostrar o código do receptor e mais algumas informações.


sábado, 14 de fevereiro de 2015

Usando display de 4X20 (4 linhas por 20 caracteres) com o 16F877A - XC8

Olá, neste exemplo vou demonstrar como usar um display de LCD 4X20 com o pic 16F877A no compilador XC8, vale lembra que os arquivos com a extensão ".c" e ".h" deve estar na mesma pasta em que é criado o arquivo main(), pois assim o compilador encontra esses arquivos,  veja que no meu código principal chamei apenas os arquivos ".h" pois os outros arquivos são chamados automaticamente pelo compilador, mas caso isso não aconteça vá na aba de projetos "source files"  e clicando no mouse como botão direito escolha a opção "add existing item...", e escolha o arquivo ".c", vamos ao Código fonte :

Código fonte do arquivo "main.c":
/*                         Usando display LCD 4X20
 * File:   main.c
 * Author: Aguivone
 *
 * Created on 14 de Fevereiro de 2015, 13:28
 */

#include <stdio.h>
#include <stdlib.h>
#include <xc.h>
#include "config_cpu.h"
#include "lcd_4X20_16F.h"
#include<delays.h>

/*
 * 
 */
void main()
{
///configuração das porta do microcontrolador///////////////////////
    TRISA = 0;//todas são saidas
    TRISB = 0;//configura portB   como saida
    TRISD = 0;//todas são saidas
    TRISC = 0;//todas são saidas
    TRISE = 0;//todas são saidas
    PORTA = 0;
    PORTB = 0;  // limpar as portas que estão configuradas como saidas
    PORTC = 0;
    PORTD = 0;
    PORTE = 0;
    ADCON0 = 0X00;//desabilita modulo ADC
    ADCON1 = 0X07;//desliga portas analogicas
    ////////////////////////////////////////////////////////////////////
    inicializar_LCD();
    GIE = 0; //desabilita GIE: Global Interrupt Enable bit
    escolhe_pos_do_cursor(1,0);//linha 1 posiçao 0
    imprime_LCD("escrevendo na linha1");
    escolhe_pos_do_cursor(2,0);//linha 2 posiçao 0
    imprime_LCD("escrevendo na linha2");
    escolhe_pos_do_cursor(3,0);//linha 3 posiçao 0
    imprime_LCD("escrevendo na linha3");
    escolhe_pos_do_cursor(4,0);//linha 4 posiçao 0
    imprime_LCD("escrevendo na linha4");
    for(;;)
    {
       // faz nada!
    }

}
Código fonte do arquivo "config_cpu.h":

/*
 * File:   config_cpu.h
 * Author: aguivone
 *
 * Created on 25 de Agosto de 2014, 16:24
 */

#ifndef CONFIG_CPU_H
#define    CONFIG_CPU_H

/*******************************configuraçôes do pic ****************************************************/
#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 = ON         //Codigo de proteção de firwmware




#define _XTAL_FREQ 20000000//usado para rotinas de  delays


#endif    /* CONFIG_CPU_H */

Código fonte do arquivo "lcd_4X20_16F.h":

The HTML source code is below.

/* 
 * File:   lcd_4X20_16F.h
 * Author: aguivone
 *
 * Created on 06 de fevereiro de 2015, 16:25
 */

#ifndef    LCD_4X20_16F_H
#define    LCD_4X20_16F_H

// ************************************************************************************
// Prototipos de funçoes
// ***********************************************************************************
extern void Saida_LCD(char a);
extern void comando_LCD(char a);
extern void limpar_LCD(void);
extern void escolhe_pos_do_cursor(char a, char b);
extern void inicializar_LCD(void);
extern void escreve_caracter(char a);
extern void imprime_LCD(char *a);
extern void rotaciona_LCD_direita();
extern void rotaciona_LCD_esquerda();
extern void long_to_char_LCD(unsigned long quant,int linha,int posicao);
extern void int_to_char_LCD(int quant,int linha,int posicao,int dig);

//#include "lcd_4X20_16F.c" //quando não reconhecer automaticamente usar este recurso


#endif    /*LCD_4X20_16F_H*/

Código fonte do arquivo "lcd_4X20_16F.c":

/*
 *                          FUNÇOES USADAS NO DISPLAY DE LCD
 *
 * Compilador :      MPlabXC8
 * Microcontrolador: 16F877A
 * Autor:            Aguivone
 * Versão:           1
 * Data de criação:  10/02/2015.
 */
//Biblioteca para fazer a comunicação com o display de LCD 16X2
//usando o modo 4 bits
//testada com a familia PIC16F mas pode facilmente ser usada para outras familias de pic
#include <xc.h>
#include "config_cpu.h"
#include<delays.h>

//configuracao dos pinos usado para comunicar com o display, onde os defines são os nomes dos pinos do display.

#define RS RD2
#define EN RD3
//pinos de dados
#define D4 RD4
#define D5 RD5
#define D6 RD6
#define D7 RD7
//
//funçoes desta biblioteca
//
void Saida_LCD(char a)
{
    if(a & 1)
        {D4 = 1;}
    else
        {D4 = 0;}
        if(a & 2)
        {D5 = 1;}
    else
        {D5 = 0;}
        if(a & 4)
        {D6 = 1;}
    else
        {D6 = 0;}
        if(a & 8)
        {D7 = 1;}
    else
        {D7 = 0;}

}
void comando_LCD(char a)
{
    RS = 0;             // => RS = 0
    Saida_LCD(a);
    EN  = 1;             // => E = 1
        __delay_ms(4);       //tempo necessario para display reconhecer
        EN  = 0;             // => E = 0
}

void limpar_LCD(void)
{
    comando_LCD(0);
    comando_LCD(1);
}

void escolhe_pos_do_cursor(char a, char b)
{
    char temp,z,y;
    if(a == 1)
    {
      temp = 0x80 + b;
        z = temp>>4;
        y = temp & 0x0F;
        comando_LCD(z);
        comando_LCD(y);
    }
    else if(a == 2)
    {
        temp = 0xC0 + b;
        z = temp>>4;
        y = temp & 0x0F;
        comando_LCD(z);
        comando_LCD(y);
    }
        if(a == 3)
    {
      temp = 0x94 + b;
        z = temp>>4;
        y = temp & 0x0F;
        comando_LCD(z);
        comando_LCD(y);
    }
    else if(a == 4)
    {
        temp = 0xD4 + b;
        z = temp>>4;
        y = temp & 0x0F;
        comando_LCD(z);
        comando_LCD(y);
    }
}

void inicializar_LCD()
{
  Saida_LCD(0x00);
   __delay_ms(20);
  comando_LCD(0x03);
  __delay_ms(5);
  comando_LCD(0x03);
  __delay_ms(11);
  comando_LCD(0x03);
  /////////////////////////////////////////////////////
  comando_LCD(0x02);
  comando_LCD(0x02);
  comando_LCD(0x08);
  comando_LCD(0x00);
  comando_LCD(0x0C);
  comando_LCD(0x00);
  comando_LCD(0x06);
}

void escreve_caracter(char a)
{
   char temp,y;
   temp = a&0x0F;//pega a parte baixa do byte
   y = a&0xF0;//pega parte alta do byte
   RS = 1;             // => RS = 1
   Saida_LCD(y>>4);             //Envia a parte alta mas antes rotaciona 4 vezes
   EN = 1;
   __delay_us(40);
   EN = 0;
   Saida_LCD(temp);// envia a parte baixa
   EN = 1;
   __delay_us(40);
   EN = 0;
}

void imprime_LCD(char *a)
{
    int i;
    for(i=0;a[i]!='\0';i++)
       escreve_caracter(a[i]);
}

void rotaciona_LCD_direita()
{
    comando_LCD(0x01);
    comando_LCD(0x0C);
}

void rotaciona_LCD_esquerda()
{
    comando_LCD(0x01);
    comando_LCD(0x08);
}

void long_to_char_LCD(unsigned long quant,int linha,int posicao)//valor, posicão inicial,linha a ser usada
{
       char convert_char2='0';
       char convert_char3='0';
       char convert_char4='0';
       escolhe_pos_do_cursor(linha,posicao);//linha 1 posiçao 1
       if(quant <10)
       {
         convert_char2=' ';
         convert_char3=' ';
       }
       else
       {
           if(quant <100)
            {
              convert_char2=' ';
            }
       }

       while(quant>=100)
        {
         quant=quant-100;
         convert_char2++;
         }
       while(quant>=10)
        {
         quant=quant-10;
        convert_char3++;
         }
       while(quant>=1)
        {
           quant=quant-1;
         convert_char4++;
         }
          escolhe_pos_do_cursor(linha,posicao);//linha , posiçao
          escreve_caracter(convert_char2);
          escolhe_pos_do_cursor(linha,posicao+1);//linha , posiçao
          escreve_caracter(convert_char3);
          escolhe_pos_do_cursor(linha,posicao+2);//linha , posiçao
          escreve_caracter(convert_char4);
}
void int_to_char_LCD(int quant,int linha,int posicao,int dig)//valor, posicão inicial,linha a ser usada,numero de digitos
{
       char convert_char3='0';
       char convert_char4='0';
       escolhe_pos_do_cursor(linha,posicao);//linha 1 posiçao 1


       while(quant>=10)
        {
         quant=quant-10;
        convert_char3++;
         }
       while(quant>=1)
        {
           quant=quant-1;
           convert_char4++;
         }

          if(dig == 2)
          {
           escolhe_pos_do_cursor(linha,posicao);//linha , posiçao
           escreve_caracter(convert_char3);
           escolhe_pos_do_cursor(linha,posicao+1);//linha , posiçao
           escreve_caracter(convert_char4);
          }
          else
          {
            escolhe_pos_do_cursor(linha,posicao);//linha , posiçao
            escreve_caracter(convert_char4);
          }
}

A simulação: