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 

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!


terça-feira, 12 de maio de 2015

Voltímetro RMS - para medir tensão alternada(AC) via RS-232.


           Este exemplo mostra como medir a tensão RMS senoidal(a mesma da rede elétrica), para isto o circuito deve ler o valor de pico e calcular o valor RMS, neste exemplo eu calculo o valor de pico a partir da tensão RMS, mas pode ser fazer o contrario também, basta lembra que  Vrms = VP/(1,41...) ou seja valor de pico dividido pela raiz quadrada de 2.
           Um dos maiores problemas é ler a tensão 220V ou 110V diretamente com o PIC dai tem se as opções de recorrer a um chip dedicado a isso(que custa caro e é difícil de encontrar) ou usar um transformador(mas dai a corrente de magnetização e as próprias características elétrica do mesmo não são padrão, o que gerar certo retrabalho), então a forma mais simples foi usar divisores resistivos,mas dai teria que montar outros circuitos externos para manter o sinal em níveis desejado pegando os pulsos negativos e positivos, dai dentro das possibilidades de simplificar o projeto montei o seguinte circuito:

            Caso não tenha experiência com eletrônica não monte o circuito físico("não me responsabilizo por eventuais choques ou danos materiais"), monte a seguinte tela de simulação no programa proteus:

            Veja que na simulação o circuito marcando exatamente a mesma tensão que o voltímetro AC. Como o circuito foi montado em uma protoboard pode ser que dê uma pequena variação na tensão medida no circuito física de vcs(mas mesmo assim estará próximo do valor real), se for conferir a tensão com um multímetro verá que se estiver na escala 200V será bem próximo ou igual se tiver na escala de 1000V dará uma diferença maior pois o próprio multímetro tem uma variação de -+5V (nesta escala), um detalhe interessante que reparei aqui nos teste de bancada foi que dependendo do lado que eu coloco o pino dá diferença no valor(ou seja a posição Fase e Neutro deve ser respeitada), coloquei um transformador para avaliar a situação e vi que os picos positivos da senoide eram maiores e talvez isso afeta o circuito de alguma forma, talvez pela própria fonte de alimentação do circuito, mas o fato é que mesmo assim o circuito funcionou de forma satisfatoria.
          Os resistores de 100K são para limitar a corrente e promover uma queda de tensão (isso evita que os diodos entrem em curto na sua região de tensão reversa), a tensão medida é o valor da queda de tensão no resistor de 3,3K(R3),após a retificação do sinal pelos diodos(1N4007) existe um segundo divisor de tensão que irá deixar passar uma pequena corrente suficiente para o microcontrolador ler.
         Com este circuito nas mãos o próximo passo era ler diferentes tensões alternadas e montar uma equação que convertesse as unidades lidas pelo modulo ADC do PIC para o valor RMS.Um detalhe importante deste circuito é a captura do maior valor lido durante o período de uma senoide(adotei 25ms pois assim abrange uma faixa maior e pode operar tanto em 50HZ quanto em 60HZ).
        No código você pode usar ele com o simulador ou com o circuito real(físico),basta  comentar uma das linhas :
        circuito_real(); // para usar o circuito fisico
        simulador(); //para usar o simulador
    Isso se deve pelo fato dos circuitos serem diferentes.

Segue abaixo o código fonte:

/*
 *                             voltímetro AC usando RS-232 no MPlab XC8
 *
 * Compilador : MPlabXC8
 * Microcontrolador: 16F877A
 * Autor: aguivone
 * Versão: 1
 * Data :  12 de março de 2015
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <delays.h>
#define _XTAL_FREQ 20000000    // cristal de 20 Mhz
#include <xc.h>

/////////////////////////////////////////////////////////configuraçôes//////////////////////////////////////////////////

#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable bit (BOR disabled)
#pragma config LVP = OFF        // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off)
#pragma config WRT = OFF        // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)



////////////////////////////////////////////////variaveis globais///////////////////////////////////////////////////////////
#define MAXIMO  20 //tamanho máximo de amostra deve ser numero par(e maior que 4) para funcionar
// saiba que na verdade isso faz com que se tenha um impar(pois começa em 0)
char caracter;
bit flag_interrupcao = 0;
    
float variavel_aux=0;
unsigned long tensao = 0;

void delay_ms(long val)
   {
       while(val>0)
       {
           __delay_ms(1);//resolve bug do compilador
           val--;
       }
   }
///////////////////////////////////////////////////interrupção//////////////////////////////////////////////////////////////
void interrupt interrupcoes(void)//vetor de interrupção
 {
    if(RCIF)
    {//se interrupção de recepção da serial
        //aqui não será usado essa funcão
     caracter = RCREG;
     flag_interrupcao = 1;
     RCIF = 0;//  limpa flag de interrupção de recepção
    }
    if(ADIF)
    {//se interrupção do modulo analogico!
        ADIF = 0;//limpa flag
    }
 }

/////////////////////////////////funçoes usadas pela uart //////////////////////////////////////////////////////
void inicializa_RS232(long velocidade,int modo)
{////por padrão é usado o modo 8 bits e sem paridade, mas se necessario ajuste aqui a configuração desejada.
    //verifique datasheet para ver a porcentagem de erro e se a velocidade é possivel para o cristal utilizado.
    RCSTA = 0X90;//habilita porta serial,recepção de 8 bit em modo continuo,assincrono.
    int valor;
        if(modo == 1)
        {//modo = 1 ,modo alta velocidade
         TXSTA = 0X24;//modo assincrono,trasmissao 8 bits.
         valor =(int)(((_XTAL_FREQ/velocidade)-16)/16);//calculo do valor do gerador de baud rate
        }
        else
        {//modo = 0 ,modo baixa velocidade
         TXSTA = 0X20;//modo assincrono,trasmissao 8 bits.
         valor =(int)(((_XTAL_FREQ/velocidade)-64)/64);//calculo do valor do gerador de baud rate
        }
    SPBRG = valor;
    RCIE = 1;//habilita interrupção de recepção
    TXIE = 0;//deixa interrupção de transmissão desligado(pois corre se o risco de ter uma interrupção escrita e leitura ao mesmo tempo)
}
void escreve(char valor)
{
    TXIF = 0;//limpa flag que sinaliza envio completo.
    TXREG = valor;
    while(TXIF ==0);//espera enviar caracter
}
void imprime(const char frase[])
{
     char indice = 0;
     char tamanho = strlen(frase);
      while(indice < tamanho ) ///veja que o programa pode travar se aqui não tiver as duas aspas
       {
           escreve(frase[indice]);
           indice++;
       }
}
void long_to_char(long quant)
{
       char convert_char6='0';
       char convert_char7='0';
       char convert_char8='0';
       char convert_char9='0';
       while(quant>=1000)
        {
         quant=quant-1000;
         convert_char6++;
         }
       while(quant>=100)
        {
         quant=quant-100;
         convert_char7++;
         }
       while(quant>=10)
        {
         quant=quant-10;
        convert_char8++;
         }
       while(quant>=1)
        {
           quant=quant-1;
         convert_char9++;
         }
          escreve(convert_char6);
          escreve(convert_char7);
          escreve(convert_char8);
          escreve(',');
          escreve(convert_char9);

}

///////////////////////////////////////funçoes usadas pelo conversor AD//////////////////////////////////////////
void inicializa_AD()
{
    ADCON0 = 0X81;//freq. div. por 32(20mhz o que leva a 1.6us cada aquisição) ;habilita modulo de conversão'
    ADCON1 = 0X84;//leitura do valor justificado a direita,apenas RA0,RA1 e RA3 são portas analogicas.
    ADIE = 1;//não habilita interrupção de conversão analogica
    ADIF = 0;
}

// ===================================================================================
// Função:    _ler_adc_un
// Parâmetros: não tem.
// Retorno   : unsigned long ultensao.
// Descrição : retorna o valor lido em unidades de medidas.
// ===================================================================================

unsigned long ler_adc_un(void)
{
    ADCON0bits.GO = 1; //inicia conversão
    while (ADCON0bits.GO_DONE == 1); //espera finalizar leitura
    return((ADRESH << 8) | ADRESL);
}
// ===================================================================================
// Função:    _Valor_pico
// Parâmetros: não tem.
// Retorno   : unsigned long tensao.
// Descrição : retorna o valor lido em unidades de medidas.
// ===================================================================================
unsigned long Valor_pico()//não colocar mais que 60 amostras
{
    unsigned long tensao_temporaria; 
    unsigned int conta=0;
    unsigned long tensao_maxima = 0;
    //tempo de 1 senoide completa em 50hz - mas funciona tambem em 60hz
    //caso queira apenas 60hz use o valor 166
        while(conta < 2500)//tempo de amostra de 25ms(assim fica compativel com 50hz))
        {
            tensao_temporaria = ler_adc_un();
            if(tensao_temporaria > tensao_maxima)//pega sempre o maior valor valor de pico
            {
               tensao_maxima =  tensao_temporaria;
            }         
            __delay_us(10); 
            conta++;  
        }  
    return (tensao_maxima);
}
// ===================================================================================
// Função:    _simulador
// Parâmetros: não tem.
// Retorno   : não tem.
// Descrição : faz calculo e joga na interface serial . //usado somente na simulação do proteus
// ===================================================================================
void simulador()
{
    if(tensao != 0) //só trata se tiver lido alguma tensão
        {
            imprime("\n\r valor pico - valor RMS");
            imprime("\n\r ");
            variavel_aux = (489*tensao)/10000; //para ter mais precisão - valor de pico 
            long_to_char((unsigned long)variavel_aux);
            imprime(" - ");
            variavel_aux = (347*tensao)/10000; //para ter mais precisão - valor RMS
            long_to_char((unsigned long)variavel_aux);
        }
}
// ===================================================================================
// Função:    _circuito_real
// Parâmetros: não tem.
// Retorno   : não tem.
// Descrição : faz calculo e joga na interface serial . //usado para ler tensão de 0 a 350V(teorico pois só foi testado até 230V)
// ===================================================================================
void circuito_real()
{
    if(tensao != 0) //só trata se tiver lido alguma tensão
        {
            imprime("\n\r valor RMS - valor pico");
            imprime("\n\r ");
            variavel_aux = (((100*tensao) + 258)/278)*10; //para ter mais precisão - valor de pico 
            long_to_char((unsigned long)variavel_aux);
            imprime(" - ");
            variavel_aux = (variavel_aux*141)/100; //para ter mais precisão - valor RMS
            long_to_char((unsigned long)variavel_aux);
        }
}
void main(void)
{
    TRISC = 0X80;//configura portC  C7 (pino RX) como entrada
    PORTC = 0;  // limpar as portas que estão configuradas como saidas
    TRISA = 0XFF;//configura portA como entrada
    inicializa_RS232(9600,1);//modo de alta velocidade
    inicializa_AD();
    PEIE = 1;//habilita interrupção de perifericos do pic
    GIE = 1; //GIE: Global Interrupt Enable bit
    imprime("Voltimetro RMS usando a serial!");
    for(;;)
    {   
        tensao = Valor_pico();
        tensao = tensao + Valor_pico();
        tensao = tensao + Valor_pico();
        tensao = (tensao + Valor_pico())/4;//pega 4 amostras e tira uma media logo será 100ms
         //comente a linha que não for usar
       // circuito_real();
        simulador();
        delay_ms(500);//espera 0.5 segundo
    }//loop infinito

}