quinta-feira, 4 de janeiro de 2024

Frequencímetro com 2 canais usando o PIC16F887 e mostrando via serial RS232 (frequencímetro duplo)

     Olá neste primeiro projeto de 2024 vou colocar um exemplo de frequencímetro duplo para medir 2 frequências através dos CCP'S e enviar via seria, além de funções prontas para calculo da frequência e configuração do prescaler para cada faixa de frequência desejada.     

    Nos testes do simulador e no projeto físico funcionou muito bem e com muita precisão, um exemplo de simulação é visto na figura a seguir:


    Note que os leds são pra indicar que esta lendo pulsos nos pinos dos ccp's , como foi configurado para cada borda de subida logo terá a o valor de frequência menor. Para este foi colocado o mesmo sinal nos 2 canais para simular.

O código fonte :

/*
 *
 * Compilador : MPlabXC8
 * Microcontroladores compativeis : 16F886 e 887
 * Autor: Aguivone Moretti Fógia
 * Versão: 1.1.2
 * Data :  02/01/2024 - inicio de testes com oscilador interno (de 8MHZ) - 100 a 8000hz no simulador 
 * Data :  02/01/2024 - inicio de testes com oscilador interno (de 20MHZ) - 
 * descrição : Frequencimetro 
 * 
 */
#include <stdio.h>
#include <string.h> //para usar funçoes de string deve se adicionar este header
#include <stdlib.h>
// se igual a 8MHZ:
/* #define _XTAL_FREQ 8000000
   #pragma config FOSC = INTRC_NOCLKOUT// Oscillator Selection bits (INTOSCIO oscillator: I/O function on RA6/OSC2/CLKOUT pin, I/O function on RA7/OSC1/CLKIN)
 */ 
// se igual a 20MHZ:
  #define _XTAL_FREQ 20000000
  #pragma config FOSC = HS        //  cristal de 20mhz
#include <xc.h>
/////////////////////////////////////////////////////////configuraçôes//////////////////////////////////////////////////////

#pragma config WDTE  = OFF       // Watchdog Timer Enable bit (WDT habilitado)
#pragma config PWRTE = OFF       // Power-up Timer Enable bit (PWRT enabled)
#pragma config MCLRE = OFF      // RA5/MCLR/VPP Pin Function Select bit (RA5/MCLR/VPP pin function is digital I/O, MCLR internally tied to VDD)
#pragma config BOREN = OFF      // Brown-out Detect Enable bit (BOD disabled)
#pragma config LVP   = OFF        // Low-Voltage Programming Enable bit (RB4/PGM pin has digital I/O function, HV on MCLR must be used for programming)
#pragma config CPD   = OFF         // Data EE Memory Code Protection bit (proteção da eeprom)
#pragma config CP    = OFF         // Flash Program Memory Code Protection bit (proteção do codigo)

///////////////defines utilizados///////////////////////////////////////////////////////////////////////////////////////////
//o pino R/W é conectado ao terra pois não é feita nenhuma leitura no display
//configuração:
//
// pino da porta selecionada / pino display
//
// C4 = pino de D4 do display
// C5 = pino de D5 do display
// C6 = pino de D6 do display
// C7 = pino de D7 do display
//
// C1 = pino de RS do display
// C2 = pino de EN do display
//
/*************************** configuração dos pinos **************************************************/
////////PINOS DO DISPLAY DE LCD ///////////////

//16F88
/*
 #define BIT_0              RB0
 #define BIT_1              RB1
 #define BIT_2              RB3
 #define BIT_3              RB4

 #define PIN_RS485          RA1 //pino de controle da rs485
 #define EN                 RA2
 #define RS                 RA3
///////// PINO DE SINALIZAÇÃO //////////////
#define  LED_STATUS         RA4
//////////PINOS DE ENTRADA  ///////////////
#define  BOT_FUNCAO         RB7
#define  BOT_SELECAO        RB6
#define  BOT_OK             RA6
*/
//16F887
#define  LED_STATUS         RB0
#define  LED_freq1          RB1
#define  LED_freq2          RB2

