Pic apostila
Upcoming SlideShare
Loading in...5
×
 

Pic apostila

on

  • 525 views

 

Statistics

Views

Total Views
525
Views on SlideShare
525
Embed Views
0

Actions

Likes
0
Downloads
54
Comments
0

0 Embeds 0

No embeds

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Pic apostila Pic apostila Document Transcript

  • MICROCONTROLADORES PIC PROGRAMAÇÃO EM LINGUAGEM C HANDS ON! O QUE É UM MICROCONTROLADOR? O microcontrolador PIC é um componente eletrônico fabricado pela MICROCHIP, capaz de ser programado para realizar operações lógicas e aritméticas, interagindo com
  • periféricos (leds, botões, sensores ou até outro PIC). Deste fato vem o nome PIC (Peripheral Integrated Controller, ou controlador integrado de periféricos). Um microcontrolador (uC), diferentemente de um microprocessador, possue memória volátil (RAM) e não volátil (EEPROM), além de outros periféricos internos, o que o torna bastante útil. Este é capaz de operar independente de memórias externas, além de poder ser programado em linguagem de alto nível, como a linguagem C. ESTRUTURA BÁSICA DO PIC Nesta apostila serão abordados os modelos PIC16F628A, e PIC16F876A, pois estes estão inclusos em uma boa gama de possíveis soluções. Apesar da estrutura aqui mencionada tratar destes específicos modelos, esta descreve a grande parte dos microcontroladores. MEMÓRIA FLASH- É a memória em que será guardado o programa propriamente dito, no formato de “linguagem de máquina”. Foi desenvolvida na década de 80 pela Toshiba e é do tipo EEPROM, porém se assemelha com a RAM, permitindo que múltiplos endereços sejam apagados ou escritos em uma só operação. MEMÓRIA SRAM- Static RAM, significa que não precisa ser periodicamente atualizada como as RAMs comuns, que sem atualização perdem seus dados. É a memória volátil, rapidamente acessada, e que é apagada toda vez que a alimentação se ausenta. Local onde são armazenadas as variáveis declaradas no programa. MEMÓRIA EEPROM- É a memória não volátil, que mantém os dados escritos independente da alimentação do PIC. É acessada quando se deseja armazenar dados por períodos indeterminados, como a senha de um usuário. CPU (Central Processing Unit)- É um conjunto composto por: • PC: (Program Counter) Seleciona na FLASH o corrente comando a ser executado. • WREGs: (Work Registeres) Registradores especias de trabalho. São acessados constantemente e servem para realizar operações de movimentação e tem acesso direto à ULA. 2
  • ULA: Unidade Lógica e Aritmética do uC (microcontrolador), responsável pelas operações lógicas e aritiméticas dos WREGs. FUSÍVEIS: Periféricos especiais responsáveis pelo pleno funcionamento do sistema. Garantem que o programa não “trave” e que o uC não opere sob tensões abaixo ou acima do permitido. PERIFÉRICOS: Circuitos auxiliares que livram o programa de realizar trabalhos específios. Funcionam de forma paralela ao programa do usuário (na FLASH), ocupando o código apenas para ser configurado ou entregar o resultado de algum serviço. Os periféricos abordados nesse documento são: • GPIO (General Purpose Input/Output): Usados para entradas digitais, como um botão ou uma chave, e saídas digitais, como um LED ou um motor DC. O programa usuário pode setar uma saída, colocando um pino externo do PIC em nível lógico ‘0’(0V) ou ‘1’(5V). • ADC (Analog to Digital Converter): Usado para recolher sinais analógicos externos e transormá-los em digitais, para então serem processados pelo programa. • USART (Universal Synchronous and Asynchronous Receiver-Transmitter): Periférico responsável por enviar e receber dados através do protocolo RS-232. • TIMERS: Temporizadores de uso geral. Garantem precisão de tempo entre eventos. • 3 View slide
  • Figura 1 – Estrutura interna básica HARDWARE Para o pleno funcionamento do uC é necessário que: • Ele esteja corretamente alimentado, com seu pino Vdd ligado em 2,2 à 5,5V e seu pino Vss ligado ao GND. (Os exemplos deste documento usam a tensão de alimentação de 5V). É recomendado que um capacitor de 100nF (de cerâmica com o rótulo 104) seja posto na alimentação, próximo aos terminais do uC, pois este funciona como um filtro contra ruídos gerais. • O pino MCLR (Masterclear) esteja em 5V. Ao ir para 0V este pino provoca um reset externo do PIC. • Os pinos OSC1 e OSC2 estejam ligados no oscilador externo, que nos exemplos será um cristal de quartzo de 4MHz. Deve-se ressaltar que a frequência interna (chamada de Fcy) do uC vale ¼ da frequência usada como fonte de relógio (Fosc). Para uma correta geração de clock externo, é recomendo o uso de dois capacitores de desacoplamento nos terminais do cristal, no valor de 22pF. 4 View slide
  • O uC PIC16F628A tem o benefíco de poder usar um clock interno de 4MHz, além de não precisar do MCRL. Isso permite que os três pinos correspondentes sejam usados como GPIO. Esta será a configuração usada para este uC nos nossos exemplos. Figura 2 – Hardware básico: PIC16F628A Figura 3 – Hardware básico: PIC16F876A SOFTWARE 5
  • Como ambiente de desenvolvimento, será usado o compilador PCWH da CCS. Este é constituído de um IDE gráfico que pode ser executado em qualquer plataforma Windows. O compilador, como o próprio nome já diz, tem o papel de compilar o código em linguagem C para a linguagem de máquina, a qual está pronta para ser gravada na memória FLASH do uC. Deve-se ressaltar que este compilador é case insensitive, ou seja, não diferencia letras maíusculas de minúsculas. Portanto, se forem declaradas as variáveis “Temper_Atual” e “temper_atual”, o compilador acusará erro, pois é visto como ambiguidade. Os programas embarcados seguem, como os outros tópicos já vistos, uma estrutura básica. Quando o dispositivo é ligado o programa inicia sua execução numa fase chamada de “inicialização”. Nessa fase é feita toda a configuração do sistema: • Quais pinos serão entradas ou saídas. • Quais periféricos serão utilizados e como serão utilizados. • Declaração das variáveis usadas pela função main. • Chamadas de funções (ou comandos) que devem ser executados apenas no início do processo. Deve ficar claro que o código pertencente a essa fase é executado apenas uma vez, sendo executado novamente apenas se o uC for reiniciado. A partir disto o uC está pronto para iniciar sua fase de trabalho propriamente dito. Essa fase recebe o nome de “loop infinito”, pois é onde o programa deve permanecer enquanto o dispositivo estiver energizado. Basicamente, as ações sequencias nesse laço são: • Leitura das entradas. • Processamento dos dados. • Atualização das saídas. 6
  • Figura 4 – Fluxo básico de um programa embarcado Pode-se adotar como softwares iniciais: //--Programa Esqueleto-----// //--Programa Esqueleto-----// #include <16F628A.h> #fuses INTRC_IO, NOMCLR #use delay(clock=4000000) #include <16F876A.h> #fuses XT #use delay(clock=4000000) #use fast_io(a) #use fast_io(b) #use fast_io(a) #use fast_io(b) #use fast_io(c) void main(void){ set_tris_a(0b11111111); set_tris_b(0xff); void main(void){ set_tris_a(0b11111111); set_tris_b(0xff); set_tris_c(0xff); while(true){ } while(true){ } } //-------------------------// } //---------------------// Explanação: #include <16FXXXA.h> Biblioteca que será utilizada para o compilador gerar o código específico para o dispositivo utilizado. #fuses INTRC_IO, NOMCLR, XT 7
  • Fusíveis configurados. INTRC_IO significa que será utilizado como fonte de relógio o oscilador interno, que é de 4 MHz. NOMLCR habilita o pino 4 do 16F628 para ser usado como GPIO. XT indica que a fonte de relógio é um cristal externo de frequência <= 4MHz. Não serão abordados todos os fusíveis, porém estes podem ser vistos no CCS em “View” -> “Valid Fuses”. #use delay(clock=4000000) Comando que fornece a base de tempo para as rotinas de delay usadas no programa, que serão vistas mais adiante. #use fast_io(a) //(b ou c) Configura todos os pinos como GPIO (General purpose In/Out). Cada pino do uC pode ser configurado com diferentes propósitos, o que pode ser visto nas Figuras 2 e 3. Por omissão, diremos que todos os pinos serão GPIO. Assim, se quisermos outra função para algum deles, indicaremos adiante, atualizando o seu propósito. Os canais GPIO no PIC seguem um padrão: cada um deve estar associado a um byte e a um bit. Os bytes são nomeados por vogais e são chamados pelo datasheet de PORT, como PORTA, PORTB, PORTC, etc. Os bits são mapeados por números, assim, podemos observar nas Figuras 2 e 3 que o canal A3 (apresentado como RA3) está no pino 2 do 16F628, e no pino 5 do16F876. Como cada PORT é um byte, cada um tem 8 canais. Por exemplo, o PORTA vai de A0 até A7, e este último é identificado pelo programa do usuário como “pin_a7”. O PIC16F628 contém as portas A e B, enquanto o PIC16F876 tem as portas A, B e C. Porém, nem todos os canais endereçáveis em teoria estão disponíveis nos pinos externos. void main(void){ Função principal de qualquer programa em C. É onde realmente começa o programa que será embarcado. Os comandos mencionados anteriormente são chamados de “diretivas do compilador”, ou seja, são usados pelo compilador mas não serão executados pelo PIC. O compilador utiliza esses comandos para configurar registradores especiais no momento da gravação. Portanto, se você escreveu “#fuses XT”, o registrador responsável por indicar a fonte de relógio será configurado no momento do download, e não na inicialização. set_tris_a(0b11111111); 8
  • Primeiro comando do programa (para este exemplo). Essa função configura os canais GIPO como entrada ou saída. ‘1’ significa “IN” e ‘0’ “OUT”, o que facilita a memorização pela semelhança: 1->In e 0->Out. O prefixo “0b” indica que o número adiante está representado em binário. Para representar um número em hexadecimal, o prefixo usado é “0x”, portanto essa função pode ser escrita: “ set_tris_a (0xff);”. Se um número não tiver nenhum prefixo, este será interpretado pelo compilador como no formato decimal, que é nossa forma natural. Podemos dizer então que essa função também pode ser escrita na forma: “ set_tris_a (255);”, que surtirá o mesmo efeito. Convém escrever tal função em binário pela melhor visualização dos canais, que estão dispostos em ordem decrescente. Portanto, se o primeiro pino do 16F628 for usado para acender um LED, deve-se configurar o canal A2 como saída, escrevendo: “set_tris_a (0b11111011);”. Recomenda-se que todos os canais que não serão utilizados sejam configurados como entrada, pois estarão em alta-impedância, admitindo qualquer valor. Quanto mais pinos são configurados como saída, maior é a probabilidade do usuário acidentalmente tocá-los ao manusear o protoboard, e isso pode ocasionar a queima do dispositivo. while(true){ Equivale a escrever “while(1==1){” ou “while(1){”. Esse é o tal loop infinito, onde o programa permanecerá indefinidamente. É dentro desse while que o programador deve construir a fase de trabalho do uC. 1º EXEMPLO: Hello World! //-------- Pisca LED -------- while(true){ output_bit(pin_a0,flag); delay_ms(500); flag=!flag; } #include <16F628A.h> #fuses INTRC_IO, NOMCLR #use delay(clock=4000000) } #use fast_io(a) #use fast_io(b) //-------------------------// void main(void){ short flag=0; set_tris_a(0b11111110); set_tris_b(0xff); //-------- Pisca LED -------#include <16F876A.h> #fuses XT #use delay(clock=4000000) 9
  • #use fast_io(a) #use fast_io(b) #use fast_io(c) while(true){ output_high(pin_a0); delay_ms(500); output_low(pin_a0); delay_ms(500); } void main(void){ set_tris_a(0b11111110); set_tris_b(0xff); set_tris_c(0xff); } //-------------------------// Explanação: short flag=0; Declaração e inicialização da variável de 1 bit chamada “flag”. Também pode ser escrito nas formas: “ boolean flag=0;” ou “int1 flag=0;” output_high(pin_a0); Coloca o pino mencionado em nível lógico 1 (5V). output_low(pin_a0); Coloca o pino mencionado em nível lógico 0 (0V). output_bit(pin_a0, flag); Coloca o pino mencionado no nível lógico correspondente ao valor corrente da “flag”. delay_ms(500); O programa literalmente pára durante o valor de tempo informado (em milisegundos). O próprio compilador gera o código responsável por esta ação, através de contadores, os quais se baseiam em “#use delay(clock=4000000)”. flag=!flag; Realiza o “complemento de um” da variável “flag”. O símbolo “!” corresponde à operação lógica NOT. Os pinos configurados como entrada drenam uma corrente extremamente pequena, na ordem de picoampéres, pois são colocados em alta-impedância. Assim, podemos considerá-la desprezível. Os pinos definidos como saída não podem receber sinal externo (corrente), pois, como já dito anteriormente, isso certamente causará o falecimento do uC. Estes dois dispositivos abordados podem fornecer até 25mA por pino de saída. Portanto, se a carga utilizada drenar mais do que esse valor, deve ser utilizado um driver, como um transistor. 10
  • Figura 5 – Exemplo 1: hardware 16F876 Figura 6 – Exemplo 1: hardware 16F628 Compilação do programa: Inicialmente abra o compilador PICC (PCWHD), clique no símbolo de “pastas” (primeiro acima), e em seguida 11
  • “Close All”. Agora, para criar um novo projeto, clique novamente no símbolo de “pastas”, e após isso em “New” -> “Source File”. Escolha o diretório onde ficará salvo este primeiro programa e o nomeie de “exemplo1”. Digite o código de exemplo para o uC escolhido e em seguida compile o programa em “Compile” -> “Compile”, ou através da tecla de atalho “F9”. Neste processo o compilador gera 5 arquivos de saída, porém será utilizado apenas o “exemplo1.hex”, que é o código que será transferido para a memória FLASH do uC. Gravação do programa: Coloque o uC escolhido no protoboard e, antes de colocar os outros componentes (capacitor, etc.), insira apenas os jumpers relativos ao ICSP (In Circuit Serial Programming). Plugue agora o cabo do ICSP no protoboard e no gravador PICKit2, e plugue o cabo USB do gravador no PC. Feito isso, abra o software PICKit2. Se o procedimento dito foi feito corretamente, deve aparecer uma mensagem confirmando a detecção do dispositivo. Se isso não ocorreu, confira se houve alguma falha no dito procedimento (ordem dos jumpers, mau contato, etc.), e então clique em “Tools” -> “Check Communication”. Não se deve continuar a partir deste ponto até que o uC não tenha sido detectado. Antes de transferir o código, desabilite a função “Tools” -> “Fast Programming”, pois esse modo está bem mais susceptível a falhas de transferência, além de ser pequena diferença em relação ao modo normal. Para verificar se o código foi transferido corretamente, habilite a função “Programmer” -> “Verify on Write”. Feito tudo, clique em “File” -> “Import Hex”. Escolha o “exemplo1.hex” que foi gerado na compilação e, depois de importado o arquivo, clique finalmente em “Write” para realizar a transferência do código para o uC. Confirmado o sucesso do download, desconecte o plugue ICSP do protoboard, insira os componentes necessários e energize o circuito. Se tudo deu certo, o LED piscará a cada 1 segundo. Os componentes não precisam ser retirados para realizar um novo download. Porém, é recomendado que sempre se inicie a construção de um hardware pelos jumpers do ICSP, pois na ocorrência de uma falha (muito 12
  • comum) deve-se reduzir o hardware para encontrar o defeito. Outra maneira de realizar o download é pelo botão no PICKit2 (hardware). Essa função é habilitada em “Programmer” -> “Write on PICkit Button”. Quando tal botão é pressionado, o arquivo .hex que está sendo apontado no campo “Source” do PICKit2 (software) é transferido para o uC (devidamente conectado). O mesmo efeito é obtido pelo botão “Write”, eliminando a necessidade de toda vez importar o arquivo a ser transferido. Para testar essa funcionalidade, habilite-a primeiro, depois, se o campo “Source” estiver apontando para o “exemplo1.hex”, mude o tempo de delay no programa (compilador) para 100ms e o recompile. Vá agora para o PICKit2 (software) e, observando a tela, pressione o botão no PICKit2 (hardware). O programa percebe que houve uma alteração e realiza um “reloading”. Os exemplos seguintes abordarão apenas o PIC16f876A, por questões de total semelhança. Sempre que for escrever um novo programa que possa se basear no atual, copie o texto deste, clique em “Close All”, depois em “New” -> “Source File”. Cole o texto copiado. 2º EXEMPLO: Push-button /----------------------- push-button ------------------------#include <16F876A.h> #fuses XT #use delay(clock=4000000) set_tris_b(0xff); set_tris_c(0xff); while(true){ if(input(pin_c4)) output_high(pin_a0); else output_low(pin_a0); } #use fast_io(a) #use fast_io(b) #use fast_io(c) void main(void){ set_tris_a(0b11111110); /--------------------------------------------------------------- Explanação: input(pin_c4); 13
  • Realiza a leitura externa do nível lógico no pino mencionado e a retorna para o programa. Figura 7 – Exemplo 2: hardware 16F876 Em uma entrada utilizada para ler um botão, deve-se usar um resistor ligado ao terra. Sem o resistor (chamado de pull-down), se o botão não estiver pressionado, o nivel lógico na entrada não será 0, pois este estará alta impedância. Esse estado pode ser denominado floating (flutuando), onde o sinal não tem um valor fixo, fica variando aleatoriamente em função de ruídos locais. Atenção ao ligar um push-button de 4 terminais no protoboard. Dois de seus terminais estão conectados aos outros dois. Portanto, se um dos terminais foi ligado à 5V, o outro terminal conectado à esse também estará em 5V, inviabilizando o uso do seu ramo no protoboard. 3º EXEMPLO: INTERRUÇÃO EXTERNA Podemos engatilhar uma rotina específica dentro do nosso microcontrolador a partir de sinais externos ou mesmo eventos internos do microcontrolador. Neste exemplo trabalheremos com interrupções externas, que em resumo é a execução de determinada rotina quando houver uma mudança de estado em um pino pré-determinado, sendo esta uma forma mais eficiente de controlar as atividades do microcontrolador por eventos externos, já que desta forma 14
  • não há perda de tempo ao se realizar a leitura do estado do pino a cada ciclo de trabalho . O hardware que utilizaremos a seguir é o mesmo do exemplo anterior ( push-button), exceto pelo fato que o botão deve estar conectado ao pino responsável por chamar interrupções externas , no caso do PIC16F876A é o pino rb0. //--------INTERRUPÇÃO EXTERNA---------------------------------------#include <16F628A.h> #fuses INTRC_IO, NOMCLR #use delay(clock=4000000) #use fast_io(a) #use fast_io(b) }} #INT_RB Void piscaled_int_ext(void) { if(input(pin_b0)) { delay_ms(20); if(input(pin_b0)) { output_bit(pin_a0,cont); if(cont==0) cont=1; else cont=0; }}} short int cont = 0; void main(void){ cont=0; set_tris_a(0xff); set_tris_b(0b11111011); ext_int_edge(L_TO_H); enable_interrupts(GLOBAL); enable_interrupts(INT_RB); while(true){ sleep(); //--------INTERRUPÇÃO EXTERNA---------------------------------------- Explanação: ext_int_edge(L_TO_H); Esta função define que o microcontrolador entrará em interrupção quando o pino de interrupção externa em questão passar do estado 0 para 1( low to high). enable_interrupts(GLOBAL); Função responsável por habilitar qualquer tipo de interrupção que microcontrolador tenha, deve ser usada antes de habilitar qualquer interrupção especificamente. enable_interrupts(INT_RB); Especifica a interrupção que será usada, no caso interrupção externa por alteração de estado nos pinos RB que são responsáveis pelas interrupções externas. 4º EXEMPLO: Display LCD 2x16 //----------------------------- LCD ------------------------------------#include <16F876A.h> #fuses XT #use delay(clock=4000000) 15
  • #use fast_io(a) #use fast_io(b) #use fast_io(c) #include <lcdt.c> #define lig output_high #define des output_low #define seta output_bit #define esp delay_ms #define led pin_a0 #define bot pin_c4 #define bl pin_c7 void main(void){ unsigned int i; set_tris_a(0b11111110); set_tris_c(0b01111111); } lig(bl); des(led); for(i=0;i<6;i++){ seta(led,!input(led)); esp(400); } while(true){ if(input(bot)){ printf(lcd_putc,"f Feliz"); printf(lcd_putc,"n :)"); lig(led); lig(bl); }else{ printf(lcd_putc,"f Triste"); printf(lcd_putc,"n :("); des(led); des(bl); } esp(200); } lcd_init(); printf(lcd_putc,"fCurso de uC PIC "); printf(lcd_putc,"nPET-EngElet.Ufes”); //------------------------------------------------------------// Explanação: #include <lcdt.c> Inclusão da biblioteca do LCD. Esse arquivo deve estar na mesma pasta onde será salvo o programa que a utiliza. #define lig output_high Amarração entre duas entidades. Serve para aumentar a legibilidade do código. O compilador substitui a parte esquerda pela direita no momento da compilação do programa. unsigned int i; Declaração da variável de 8 bits e sem sinal (0 – 255) chamada “i”. lcd_init(); Inicialização da biblioteca do LCD. Essa função configura os pinos da porta B (menos o B0, que pode ser configurado na “main()”), e prepara o LCD para iniciar sua operação. printf(lcd_putc,"fCurso de uC PIC "); Função de impressão no LCD. Existem comandos especiais: f -> Limpa a tela toda e põe o cursor na primeira posição (1,1). • n -> Põe o cursor no início da segunda linha. • b -> Retorna o cursor uma posição. • Os caracteres são impressos sempre na posição corrente do cursor, que pode ser alterada pela função 16
  • “lcd_gotoxy(x,y);”. A função “lcd_getc(x,y);” retorna o atual caractere que ocupa a posição informada. Como os canais B6 e B7 são utilizados para gravação e comunicação com o LCD, deve-ser usar dois resistores para aumentar a impedância de entrada dos terminais PGD e PGC do PICKit2. Sem tais resistores, o LCD e o PIC drenam, juntos, muita corrente do gravador, resultando numa distorção do sinal de gravação. O pino 3 do display é o sinal “V0”, que determina o contraste entre a parte escrita e o plano de fundo da tela. O potenciômetro deve ser regulado para obter-se uma boa visualização. Figura 8 – Hardware Display LCD 5º EXEMPLO: Conversão A/D No instante em que o programa usuário requisita ao módulo um processo de conversão A/D, o corrente valor de tensão na entrada especificada é convertido em um valor digital, para então ser manipulado pelo programa. Neste exemplo o uC será configurado para uma conversão de 8 bits, e o range de tensão a ser convertida será o padrão, ou seja, a própria alimentação do PIC (0–5V). Portanto, essa faixa será dividida em 256 partes iguais, e o resultado de uma conversão será o byte correspondente a parte em que se encontra a tensão convertida. 17
  • Figura 9 – Função de transferência da conversão A/D desse exemplo //------------------------ Voltímetro ----------------------------------#include <16F876A.h> #device adc=8 #fuses XT #use delay(clock=4000000) lcd_init(); printf(lcd_putc,"f Voltimetro "); printf(lcd_putc,"nPETEngElet.Ufes"); esp(2000); output_high(pin_c7); #use fast_io(a) #use fast_io(b) #use fast_io(c) #include <lcdt.c> #define esp delay_ms void main(void){ unsigned int digital; float tensao; while(true){ digital=read_adc(); tensao= 5.0*(float)digital/255.0; printf(lcd_putc,"fT: %1.2f",tensao); printf(lcd_putc,"nD: %3u",digital); esp(500); } } set_tris_a(0b11111111); set_tris_c(0b01111111); setup_adc_ports(ALL_ANALOG); setup_adc(ADC_CLOCK_INTERN AL); set_adc_channel(0); //---------------------------------------------------------------------// Explanação: O PIC16F628A não tem conversor A/D. #device adc=8 Configura a resolução da conversão. Pode ser observado na Figura 9 que a variação mínima que essa conversão pode reconhecer é de aproximadamente 20mV. Isso significa que 18
  • um sinal de 10mV e outro de 15mV serão convertidos para o mesmo valor (0). Isso pode ser ruim para determinadas aplicações. Por exemplo, o sensor de temperatura LM35 fornece em sua saída 10mV/°C. Uma conversão de 8 bits não é capaz de perceber a diferença entre 0 e 1°C. Porém, pode-se melhorar a resolução, utilizando 10 bits. Isso torna o sistema capaz de perceber uma diferença mínima de 5mV (0,5°C). Não se deve esquecer de utilizar uma variável do tipo “unsigned long int” (16 bits), cuja impressão é feita pela sintaxe “%4lu”. Outra forma de melhorar a resolução é diminuir o range do valor a ser convertido. Para isso, são utilizados sinais externos como V REFH e/ou VREFL, os quais podem ser configurados na próxima função. setup_adc_ports(ALL_ANALOG); Configura todas as portas possíveis como analógicas. Neste caso, nenhuma delas poderá ser utilizada como GPIO. Para ver as possíveis configurações do periférico ADC, no IDE CCS, clique com o botão direito em #include <16F876A.h> -> “Open File at Cursor”, e vá à linha 220. Por omissão (default), serão adotados como VREFH e VREFL os sinais de alimentação, VDD e VSS, respectivamente. setup_adc(ADC_CLOCK_INTERNAL); Configura a frequência de operação (fonte de relógio) do hardware. setup_adc_channel(0); Este PIC tem vários canais analógicos, porém apenas um módulo de conversão. Essa função indica o canal que será ligado ao módulo para o próximo processo. Como este exemplo utiliza apenas um canal, este foi configurado na “inicialização” e não sofrerá alteração. Em uma aplicação que utiliza múltiplos canais, esta função deve estar no “loop infinito”, pois será dinâmica. O compilador recomenda que entre a configuração de um canal e a leitura do mesmo deve haver um intervalo de 10μs. Vá no “Help” do compilador (F1), na aba “Index”, e digite: “setup_adc_channel”. Veja o tópico “Examples”. digital=read_adc(); Realiza a conversão A/D do último canal configurado e atribui o resultado do processo à variável “digital”. Note que no programa deste exemplo o backlight do display é ligado após o comando “esp(2000);”. Isso implica que, em algumas vezes, o backlight estará aceso durante a apresentação, e nas outras ele estará apagado. Isso se dá ao fato de que um canal configurado como saída, se não for 19
  • atualizado, assumirá valores imprevisíveis. Essa é uma comum fonte de erros, pois saídas podem iniciar-se indevidamente acionadas e provocar prejuízos. Recomenda-se que, logo após configurar as entras/saídas, se desative todas as saídas. Figura 10 – Hardware ADC 6º EXEMPLO: UART A UART é definida como um periférico de comunicação utilizado para troca de informações entre dispositivos digitais. Este módulo se baseia no protocolo RS-232, o mais popular padrão de comunicação assíncrona, ou seja, entre dispositivos com fontes de relógio distintas. Por isso, a grande maioria dos uCs possuem este hardware integrado. Apesar de seguirem o mesmo protocolo, um uC e uma porta serial de um PC não podem ser diretamente ligados, pois a representação elétrica dos símbolos (bits 0 e 1) não é a mesma. Os uCs utilizam o padrão TTL, ou seja, o bit 0 é representado como 0V e o bit 1 como 5V (ou a alimentação, caso seja diferente). Uma porta serial, bem como aparelhos industriais, reconhecem o sinal lógico 1 um valor entre -25V e -3V, e o sinal lógico 0 um valor entre 3 e 25V (geralmente usa-se ±12V). Essa escolha se dá ao fato de aumentar a relação sinal/ruído, permitindo uma maior taxa de transmissão, maior distância do cabo que conecta os dispositivos, além de diminuir a TEB (taxa de erro de bit). 20
  • Para que um uC e um PC possam trocar informações existe o MAX232, que é um conversor de padrão elétrico para o RS-232. Este módulo também pode ser nomeado como USART, que significa “Universal Synchronous and Asynchronous Receiver-Transmitter”. Nessa configuração, além dos sinais RX e TX, é transmitido também um sinal de relógio, fornecido por apenas um dos nós envolvidos. Uma comunicação síncrona permite uma taxa de transmissão mais elevada, pois o instante da leitura do sinal é bem definido devido à referência do relógio. O exemplo demonstrado aqui abordará a comunicação assíncrona. Em um sistema de comunicação RS-232, todos os elementos envolvidos devem ser configuradados com os mesmos parâmentros, pois só assim o sucesso da transmissão é garantido. Como se os nós da rede “falassem a mesma língua”. Os parâmetros desse protocolo são: • Baud rate: Define a taxa de transmissão e consequentemente o tempo de bit (tbit), que é o inverso desse número. O valor mais comum é 9600 bps, e outros bastante usados são 2400, 4800, 19200 e 115200 bps (bits por segundo). • Start bit: É um parâmetro imutável, unitário, tem valor lógico zero e dura 1 tbit, como todos os outros bits transmitidos. • Payload: També chamado de carga útil, é o dado transmitido. Pode assumir de 5 a 9 bits, e seu valor padrão é 8 bits (1 byte). • Parity bit: Bit de paridade. Tem o papel de identificar erros na transmissão e, se presente, fica entre o payload e o stop bit. Pode ser configurado de três formas: paridade par, paridade ímpar ou ausente. Na paridade par, ele é inserido de forma a tornar o número de ‘1s’ no “payload+parity bit” um valor par. Analogamente funciona a paridade ímpar. Dessa forma, se for enviado o caractere ‘a’ (01100001) e os dispositivos envolvidos foram configurados com paridade par, o bit de paridade assumirá valor ‘1’, totalizando quatro números ‘1s’. Se qualquer um dos bits for lido erroneamente (devido à ruído), o bit de paridade (que no caso é ‘1’) acusará o erro na transmissão, e o dado recebido será descartado. 21
  • Existe, no entanto, a probabilidade (mesmo que ínfima) de dois bits serem alterados numa mesma transmissão, e assim o mecanismo de paridade não detectará o erro. • Stop bit: Pode ser unitário ou duplo, e tem valor lógico um. No PIC, um canal serial pode ser implementado por hardware, ou seja, a UART, ou por software. Nessa última, o programa embarcado é integralmente responsável pela transmissão/recepção dos dados, tornando a CPU indisponível para realizar qualquer outra tarefa durante esse ato. As duas implementações surtem o mesmo efeito (externo) e o receptor segue o mesmo algorítmo para capturar o dado: Figura 11 – Transmissão de um caractere ‘a’ (0x61 ≡ 0b01100001). Oito bits de payload, sem bit de paridade e um stopbit • • • • • A – O canal é constantemente observado. Espera-se o startbit. B – A transição negativa indica o início do startbit. Aguarda-se então ½ tbit. C – O startbit é verificado, pois a transição pode ter sido causada por ruído. Se verdadeiro, aguarda-se 1 tbit e o processo continua. Se não, o processo volta ao estado A. D – Inicia-se o processo de aquisição. O bit menos significativo (LSB) é capturado e aguarda-se 1 tbit para a aquisição do próximo bit. Todos os outros bits do payload são capturados desta mesma forma. E – É verificado o stopbit, pois existe a chance de ruídos terem validado as etapas B e C. Se verdadeiro, o dado recebido é finalmente entregue 22
  • ao programa usuário. Se falso, o dado é descartado. Retorna-se ao estado A. //------------------------------- TX -------------------------------------#include <16F628A.h> #fuses INTRC_IO, NOMCLR #use delay(clock=4000000) #use fast_io(a) #use fast_io(b) #use rs232(baud=9600, rcv=pin_b1, xmit=pin_b2, parity=N) char dado=‘a’; void main(void){ set_tris_a(0xff); set_tris_b(0b11111011); enable_interrupts(GLOBAL); enable_interrupts(INT_EXT); while(true){ sleep(); } } #INT_EXT void trata_int_ext(void){ putc(dado++); if(dado==‘f’) dado=‘a’; delay_ms(200); } ext_int_edge(L_TO_H); //-----------------------------------------------------------------------// //------------------------------ RX --------------------------------------#include <16f876A.h> #fuses XT #use delay(clock=4000000) #use fast_io(a) #use fast_io(b) #use fast_io(c) #include <lcdt.c> #use rs232(baud=9600, rcv=pin_c7, xmit=pin_c6, parity=N) dado=getc(); flag=1; } void main(void){ set_tris_a(0xff); set_tris_c(0b10111111); lcd_init(); enable_interrupts(GLOBAL| INT_RDA); printf(lcd_putc,"fDado: "); short flag=1; char dado=‘k’; while(true){ if(flag){ printf(lcd_putc,"n %c",dado); flag=0; } #INT_RDA void trata_int_rx(void){ 23
  • } } //--------------------------------------------------------------------// Explanação: #use rs232(baud=9600, rcv=pin_b1, xmit=pin_b2, parity=N) Definição dos parâmetros do canal serial criado. Se os pinos mencionados são RX e TX (UART), o canal é implementado por hardware. Porém, pode-se usar quaisquer outros pinos de I/O para se criar um canal. Nesse caso, a implementação é feita por software, e o próprio compilador é o responsável por esta tarefa. char dado=‘k’; Declaração e inicialização da variável global do tipo “char” (8 bits) chamada “dado”. É dita global porque não foi declarada dentro de uma função, e, portanto, pode ser acessada por qualquer uma do programa. ext_int_edge(L_TO_H); Configuração da interrupção externa, realizada pelo pino RB0/INT. O parâmetro “L_TO_H” indica que, se habilitada, a interrupção ocorrerá na transição low to high, ou seja, na borda de subida do sinal externo. Portanto, deve-se usar no pino RB0/INT um push-button com um resistor de pulldown, assim como descrito no segundo exemplo. enable_interrupts(GLOBAL); Para que qualquer interrupção seja acionada, é necessário habilitar a chave interna chamada GLOBAL. Esse mecanismo foi criado para que se possa desabilitar todas as fontes de interrupção (que foram previamente habilitadas) através de um único comando. Assim, se o programa entra em uma região crítica, onde não pode ser interrompido, não se faz necessário desabilitar as interrupções uma a uma. Funciona como um “disjuntor geral” das interrupções. enable_interrupts(INT_EXT); Habilita a interrupção externa. enable_interrupts(INT_RDA); Habilita a interrupção por recepção de dado na UART. sleep(); Coloca o dispositivo em modo sleep. Nesse estado o núcleo pára de trabalhar e alguns periféricos são desabilitados, diminuindo drasticamente o consumo de corrente. O sistema é “despertado” com a ocorrência de uma 24
  • interrupção, que nesse caso é a externa. Nem todas as fontes de interrupção podem acordar o processador, como, por exemplo, o Timer0. #INT_EXT Diretiva que indica ao compilador que a próxima função é a rotina de tratamento para a interrupção externa. No instante em que a interrupção ocorre, o programa principal (main()) pára onde estiver e a função “void trata_int_ext(void)” é executada. Após o seu término, o programa principal volta a ser executado a partir de onde parou. #INT_RDA Indica ao compilador que a próxima função é a rotina de tratamento para a interrupção gerada pela recepção de um dado. putc(dado++); Comando para enviar um caractere através do canal serial criado. Se este canal usa o pino TX como saída, a variável “dado” é simplesmente transferida para o TXREG (registrador de transmissão), pois a UART se encarregará de gerar o sinal correspondente. Para o programa, esta tarefa consome poucos ciclos de relógio, bem como instruções. Porém, se o canal serial utiliza outro pino como saída, o programa é o responsável por gerar o sinal adequado. Neste caso, o compilador insere no código (no momento da compilação) uma função responsável por realizar tal tarefa. Deve ficar claro que, durante a execução dessa função, o processador não pode realizar outra tarefa ou ser interrompido, pois o sinal de transmissão pode ser comprometido. dado=getc(); Transferência do dado recebido por um canal para uma região na memória RAM (variável “dado”). Esta função deve estar no início da ISR (Interrupt Service Routine) referente ao “#INT_RDA”, ou após a validação do teste da flag “kbhit()”, pois é quando um novo dado estará disponível. Esta flag foi criada para indicar o instante em que um dado é recebido, caso não se queira usar a interrupção “ #INT_RDA”, ou caso tenha-se escolhido um canal por software (o qual não pode gerar tal interrupção). Se o canal é por hardware (UART), essa flag é “levantada” no instante em que o stopbit é confirmado, indicando que o dado está disponível no RXREG. No entanto, se a implementação é por software, essa flag é setada assim que se identifica o início do 25
  • startbit. A partir daí a função “getc()” invoca a sub-rotina (criada pelo compilador) responsável por receber o frame. Em ambos os casos, a execução da função “ getc()” faz com que a flag “kbhit()” seja zerada de forma automática, preparando o sistema para uma nova recepção. 7º EXEMPLO: Timer 0 Os Timers são periféricos responsáveis pela contagem de tempo, mas que também podem ser usados para contar pulsos de um sinal externo. São muito úteis quando se deseja um intervalo de tempo preciso entre eventos, pois podem ser configurados pelo programa usuário para contar uma certa quantidade de tempo (manipulada como número de clocks). Podem realizar o papel da função “ delay_ms(X);”, mas o uso de um timer permite que o programa trabalhe em outras atividades enquanto o tempo está sendo contado. Esse exemplo aborda apenas o Timer0, que é um contador crescente e de oito bits (assume valores de 0 a 255). Porém, os outros timers presentes nesses dois PICs funcionam a partir do mesmo mecanismo, com outras poucas particularidades. Para utilizar o Timer0 o programador deve inicialmente configurar a fonte de contagem, a qual está associada a uma freqüência. Para preparar o Timer0 para contar uma certa quantidade de tempo deve-se obter, com uma simples “regra de três”, quantos pulsos da fonte de contagem corresponde ao intervalo de tempo desejado. Como a contagem é crescente, retira-se de 256 o número de pulsos obtido, e o resultado é carregado para o Timer0. A partir disto, o programa habilita a contagem e fica livre para realizar outras atividades, pois o Timer0 estará sendo incrementado na freqüência da fonte de contagem. Quando se atinge o numero 255, o próximo incremento faz com que a contagem assuma o valor zero. Nesse instante o Timer0 acusa o estouro (overflow) ao programa, indicando que se passou o tempo desejado. O programador pode ainda habilitar a ocorrência de uma interrupção no momento do estouro, fazendo com que seja executada uma função específica nesse instante. Por fim, o programa usuário pode também ler o valor atual do Timer0 para saber quanto tempo se passou desde o início da contagem. 26
  • Figura 12 – Interface entre o programa usuário e Timer0 Por exemplo, se a fonte de contagem tem freqüência de 1MHz e se deseja “agendar” um estouro para daqui a 150μs, deve-se carregar no Timer0 o número 106 (=256150), pois será contado de 106 até 256 (que corresponde ao zero devido ao overflow). No entanto, se é necessário que os estouros ocorram periodicamente com intervalos de 150μs, deve-se carregar o Timer0 a cada estouro. Senão, após o primeiro estouro será contado de 0 a 256, totalizando 256μs. Normalmente usa-se a fonte de relógio interna para contagem, pois não necessita de hardware adicional. E para que se possa trabalhar com várias faixas de freqüências existe o mecanismo de “prescale”. O valor atribuído a esse parâmetro será usado como divisor do clock interno. Portanto, se é desejado que o Timer0 seja incrementado com a metade da freqüência interna, o “prescale” deve configurado com o número 2. Pode-se usar os valores 2, 4, 8, 16, 32, 64, 128 ou 256. Eis outro exemplo para esclarecer o funcionamento: deseja-se obter eventos com intervalos de 2048μs. Sabe-se que nos exemplos abordados neste documento usa-se freqüência de 4MHz (Fosc), e que a freqüência interna desses PICs (Fcy) são ¼ da Fosc, ou seja, 1MHz (vide página 3). Portanto, a partir de Fcy, só é possível contar até 256μs. Para atingir o intervalo desejado pode-se utilizar um contador para saber quantos estouros se passaram, ou ajustar o prescale, o que é bem mais simples. Se esse for configurado com o valor 8, o Timer0 será incrementado a cada 8μs (Fcy/8), e, portanto, sua contagem pode ser de 27
  • até 2048μs, que é o intervalo desejado. Para este caso não é necessário carregar nenhum valor ao Timer0, pois este deve contar de 0 até 256 (que é o zero da próxima contagem). Figura 13 – Comportamento temporal do Timer0 para este subexemplo O primeiro exemplo deste documento (Pisca LED) pode ser implementado com o uso do Timer0, sem a necessidade do programa ter que trabalhar contando o tempo, e com maior precisão. Isso ocorre no próximo código, que faz um LED piscar a 0,5Hz. //-------------------------- Pisca LED ---------------------------------#include <16F876A.h> #fuses XT #use delay(clock=4000000) #use fast_io(a) #use fast_io(b) #use fast_io(c) short led; unsigned int cont; #INT_TIMER0 void trata_tmr0(void){ set_timer0(131+get_timer0()); if(++cont==125){ cont=0; led=!led; output_bit(pin_a0,led); } } void main(void){ set_tris_a(0b11111110); set_tris_b(0b11111111); set_tris_c(0b11111111); setup_timer_0(RTCC_INTERNAL| RTCC_DIV_64); set_timer0(131); 28
  • enable_interrupts(GLOBAL| INT_TIMER0); } } while(true){ //---------------------------------------------------------------------// Explanação: Nesse programa o Timer0 é incrementado a cada 64μs e o período entre estouros corresponde a 125 incrementos, ou seja, 8ms. Um contador é usado para indicar que se passaram 125 (coincidência) estouros desde a última alteração da saída ligada ao LED. Detectada essa ocorrência, a saída tem seu nível lógico alterado e o contador é zerado para recomeçar uma nova contagem. Portanto, o estado do LED muda a cada 64μs x 125 x 125 = 1 segundo. #INT_TIMER0 Indica ao compilador que a próxima função é a rotina de tratamento para a interrupção gerada pelo estouro do Timer0. set_timer0(131+get_timer0()); A cada estouro o programa carrega o Timer0 com o valor 131. Faz a contagem correspondente ao período de estouro ser de 131 a 256, ou seja, 125. A função “ get_timer0()” serve para aumentar a precisão da contagem, pois a função “set_timer0(131+get_timer0());” não é executada exatamente no momento do estouro. Existe um pequeno intervalo de tempo entre o estouro e a execução da rotina de tratamento, denominado “tempo de latência da interrupção”. Supõe-se que esse pequeno intervalo corresponde a 4 incrementos do Timer0. Se não houvesse a função “get_timer0()”, o Timer0 seria carregado com o valor 131, porém nesse instante já haveria se passado 4 incrementos desde o último estouro. Isso faria com que o os estouros ocorressem a cada 129 incrementos, o que destoa do cálculo realizado. O uso de “get_timer0()” implica que o valor carregado seja 135 e a contagem até o próximo estouro seja de 121 incrementos. Esse intervalo mais o tempo de latência ocasionam 125 incrementos entre estouros. if(++cont==125){ 29
  • Teste que indica se passaram 125 incrementos da variável “conta” desde a última alteração da saída. setup_timer_0(RTCC_INTERNAL|RTCC_DIV_64); Configura o clock interno como fonte de contagem e o prescaler com 64, além de habilitar o funcionamento do Timer0. Entretanto, a interrupção só ocorrerá se for habilitada, o que é feito na próxima função. enable_interrupts(GLOBAL|INT_TIMER0); Habilita a interrupção referente ao estouro do Timer0. Figura 14 – Comportamento temporal do Timer0 e cont para esse exemplo 30
  • Apêndice: Tabela ASCII: Quando se utiliza a comunicação serial os caracteres enviados entre dispositivos são codificados de acordo com a tabela ASCII, enviando-se o valor binário correspondente ao caractere desejado. • Figura 15 – Tabela de símbolos ASCII e seus respectivos valores em decimal, octal e hexadecimal • Pinagem PIC 16f628 e 16f876a Podemos extrair inúmeras informações do dispositivo olhando somente para a configuração de pinos.  VDD/VSS Pinos utilizados para a alimentação do dispositivo, sendo VDD a alimentação positiva e VSS a alimentação negativa. Deve ser verificado sempre a tensão a ser utilizada. Normamlemte essa pode ser de 2.2 V a 5.5 V, porém alguns modelos não podem ultrapassar um valor máximo de 3.6 V.  VPP/MCLR Pino ativação do chip. Este só estará ligado se a tensão nesse pino for VDD. 31
  •          Ry#: Exemplo : y: Port ao qual o pino está ligado. Para os dispositivos abordados nesse material vemos os ports A, B e C #: "Posição" do pino no port. Comumente vemos números de 0 a 7, formando os ports de 8 bits (0 a 7). Os pinos com essa nomenclatura são utilizados como entradas e/ou saídas digitais. Deve-se consultar quais destes podem ser utilizados como entrada ou saída. Como exemplo o pino RA5 do PIC 16f628 pode ser utilizado somente como entrada. RX/TX: Pinos ligados à comunicação serial (RS232), diretamente ligados ao periférico UART ou USART, que tira a carga da comunicação da rotina principal. O pino RX é utilizado para o recebimento de dados e o pino TX utilizado para enviar dados. AN#: Pinos com essa nomeclatura são utilizados como canais de conversão A/D, sendo # o número do canal. VREF+/VREFEstes pinos são utilizados para definir limeites superiores e inferiores à conversão A/D, respectivamente. Se fizermos V REF+ = 3V e VREF-=1V, somente valores analógicos entre 1 e 3 V serão considerados, e toda a resolucão será concentrada nesse intervalo, de modo que para uma conversão de 8 bits, uma leitura de 0 representará 1V e uma leitura de 255 representará 3V T#CKI/ T#OSO/ T#OSI: Os pinos T#CKI e T#OSI são utilizados como base de clock para o timer # do microcontrolador, sendo que para um mesmo timer somente um deve ser utilizado. O T#OS0 é utilizado para externar a base de clock do timer #. OSC1/OSC2 Pinos de entrada para o oscilador quando se utiliza um cristal. CLKIN/CLKOUT Quando se utiliza um oscilador capacitivo ou uma onda quadrada como base de tempo do PIC, deve-se conectar esse sinal ao pino CLKIN. Este pode ser externado pelo pino CLKOUT. CMP#/CCP# Pinos diretamente ligados ao periférico captura/comparação/PWM, sendo sua função configurada por software para gerar um sinal de frequência fixa e tempo alto/baixo variáveis(PWM), guardar o valor de contagem do timer associado com um estímulo externo(captura) e controlar o pino associado quando o contador chegar ao valor desejado(comparação). SS/SCK/SDI/SDO Pinos associados à comunicação SPI, sendo SS (slave select) o pino de seleção do dispositivo a transmitir, SCL o pino de clock 32
  •    compartilhado, SDI e SDO os canais de recebimento e transimssão, respectivamente. SDA/SCL Pinos associados à comunicação I2C, sendo SCL o pino de clock compartilhado e SDA o pino de comunicação bidirecional. CK/DT Os pinos CK e DT são utilizados para comunicar-se pela USART de forma síncrona, sendo CK o clock e DT o canal de dados. PGC/PGD/PGM Pinos associados à gravação do chip. Em PGC deve ser fornecido o clock, em PGD os dados a serem programados e PGM é utilizado somente para gravação de baixa tensão.  INT Pino utilizado para a interrupção externa. Se houver uma variação nesse pino a rotina de interrupção é engatilhada. FIN. 33