segunda-feira, 29 de fevereiro de 2016

PIC 16F648A comunicando via WIFI com ESP8266.

            Olá, a melhor forma de se comunicar hoje com qualquer PC ou smartphone é via wifi ou bluetooth,como já foi abordado aqui no blog o uso do bluetooth vamos hoje "brincar" com a wifi.Antes porém é preciso saber de alguns detalhes, que vou comentar logo a seguir.
           Para comunicar pelo padrão TCP/IP é preciso abrir um "socket'(canal de comunicação) , uma vez aberto o socket é possível comunicar via pagina http ou enviando os dados diretamente a um dispositivo, por este motivo neste exemplo demonstro como fazer as 2 interfaces.Para a comunicação via socket tudo que for enviado ao microcontrolador é devolvido(funcionando exatamente igual a um terminal serial rs-232).Para a comunicação http criei uma pequena página(pois este microcontrolador tem pouca memoria) que permite ligar ou desligar um led, essa pagina é acessada via navegador do google chrome(não funcionou muito bem no firefox e internet(pode ter sido alguma configuração do navegador)),para isso basta digitar o IP+Porta se for usada a porta 80(padrão) não será necessário digitar no buscador a porta.
           Uma configuração de rede que deve ser respeitado é quanto ao DHCP(que pegar um ip automaticamente), no modulo deixei ele desligado para conseguir trabalhar com IP fixo, mas no PC pode até ficar habilitado embora eu recomendo que para a conexão entre pc e placa já deixe o DHCP desligado e coloque um IP diferente do modulo wifi, por exemplo aqui estou usando como gateway do modulo e IP o valor = 192.168.5.1 na porta 5000, então meu pc deve estar no ip 192.168.5.10 por exemplo e configure como gateway o IP 192.168.5.1 deixando a mascara com o valor 255.255.255.0.Se deixar o DHCP de ambos ligado toda vez que você se conectar será gerado um novo ip que talvez vc nem saiba qual será.
           O modulo ESP8266 tem a possibilidade de trabalhar como "Acess point"(AP) ou "station"(estação de trabalho) e ainda como ambos, para este exemplo deixei ele como AP.Um ponto um pouco chato de se configurar este modulo é que as respostas dos comandos devem ser sempre checadas e não se tem a possibilidade de configurar ele todo de uma vez só, ou seja deve ser configurado parâmetro por parâmetro, para isso criei uma maquina de estados (Inicializa_ESP8266), que controla a inicialização do modulo.Dependendo de como for a configuração desejada alguns comandos retornam erros o que complica mais um pouco a situação nossa como programadores de firmware(como eu sofri com esse modulo pois não encontrei material sobre ele para pic somente para arduino).
             Este modulo apesar de ser simples para configurar exige um certo conhecimento de comunicação TCP e arquitetura de redes sem fio, pois isso é fundamental para que se alcance o resultado desejado,pois parâmetros específicos como DHCP,canal de frequência da wifi, protocolo TCP/IP,etc... , com o exemplo aqui é possível ter um caminho de exemplo para que vocês possam prosseguir com seu projetos.Link pra baixar o programa para testar o modulo na ethernet(terminal): https://drive.google.com/file/d/0Bx9JeN0dmzXxaXh5MHI4aF9fT1U/view?usp=sharing ,com este programa você pode acessar dispositivos por meio de protocolo TCP/IP, roda no windows.
             Vamos conhecer a aparência e pinagem do modulo ESP8266 na figura abaixo, juntamente com a pinagem do microcontrolador.


            Fisicamente um  limitante é o hardware que trabalha com tensões diferentes(3,3V e 5V) dai a solução que adotei foi a do esquemático a seguir,o ponto critico é que em altas velocidades da porta serial o microcontrolador deve trabalhar em 5V para não prejudicar a comunicação.A solução consiste de divisor resistivo para casar os valores de tensão.

             Para alimentar o circuito usei os tradicionais LM7805, sendo um ligado ao microcontrolador e outro ligado a um segundo regulador LD1086V33(3,3V) e só então ligado ao modulo wifi.Coloquei 3 leds para indicações de status, conexão(abertura de socket) e falha(usado também para ser acionado pela web).Quando o circuito é alimentado o microcontrolador irá tentar configurar o modulo caso não dê certo pisca os leds verde e amarelo,caso contrário apenas o led verde pisca.
             No firmware o único arquivo adicional é o da comunicação serial "serial_rs232.c", não uso o famoso "print" do código C por consumir mais memoria do que esta funções(pois as que implementei são mais enxuta, o que as vezes gera um pouquinho de trabalho mas o código se torna mais leve).
           
O código fonte:


/*
*
 *                               Comunicando via WIFI com modulo ESP8566
 *
 * Compilador : MPlabXC8
 * Microcontrolador: 16F648A
 * Autor: aguivone
 * Versão: 1
 * Data :  19 de janeiro de 2016
 * Atualizações :  19/02/2016 - melhorando a inicialização
 * Atualizações :  23/02/2016 - melhorando a inicialização
 * Atualizações :  29/02/2016 - finalizado.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h> //para usar funçoes de string deve se adicionar este header
#define _XTAL_FREQ 20000000    // oscilador interno de 4 Mhz
#include <xc.h>
#include "serial_rs232.c"
/////////////////////////////////////////////////////////configuraçôes//////////////////////////////////////////////////
#define LED_CONEX RB3
#define LED_WEB RB5
#define LED_STATUS RB0
#define TAM_MAX 40 //tamanho maximo do buffer da serial
#define NUM_MAX_TENTATIVA 3 //numero maximo de tentativa em cada comando
#define TEMPO_RX 100 //tempo max entre caracteres recebidos(timeout)
#define FIM_CONF 'x'
#define TIMEOUT_CONF 40//40S//tempo maximo sem resposta de envio


#pragma config FOSC = HS 
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = ON      // Power-up Timer Enable bit (PWRT ligado) para que o microcontrolador inicialize corretamente
#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 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 de tempo
int  giTempo_us=0;
long glTempo_ms=0;
long glTempo_led=0;
long glTempo_rx=0;
  //variaveis da serial
char gcCaracter=0;
unsigned char gucStatus_serial='N';//N = sem dados,R= recebendo dados , F=fim de recepção
unsigned char gucEstado_de_envio='0';//0 = sem dados,V=dados validados , N=dados não validado
unsigned long gulTam_recebido=0;
unsigned char gucBuffer_serial[TAM_MAX];//só pra testar
  //variaveis da maquina de estado
unsigned char gucStado_do_modulo='A';//inicia em A
unsigned long  gulTempo_de_espera=0;
unsigned long  gulTimeout_de_envio=0;
unsigned int  guiTentativa_envio=0;



//*******************************************************************************/
//===================================================================================
//Função:    _limpa_buffer
//Parâmetro: não tem.
//Retorno:   não tem.
//Descrição: usada para limpar o buffer de recpção.
//===================================================================================
void limpa_buffer()
{
    long tamanho = 0;
    while(tamanho<TAM_MAX)
    {
        gucBuffer_serial[tamanho]=0X00; 
        tamanho++;
    }    
}
//*******************************************************************************/
//===================================================================================
//Função:    _trata_int_conf
//Parâmetro: não tem.
//Retorno:   não tem.
//Descrição: usada para pegar as respostas das configurações.
//===================================================================================

