sexta-feira, 28 de agosto de 2015

Emulando uma porta serial no PIC12F675

 Olá,
            As vezes precisamos usar um microcontrolador como o nosso "pequeno notável" PIC12F675, pois tem somente 8 pinos ficando ideal para aplicações em que a simplicidade e redução de tamanho são significativo,mas apesar de contar com um conversor analógico digital não tem uma porta serial para entregar uma informação, qual a solução? Isso mesmo criar uma porta serial virtual ou seja emular uma porta serial.Esse algoritmo pode ser usado em outros microcontroladores para ser ter mais portas seriais(sem a necessidades de comprar um microcontrolador dedicado).
            A primeira coisa que deve ser feita é escolher o cristal a ser utilizado pois a velocidade de comunicação depende disto,neste exemplo vou colocar pra 3 tipos de configurações:usando o oscilador interno,usando um cristal externo de 4MHZ e usando um cristal de 20MHZ.
           Para ajustar os tempos de bits tive que definir 2 variáveis de controle ; "TIME_BAUD" ,usado pela transmissão e "BAUD_RX" usado pela recepção(o motivo desta variável é compensar atrasos de leitura do pino),as velocidades testadas foram 4800bps,9600bps e 19200bps, os tempos de cada bit são 208us,104us e 52us respectivamente.
           Para o cristal de 4MHZ ou o oscilador interno(que é de 4MHZ mas é do tipo RC) o melhor desempenho foi na faixa de 4800bps, enquanto o de 20MHZ funcionou bem nas três velocidades, pois os tempos de execução de rotina se tornam pouco expressivo nos tempos de envio e recepção.Os resultados foram colocados no final do código para ajudar a orientar no processo de uso do algoritmo.
           Neste exemplo o ao ser ligado o circuito ele envia uma mensagem para que o usuário digite algo no terminal serial e então repete o carácter, conta ainda com um botão que ao ser pressionado enviar o carácter "A", veja abaixo como ficou o código fonte e a simulação:

O código-fonte:

/*
 *                                               Emulando(cria uma porta virtual) uma porta serial
 *
 * Compilador : MPlabXC8 V.3.0
 * Data: 28/08/2015.
 * Microcontrolador: 12F675.
 * Autor: aguivone.
 * Versão: 1.
 * 
 * Emulado para 8 bits de dados/sem paridade/1bit de stop bit
 * Emulado para 1 bit de start e stop 
 * sem paridade
 * 
 * Antes de usar leia os parametros no final do codigo.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>//para trabalhar com string
#include <xc.h>
#define  _XTAL_FREQ  20000000//usado pela rotina de tempo do pic
/////////////////////////////////////////////////////////configuraçôes//////////////////////////////////////////////////
#pragma config FOSC = HS  // Oscillator Selection bits (INTOSC oscillator: I/O function on GP4/OSC2/CLKOUT pin, I/O function on GP5/OSC1/CLKIN)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = ON       // Power-Up Timer Enable bit (PWRT enabled)
#pragma config MCLRE = OFF      // GP3/MCLR pin function select (GP3/MCLR pin function is digital I/O, MCLR internally tied to VDD)
#pragma config BOREN = OFF      // Brown-out Detect Enable bit (BOD disabled)
#pragma config CP = OFF         // Code Protection bit (Program Memory code protection is disabled)
#pragma config CPD = ON         // Data Code Protection bit (Data memory code protection is enabled)

#define  BOT  GP2//configura o pino que será RX
///parametros da porta serial emulada////////////////////////////////////////////////////////////////
#define  TIME_BAUD  45//19200 bps - 20MHZ
#define  BAUD_RX    42//19200 bps - 20MHZ
#define  TX  GP0//configura o pino que será TX
#define  RX  GP1//configura o pino que será RX
/////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////Funçoes utilizadas/////////////////////////////////////////////////////////////
void escreve_char(char dados) {
    int contagem = 0;
    TX = 0;
    while (contagem < 8) //envia dados + stop bit
    {
        __delay_us(TIME_BAUD);
        if ((dados & 0X01) == 0X01) //testa bit menos significativo (LSB)
        {
            TX = 1;
        } else {
            TX = 0;
        }
        dados = dados >> 1; //rotaciona bit
        contagem++;
    }
    __delay_us(TIME_BAUD); //tempo do ultimo bit
    TX = 1; //volta pra nivel alto (fim de transmissão de caracter)
    __delay_us(TIME_BAUD); //stop bit
}
///é interessante observar que está rotina irá trabalhar melhor se for 
///usado na rotina de interrupção de estado no pino, aqui vou deixar na rotina principal por 
// ser um exemplo simples
char ler() {
    int contagem = 0;
    char RX_dados = 0X00;
    if (RX == 0)//tem dados pra ler
    {
        RX_dados = 0X00;
        while (contagem < 8) //recebe dados + stop bit
        {
            __delay_us(BAUD_RX); //compensa algum delay na recepção 
            if (RX == 1) {
                RX_dados = (RX_dados | 0X80); //seta bit mais significativo (MSB)
            }
            RX_dados = RX_dados >> 1;
            contagem++;
        }
        contagem = 0;
        while ((RX == 0) && (contagem < 4)) {
            contagem++; //time out para não travar,tempo de 4 bits
            __delay_us(TIME_BAUD);
        }
    }
    return (RX_dados);
}

void escreve_frase(char *frase) {
    int tam_frase = strlen(frase);
    int contagem = 0;
    while (contagem < tam_frase)           
    {
        escreve_char(frase[contagem]);
        contagem++;
    }

}
//////////////////////////////////////////////////////Rotina principal///////////////////////////////////////////////////////////////

void main(void) {
    TRISIO = 0X06; //configura gp1 e gp2 como entrada
    CMCON = 7; //desabilita comparadores
    ANSEL = 0; //habilita as portas de I/O
    char teste;
    TX = 1; //prepara pino de TX
    escreve_frase("testando comunicação serial emulada!");
    escreve_frase("\n\r digite algo?");
    for (;;) {

        if (BOT == 1) {//quando o botão é pressionado escreve 1 caracter
            escreve_char('A');
            __delay_ms(100);
        }
        teste = ler(); //verifica se tem dados pra ser lido 
        if (teste != 0X00)//tem dados se for diferente de 0X00(este caracter pode ser alterado)
        {
            escreve_frase("\n\r");
            escreve_frase("Caracter digitado = ");
            escreve_char(teste); //escreve o caracter digitado

        }

    }

}

//NOTAS DE PARAMETRIZAÇÃO:

//***********************************************************************************************************
//oscilador interno do pic 12F675(testado fisicamente )//////////////////////////////////////////////////////
// configure assim o fuse do oscilador interno => #pragma config FOSC = INTRCIO
// configure assim o fuse do cristal de 4MHZ => #pragma config FOSC = XT
//
//#define  TIME_BAUD  14//19200 bps - 4MHZ - usando cristal 
//#define  TIME_BAUD  16//19200 bps - 4MHZ - usando oscilador interno
//#define  BAUD_RX    12//19200 bps - 4MHZ - osc. interno - recepção não fica confiavel 

//#define  TIME_BAUD  68//9600 bps - 4MHZ - usando oscilador interno
//#define  BAUD_RX    58//9600 bps - 4MHZ - cristal 4MHZ - recepção funcionou bem(houve pouquissimo erros) - no proteus esse valor não funciona
//#define  BAUD_RX    60//9600 bps - 4MHZ - recepção funcionou bem(houve pouquissimo erros)

//#define  TIME_BAUD  175//4800 bps - 4MHZ 
//#define  BAUD_RX    165//4800 bps - 4MHZ - recepção funcionou muito bem(100% confiavel fisicamente, mas no proteus apresenta falhas)


//*************************************************************************************************
// oscilador com cristal de 20MHZ (testado fisicamente funcionou ok)
// configure assim o fuse do cristal => #pragma config FOSC = HS
//#define  TIME_BAUD  45//19200 bps - 20MHZ
//#define  BAUD_RX    42//19200 bps - 20MHZ 

//#define  TIME_BAUD  97//9600 bps - 20MHZ
//#define  BAUD_RX    95//9600 bps - 20MHZ 

//#define  TIME_BAUD  202//4800 bps - 20MHZ
//#define  BAUD_RX    198//4800 bps - 20MHZ 

A simulação:


     

domingo, 9 de agosto de 2015

Bluetooth com PIC 16F648A

Olá!
       Hoje vou mostrar como comunicar com um celular Android via Bluetooth, para isto vamos usar um modulo especifico para isso e que já contém toda a pilha e o protocolo bluetooth junto(o que facilita o trabalho).
       O modulo bluetooth funciona com uma interface RS232 com o microcontrolador, funciona como se fosse uma porta serial sem fio(pois no protocolo Bluetooth existe o suporte a esse tipo de dispositivo, pois poderíamos conectar como um dispositivo de áudio, vídeo, telefone ou quaisquer outra forma que é suportada pelo protocolo),para configurar velocidade da porta serial e outros detalhes do modulo é recomendável ler o datasheet, para conseguir entrar no modo de configuração(modo AT) basta ligar-ló com o botão da placa pressionado e o pino "EN" alimentado com +5V,após entrar no modo AT(o led passa a piscar a cada 1 segundo) é possível mudar varias configurações dele.Por padrão  a velocidade de comunicação é de 38400, 1 stopbit,sem paridade.
       Neste exemplo vou demonstrar como ligar e desligar um led além de testar um botão, tudo que é enviado ao microcontrolador é retornado pela porta serial, para isto fiz um programa que roda no android para testar essa funcionalidade do modulo bluetooth, este programa foi feito no "App Inventor 2"(clique aqui para pegar um print da tela de componentes,da tela de algoritmo e o aplicativo) que é um software que roda on line desenvolvido pela faculdade de Massachusetts, pretendo colocar mais uma aba no site para mostrar como está ficando o algoritmo, para instalar basta baixar o arquivo com extensão "apk"(bluetooth.apk), pode ser que dependendo do celular o aplicativo fique com os botões e textos diferentes devido a compatibilidade.
       Na figura a seguir tem um esquemático de como ficou a ligação do modulo ao microcontrolador 16F648A:

O código fonte do microcontrolador 16F648A:

/*
 *                                              Usando a interface bluetooth no MPlab XC8
 *
 * Compilador : MPlabXC8
 * Microcontrolador: 16F648A
 * Autor: aguivone
 * Versão: 1
 * Data :  05 de agosto de 2015
 */