long periodo_anterior_1=0;
long periodo_anterior_2=0;
long periodo1=0;
long periodo2=0;
char  CCP1_mult,CCP2_mult;
float aux = 0;
float fator_de_div = 0;
void CCP_div_por(char CCP1_V,char CCP2_V)
{//valor da divisão do CCPs
    CCP1CON = 0X05;//captura a cada borda 
    CCP2CON = 0X05;//por padrao
    CCP1_mult = CCP1_V;
    if(CCP1_mult == 4)
    {
       CCP1CON = 0X06; 
    }
    if(CCP1_mult == 16)
    {
       CCP1CON = 0X07; 
    }
    CCP2_mult = CCP2_V;
    if(CCP2_mult == 4)
    {
       CCP2CON = 0X06; 
    }
    if(CCP2_mult == 16)
    {
       CCP2CON = 0X07; 
    } 
}
void prescaler_por(char prescaler)
{
    switch(prescaler)
    {
        case 1:///por 1
                fator_de_div =  _XTAL_FREQ/4;
                T1CON = 0X01;
        break;
        case 2: /// por 2
                fator_de_div =  _XTAL_FREQ/8;
                T1CON = 0X11;
        break;
        case 4: /// por 4
                fator_de_div =  _XTAL_FREQ/16;
                T1CON = 0X21;
        break;
        case 8:/// por 8
                fator_de_div =  _XTAL_FREQ/32;
                T1CON = 0X31;
        break;
    }
}
void interrupt interrupcoes(void)//vetor de interrupção
 {
        long periodo_atual_1  = CCPR1;//pega valores  depois vê se é pra tratar
        long periodo_atual_2  = CCPR2;        
        /*if(TMR1IF)
        { //não será usado aqui
            TMR1IF = 0;
        }*/
        if(CCP1IF)
        {
            CCP1IF = 0;//limpa flag de interrupção
                   if(periodo_atual_1> periodo_anterior_1)
                    {
                      periodo1 = periodo_atual_1 - periodo_anterior_1;
                    }
                    else
                    {
                      periodo1 = (65535 - periodo_anterior_1) + periodo_atual_1;
                    }             
            LED_freq1 = ~LED_freq1;
            periodo_anterior_1 = periodo_atual_1;    
            
        }
        if(CCP2IF)
        {
            CCP2IF = 0;//limpa flag de interrupção
                   if(periodo_atual_2> periodo_anterior_2)
                        {
                          periodo2 = periodo_atual_2 - periodo_anterior_2;
                        }
                        else
                        {
                          periodo2 = (65535 - periodo_anterior_2) + periodo_atual_2;
                        }             
            LED_freq2 = ~LED_freq2;
            periodo_anterior_2 = periodo_atual_2;
        }
        if(RCIF == 1)
        {//interrupção de recepção de dados
            char aux;
            //lembrar de por um timeout pra não ficar travado esperando dados
            RCIF = 0;//  limpa flag de interrupção de recepção
            aux = RCREG;  //deve ler para não trava o chip esperando tratar a interrupçao
            // não será utilizado aqui
        }
}
void imprime_rs232(char frase[],int tamanho)
{ //para conseguir imprimir em hexa sem problemas é melhor passar o tamanho do pacote
       int indice = 0;
       __delay_us(1);
       for(indice = 0;indice < tamanho;indice++)
       {
           while(TRMT==0);//espera enviar caracter, esvaziando o shift register , usando o trmt é mais garantido a entrega 
           TXREG = frase[indice];//no datasheet diz que ao carregar o TXREG limpa o bit TXIF           
           while(TRMT==0);
       }        
}
void inicializa_RS232(long velocidade,int modo,char pega_ref)
{////por padrão é usado o modo 8 bits e sem paridade, mas se necessario ajuste aqui a configuração desejada.
    //verifique datasheet para ver a porcentagem de erro e se a velocidade é possivel para o cristal utilizado.
    RCSTA = 0X90;//habilita porta serial,recepção de 8 bit em modo continuo,assincrono.
    long valor;
        if(modo == 1)
        {//modo = 1 ,modo alta velocidade
         TXSTA = 0X24;//8bits/transmissão habilitado/modo assincrono/highspeed
         valor =(((_XTAL_FREQ/velocidade)-16)/16);//calculo do valor do gerador de baud rate -> 10_06_2022
        }
        else
        {//modo = 0 ,modo baixa velocidade
         TXSTA = 0X20;////8bits/transmissão habilitado/modo assincrono/lowspeed
         valor =(((_XTAL_FREQ/velocidade)-64)/64);//calculo do valor do gerador de baud rate
        }
    if(pega_ref == 0)
    {//valor calculado
       SPBRG =(int)valor; // veja a pagina 101 do datasheet já tem calculado para 8MHZ
    }
    else
    {//valor colocado diretamente
      SPBRG =(int)velocidade; //coloca valor direto baseado no data sheet  
    }
    RCIE = 1;//habilita interrupção de recepção
    PEIE =1;
    TXEN = 1; // habilita o modulo de transmisão
    TXIE = 0;//deixa interrupção de transmissão desligado(pois corre se o risco de ter uma interrupção escrita e leitura ao mesmo tempo)
}
void long_to_char(long valor)
{
    char Buffer_serial_out[5]; //delimitador pra facilitar jogar no excel
    Buffer_serial_out[0]='0';
    Buffer_serial_out[1]='0';
    Buffer_serial_out[2]='0';
    Buffer_serial_out[3]='0';
    Buffer_serial_out[4]='0';
    if(valor < 65535)
    {
        while(valor>=10000)
             {
                 valor = valor - 10000;
                 Buffer_serial_out[0]++;
             }
        while(valor>=1000)
           {
               valor = valor - 1000;
               Buffer_serial_out[1]++;
           }
        while(valor>=100)
           {
               valor = valor - 100;
               Buffer_serial_out[2]++;
           }
        while(valor>=10)
           {
               valor = valor - 10;
               Buffer_serial_out[3]++;
           }
        while(valor>=1)
           {
               valor = valor - 1;
               Buffer_serial_out[4]++;
           }
        imprime_rs232(Buffer_serial_out ,5);
    }

}