void trata_int_conf()
{
            if(gucStatus_serial == 'R')
            {//primeiro dado
              gucBuffer_serial[gulTam_recebido] = gcCaracter; 
              gulTam_recebido++;
              if(gulTam_recebido>(TAM_MAX-1))
              { 
                  gucStatus_serial = 'N';//sobrescreve o vetor
              }
            }
            if(gucStatus_serial == 'N')
            {//primeiro dado
              gucBuffer_serial[0] = gcCaracter; 
              gulTam_recebido =1;
              gucStatus_serial = 'R';
            } 
}
//*******************************************************************************/
//===================================================================================
//Função:    trata_int_dados
//Parâmetro: não tem.
//Retorno:   não tem.
//Descrição: usada para pegar os dados(após a configuração).
//===================================================================================
void trata_int_dados()
{
     if(gcCaracter == '\n')//caracter de retorno de carro "LR" (depois vejo, talvez verificar se tem o \n))
            {//final da frase
             if((gucBuffer_serial[gulTam_recebido-2]=='D')||(gucBuffer_serial[gulTam_recebido-2]=='T'))
                {//verifica se está abrindo ou fechando o socket(comunicação)                 
                 if((gucBuffer_serial[gulTam_recebido-5]=='N')&&(gucBuffer_serial[gulTam_recebido-4]=='E')&&(gucBuffer_serial[gulTam_recebido-3]=='C'))
                    {//abrindo conexão recebe "CONNECT"
                       LED_CONEX = 1;
                       gulTam_recebido=0;//zera variavel de tamanho
                       gucStatus_serial = 'N';//prepara para pegar novos dados
                    }
                  if((gucBuffer_serial[gulTam_recebido-5]=='O')&&(gucBuffer_serial[gulTam_recebido-4]=='S')&&(gucBuffer_serial[gulTam_recebido-3]=='E'))
                    {//fechando conexão recebe "CLOSED"
                       LED_CONEX = 0;
                       gulTam_recebido=0;//zera variavel de tamanho
                       gucStatus_serial = 'N';//prepara para pegar novos dados
                    }                   
                }
             
            }
            if(gucStatus_serial == 'R')
            {//está recebendo dados
              gucBuffer_serial[gulTam_recebido] = gcCaracter; 
              gulTam_recebido++;
              if(gulTam_recebido>TAM_MAX-2)
              {//gera aviso de estouro pois o buffer de recpção não consegue armazenar o pacote todo.
                  gucStatus_serial = 'E';
              }
            }
            if(gucStatus_serial == 'N')
            {//primeiro caracter capturado 
              gucBuffer_serial[0] = gcCaracter; 
              gulTam_recebido =1;
              gucStatus_serial = 'R';
            } 
}
//*******************************************************************************/
//===================================================================================
//Função:    _interrupcoes
//Parâmetro: não tem.
//Retorno:   não tem.
//Descrição: usada para tratar as interrupções geradas pelo microcontrolador.
//===================================================================================
void interrupt interrupcoes(void)//vetor de interrupção
 {
   if(T0IF)
    {//rotinas de tempo(15us))
        giTempo_us++;
        TMR0 = 254;//reinicia timer com o valor calculado
        T0IF = 0;
        if(giTempo_us>66)//1ms
        {//executa a cada 1ms
            giTempo_us = 0;
            glTempo_ms++;
            if(glTempo_rx > 0)
            {
              glTempo_rx--;
            }
            if(gulTempo_de_espera > 0)
            {
              gulTempo_de_espera--;
            }
            if(glTempo_led>0)
            {
              glTempo_led--;  
            }
            if(glTempo_ms>1000)
            {//executa a cada 1s
               glTempo_ms=0;
               gulTimeout_de_envio++;
            }
        }
        //62.5us + os delay da rotinas  = 75us
    }
   if(RCIF)//verifica interrupção da serial
    {//interrupção da serial
        gcCaracter = RCREG;
        if(gucStado_do_modulo == FIM_CONF+1)
            {//Modulo já foi configurado
               trata_int_dados();
            }
        else
        {
           trata_int_conf();
        }
      glTempo_rx = TEMPO_RX;  
      RCIF =0;//limpa flag de interrupçao
    }
   
}
//===================================================================================
//Função:    _inicializa_CPU
//Parâmetro: não tem.
//Retorno:   não tem.
//Descrição: configura o microcontrolador.
//===================================================================================
void inicializa_CPU()
{
    TRISA = 0XFF;//todas são entradas
    TRISB = 0X02;//configura portB  como saida exceto rb2(pino RX))
    PORTB = 0;  // limpar as portas que estão configuradas como saidas
    CMCON = 0X07;
    ///////////inicializa o timer0 
    OPTION_REG = 0X03;//timer0 com prescaler dividido por 16
    T0IE = 1;//habilita interrupção do timer0
    TMR0 = 254;//zera registrador de contagem
    /////////////////////////////////////////////
    inicializa_RS232_HS(115200,20);//modo de alta velocidade de clock
    //  inicializa_RS232(9600,1,20);//na simulação(pra não sobrecarregar o simulador se necessario))
    PEIE = 1;//habilita interrupção de perifericos do pic
    GIE = 1; //GIE: Global Interrupt Enable bit
    LED_CONEX=0;
    LED_WEB=0;
}
//===================================================================================
//Função:    _Verifica_resposta
//Parâmetro: char prox,char anterior,unsigned char procurar[].
//Retorno:   não tem.
//Descrição: verifica se o comando foi aceito.
//===================================================================================
void Verifica_resposta(char prox,char anterior,unsigned char procurar[])
{    
    if(glTempo_rx == 0)//espera receber o comando
            {
                if(BuscaPalavra(gucBuffer_serial,procurar) != 0XFFFF)
                {  
                  gucStado_do_modulo = prox;//vai para proximo passo
                  guiTentativa_envio = 0;
                }
               else
                {
                  if(guiTentativa_envio >= NUM_MAX_TENTATIVA)
                   {///registra falha na inicialização
                    gulTimeout_de_envio = 250;//Deu erro na configuração
                   } 
                  else
                  {
                   gucStado_do_modulo = anterior;//passo anterior
                  }
                }
            }
}
//*******************************************************************************/
//===================================================================================
//Função:    Envia_comando
//Parâmetro: unsigned char dados[],char mod,int multi_tempo.
//Retorno:   não tem.
//Descrição: usada para enviar comandos.
//===================================================================================
void Envia_comando(unsigned char dados[],char mod)
   {
        PEIE = 0;//desabilita interrupção de perifericos do pic        
        gulTimeout_de_envio=0;
        gucStado_do_modulo = mod;
        limpa_buffer();
        gucStatus_serial = 'N';//põe pra ir pro inicio do vetor
        gulTam_recebido =0;//zera o tamanho
        imprime_RS232(dados);        
        glTempo_rx = TEMPO_RX*3;//seta tempo de recepção de dados um pouco maior para primeira amostra  
        guiTentativa_envio++; 
        PEIE = 1;//habilita interrupção de perifericos do pic   
    }