#include <stdio.h>
#include <string.h> //para usar funçoes de string deve se adicionar este header
#include <stdlib.h>
#define _XTAL_FREQ 16000000    // cristal de 20 Mhz
#include <xc.h>
/////////////////////////////////////////////////////////configuraçôes//////////////////////////////////////////////////

#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator: High-speed crystal/resonator on RA6/OSC2/CLKOUT and 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 = 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 (Data memory code protection off)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

////////////////////////////////////////////////////Defines////////////////////////////////////////////////////////////////
#define LIGA_MODULO               PORTBbits.RB4
#define HAB_CFG_MOD               PORTBbits.RB5  //esse pino não será usado neste exemplo 
#define LED                       PORTBbits.RB6  
#define BOTAO                     PORTBbits.RB7  
////////////////////////////////////////////////variaveis globais///////////////////////////////////////////////////////////
char caracter,dados;
bit flag_interrupcao = 0;

/////////////////////////////////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_RS232(char valor)
{
    TXIF = 0;//limpa flag que sinaliza envio completo.
    TXREG = valor;
    while(TXIF ==0);//espera enviar caracter
}
void imprime_RS232(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_RS232(frase[indice]);
           indice++;
       }
}

///////////////////////////////////////////////////interrupção//////////////////////////////////////////////////////////////
void interrupt interrupcoes(void)//vetor de interrupção
 {
    if(RCIF)
    {//se interrupção de recepção da serial
     caracter = RCREG;
     int erro = 0;
     if(caracter == 'A')
     {//liga led
         LED = 1;
         escreve_RS232(caracter);//indica que ligou
     }
     if(caracter == 'B')
     {
         escreve_RS232(caracter);//indica que desligou
         LED = 0;
     }
     if((caracter != 'A')&&(caracter != 'B'))
     {
        escreve_RS232(caracter);//retorna o que leu
     }     
     flag_interrupcao = 1;
     RCIF = 0;//  limpa flag de interrupção de recepção
    }
 }
void espera_resposta()
{
        int tempo=0;
        while((caracter == 'Z')&&(tempo < 10))
        {//espera resposta ou 1s pra saber quando enviar o proximo dado
          __delay_ms(100);//tempo para entra no modo AT
          tempo++;
        }
        __delay_ms(100);//tempo para garantir todo envio;
}

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

void main(void)
{
    TRISB = 0X82;//configura portB,  B1(pino RX) e B7 como entrada
    PORTB = 0;  // limpar as portas que estão configuradas como saidas  
    inicializa_RS232(38400,1);//Quando vai comunicar pelo modo "AT" precisa de estar neste valor de comunicação
    PEIE = 1;//habilita interrupção de perifericos do pic
    GIE = 1; //GIE: Global Interrupt Enable bit
    LIGA_MODULO = 0; //esse pino não será usado neste exemplo   
    for(;;)
    {
        if(BOTAO == 1)
        {
           imprime_RS232("Botão pressionado!");
           __delay_ms(500);//debouce
        }
    }//loop infinito
}

A Demonstração:




colocar o video aqui 

links para baixar os arquivos,clique aqui!