//*******************************Rotina principal*********************************************/

void main(void)
{    
    TRISA = 0XF7;//configura portA  pino habilita rs    
    TRISB = 0X00;//configura portB
    TRISC = 0X06;//configura portC //tx é saida
    TRISD = 0X00;//configura portD  // somente no 16F887
    TRISE = 0XFF;//configura portD     
    PORTA = 0;
    PORTB = 0;//deixa pino tx em nivel alto
    PORTC = 0;
    PORTD = 0;// somente no 16F887 
    GIE = 0;//já desliga as interrrupções para terminar as configurações
    OPTION_REG = 0X80; // pull up desabilitados e prescaler para timer 0 - desabilita watchdog
    INTCON = 0;//desabilita todas as interrupções 
    //CMCON = 0X07;//desliga comparadores  no 16F887 é outro
    //---------------------------------------------------------
   // CCP1CON = 0X05;   //Capture mode, every rising edge
   // CCP2CON = 0X05;
     CCP_div_por(1,1);// modo do ccp dividido por 1,4 ou 16.
    //---------------------------------------------------------
    ANSEL = 0X00; //portas digitais e AN0 e AN1.
    ANSELH = 0X00; //portas digitais e AN8 e AN13.// 
    OSCTUNE = 0X1F;  // oscilador vel maxima(para que o baud rate tambem dê certo)
    // ----------------------------------------------------------------------------------------------------------
    // para oscilador interno de 8 mhz use :           
         // OSCCON = 0XFE ; // oscilador interno com frequencia de 8mhz ->16F88
       //  OSCCON = 0XF5 ; // oscilador interno com frequencia de 8mhz ->16F887         
     //----------------------------------------------------------------------------------------------------------    
    // para cristal de 20 Mhz use:   
     OSCCON = 0XFE; // oscilador de 20mhz
     //----------------------------------------------------------------------------------------------------------
    inicializa_RS232(19200,1,0);
    CCP1IE = 1;//habilita interrupçao de ccp
    CCP2IE = 1;//habilita interrupçao de ccp
   // T1CON=1;	//Timer on(para fazer as contagem)) qunado uas a afunção abaixo não precisa por este
    prescaler_por(1);
   // TMR1IE = 1;//habilita interrupção de estouro de timer para verificar quantos estouros houve não será usado
    PEIE = 1;//habilita interrupção de hardware
    GIE = 1;
    imprime_rs232("Frequencimetro duplo com pic \n\r",30);
   for(;;)
     {
       __delay_ms(10);
       GIE = 0;
       //imprime o valor da frequencia atual a cada 200ms 
       imprime_rs232("\n\r FREQUENCIAS : ",17);
       aux =  fator_de_div / periodo1;
       aux  = aux*CCP1_mult;
       if(aux > 99999)
       {
           aux/1000;
           periodo1  = (long)aux;
           long_to_char(periodo1);//valor de frequencia calculada
           imprime_rs232("KHz ",3);
       }
       else
       {
           periodo1  = (long)aux;
           long_to_char(periodo1);//valor de frequencia calculada
           imprime_rs232("Hz - freq.2 = ",3);
       }
       aux =  fator_de_div / periodo2;
       aux  = aux*CCP2_mult;
       if(aux > 99999)
       {
           aux/1000;
           periodo2  = (long)aux;
           long_to_char(periodo2);//valor de frequencia calculada
           imprime_rs232("KHz ",3);
       }
       else
       {
           periodo2  = (long)aux;
           long_to_char(periodo2);//valor de frequencia calculada
           imprime_rs232("Hz ",3);
       }
       LED_STATUS = ~LED_STATUS; 
       GIE = 1;
     }//loop infinito
}


//  Notas:  *****************
/*
 estando com o CCP1 e CCP2 sem divisão ( div por 1) e com cristal de 20mhz
 * a faixa de range confiavel é aprox :
 * prescaler para  1 :  a partir de 80hz a 100Khz(estimado)
 * prescaler para  2 :  a partir de 40hz a 70khz(etimado) 
 * prescaler para  4 :  a partir de 10hz a 30khz(simulado)
 * prescaler para  8 :  a partir de 10hz a 25khz(simulado)
 * 
 * para faixa maxima o simulador proteus é limitado o ideal é usar um frequencimetro 
 * isso se deve ao estouro do timer para frequencia baixa(se for operar nessas frequencia use clock menor)
 */

formatado por : http://hilite.me/  acessando em 04/01/2024.

Nenhum comentário :

Postar um comentário

olá,digite aqui seu comentário!