//===================================================================================
//Função:    _Inicializa_ESP8266
//Parâmetros: não tem.
//Retorno:   não tem.
//Descrição: é uma maquina de estado que configura o modulo ESP8266.
//===================================================================================
//maquina de estado de inicialização do  modulo
void Inicializa_ESP8266(void)
{
    switch(gucStado_do_modulo)
   {
       case 'A'://teste inicial pra saber se o modulo está ativo
       { 
           Envia_comando("AT\r\n",'B');//comando , proximo passo
       }break;
       case 'B'://verifica resposta
       { 
            if(glTempo_rx == 0)//espera receber o comando
            {
                if(BuscaPalavra(gucBuffer_serial,"OK") != 0XFFFF)
                {  
                  gucStado_do_modulo = 'C';//vai para proximo passo
                  guiTentativa_envio = 0;
                }
               else
                {                    
                  gucStado_do_modulo = 'A';//passo anterior
                }
            }
       }break;       
       case 'C':
       { //configura o modo que o modulo vai funcionar, aqui deixei para funcionar como AP
        //   Envia_comando("AT+CWMODE=3\r\n",'D');//comando , proximo passo  - AP ou servidor
          Envia_comando("AT+CWMODE=2\r\n",'D');//comando , proximo passo - AP
        //   Envia_comando("AT+CWMODE=1\r\n",'D');//comando , proximo passo - servidor
       }break;
       case 'D'://verifica resposta
       { 
           Verifica_resposta('E','C',"OK");//colopra onde vai e qual é a palavra procurado
       }break;
       case 'E'://manda resetar o modulo 
       {    
           Envia_comando("AT+RST\r\n",'F');//comando , proximo passo  
       }break;
       case 'F'://verifica resposta
       {
           gulTimeout_de_envio=0;//zera variavel de contagem
           while(gulTimeout_de_envio < 15);//espera pelo menos 10segundos para resetar com segurança
           gulTimeout_de_envio=0;//zera variavel novamente
           Verifica_resposta('G','E',"ready");
       }break;
       case 'G':
       { //configura o modo que o modulo vai funcionar, aqui deixei para funcionar como AP
           Envia_comando("ATE0\r\n",'H');//pede pra não repetir o comando
       }break;
       case 'H'://verifica resposta
       { 
           Verifica_resposta('I','G',"OK");//colopra onde vai e qual é a palavra procurado
       }break;       
       case 'I':
       { 
           Envia_comando("AT+CIPMUX=1\r\n",'J');//comando , proximo passo//cofigura para conexões multiplas
         //  Envia_comando("AT+CIPMUX=0\r\n",'J');//comando , proximo passo//cofigura para conexao simples
       }break;
       case 'J'://verifica resposta
       { 
           Verifica_resposta('L','I',"OK");
       }break;       
       case 'L'://configura DHCP e modo de operação
       { //quando DHCP é ligado ele busca ip automaticamente
       //   Envia_comando("AT+CWDHCP=2,1\r\n",'M');//só aceitou o modo AP+STATION/dhcp ligado.(pega ip automatico))
       //  Envia_comando("AT+CWDHCP=2,0\r\n",'M');//AP+STATION/dhcp desligado.(para usar ip fixo))
             Envia_comando("AT+CWDHCP=0,0\r\n",'M');//modo AP(o sinal fica mais estavel)/DHCP desligado(para usar ip fixo) 
       }break;       
       case 'M'://verifica resposta
       { 
           Verifica_resposta('N','L',"OK");
       }break;       
       case 'N'://Nome do ponto de wifi,senha,canal(deixei o canal de freq.7)),tipo de segurança(0 = aberta))
       { //
          Envia_comando("AT+CWSAP=\"MICROCONTROLARES-C\",\"senha\",7,0\r\n",'O');
       }break;  
       case 'O'://verifica resposta
       { 
           Verifica_resposta('P','N',"OK");
       }break;
       case 'P':
       { //configura para criar servidor na porta 5000
           Envia_comando("AT+CIPSERVER=1,5000\r\n ",'Q'); 
        }break;
       case 'Q'://verifica resposta
       {             
           Verifica_resposta('R','P',"OK");
       }break;              
       case 'R':
       { //configura para criar servidor na porta 5000
           Envia_comando("AT+CIPAP=\"192.168.5.1\" \r\n",'S');//usa este IP para gateway e para o servidor 
        }break;
       case 'S'://verifica resposta
       {             
           Verifica_resposta('T','R',"OK");
       }break;       
       case 'T':
       { //seta time out
           Envia_comando("AT+CIPSTO=100\r\n",'U');//comando , proximo passo  
        }break;
       case 'U'://verifica resposta
       {             
                gucStado_do_modulo = FIM_CONF;//não precisa de confimação
       }break;
     
           
       
    } 
}
//===================================================================================
//Função:    _envia_pacote
//Parâmetros: long *tamanho,char *pacote[].
//Retorno:   não tem.
//Descrição: usado para o modo socket(sem usar uma pagina web, envia um pacote TCP).
//===================================================================================
void envia_pacote(long *tamanho,char *pacote[])
{
   char valor[]="0000";
   int pos = LongToChar(tamanho,valor);
   imprime_RS232("AT+CIPSEND=0,");//para conexão multipla informa o canal 
  // imprime_RS232("AT+CIPSEND=");//na conexoes simples não precisa informar o canal
        while(pos<4)
        {
            escreve_RS232(valor[pos]);  
            pos++;  
        }
   escreve_RS232(0X0D);
   escreve_RS232(0X0A);
    glTempo_rx = TEMPO_RX/2;
    while(glTempo_rx > 0);//espera receber a resposta do comando 
    pos=0;// 
        while(pacote[pos] != 0X00 )//envia os dados recebidos novamente
            { //imprime na porta TCP os dados recebidos(o tamanho max aqui será uns 30 caracteres))
               escreve_RS232(pacote[pos]);
               pos++;
            } 
    escreve_RS232(0X0D);
    escreve_RS232(0X0A);
}
//===================================================================================
//Função:    _envia_pagina
//Parâmetros: não tem.
//Retorno:   não tem.
//Descrição: Monta e envia a pagina HTML.
//===================================================================================
void envia_pagina()
{//lembre que \" = " logo conta como 1 caracter e não 2
   limpa_buffer();//solicita pra limpar o buffer serial 
   ///como vou conectar apenas 1 então o canal é 0 
   imprime_RS232("AT+CIPSEND=0,285");//para conexão multipla informa o canal (prepara envio e tamanho))
  // imprime_RS232("AT+CIPSEND=");//na conexoes simples não precisa informar o canal
    escreve_RS232(0X0D);
    escreve_RS232(0X0A);
    glTempo_rx = TEMPO_RX;
    while(glTempo_rx > 0);//espera receber a resposta do comando 
    imprime_RS232("<html><head><title>Teste</title></head><h1>PROGRAMA DE TESTE</h1>");
    imprime_RS232("<body><p><form method=\"GET\"></p>");
    imprime_RS232("<p><input type=\"radio\" name=\"Bot\" value=\"1\" CHECKED> Ligar led.</p>");
    imprime_RS232("<p><input type=\"radio\" name=\"Bot\" value=\"0\" > Desligar led.</p>");
    imprime_RS232("<p><input type=\"submit\"  value=\"Enviar\"></p></body></html>");
    escreve_RS232(0X0D);
    escreve_RS232(0X0A);
}
//////////////////////////////////////////////////////Rotina principal///////////////////////////////////////////////////////////////
//===================================================================================
//Função:    _main
//Parâmetros: não tem.
//Retorno:   não tem.
//Descrição: função principal.
//===================================================================================
///////////////http://microcontroladores-c.blogspot.com.br//////////////////////////////////////////
void main(void) 
{
    inicializa_CPU();
     for(;;)
    {        
        if(glTempo_led == 0)
        {//led de status            
            LED_STATUS = ~LED_STATUS;
            if(gucStado_do_modulo == FIM_CONF+1)
            {//Modulo pronto pra funcionar
              glTempo_led = 100;//tempo mais rapido  
            }
            else
            {
               glTempo_led = 500;//tempo normal 
               if(gulTimeout_de_envio>TIMEOUT_CONF)//se for maior que 30S o tempo de espera então indica falha
                {
                   LED_WEB = ~LED_WEB; //modulo não responde - falha
                }
            }
        }
        if(gucStado_do_modulo == FIM_CONF+1)
            {//Modulo pronto pra funcionar
                if((glTempo_rx == 0)&&(gulTam_recebido<8))//tamanho minimo em cada recpção de dados
                 {//descarta
                     gulTam_recebido=0;//zera variavel de tamanho
                       gucStatus_serial = 'N';//prepara para pegar novos dados
                 }
                if(gucStatus_serial == 'E')//espera terminar tempo de time out
                {//houve estouro do buffer logo a comunicação é via pagina HTTP 
                    if(BuscaPalavra(gucBuffer_serial,"GET") != 0XFFFF)//conexão por modo get
                        {//pagina web solicitada ou resposta recebida
                             if(BuscaPalavra(gucBuffer_serial,"GET / HTTP") != 0XFFFF)//solicitou pagina web
                                 envia_pagina();
                            if(BuscaPalavra(gucBuffer_serial,"GET /?Bot") != 0XFFFF)//resposta da pagina web 
                               {
                                  if(BuscaPalavra(gucBuffer_serial,"Bot=0") != 0XFFFF)//vê se é pra desligar led
                                    LED_WEB=0;
                                  if(BuscaPalavra(gucBuffer_serial,"Bot=1") != 0XFFFF)//vê se é pra desligar led
                                    LED_WEB=1;
                               }                                                                                      
                        }                    
                       gulTam_recebido=0;//zera variavel de tamanho
                       gucStatus_serial = 'N';//prepara para pegar novos dados 
                   
                }
                else
                {//é comunicação via socket
                    if((glTempo_rx == 0)&&(gulTam_recebido>8))//tamanho minimo
                    {//verifica se tem dados pra ler pois o tempo limite pra finalizar recpção já passou
                          if(BuscaPalavra(gucBuffer_serial,"+IPD") != 0XFFFF)
                           {//chegou dados validado
                               long auxiliar = BuscaPalavra(gucBuffer_serial,":");
                               long tamanho ;//tamanho dos dados
                               long pos_buffer=0;//tamanho dos dados
                               unsigned char gucBuffer_tratamento[TAM_MAX];
                               if(auxiliar != 0XFFFF)
                               {//pega o tamanho dos dados
                                   if(CharToInt(gucBuffer_serial[auxiliar-2])<10)
                                        {// o tamanho dos dados é maior que 10
                                             tamanho = (10*CharToInt(gucBuffer_serial[auxiliar-2]))+CharToInt(gucBuffer_serial[auxiliar-1]);
                                            if(CharToInt(gucBuffer_serial[auxiliar-3])<10)
                                             {
                                              tamanho = (100*CharToInt(gucBuffer_serial[auxiliar-3]))+tamanho; 
                                             }                                             
                                        }
                                        else
                                        {//o tamanho é menor que 10
                                          tamanho = CharToInt(gucBuffer_serial[auxiliar-1]);
                                        } 
                                       auxiliar++;//pra não pegar o ":"
                                       while( pos_buffer < tamanho )
                                       { //copia os dados para o vetor de tratamento
                                           gucBuffer_tratamento[pos_buffer] = gucBuffer_serial[auxiliar+pos_buffer];
                                           pos_buffer++;
                                       } 
                                       gucBuffer_tratamento[pos_buffer]=0X00;//caracter null 
                               }
                               //limpa_buffer();//solicita pra limpar o buffer serial   
                               envia_pacote(tamanho,gucBuffer_tratamento);
                           }                           
                          gulTam_recebido=0;//zera variavel de tamanho
                          gucStatus_serial = 'N';//prepara para pegar novos dados
                    }                 
                }        
            }
        else
        {//durante inicialização
            if(gucStado_do_modulo < FIM_CONF)
            {//inicializa o modulo
               if(gulTimeout_de_envio<TIMEOUT_CONF)//se for menor que 30S o tempo de espera terminar configuração           
                {
                    Inicializa_ESP8266();
                }
            }
            if(gucStado_do_modulo == FIM_CONF)
            {
             ///neste momento a WIFI já esta configurada e pronta pra receber dados   
              gucStado_do_modulo = FIM_CONF+1;
            }    
        }        
    }//loop infinito

}

