ROGERCOM – Curso USB – [Link].
com
Trabalhando com o microcontrolador PIC 16F877
A partir desta aula irei dar informações importantes sobre as ferramentas
necessárias para se programar um microcontrolador PIC 16F877. O objetivo aqui
não é ensinar sobre os detalhes dos microcontroladores, pois seria necessário
escrever um outro módulo para falar sobre o assunto. O que irei ensinar são noções
básicas sobre as ferramentas necessárias para se programar e gravar um
microcontrolador PIC, usando a linguagem C.
A linguagem nativa de programação de microcontroladores é o Assembler, pois
seus comandos de programação estão associados diretamente a endereços de
memória e registradores do microcontrolador. Por esse motivo, um programa escrito
em Assembler será compacto e, assim, será executado mais rapidamente. Há
algumas desvantagens em se programar todo um código em Assembler, como a
demora na programação (pois, temos que programar endereço por endereço de
memória), dificuldade em se dar manutenção no código futuramente, pois a
programação é baseada em saltos GOTOs, as rotinas ficam todas "amarradas"; não
é um código portável (por exemplo, se for preciso migrar para um outro modelo de
microcontrolador, é preciso criar um novo programa a partir do zero, pois a maioria
das rotinas estão associadas a endereços físicos de memória,do modelo do
microcontrolador anterior.
Antigamente os microcontrolaores tinham pouco espaço de memória para
armazenar programas e, portanto, era necessário economizá-la ao máximo, para
que se pudesse armazenar o código do programa. Nessa condição o compilador
Assembler era de suma importância para gerar um código pequeno. Mas, com a
evolução da microeletrônica, hoje em dia, os microcontroladores dispõem de uma
quantidade muito maior de memória de programa. Agora, com os microcontroladores
podendo armazenar mais códigos de programa, é muito mais produtivo e seguro,
programar usando uma linguagem de alto nível como o C, que é portável, rápida,
compacta e eficiente, sem falar na facilidade em se dar manutenção no código
futuramente.
O Assembler nunca deve ser desprezado, pois, quando necessitarmos de uma
rotina que precise de toda a velocidade que o hardware dispõe para melhorar a
eficiência de uma rotina, poderemos criar esta em Assembler e chamá-la a partir da
linguagem C.
Para o desenvolvimento de nossos projetos iremos usar o PIC 16F877, pois
este tem 33 I/O (entradas/saídas), e vários periféricos como: USART, EEPROM,
Timers, PWM, A/Ds, PSP, comunicação síncrona e assíncrona. Assim, poderemos
controlar relês, LEDs, sensores A/C, comunicação serial, I2C, Paralela, etc., usando
um único modelo de microcontrolador para desenvolvermos todos os projetos
propostos neste curso. Quem desejar poderá criar os projetos usando outros
microcontroladores e compiladores disponíveis no mercado.
Aula 15 – Módulo-USB / Página 137
ROGERCOM – Curso USB – [Link]
Neste tópico do curso iremos usar as seguintes ferramentas para programar o
microcontrolador PIC 16F877:
O ambiente MPLAB IDE da Microchip versão 7.21 ou superior;
O compilador PCW C Compiler IDE da CCS versão Trial 30 dias;
Uma gravador serial que dê suporte ao PIC 16F877 e grave a partir
do MPLAB (PICStart Plus).
Obs.: Os programas dessa aula foram gravados no PIC 16F877, usando o gravador
McFlash da Mosaico ([Link]).
Antes de começarmos a programar, vamos conhecer algumas características do
microcontrolador PIC 16F877, como, as portas disponíveis, o nome e o número de
cada pino, o tamanho da memória de programa entre outras.
Quem deseja se aprofundar na programação de microcontroladores PIC usando
a linguagem C, pode aprender muito através do livro "Microcontroladores PIC -
Programação em C" escrito pelo autor Fábio Pereira - Editora Érica. Todos os
programas exemplos desse livro foram escritos com base nos compiladores da CCS,
onde há uma biblioteca de funções para facilitar a programação.
Nesta aula são apresentados vários circuitos exemplos e seus respectivos
programas fonte. Já na próxima aula iremos aprender a usar o compilador da CCS e
o MPLAB para efetuar a gravação no PIC 16F877.
Características básicas do PIC 16F877:
- 33 pinos de I/O;
- 8k de memória de programa FLASH;
- 368 bytes de memória RAM;
- 256 bytes de memória EEPROM;
- Velocidade máxima de trabalho 20Mhz;
- Watchdog timer (WDT);
- 3 Timers;
- 8 canais A/D de 10 bits;
- 1 USART síncrona/assíncrona;
- Porta Paralela escrava (PSP);
- Porta serial síncrona SSP, SPI e I2C;
Entre outras.
Aula 15 – Módulo-USB / Página 138
ROGERCOM – Curso USB – [Link]
Figura 1 - Pinagem do PIC 16F877
A figura acima mostra todos os pinos do PIC 16F877, com seus respectivos
nomes/funções. Os pinos (RA0 a RA5) estão associados a porta 'A'. (RE0, RE1 e
RE2) a porta "E". (RC0 a RC7) a porta "C". (RD0 a RD7) a porta "D". E por último, os
pinos (RB0 a RB7) associados a porta "B". Cada um destes pinos pode ser usado
como entrada ou saída e são definidos na programação. Observe que a maioria dos
pinos teem mais de uma função. Como exemplo veja o pino 10 que teem as funções
de entrada/saída digital (RE2) e de selecionar um chip SPI (CS); ou a função de um
canal A/D (AN7).
Figura 2 - Simples diagrama interno do PIC16F877
Como um simples exemplo didático da estrutura interna do microcontrolador PIC
16F877, é possível observar na figura acima, que ele é composto de vários módulos
como: uma unidade lógica e de controle, memórias RAM e ROM, Portas de entradas
e saídas e também vários periféricos de grande utilidade para a implementação de
muitos projetos interessantes. Grosso modo, um microcontrolador tem várias
Aula 15 – Módulo-USB / Página 139
ROGERCOM – Curso USB – [Link]
características de um microcomputador. Sendo assim, em alguns projetos, não
necessitamos de um computador para executar determinadas funções, pois o
microcontrolador dispõe do que é preciso para o projeto funcionar.
Se desejarmos obter uma informação processada pelo microcontrolador de
forma visual, poderemos conectar a ele um display LCD, LEDS ou mesmo conectar
a um microcomputador através de uma de suas portas: Serial, Paralela, USB etc.
Descrição de algumas funções do compilador CCS usadas aqui em nossos
exemplos:
Função set_timer0()
Altera o conteúdo interno do timer0. Esta função aceita como parâmetro uma
variável ou uma constante de 8 bits.
Exemplo:
set_timer0(131); //inicializa o valor do timer0 em 131.
Função get_timer0()
Obtém o valor interno do timer0. Ela retona um valor de 8 bits.
Exemplo:
int8 valor;
valor = get_timer0();
Função set_tris_d()
Escrita e leitura na porta D. Ou seja, define se um ou vários pinos são entradas ou
saídas na porta D.
Um bit '1' indica que o respectivo pino associado a uma porta será usado como
entrada (Input). E um bit '0', saída (Output). O parâmetro pode ser passado à função
set_tris_d() também no sistema de numeração binário usando a constante 0b.
Exemplo:
set_tris_d(0b01000001); //Os pinos RD6 e RD0 são definidos como entradas e os demais como
saídas.
ou em hexadecimal:
set_tris_d(0x41);
Função setup_timer_0()
Configura o prescaler do timer0. O parâmetro passado é uma constante
predeterminada usada para dividir o sinal de contagem por um prescaler (divisor de
freqüência).
Exemplo:
setup_timer_0(RTCC_INTERNAL | RTCC_DIV_64); //Informa que o timer0() usa
interrupção interna, e que a freqüência é dividida por 64 (prescaler).
Função enable_interrupts()
Usada para habilitar uma interrupção.
Exemplos:
enable_interrupts( GLOBAL ); //Habilita o sistema de interrupção geral.
Aula 15 – Módulo-USB / Página 140
ROGERCOM – Curso USB – [Link]
enable_interrupts( int_timer0 ); //Habilita interrupção do timer0().
enable_interrupts( int_rda ); //Habilita interrupção para leitura de caracter pela USART.
ou
enable_interrupts(GLOBAL | int_timer0 | int_rda); //Ou todas através do operador OR ' |'.
Função input()
Lê o estado de um pino de uma determinada porta.
Exemplo:
boolean estado;
estado = input(pin_d2); //Lê o estado do pino RD2 da porta D.
Se o valor da variável estado for '0' (false), isso indica que o nível lógico do pino RD2
é baixo (low). Se for '1' (true), o nível lógico no pino RD2 é alto (high).
Função output_bit()
Liga ou desliga um determinado pino.
Essa função aceita dois parâmetros: o primeiro é o pino e o segundo é se ele será
ligado ou desligado.
Exemplos:
output_bit(pin_d0, 1); //Põe pino RD0 em nível alto (high). Pode-se usar a constante true em
vez de '1'.
output_bit(pin_d0, 0); //Põe pino RD0 em nível baixo (low). Pode-se usar a constante false em
vez de ' 0'.
Função output_high()
Liga (põe em nível alto) um determinado pino.
Exemplos:
output_high(pin_d2); //Põe o pino RD2 da porta D em nível Alto.
É o mesmo que usar:
output_bit(pin_d2, true);
Função output_low()
Desiga (põe em nível baixo) um determinado pino.
Exemplos:
output_low(pin_d2); //Põe o pino RD2 da porta D em nível Baixo.
É o mesmo que usar:
output_bit(pin_d2, false);
Função delay_ms()
Retarda o processamento do programa em milisegundos.
Exemplos:
delay_ms(15); //Aguarda 15 milisegundos.
delay_ms(1000); //Aguarda 1 segundo.
Função getc()
Aguarda e chegada de um caracter na porta serial (USART).
Exemplo:
char valor;
valor = getch(); //Espera a chegada de um caracter na Serial e armazena-o na variável valor.
Aula 15 – Módulo-USB / Página 141
ROGERCOM – Curso USB – [Link]
Função printf()
Envia um ou mais caracteres formatados, pela porta serial (USART). Usa os
mesmos caracteres padrão de formatação da função printf() ANSI.
Exemplos:
printf("Curso USB/Serial.\r\n"); //Envia uma sting constante.
printf("%d", 85); //Envia um número decimal.
int valor = 0x3F;
printf("%X", valor); //Envia um número hexadecimal.
char *texto = "Teste de transmissão.\r\n";
printf("%s", texto); //Envia uma string a partir de uma variável
Diretivas
#int_rda
Usada para definir uma função de interrupção para leitura dos caracteres que
chegam no buffer da porta serial.
#include <16F877.h>
inclui o arquivo cabeçalho usado para se trabalhar com o PIC 16F877. Esse arquivo
contém várias constantes e definições úteis.
#use delay(clock=4000000)
Define o valor do relógio do PIC. Este valor tem que ser idêntico ao da freqüência do
cristal ou resonador externo. Para usar as funções delay_us(), delay_ms(),
delay_cycles() é necessário definir #use delay().
#fuses XT, PUT, NOWDT, NOBROWNOUT, NOLVP
Esta diretiva define quais fusíveis interno do PIC são configurados. Os parâmetros
são armazenados no arquivo .HEX e são usados somente na hora da gravação.
Veja o significado de alguns fusíveis:
Tipo de osciladores:
LP - Low Power (oscilador < 200Khz);
RC - Oscilador Resistor/Capacitor;
XT - Oscilador a cristal <= 4Mhz;
HS - High Speed (alta velocidade) > 4Mhz.
Outros fusíveis:
PUT ---> (Power Up Timer). Temporizador de power up ligado;
NOPUT ---> (No Power Up Timer). Temporizador de power up desligado;
Aula 15 – Módulo-USB / Página 142
ROGERCOM – Curso USB – [Link]
WDT ---> (Watch Dog Timer) habilitado. O programa é resetado se por algum
motivo, travar;
NOWDT ---> (No Watch Dog Timer) desabilitado. Se travar, o programa não é
resetado;
BROWNOUT ---> Reset por queda de tensão habilitado. Sempre que a alimentação
cai a um valor mínimo;
NOBROWNOUT ---> Reset por queda de tensão desabilitado. O PIC não é
resetado;
LVP ---> Programação em baixa tensão habilitada;
NOLVP ---> Programação em baixa tensão desabilitada;
PROTECT ---> Protege todo o código do programa contra leitura;
NOPROTECT ---> Desabilita a proteção do código do programa contra leitura.
Obs.: Nem todos os modelos de PIC aceitam todas as constantes de fusíveis.
#use rs232(BAUD=9600, PARITY=N, BITS=8, XMIT=pin_c6, RCV=pin_c7)
Configura os parâmetros da comunicação serial.
BAUD ---> Velocidade da transmissão (depende da freqüência do cristal).
PARITY ---> Paridade (N=nenhuma, E=par, O=ímpar);
BITS ---> Bits de dados (5, 6, 7, 8 ou 9);
XMIT ---> Especifica o pino que irá transmitir os dados ( no 16F877 o pin_c6 é fixo
usando o USART);
RCV ---> Especifica o pino que irá receber os dados ( no 16F877 o pin_c7 é fixo
usando o USART);
Obs.: É possível escolher outros pinos para XMIT e RCV, porém, todo o processo
de transmissão e recepção de dados ficará por conta do software.
Definindo a direção dos pinos de I/O
No PIC, os pinos de I/O podem ser definidos tanto como entradas como saídas,
sendo assim, através de um único pino, podemos envia ou receber um sinal, para
isso, é preciso definir a direção do pino através da função set_tris_X(). No nosso
exemplo abaixo estamos usando a porta D. Se desejarmos definir todos os pinos da
porta D como entradas, usamos a função set_tris_d(0b11111111). Para definirmos
todos os pinos da porta D como saídas, usamos set_tris_d(0b00000000) com todos
os bits zeros. Os bits estão associados aos pinos (RD7 a RD0) da direita para a
esquerda, ou seja, bit mais significativo igual a RD7, e menos significativo igual a
RD0.
Aula 15 – Módulo-USB / Página 143
ROGERCOM – Curso USB – [Link]
Figura 3 - Direção dos pinos da porta D
Exemplos de circuitos e programação usando o PIC16F877
Para assimilarmos mais conhecimentos sobre o PIC 16F877, vamos praticar
através do desenvolvimento de pequenos circuitos, para ligar/desligar LEDs e lê
botões. Isso é suficiente para quem nunca programou um microcontrolador, para
levar adiante um estudo mais abrangente e preciso sobre o assunto.
Um bom conhecimento da linguagem C ou do Assembler é de suma importância
para o desenvolvimento de projetos eficientes e seguros. Mas tudo precisa de um
começo. Aproveite, pois o começo é agora.
Figura 4 - Circuito para piscar um LED
O circuito acima esquematiza a configuração básica para fazer o PIC 16F877
funcionar eletricamente. Os capacitores C1 e C2 podem assumir valores entre 15 a
33pF e são usados em paralelo com o cristal de 4Mhz para estabilizá-lo. O pino 1
ligado ao +5v da fonte de alimentação através do resistor de 1K é usado para
resetar o PIC.
O LED1 conectado ao pino 21 (RD2) será controlado pelo programa mostrado
no Fonte 1 logo abaixo:
Aula 15 – Módulo-USB / Página 144
ROGERCOM – Curso USB – [Link]
Fonte 1 CCS Compiler - faz o LED da figura 4 piscar (uso de loop while() e delay_ms())
#include <16F877.h>
#use delay(clock=4000000)
#fuses XT, PUT, NOWDT, NOBROWNOUT, NOLVP
void main(void)
{
set_tris_d(0b00000000); //Todos os pinos da porta 'D' como saídas.
while( true ) //Loop infinito.
{
output_high(pin_d2); //Liga LED.
delay_ms(1000); //espera 1 segundo.
output_low(pin_d2); //Desliga LED.
delay_ms(1000); //espera 1 segundo.
}
}
O programa Fonte 1 acima deve ser compilado e gravado no PIC 16F877 para
controlar o circuito da Figura 4, fazendo com que o LED1 pisque em intervalos de 1
segundo.
A função set_tris_d(0b00000000); define todos os pinos da porta D como
saídas. A função output_high(pin_d2) liga o LED1, e output_low(pin_d2) desliga-o.
Delay_ms(1000) mantém o LED1 aceso ou apagado por 1 segundo. While() entra
num loop infinito para manter o programa em execução.
Fonte 2 - CCS Compiler - faz o LED da figura 4 piscar (uso do Timer0)
#include <16F877.h>
#use delay(clock=4000000)
#fuses XT, PUT, NOWDT, NOBROWNOUT, NOLVP
#int_timer0
void MeuTimerPiscaLED()
{
static boolean PiscaLed; //para piscar o led.
static int contador; //para contar o tempo de 1 segundo.
set_timer0(131-get_timer0());
contador++;
if(contador == 125) //Se contou 125 (1 segundo).
{
contador=0; //zera contagem.
PiscaLed = !PiscaLed; //Inverte bit;
output_bit(pin_d0, PiscaLed); //Liga/desliga LED.
}
}
//----------------------------------------------------------------------
//Programa principal.
//----------------------------------------------------------------------
void main(void)
{
set_tris_d(0b00000000); //Todos os pinos da porta 'D' como saídas.
setup_timer_0(RTCC_INTERNAL | RTCC_DIV_64); //Define
prescaler.
Aula 15 – Módulo-USB / Página 145
ROGERCOM – Curso USB – [Link]
set_timer0(131); //Inicializa timer0 com 0 valor 131.
enable_interrupts( global|int_timer0 ); //Habilita interrupções.
while( true ) //Loop infinito - aguarda interrupção acontecer.
{
//Aqui pode ser executado o fluxo normal do programa
//independente do Timer0.
}
}
O programa Fonte 2 acima faz o mesmo que o programa Fonte 1. A diferença é
que este usa o Timer0, para fazer o LED1 piscar independente do fluxo normal no
corpo do programa principal main(). Ou seja, é como se dois programas estivessem
sendo executados ao mesmo tempo. Quanto mais veloz for o cristal usado, melhor
será a performance da execução das instruções pelo PIC.
Os parâmetros das funções setup_timer_0() e set_timer0() definem o prescaler
e o valor que inicializa o Timer0. Os parâmetros da função enable_interrupts()
habilitam o sistema de interrupção geral e a interrupção do Timer0.
Figura 5 - Circuito para lê o estado de um botão e acender um LED
O circuito acima usa dois pinos do PIC: o pino 27 (RD4) é conectado ao botão
(interruptor K) e ligado a um resistor pull-up para manter a estabilidade do sinal de
entrada no pino. O pino 21 (RD2) é usado para controlar o acendimento do LED1.
Para comprovar o funcionamento do circuito, o botão (interruptor K) deverá ser
pressionado para que o LED1 acenda e, solto para que o mesmo se apague.
Aula 15 – Módulo-USB / Página 146
ROGERCOM – Curso USB – [Link]
Fonte 3 - CCS Compiler - Programa para gravar o PIC e controlar o circuito da Figura 5
#include <16F877.h>
#use delay(clock=4000000)
#fuses XT, PUT, NOWDT, NOBROWNOUT, NOLVP
//---------------------------------------------------------------------------
//Programa Principal.
//---------------------------------------------------------------------------
void main(void)
{
boolean TravaPress = true; //Inicia variável para controle do botão.
set_tris_d(0b00010000); //somente o pino d4 da porta 'D' como entrada.
while( true ) //Loop infinito.
{
if( input(pin_d4) == 0) //Se leu '0' (zero) o botão foi pressionado.
{
if(TravaPress == true) //Para executar o código abaixo uma única vez.
{
TravaPress = false; //Nega a condição acima.
output_high(pin_d2); //Liga LED.
delay_ms(15); //bounce - retardo para eliminar ruído ao pressionar botão.
}
}else{ //Se o botão foi solto.
if(TravaPress == false) //Para executar o código abaixo uma única vez.
{
TravaPress = true; //Nega a condição acima.
output_high(pin_d2); //Desliga LED.
delay_ms(15); //bounce - retardo para eliminar ruído ao pressionar botão.
}
}
}
}
O programa acima deve ser compilado para gravar o PIC 16F877 e controlar o
circuito da Figura 5. O funcionamento é simples: quando o botão (interruptor K) é
mantido pressionado, o LED1 permanece aceso. Quando o interruptor é solto o
LED1 é apagado.
Já o programa funciona assim: enquanto input(pin_d4) retorna o valor '0' (botão
pressionado) a variável TravaPress se torna false, e a função output_high(pin_d2)
liga o LED1. Mesmo que o botão permaneça pressionado as funções dentro do
corpo do if(TravaPress == true){ } não são mais processadas. Quando o botão
(interruptor K) for solto, a função input(pin_d4) retornará um valor '1' fazendo com
que as instruções dentro do corpo { } do 'else' sejam processadas, executando a
função output_low(pin_d2) desligando o LED1.
Aula 15 – Módulo-USB / Página 147
ROGERCOM – Curso USB – [Link]
Figura 6 - Conectando o PIC 16F877 na porta RS232 do PC
O circuito acima é conectado à porta RS232 do computador por meio do driver
Max232. O LED1 é ligado ou desligado através do computador. O botão (interruptor
K) é usado para enviar uma string através da RS232 quando o mesmo é
pressionado ou solto.
Aula 15 – Módulo-USB / Página 148
ROGERCOM – Curso USB – [Link]
Figura 7 - Conectando o PIC 16F877 no Bus USB através do FT232BM
O circuito da Figura 7 acima funciona da mesma forma que o circuito da Figura
6, com a única diferença de que este acima funciona conectado ao Bus USB,
através do circuito Bus powered da Aula 3 - Figura 5, que usa o chip FT232BM.
Fonte 4 - CCS Compiler - Programa para controlar os circuitos das Figuras 6 e 7.
#include <16F877.h>
#use delay(clock=4000000)
#fuses XT, PUT, NOWDT, NOBROWNOUT, NOLVP
#use rs232(BAUD=9600, parity=N, BITS=8, XMIT=pin_c6, RCV=pin_c7)
//---------------------------------------------------------------------------
//Interrupção de recebimento de caracter pela UART.
//---------------------------------------------------------------------------
#int_rda
void Serial_Detecta_Car()
{
char ch;
ch = getc(); //Pega o caracter no registrador da UART.
if( ch == 'l' ) //Se recebeu o caracter L.
{
output_high(pin_d1); //Liga LED.
}
if( ch == 'd' ) //Se recebeu o caracter D.
{
output_low(pin_d1); //Desliga LED.
}
}
//---------------------------------------------------------------------------
//Programa Principal.
//---------------------------------------------------------------------------
Aula 15 – Módulo-USB / Página 149
ROGERCOM – Curso USB – [Link]
void main(void)
{
boolean TravaPress = true; //Inicia variável para controle do botão.
enable_interrupts(int_rda); //Habilita interrupção de recebimento.
enable_interrupts(GLOBAL); //Habilita registrador de interrupção.
set_tris_d(0b00000100); //somente o pino 2 da porta 'D' como entrada.
while( true ) //Loop infinito.
{
if( input(pin_d2) == 0) //Se leu '0' (zero) o botão foi pressionado.
{
if(TravaPress == true) //Para executar o código abaixo uma única vez.
{
TravaPress = false; //Nega a condição acima.
printf("Botão pressionado.\r\n"); //Envia para a porta rs232.
delay_ms(15); //bounce - retardo para eliminar ruído ao pressionar botão.
}
}else{ //Se o botão foi solto.
if(TravaPress == false) //Para executar o código abaixo uma única vez.
{
TravaPress = true; //Nega a condição acima.
printf("Botão solto.\r\n"); //Envia para a porta rs232.
delay_ms(15); //bounce - retardo para eliminar ruído ao pressionar botão.
}
}
}
}
O programa acima funciona de forma semelhante ao do Fonte 3, mas com
algumas diferenças: o LED1 é aceso quando o caracter 'l'-ligado for enviado pelo
computador, tanto pela RS232 como pelo Bus USB. Esse LED é apagado quando
for enviado o caracter 'd'-desligado. A leitura desses caracteres é feita pela função
getc() que lê um caracter através do buffer do USART do PIC 16F877. Quando o
botão (interruptor K) for pressionado, a string "Botão pressionado.\r\n" é enviada ao
computador através do USART, pela função printf(). Quando for solto, é enviada a
string "Botão solto.\r\n".
Após a compilação, use o programa Fonte 4 para gravar o PIC16F877 nos
circuitos da Figura 6 e da Figura 7.
Na próxima aula iremos usar o MPLAB da Microchip ®, o compilador da CCS ®
e uma placa para gravar o PIC 16F877.
Aula 15 – Módulo-USB / Página 150