O código do arquivo serial:

/*
 *                          FUNÇOES USADAS NA SERIAL 
 *
 * Compilador :      MPlabXC8
 * Microcontrolador: 16F877A
 * Autor:            Aguivone
 * Versão:           1.0.1
 * Descrição:   ->Assim como o printf tem a função de comunicar via rs232, mas com o diferencial de ter um menor processamento e gasto de memoria
 *              ->Mas exige um pouco mais de trabalho para imprimir caracteres numericos 
 * Data de criação:  08/09/2014.
 * Data de atualização : 29/01/2016 - melhorando o baud rate para altas velocidades
 */

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

//===================================================================================
//Função:    _inicializa_RS232
//Parâmetros: unsigned long ulVelocidade
//          : unsigned int uiModo
//Retorno:   não tem retorno.
//Descrição: usada para iniciar a porta serial com velocidades até 38400.
//===================================================================================
void inicializa_RS232(float fVelocidade,unsigned int uiModo,float fClock)
{//// 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.
    float valor;
        if(uiModo == 1)
        {//modo = 1 ,modo alta velocidade
         TXSTA = 0X24;//modo assincrono,trasmissao 8 bits.
        // valor =(((_XTAL_FREQ/ulVelocidade)-16)/16);//calculo do valor do gerador de baud rate
         valor =(((fClock/fVelocidade)-16)/16);//calculo do valor do gerador de baud rate
        }
        else
        {//modo = 0 ,modo baixa velocidade
         TXSTA = 0X20;//modo assincrono,trasmissao 8 bits.
         //valor =(((_XTAL_FREQ/ulVelocidade)-64)/64);//calculo do valor do gerador de baud rate
         valor =(((fClock/fVelocidade)-64)/64);//calculo do valor do gerador de baud rate
        }
    SPBRG =(int)valor;
   // PIE1 = 0X20;
    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)
}
//===================================================================================
//Função:    _inicializa_RS232_HS
//Parâmetros: unsigned long ulVelocidade
//          : unsigned int uiModo
//Retorno:   não tem retorno.
//Descrição: usada para iniciar a porta serial com velocidades acima 38400.
//===================================================================================
void inicializa_RS232_HS(float fVelocidade,int iClock)//o clock em mhz
{//// 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.
    TXSTA = 0X24;//modo assincrono,trasmissao 8 bits.modo = 1 ,modo alta velocidade
    switch(iClock)//cadastrei somente os cristais mais usados
   {
       case 4:
       { 
           if(fVelocidade > 57600)
           {//é 115200
             SPBRG =1;  
           }
           else
           {//é 57600
            SPBRG =3;   
           }
       }break;
       case 10:
       { 
           if(fVelocidade > 57600)
           {//é 115200
             SPBRG =4;  
           }
           else
           {//é 57600
            SPBRG =10;   
           }
       }break;
       case 16:
       { 
           if(fVelocidade > 57600)
           {//é 115200 ou 250000
               if(fVelocidade == 115200)
               {//é 115200
                 SPBRG =8; 
               }
               else
               {//é 250000
                 SPBRG =3;  
               }
           }
           else
           {//é 57600
            SPBRG =16;   
           }
       }break;
       case 20:
       { 
           if(fVelocidade > 57600)
           {//é 115200,250000,625000 ou 1250000(1,25Mbs))
               if(fVelocidade == 115200)
               {//é 115200
                 SPBRG =10; 
               }
               if(fVelocidade == 250000)
               {
                 SPBRG =4; 
               }
               if(fVelocidade == 625000)
               {
                 SPBRG =1; 
               }
               if(fVelocidade == 1250000)
               {
                 SPBRG =0; 
               }  
           }
           else
           {//é 57600
            SPBRG =21;   
           }
       }break;
    }
   // PIE1 = 0X20;
    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)
}
//===================================================================================
//Função:    _escreve_RS232
//Parâmetros: char cValor
//Retorno:   não tem retorno.
//Descrição: usada para escrever 1 caracter.
//===================================================================================
 void escreve_RS232(char cValor)
{
    TXIF = 0;//limpa flag que sinaliza envio completo.
    TXREG = cValor;
    while(TXIF ==0);//espera enviar caracter
}
//===================================================================================
//Função:    _imprime_RS232
//Parâmetros: const char ccFrase[]
//Retorno:   não tem retorno.
//Descrição: usada para escrever uma string.(maximo 255 caracteres)
//===================================================================================
 void imprime_RS232(const char *ccFrase[])
{
     unsigned char indice = 0;
     unsigned char tamanho = strlen(ccFrase);
      while(indice < tamanho ) ///veja que o programa pode travar se aqui não tiver as duas aspas
       {
          escreve_RS232(ccFrase[indice]);
           indice++;
       }
}
//===================================================================================
//Função:    _imprime_vetor_RS232
//Parâmetros: const char ccFrase[]
//Retorno:   não tem retorno.
//Descrição: usada para escrever uma vetor de caracteres.(maximo 255 caracteres))
//===================================================================================
 void imprime_vetor_RS232(unsigned char *ccFrase[])
{
     unsigned char indice = 0;
     unsigned char tamanho = strlen(ccFrase);
      while(indice < tamanho ) 
       {
           escreve_RS232(ccFrase[indice]);
           indice++;
       }
}
 //===================================================================================
//Função:    _BuscaPalavra
//Parâmetros: long ulQuant
//Retorno:   se = 0 não tem palavra igual, senão retorna a posição+1.
//Descrição: buscar uma palavra em um vetor de caracteres(máximo 65535 caracteres) e retorna primeira posição)
//===================================================================================
// As funçoes "strstr()" e "strspn()" não funcionaram bem por isto resolvi montar esta função
 //fica mais rapida
int BuscaPalavra(unsigned char *ccFrase[],unsigned char *palavra[])
{
     unsigned int indice = 0;
     unsigned int tamanho_palavra = strlen(palavra);
     unsigned int tamanho_vetor = strlen(ccFrase) - tamanho_palavra;//tamanho do vetor subtraido do tamanho da palavra
      while(indice <= tamanho_vetor) //tamanho do vetor subtraido do tamanho da palavra
       {
          if(palavra[0]==ccFrase[indice])
          {//se for parecido com a primeira letra da palavra então testa se é a palavra certa
              unsigned int tamanho_comparado=1;//inicia comparando próxima letra
              while(tamanho_comparado < tamanho_palavra) 
                 {
                      if(palavra[tamanho_comparado]!=ccFrase[indice+tamanho_comparado])
                       {
                         tamanho_comparado=tamanho_palavra+1;
                         indice = indice + tamanho_comparado;//já pula pra frente pois foi testado)
                       }
                    tamanho_comparado++;                      
                 }
              if(tamanho_comparado<tamanho_palavra+1)
              {
                return(indice); //retorna a posição da primeira ocorrencia da palavra 
              }
                 
          }
           indice++;
       }
     return(0XFFFF);//indica que não achou palavra igual
} 
 //===================================================================================
//Função:    _LongToChar
//Parâmetros: long ulQuant
//Retorno:   não tem retorno.
//Descrição: usada para escrever um numero(int ou long) pela serial
//===================================================================================
int LongToChar(long ulQuant,char *valor[])
{ 
    int ret=3;//indica apartir de quando pegar os dados para não imprimir 0 antes do numero
    if(ulQuant>=1000)//mas se quiser imprimir o zero basta usar o vetor inteiro
    {ret=0;}
    else
    {if(ulQuant>=100)
            {ret=1;}
            else
            {if(ulQuant>=10)
                {ret=2;}
            }
    }
            while(ulQuant>=1000)
            {
             ulQuant=ulQuant-1000;
             valor[0]++;
             }
            while(ulQuant>=100)
            {
             ulQuant=ulQuant-100;
             valor[1]++;
             }
             while(ulQuant>=10)
            {
             ulQuant=ulQuant-10;
             valor[2]++;
             }
            while(ulQuant>=1)
            {
             ulQuant=ulQuant-1;
             valor[3]++;
             }
    return(ret);
}
//===================================================================================
//Função:    _CharToInt
//Parâmetros: unsigned char convert_char
//Retorno:   retorna um numero.
//Descrição: usada para conveter um caracter em um numero
//===================================================================================
int CharToInt(unsigned char convert_char)
{
     switch(convert_char)
     {
         case '0':
         return(0);
         case '1':
         return(1);
         case '2':
         return(2);
         case '3':
         return(3);
         case '4':
         return(4);
         case '5':
         return(5);
         case '6':
         return(6);
         case '7':
         return(7);
         case '8':
         return(8);
         case '9':
         return(9);
         default:
             return(10);
     }
}

Depois de tanto trabalho o resultado pode ser visto no vídeo abaixo, a página http pode ser acessado por um smartphone também(testei com um samsung com android).

O vídeo de demonstração:



Até a próxima!






quinta-feira, 17 de dezembro de 2015

Lendo tensão(0 a 55V) e temperatura(sensor interno) com o ATmega328P

Olá,
       Vamos trabalhar com medição de tensão e corrente usando o ATmega328P, como ele possui um sensor de temperatura interno a gente já mata dois coelhos com um tiro só.
        A primeira coisa que se deve fazer é configurar o hardware pra isso, é preciso estar atento a entrada Aref(referência analógica), para não ter problemas na configuração faça assim: coloque o pino Avcc ligado ao +5V,o pino Aref ligado um capacitor de 100nf que estará com o outro teminal ligado ao terra(dessa forma ele funciona como um filtro, o que reduz e muito ruídos),nunca ligue este pino ao GND pois pode inutilizar este chip para a função de leitura de tensão. Para este exemplo vou usar um cristal de 16Mhz e um circuito com o max232 para enviar os dados a um PC.
       Para fazer a leitura de tensão de 0 a 55V usei um divisor resistivo igual o da figura abaixo:
      Ou seja a razão de saída é 11:1(se entra 55V sai 5V na saída), no código deixei uma linha comentada para o caso de alguém querer usar para medir de 0 a 5V.Na simulação do proteus não deu certo colocar o divisor resistivo então optei por usar 2 potenciômetro para conseguir um resultado aproximado.Outro detalhe é que na simulação o sensor interno não é simulador então lá ele assume valor 0 o que acaba resultado em -65°C.
        A equação para medir a temperatura foi retirada deste site  http://www.embarcados.com.br/arduino-uno-sensor-de-temperatura/ .

O código fonte:

//***********************************************************************************************************
//                               Usando o conversor analógico/digital
//
// no gravador configurar os fuses assim:
//  EXTENDED 0XFF 
//  HIGH     0XD8 - neste modo funciona com qualquer valor de cristal usei um de 16Mhz  
//  LOW      0XC7
//  LOKBITS  0XFF  - sem proteção
//
//
//  versão                        : 1.1
//  descrição                   : Voltimetro e leitor de temperatura
//  Autor                      : Aguivone
//  microcontrolador           : ATmega328P
//  compilador                   : AVR-GCC / Atmel AVR Studio 6.1
//  Data do projeto            : 07/08/2014 
//  Data de atualização        : 07/08/2015 - adicionado leitura de temperatura e melhoria para ler tensão
//*************************************************************************************************************
#include <avr/io.h>
#include <string.h>
#include <stdio.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>// AVRJazz Mega328 SPI I/O

////////////////////////funçoes auxiliares/////////////////////////////
#define alterna_pino(x,y) x ^= _BV(y) 
#define   LED             PD2
#define  TENSAO           PC2

char caracter;
 
void RS232_inicializa(unsigned int BAUD)
{

   unsigned int velocidade = F_CPU/16/BAUD-1;
   //veja que na simulação do proteus dá erros nas velocidade diferentes de 9600 mas na pratica funciona.
   UCSR0A = 0X20;
/* habilita receiver and transmitter buffers */
  UBRR0H = (unsigned char)(velocidade>>8);
  UBRR0L = (unsigned char)velocidade;
  UCSR0B = 0X98;//deve se habilitar somente a interrupção de recepção para não travar o microcontrolador
  /* 8 bits , 1 stop bit, assincrono , sem paridade,modo nornal */
  UCSR0C = (1<<USBS0)|(3<<UCSZ00);
}
void escreve_caracter(unsigned char carac)
{  
            _delay_loop_1(1);//tempo para estabilizar tensão            
            while ( !( UCSR0A & (1<<UDRE0)) ); //espera transmitir para enviar um novo
            UDR0 = carac;    
            while ( !( UCSR0A & (1<<UDRE0)) ); //espera transmitir desabilita a transmissão
            _delay_loop_1(2);//tempo para garantir o envio
}
void impri_serial(const char frase[80])
{
      unsigned int indice=0;
      unsigned int tamanho = strlen(frase);    
       while(indice<=tamanho)///veja que o programa pode travar se aqui não tiver as duas aspas
       {
            escreve_caracter(frase[indice]);
            indice++;
       }

}
//////////////////////////interrupções requerida para usar a recepção serial///////////////////////////////////
ISR(USART_RX_vect)
{
 caracter = (char)UDR0;
 escreve_caracter(caracter);
}
///long para char
void long_to_char(unsigned int quant, int inicio)
{
        char texto[6];
        texto[1] = '0';
        texto[2] = '0';
        texto[3] = ',';
        texto[4] = '0';
        texto[5] = '0';
        if(quant>9999)//valor maior que 100V
        {
        texto[1] = 'F';
        texto[2] = 'A';
        texto[3] = 'L';
        texto[4] = 'H';
        texto[5] = 'A';
        }
        else
        {
              while(quant>=1000)
                {
                 quant=quant-1000;
                 texto[1]++;
                 }
              while(quant>=100)
                {
                 quant=quant-100;
                 texto[2]++;
                 }
               while(quant>=10)
                {
                 quant=quant-10;
                 texto[4]++;
                 }
               while(quant>=1)
                {
                 quant=quant-1;
                 texto[5]++;
                 }
         }
         int cont = inicio;
         while(cont<=5)
         {
             escreve_caracter(texto[cont]);
             cont++;
         }  
}

////////////rotina de leitura de tensão /////////////////////////////////
void ler_adc() {
    ADCSRA |= _BV(ADSC);//inicia conversão analogico/digital
    while ( (ADCSRA & _BV(ADSC)) );//espera terminar leitura
    float valor = ADC;
    //valor = ((ADC*50)/102);//mede de 0 a 5V
    valor = ((ADC*50)/102)*11;//mede de 0 a 55V  - colocando um resitor de 10K e outro de 1K(entre o pino de entrada e gnd) 
    long_to_char(valor,1);
    return;
}
void ler_temperatura() {
    ADCSRA |= _BV(ADSC);//inicia conversão analogico/digital
    while ( (ADCSRA & _BV(ADSC)) );//espera terminar leitura
    float valor = ADC;
    // a formula é  temperatura = (ADC - 324,31)/1,22 , mas vou arrendondar para 324
    if(valor < 324)
    {
        //temperatura negativa
        valor = (324.31-valor)/1.22;;
        escreve_caracter('-');
    }
    else
    {    //temperatura positiva
        valor = (valor-324.31)/1.22;;
        escreve_caracter('+');
    }    
    long_to_char(valor,4);
    return;
}
void inicializa_adc(int valor)
{
    if(valor == 0)///Modo ler tensão
    {
        ADMUX    = 0X42;// vai usar o pino PC2 como entrada, justificado a direita e com capacitor externo no pino de referencia(estabiliza leitura)
        ADCSRA = 0XA7;//habilita o conversor adc clock dividido por 128(para 16MHZ - pois o range deve ser de 50-200khz) e trigger
        ADCSRB = 0X41;//habilita o comparador e o trigger usará a saida dele
        DIDR0 = 0XFB;////desabilita entrada analógica(reduz consumo), deixa apenas PC2 como analógica
    }
    if(valor == 1)///Modo ler temperatura
    {
        //deve ser alterado somente esses dois registradores pois 
        ADMUX    = 0XC8;// sensor de temperatura, justificado a direita e capacitor externo no pino de referencia
        //ADCSRA = 0XA7;//habilita o trigger e o conversor adc clock dividido por 128(para 16MHZ - pois o range deve ser de 50-200khz) 
        ADCSRB = 0X41;//habilita o comparador e o trigger usará a saida dele
        //DIDR0 = 0XFF;////desabilita entrada analógica(reduz consumo)
    }

 } 

////////////////////função principal/////////////////////////////////////
int main(void)
 {
  
  //////////////////inicializa port D /////////////////////////////////////////////////////////////////////
    DDRD = 0XFE;  //inicializa portD,4 a 7 entrada
    DDRC = 0x18;//configura port C como entradas exceto C3 e C4
    PORTD = 0x00;//desabilita pull ups
    DDRB = 0x2F;//configura port B
    PORTB = 0x02;//inicia com o pino de reset e desliga wiznet em nivel alto
    RS232_inicializa(9600);
    inicializa_adc(0);
    sei();//habilita interrupções
    impri_serial("Lendo tensão e temperatura ... \n\r");
///////////////////////////////////////////////////////
 for(;;)
    {  
            impri_serial("Tensão = ");
            ler_adc();
            inicializa_adc(1);//ler temp
            _delay_ms(50);//estabilizar tensão
            impri_serial("  Temp = ");
            ler_temperatura();
            impri_serial(". \n\r ");
            inicializa_adc(0);//volta a ler tensão 
            //não sei por que em alguns terminais seriais o valor não é impresso,
            //mas na simulação e em um aplicativo que fiz mostra normalmente     
            _delay_ms(500);
            alterna_pino(PORTD,LED);//led
   }//chave do laço infinito (for)
}//chave de main

A Simulação:
Saída no terminal serial:

link para baixar o terminal serial: https://drive.google.com/file/d/0Bx9JeN0dmzXxNFhxMVFLQ2NPWHc/view?usp=sharing

terça-feira, 15 de dezembro de 2015

Medindo tensão AC com o sensor AMF-16

Olá,
       hoje meu exemplo será diferente, apenas vou mostrar como funciona o sensor AC AMF16, é um sensor desenvolvido para quem não quer correr riscos tomando choques ou queimando seu hardware por conta de altas tensões a serem mensuradas.
       Este sensor é um produto que foi desenvolvido a nível de testes . Este sensor pode medir tensão senoidal,dente de serra,triangular e onda quadrada.
      A interface é algo simples bastando para isto uma porta serial do microcontrolador ou ligado a uma hardware especifico pra isso(tipo max 232 ou módulos bluetooth, confome já foi abordado em outras postagens aqui).Os dados recebidos pela porta serial estão no formato XXXX- onde "X" representa um numero de 0 a 9, o valor máximo lido é 1024(devido ao conversor de 10 bits).A porta serial deve ser configurada para Baud rate de 9600 ,8 bits de dados ,sem paridade e 1 stop bit.
     O sensor é na verdade uma placa de desenvolvimento com os conectores para fazer a integração com o hardware da sua aplicação(pode ser usado para Arduíno, pic ou qualquer outro dispositivo que tenha a porta serial integrada).Para mais informações consulte o pequeno  manual que montei e está disponível em : https://drive.google.com/file/d/0Bx9JeN0dmzXxNjhucVFIelA1MW8/view?usp=sharing .
       Para baixar o aplicativo para usar com Smartphone android acesse : https://drive.google.com/file/d/0Bx9JeN0dmzXxLTZJSHE1NHVCREU/view?usp=sharing .
       Para baixar o aplicativo para usar com o windows acesse : https://drive.google.com/file/d/0Bx9JeN0dmzXxTUpsU2xfZ1VKWDQ/view?usp=sharing.
          A seguir vou colocar os videos de demonstração, mas antes vejam a foto abaixo para saber o que é cada módulo que foi colocado na proto-o-board. O transformador é usado para alimentar a placa(sensor AMF16) ,este transformador pode ser trocado por um de 12V /100mA ou 9V/100mA(pra ficar menor e com baixo custo),os fios do secundário do transformador faz a  alimentação que passa por uma ponte de diodos(retificando a tensão) e então é ligada a alimentação da placa(veja os detalhes no manual).
         Como foi feito dois tipos de demostração tem se um modulo bluetooth(para comunicar com o android) e um circuito montado com o max 232 para comunicar com o windows.O sensor estava na configuração de uso até 1000V.  
     
         Em alguns momentos voces vão ver uma pequena divergencia de tensão isso se deve a precisão do ciruito que considero ser melhor do que a do multimetro pois ele tem mais casas decimais e devido a erros que todo multimetro tem quando se mede tensões fora de sua escala. Por exemplo meça uma tensão de 110V na escala de "x200V" e denovo numa escala de "x1000V" do multímetro e veja o quanto ele varia.Posso assegurar que o circuito funciona na escala de 1000V com tensões que vão de 30VAC a 1000VAC sem problemas de leitura, mas para melhor precisão use ele na escala de 40V para tensões de 8VAC a 40VAC, muito cuidado para não configurar errado e queimar o chip da placa veja no manual como fazer.
         Veja que a forma como os cálculos são feitos no celular e no PC dão uma diferença sutil por, exemplo no PC posso usar a divisão por raiz de 2 no Smartfone eu pego um valor aproximado de raiz de 2 e coloco pra dividir logo ocorre pequenas divergências, quanto menos arredondamentos tiver mais próximo do valor real fica.

Sensor AMF16 com o windows:


Sensor AMF16 com o Android:



Envie um email para : microcontroladores.c@gmail.com 

sexta-feira, 6 de novembro de 2015

Amperímetro com com o PIC 12F675 via porta serial (rs-232) para até 20A - Usando ACS712

           Olá pessoal que me acompanha, hoje vou postar um exemplo de como usar o chip ACS712 da empresa Alegro, para medir corrente elétrica dentre as vantagens destaco o isolamento elétrico do circuito de medição com a carga a ser testada, facilidade de uso e boa resposta ao sinais avaliados, conta ainda com a vantagem de se poder medir corrente continua ou alternada.
            Este chip se baseia na medida de corrente elétrica através do campo eletromagnético gerado em torno da área de condução do sensor, é possível com auxilio de um osciloscópio ver nitidamente a forma de onda da corrente elétrica produzida por uma fonte AC em uma carga.No nosso exemplo vou usar o chip para implementar um amperímetro que consegue medir corrente de até 20A, lembrando que é possível dependendo do modelo do chip medir até 30A conforme mostrado na figura abaixo(retirado do datasheet do fabricante):
           Como ele mede corrente AC então no momento em que não há carga o sinal de saída é a metade da tensão de alimentação do circuito ou seja para 5V você terá 2,5V, como consequência o sinal varia de 2,5V até 5V nos semiciclos positivo e de 2,5 até 0V para os semiciclo negativo, logo para medir a tensão continua o sinal de saída irá depender do sentido da corrente no circuito da carga, para resolver esse problema podemos calcular a variação de unidades de medidas, assim independente se é positivo ou negativo a variação será a mesma,mas como a tensão de saída depende da alimentação então no algoritmo de leitura o circuito deve fazer um referenciamento do valor de saída do sensor quando não houver sinal(no meu caso coloquei pra fazer isso assim que ligar o circuito e então ele começa a pegar as amostras).
            Para conectar o chip ACS712 com o pic é bem simples e usa poucos componentes externos veja na imagem abaixo como ficaria o esquema elétrico da parte de interface:
          Veja que a saída serial RS-232 deve ser ligada a um chip Max232 ou similar para que possa ser ligado ao PC, o capacitor de 10nF é para estabilizar a tensão lida e evitar pequenas flutuações.
          Por fim vou mostrar como é plotado as mensagens num terminal serial do PC, veja a próxima figura, e logo em seguida é apresentado o código fonte.Este C.I. pode ser adquirido também na forma de placas montadas (no mercado livre tem aos montes).
O código fonte:


/*
 *                                    Amperímetro - via serial          
 *
 * Compilador : MPlabXC8
 * Microcontrolador: 12F675
 * Autor: aguivone
 * Versão: 1
 * Data :  05/11/2015
 * Descrição : mede corrente usando o CI ACS712 (usa porta serial emulada)
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>  //para trabalhar com string
#include <xc.h>
#define _XTAL_FREQ 20000000

/////////////////////////////////////////////////////////configurações//////////////////////////////////////////////////
// CONFIG
#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator: High speed crystal/resonator on GP4/OSC2/CLKOUT and GP5/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     // 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 = OFF        // Data Code Protection bit (Data memory code protection is disabled)

///parametros da porta serial emulada////////////////////////////////////////////////////////////////
#define  TIME_BAUD  97//9600 bps - 20MHZ
#define  BAUD_RX    95//9600 bps - 20MHZ 
#define  TX  GP2//configura o pino que será TX - não será usado aqui
#define  RX  GP1//configura o pino que será RX

int ler_adc_un()
{
        int ulcorrente;
        PIR1bits.ADIF = 0;//limpa flag
        ADCON0bits.GO = 1; //inicia conversão
        while (PIR1bits.ADIF == 0); //espera finalizar leitura
        ulcorrente = (ADRESH << 8) | ADRESL;
        return(ulcorrente);
}

/////////////////////Funçoes utilizadas pela serial/////////////////////////////////////////////////////////////
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++;
    }

}

void long_to_char(long ulQuant)
{
        char cTexto[5]={'0','0','0','0','0'};
         while(ulQuant>=10000)
            {
             ulQuant=ulQuant-10000;
             cTexto[4]++;
             }
          if(cTexto[4] > '0')
          {//se igual a zero não imprime
           escreve_char(cTexto[4]);
          }
         while(ulQuant>=1000)
            {
             ulQuant=ulQuant-1000;
             cTexto[3]++;
             }
          escreve_char(cTexto[3]);
          escreve_char(',');
          while(ulQuant>=100)
            {
             ulQuant=ulQuant-100;
             cTexto[2]++;
             }
          escreve_char(cTexto[2]);          
           while(ulQuant>=10)
            {
             ulQuant=ulQuant-10;
             cTexto[1]++;
             }
          escreve_char(cTexto[1]);
           while(ulQuant>=1)
            {
             ulQuant=ulQuant-1;
             cTexto[0]++;
             }
          escreve_char(cTexto[0]);
          escreve_char('\n');//pula linha
          escreve_char('\r');//coloca no inicio do terminal
          
}
//////////////////////////////////////////////////////Rotina principal///////////////////////////////////////////////////////////////
void main(void) {
    CMCON = 7;//desabilita comparadores
    ANSEL = 0X31;//usa AN0 como entrada analógica e usa o clock interno do chip(2 a 6 us)
    WPU = 0X00;//desabilita pull ups
    TMR0 = 0;
    ADCON0 = 0X80;//justificado a esquerda , ref ligado ao vdd,usando AN0
    OSCCAL = 0XFF;//configura o oscilador interno para frequência maxima(4mhz)
    OPTION_REG = 0X81;//pull up desabilitado/preescaler ligado ao timer0(dividido por 4)
    PIR1 = 0X00; //desabilita interrupções não necessária
    TRISIO = 0X03;//configura gp0 e gp1 como entrada
    INTCON = 0XC0;//desabilita interrupção do timer 0 e habilita interrupção global e de periféricos
    ADCON0bits.ADON = 1;//liga modulo de conversão
    __delay_ms(1000);//debounce e tempo para preparar hardware
    int referencia = 0;
    long aux;//auxilia nas contas que necessitem de valores maiores
    int  corrente = 20; 
    while(corrente>0)//somatório de 20 amostras
    {
           referencia = ler_adc_un() + referencia;
           __delay_ms(100);
           corrente--;
    }
    referencia = referencia/20;//tira uma media para servir como referencia 
    for(;;)
    {   
           __delay_ms(500); //envia valor a cada 0,5s
           corrente = ler_adc_un();//lê o conversor 
           if((corrente >= referencia-1)&&(corrente <= referencia+1))//range de +ou- 1 unidades
           {//para filtrar erros ou pequenas variações
             long_to_char(0);
           }
           else
           {
                if(corrente >= referencia)
                {//usar a parte inferior é mais confiável
                    aux = (corrente - referencia) * 49;//dá o valor com 3 casas decimais
                    escreve_char('+');//corrente positiva
                }
                else
                {
                    aux  = (referencia - corrente) * 49;//dá o valor com 3 casas decimais
                    escreve_char('-');//corrente positiva 
                }
                    if(aux>20000)
                    {
                        escreve_frase("Perigo! valor acima da escala \n\r");
                    }
                    else
                    {
                     long_to_char(aux);
                    }                 
           }
    }//loop infinito

}


//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 confiável 

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

//#define  TIME_BAUD  175//4800 bps - 4MHZ 
//#define  BAUD_RX    165//4800 bps - 4MHZ - recepção funcionou muito bem(100% confiável 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 

       



segunda-feira, 26 de outubro de 2015

Voltímetro com Smartphone Android(usando o pic 12F675).

         Olá,esse artigo é a junção de 2 posts anteriores , bluetooth e emulando uma porta serial, a proposta aqui é juntar conversor analógico + comunicação serial +bluetooth o que resulta num voltímetro sem contato físico usando apenas a comunicação do módulo ZS-40(HC-05).
          Ao ler o modulo analógico o qual varia sua conversão de 0 a 1024, sendo então enviado este valor ao aparelho ou equipamento que irá receber("no meu caso usei um celular pra isso"), o calculo da tensão fica por conta do smatphone pois assim caso mude o range de leitura, o que pode ser feito colocando um divisor resistivo(basta fazer uma busca sobre o assunto no google), basta configurar isso no aparelho.Por exemplo se eu quiser que o pic faça a leitura de 0 a 30V, devo configurar no software do celular para valor máximo de 30 assim ele irá calcular em cima deste valor.
         O aplicativo para android está disponível no link : https://drive.google.com/open?id=0Bx9JeN0dmzXxcUszcEh4LTR1T3M .
         O vídeo de demostração do funcionamento é este :

         
O Esquemático:



O Código fonte:


/*
 *                                    voltimetro DC - via serial          
 *
 * Compilador : MPlabXC8
 * Microcontrolador: 12F675
 * Autor: aguivone
 * Versão: 1
 * Data :  15/09/2015
 * Revisado:  29/10/2015
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>//para trabalhar com string
#include <xc.h>
#define _XTAL_FREQ 20000000

/////////////////////////////////////////////////////////configuraçôes//////////////////////////////////////////////////
// CONFIG
#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator: High speed crystal/resonator on GP4/OSC2/CLKOUT and GP5/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     // 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 = OFF        // Data Code Protection bit (Data memory code protection is disabled)

///parametros da porta serial emulada////////////////////////////////////////////////////////////////
#define  TIME_BAUD  97//9600 bps - 20MHZ
#define  BAUD_RX    95//9600 bps - 20MHZ 
#define  TX  GP2//configura o pino que será TX
#define  RX  GP1//configura o pino que será RX


int tensao=0;


int ler_adc_un()
{
        int ultensao;
        PIR1bits.ADIF = 0;//limpa flag
        ADCON0bits.GO = 1; //inicia conversão
        while (PIR1bits.ADIF == 0); //espera finalizar leitura
        ultensao = (ADRESH << 8) | ADRESL;
        return(ultensao);
}

/////////////////////Funçoes utilizadas pela serial/////////////////////////////////////////////////////////////
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++;
    }

}

void long_to_char(unsigned long ulQuant)
{
        char cTexto[4]={'0','0','0','0'};
         while(ulQuant>=1000)
            {
             ulQuant=ulQuant-1000;
             cTexto[3]++;
             }
          escreve_char(cTexto[3]);
          while(ulQuant>=100)
            {
             ulQuant=ulQuant-100;
             cTexto[2]++;
             }
          escreve_char(cTexto[2]);
           while(ulQuant>=10)
            {
             ulQuant=ulQuant-10;
             cTexto[1]++;
             }
          escreve_char(cTexto[1]);
           while(ulQuant>=1)
            {
             ulQuant=ulQuant-1;
             cTexto[0]++;
             }
          escreve_char(cTexto[0]);
}
//////////////////////////////////////////////////////Rotina principal///////////////////////////////////////////////////////////////
void main(void) {
    CMCON = 7;//desabilita comparadores
    ANSEL = 0X31;//usa AN0 como entrada analogica e usa o clock interno do chip(2 a 6 us)
    WPU = 0X00;//desabilita pull ups
    TMR0 = 0;
    ADCON0 = 0X80;//justificado a esquerda , ref ligado ao vdd,usando AN0
    OSCCAL = 0XFF;//configura o oscilador interno para frequencia maxima(4mhz)
    OPTION_REG = 0X81;//pull up desabilitado/preescaler ligado ao timer0(dividido por 4)
    PIR1 = 0X00; //desabilita interrupções não necessaria
    TRISIO = 0X03;//configura gp0 e gp1 como entrada
    INTCON = 0XC0;//desabilita interrupção do timer 0 e habilitainterrupção global e de perifericos
    ADCON0bits.ADON = 1;//liga modulo de conversão
    __delay_ms(300);//debounce e tempo para preparar hardware
    //logo a frequencia de interrupção é 250khz
    for(;;)
    {   __delay_ms(300);        
        tensao = ler_adc_un();       
        long_to_char(tensao);
        
    }//loop infinito

}


//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