Escolar Documentos
Profissional Documentos
Cultura Documentos
29 de Agosto de 2012
1
Licenciado sobre Criative Commons Attribution-NonCommercial-NoDerivs
Conteúdo
1 Introdução 1
. Linguagem C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
. Hardware utilizado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
. Ambiente de programação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Instalação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Configuração do gravador ICD2 . . . . . . . . . . . . . . . . . . . . . . . 4
Criação de um novo projeto . . . . . . . . . . . . . . . . . . . . . . . . . 5
. Indentação e padrão de escrita . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
. Comentários . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
. Arquivos .c e .h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
. Diretivas de compilação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
#include . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
#define . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
#ifdef, #ifndef, #else e #endif . . . . . . . . . . . . . . . . . . . . . . . . 12
. Tipos de dados em C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Representação binária e hexadecimal . . . . . . . . . . . . . . . . . . . . . 14
Modificadores de tamanho e sinal . . . . . . . . . . . . . . . . . . . . . . . 15
Modificadores de acesso . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Modificadores de posicionamento . . . . . . . . . . . . . . . . . . . . . . . 17
Modificador de persistência . . . . . . . . . . . . . . . . . . . . . . . . . . 17
. Operações aritméticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
. Função main() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
. Rotinas de tempo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
. Operações com bits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
NOT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
AND . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
OR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
XOR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Shift . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Ligar um bit (bit set) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Desligar um bit (bit clear) . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Trocar o valor de um bit (bit flip) . . . . . . . . . . . . . . . . . . . . . . 26
Verificar o estado de um bit (bit test) . . . . . . . . . . . . . . . . . . . . 27
Criando funções através de define’s . . . . . . . . . . . . . . . . . . . . . 28
. Debug de sistemas embarcados . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Externalizar as informações . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Programação incremental . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Checar possíveis pontos de Memory-leak . . . . . . . . . . . . . . . . . . . 33
Cuidado com a fragmentação da memória . . . . . . . . . . . . . . . . . . 33
Otimização de código . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Reproduzir e isolar o erro . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
. Ponteiros e endereços de memória . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
i
2 Arquitetura de microcontroladores 36
. Acesso à memória . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
. Clock e tempo de instrução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
. Esquema elétrico e circuitos importantes . . . . . . . . . . . . . . . . . . . . . . . 41
Multiplexação nos terminais do microcontrolador . . . . . . . . . . . . . . 42
. Registros de configuração do microcontrolador . . . . . . . . . . . . . . . . . . . . 43
5 Anexos 105
. config.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
. basico.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
. Instalar gravadores/depuradores de PIC em sistemas x64 . . . . . . . . . . . . . . 108
ii
Lista de Figuras
iii
4.2 Exemplo da mudança de slots no tempo . . . . . . . . . . . . . . . . . . . . . . . 103
4.3 Linha de tempo de um sistema com 1 slot . . . . . . . . . . . . . . . . . . . . . . 103
4.4 Comportamento da linha de tempo com interrupções . . . . . . . . . . . . . . . . 103
iv
Lista de Tabelas
v
Lista de Programas
vi
Capítulo 1
Introdução
“The real danger is not that computers will begin to think like men,
but that men will begin to think like computers.” - Sydney J. Harris
Programação para sistemas embarcados exige uma série de cuidados especiais, pois estes sistemas
geralmente possuem restrições de memória e processamento. Por se tratar de sistemas com
funções específicas, as rotinas e técnicas de programação diferem daquelas usadas para projetos
de aplicativos para desktops.
Também é necessário conhecer mais a fundo o hardware que será utilizado, pois cada mi-
croprocessador possui uma arquitetura diferente, com quantidade e tipos de instruções diversos.
Programadores voltados para desktops não precisam se ater tanto a estes itens, pois eles pro-
gramam para um sistema operacional, que realiza o papel de tradutor, disponibilizando uma
interface comum, independente do hardware utilizado(Figura 1.1).
Aplicação
Sistema Operacional
Firmware
Hardware
. Linguagem C
“C is quirky, flawed, and an enormous success.” - Dennis M. Ritchie
Neste curso será utilizada a linguagem C. Esta é uma linguagem com diversas características que
a tornam uma boa escolha para o desenvolvimento de software embarcado. Apesar de ser uma
linguagem de alto nível, permite ao programador um acesso direto aos dispositivos de hardware.
1
2 Introdução
Figura 1.2: Pesquisa sobre linguagens utilizadas para projetos de software embarcado
Fonte: http://www.embedded.com/design/218600142
Como o enfoque deste curso é a programação de sistemas embarcados e não a eletrônica, utili-
zaremos um kit de desenvolvimento pronto, baseado num microcontrolador PIC.
Como periféricos disponíveis temos:
Cada componente terá seu funcionamento básico explicado para permitir o desenvolvimento de
rotinas para estes.
Instalação
A Tabela 1.1 apresenta os softwares que serão utilizados no curso.
Todos os softwares são gratuitos e estão disponíveis na internet. Para correta instalação
deve-se instalar os softwares segundo a sequência apresentada na Tabela 1.1. Anote o diretório
onde cada software foi instalado.
Após a instalação dos softwares deve-se abrir o arquivo “pic16devices.txt” (de preferência no
wordpad) que foi instalado no diretório do SDCC dentro da pasta “include\pic16” (por padrão
“C:\Arquivos de programas\SDCC\include\pic16”). No windows vista e windows 7 não é possível
editar arquivos de sistema. Neste caso clique no arquivo com o botão direito > Propriedades >
Segurança > Editar > Usuários e selecionar a opção Controle Total, depois clique em ok. Após
isso será possível editar o arquivo. Procure então a seguintes linhas:
name 18f4550
using 18f2455
Trocar a letra “f” minúscula da primeira linha, apenas do 18f4550, para um “F” maiúsculo:
name 18F4550
using 18f2455
Em seguida abra o programa MPLAB e vá ao menu “Projects -> Set Language Tool Locati-
ons”. Será apresentada uma tela similar a da Figura 1.3.
Selecione a ferramenta “Small Device C Compiler for PIC16 (SDCC16)”. Expanda a opção
“Executables”. A ferramenta “gpasm” é obtida no diretório “bin” dentro de onde foi instalado
o GPUtils, por padrão: “C:\Arquivos de programas\gputils\bin”. Para as opções sdcc16 e sdcc
link deve-se escolher o arquivo “sdcc.exe”, que é encontrado no diretório “bin” dentro do diretório
onde foi instalado o SDCC por padrão: “C:\Arquivos de programas\SDCC\bin\”. Clicar em
“OK”. A Tabela 1.2 apresenta um resumo destas opções.
Após estes passos a suíte MPLAB está pronta para trabalhar com o compilador SDCC+GPUtils.
No wizard, escolha a comunicação como USB e depois diga que a placa possui alimentação
independente “Target has own power supply”. Deixe as outras opções na seleção padrão. Antes
de clicar em concluir verifique ao final se o resumo se parece com o da Figura 1.5.
Na primeira vez que o computador se conectar ao ICD2 é possível que o MPLAB precise
atualizar o firmware do ICD2 conforme o aviso que pode ser visto na Figura 1.6.
Após estes passos o projeto estará criado. Caso a lista de arquivos do projeto não esteja
visível vá ao menu View -> Project.
Para a criação de um novo arquivo vá até o menu File -> New. Neste novo arquivo digite
alguma coisa e salve-o. Caso seja o arquivo que conterá a função principal (main) é costume
salvá-lo com o nome de “main.c”.
A cada novo arquivo criado é necessário inseri-lo no projeto. Para isso deve-se clicar na pasta
correspondente ao tipo de arquivo que se deseja incluir e em seguida “Add Files” como pode ser
visualizado na Figura 1.7.
A programação para sistemas embarcados possui diversas características diferentes da progra-
mação voltada para desktop. Do mesmo modo, existem alguns conceitos que geralmente não são
explorados nos cursos de linguagens de programação em C, mas que são essenciais para o bom
desenvolvimento deste curso. Estes conceitos serão explanados neste capítulo.
Podemos notar pelo código anterior que aquele que possui indentação facilita na verificação
de quais instruções/rotinas estão subordinadas às demais.
Outra característica de padronização está na criação de nomes de funções e de variáveis. Pela
linguagem C uma função ou variável pode ter qualquer nome desde que: seja iniciada por uma
letra, maiúscula ou minúscula, e os demais caracteres sejam letras, números ou underscore “_”.
A linguagem C permite também que sejam declaradas duas variáveis com mesmo nome caso
possuam letras diferentes apenas quanto caixa (maiúscula ou minúscula). Por exemplo: “var” e
“vAr” são variáveis distintas, o que pode gerar erro no desenvolvimento do programa causando
dúvidas e erros de digitação.
Por isso convenciona-se que os nomes de variáveis sejam escritos apenas em minúsculas.
Quando o nome é composto, se utiliza uma maiúscula para diferenciá-los como, por exemplo, as
variáveis “contPos” e “contTotal”.
Nomes de função serão escritos com a primeira letra maiúscula e no caso de nome composto,
cada inicial será grafada em maiúsculo: “InicializaTeclado()”, “ParaSistema()”.
Tags de definições (utilizados em conjunto com a diretiva #define) serão grafados exclusiva-
mente em maiúsculo: “NUMERODEVOLTAS”, “CONSTGRAVITACIONAL”.
Cada chave será colocada numa única linha, conforme exemplo anterior, evitando-se constru-
ções do tipo:
Ou
i f ( PORTA == 0 x30 ) {
PORTB = 0 x10 ; }
As regras apresentadas visam fornecer uma identidade visual ao código. Tais regras não são
absolutas, servem apenas para o contexto desta apostila. Em geral, cada instituição ou projeto
possui seu próprio conjunto de normas. É importante ter conhecimento deste conjunto e aplicá-lo
em seu código.
O estilo adotado nesta apostila é conhecido também como estilo “Allman”, “bsd” (no emacs)
ou ANSI, já que todos os documentos do padrão ANSI C utilizam este estilo. Apesar disto o
padrão ANSI C não especifica um estilo para ser usado.
. Comentários
“If the code and the comments disagree, then both are probably
wrong.” - Norm Schryer
Comentários são textos que introduzimos no meio do programa fonte com a intenção de torná-
lo mais claro. É uma boa prática em programação inserir comentários no meio dos nossos
programas. Pode-se comentar apenas uma linha usando o símbolo “//” (duas barras). Para
comentar mais de uma linha usa-se o símbolo “/*” (barra e asterisco) antes do comentário e “*/”
(asterisco e barra) para indicar o final do comentário.
. Arquivos .c e .h
Na programação em linguagem C utilizamos dois tipos de arquivos com funções distintas. Toda
implementação de código é feita no arquivo com extensão “.c” (code). É nele que criamos as
funções, definimos as variáveis e realizamos a programação do código. Se existem dois arquivos
“.c” no projeto e queremos que um deles possa usar as funções do outro arquivo, é necessário
realizar um #include.
Os arquivos “.h” (header ) tem como função ser um espelho dos arquivos “.c” disponibilizando
as funções de um arquivo “.c” para serem utilizadas em outros arquivos. Nele colocamos todos
os protótipos das funções que queremos que os outros arquivos usem.
Se quisermos que uma função só possa ser utilizada dentro do próprio arquivo, por motivo
de segurança ou organização, basta declarar seu protótipo APENAS no arquivo “.c”.
Se for necessário que um arquivo leia e/ou grave numa variável de outro arquivo é recomen-
dado criar funções específicas para tal finalidade.
O programa 1.1 apresenta um exemplo de um arquivo de código “.c” e o programa 1.2 apre-
senta o respectivo arquivo de header “.h”.
Podemos notar que no arquivo “.h” a função AtualizaDisplay() não está presente, deste modo
ela não estará disponível para os outros arquivos. Podemos notar também que para ler ou
gravar a variável “digito” é necessário utilizar as funções MudarDigito() e LerDigito(). Notar que
não existe acesso direto às variáveis. Este tipo de abordagem insere atrasos no processamento
devido a um efeito conhecido como overhead de funções, podendo inclusive causar travamentos
no sistema caso não exista espaço suficiente no stack.
#include
A diretiva de compilação #include é a responsável por permitir que o programador utilize no seu
código funções que foram implementadas em outros arquivos, seja por ele próprio ou por outras
pessoas. Não é necessário possuir o código fonte das funções que se deseja utilizar. É necessário
apenas de um arquivo que indique os protótipos das funções (como elas devem ser chamadas) e
possuir a função disponível em sua forma compilada.
Em geral um arquivo que possui apenas protótipos de funções é denominado de “Header” e
possui a extensão “.h”.
#define
Outra diretiva muito conhecida é a #define. Geralmente é utilizada para definir uma constante,
mas pode ser utilizada para que o código fonte seja modificado antes de ser compilado.
#define CONST 15
void main ( void )
void main ( void )
{
{ 45
printf ( "%d" , 15 ∗ 3 ) ;
printf ( "%d" , CONST ∗ 3 ) ;
}
}
void MostraSaidaPadrao ( )
{
#include <s t d i o . h>
#ifdef PADRAO Serial
#define PADRAO S e r i a l
char ∗ msg = " SERIAL " ;
void main ( void )
#e l s e SERIAL
{
char ∗ msg = " LCD " ;
MostraSaidaPadrao ( ) ;
#endif
}
printf ( msg ) ;
}
Pelo código apresentado percebemos que a mesma função MostraSaidaPadrao(), apresenta re-
sultados diferentes dependendo de como foi definida a opção PADRAO.
Os define’s também ajudam a facilitar a localização dos dispositivos e ajustar as configurações
no microcontrolador. Todo periférico possui um ou mais endereços para os quais ele responde.
Estes endereços podem variar inclusive dentro de uma mesma família. Por exemplo: o endereço
da porta D (onde estão ligados os leds) é 0xF83. Para ligar ou desligar um led é preciso alterar
o valor que esta dentro do endereço 0xF83. Para facilitar este procedimento, é definido um
ponteiro para este endereço e rotulado com o nome PORTD. Definir OFF como 0 e ON como 1
facilita a leitura do código.
Toda vez que a função LerTemperatura() for chamada, ela deve fazer um teste e se o valor for
maior que um patamar chamar a função EnviaSerial() com o código 0x30. Para isso o arquivo
temp.h deve incluir o arquivo serial.h.
Toda vez que a função LerSerial() receber um valor, ela deve chamar a função AjustaCalor()
e repassar esse valor. Para isso o arquivo serial.h deve incluir o arquivo temp.h
5 #endif //TAG_CONTROLE
O problema é que deste modo é criada uma referência circular sem fim: o compilador lê o
arquivo serial.h e percebe que tem que inserir o arquivo temp.h. Inserindo o arquivo temp.h
percebe que tem que inserir o arquivo serial.h, conforme pode ser visto na Figura 1.8.
temp.h
#include “serial.h”
char LerTemperatura(void);
void AjustaCalor(char val); serial.h
#include “temp.h”
char LerSerial(void);
void EnviaSerial(char val);
temp.h
#include “serial.h”
char LerTemperatura(void);
void AjustaCalor(char val);
A solução é criar um dispositivo que permita que o conteúdo do arquivo seja lido apenas uma
vez. Este dispositivo é implementado através da estrutura apresentada no programa 1.3.
Segundo o código acima, o conteúdo que estiver entre o #ifndef e o #endif, só será mantido
se a tag “TAG_CONTROLE” NÃO estiver definida. Como isto é verdade durante a primeira
leitura, o pré-compilador lê o arquivo normalmente. Se acontecer uma referência cíclica, na
segunda vez que o arquivo for lido, a tag “TAG_CONTROLE” já estará definida impedindo
assim que o processo cíclico continue, conforme pode ser visto na Figura 1.9.
Geralmente se utiliza como tag de controle o nome do arquivo. Esta tag deve ser única para
cada arquivo.
temp.h
#infdef TEMP_H
#define TEMP_H
#include “serial.h”
char LerTemperatura(void);
void AjustaCalor(char val); serial.h
#endif
#infdef SERIAL_H
#define SERIAL_H
#include “temp.h”
//tag já definida,
//pula o conteúdo
#endif
Podemos notar que as variáveis que possuem maior tamanho podem armazenar valores mai-
ores. Notamos também que apenas os tipos float e double possuem casas decimais.
5. Apresentar o resultado
negativos, em compensação podem atingir o dobro do valor de um tipo signed. Na Tabela 1.5
são apresentadas algumas variações possíveis.
Na linguagem C, por padrão os tipos são sinalizados, ou seja, possuem parte positiva e
negativa. Por isso é raro encontrar o modificador signed.
Modificadores de acesso
Durante o processo de compilação, existe uma etapa de otimização do programa. Durante esta
etapa, o compilador pode retirar partes do código ou desfazer loops com períodos fixos. Por
exemplo o código abaixo:
// S t a r t i n g pCode b l o c k
S_Teste__main code
_main :
. line 19 // T e s t e . c w h i l e (X!=X) ;
RETURN
Enquanto a variável “x” for diferente de “x” o programa não sai do loop. O compilador
entende que esta condição nunca irá acontecer e elimina o loop do código final como podemos
ver no código gerado, a rotina de return está logo após a inicialização do programa _main. Para
variáveis comuns o valor só é alterado em atribuições diretas de valor ou de outras variáveis: (x
= 4;) ou (x = y;).
Entretanto existe uma condição onde a variável x pode alterar seu valor independentemente
do programa. Se esta variável representar um endereço de memória associado a um periférico
físico, seu valor pode mudar independentemente do fluxo do programa. Para indicar esta situação
ao programa utilizamos a palavra reservada volatile.
// S t a r t i n g pCode b l o c k
S_Teste__main code
_main :
_00105_DS_ :
. line 19 // T e s t e . c w h i l e (X != X) ;
MOVLW 0 x83 // p r i m e i r a p a r t e do e n d e r e ç o
MOVWF r0x00
MOVLW 0 x0f // segunda p a r t e do e n d e r e ç o
MOVWF r0x01
MOVFF r0x00 , FSR0L
MOVFF r0x01 , FSR0H
MOVFF INDF0 , r0x00 // r e a l i z a p r i m e i r a l e i t u r a
MOVLW 0 x83 // p r i m e i r a p a r t e do e n d e r e ç o
MOVWF r0x01
MOVLW 0 x0f // segunda p a r t e do e n d e r e ç o
MOVWF r0x02
MOVFF r0x01 , FSR0L
MOVFF r0x02 , FSR0H
MOVFF INDF0 , r0x01 // r e a l i z a segunda l e i t u r a
MOVF r0x00 , W
XORWF r0x01 , W
BNZ _00105_DS_ // f a z o t e s t e para i g u a l d a d e
RETURN
Podemos perceber que, deste modo, o compilador é forçado a ler a variável x duas vezes e realizar
o teste para ver se ela permanece com o mesmo valor.
Em algumas situações é necessário indicar que algumas variáveis não podem receber valores
pelo programa. Para isto utilizamos a palavra reservada const. Utilizamos este modificador
para indicar que a variável representa um local que apenas pode ser lido e não modificado, por
exemplo uma porta para entrada de dados. Nesta situação é comum utilizar as palavras volatile
e const junto.
Modificadores de posicionamento
As variáveis podem ser declaradas utilizando os modificadores near e far. Estes modificadores
indicam ao compilador em qual região de memória devem ser colocadas as variáveis.
A região near geralmente se refere à “zero page”. É uma região mais fácil de ser acessada. A
região far exige mais tempo para executar a mesma função que a near.
Podemos pensar nestas regiões como a memória RAM e a memória Cache do computador.
A segunda é mais rápida, mas possui um alto custo e por isso geralmente é menor. Em algumas
situações é interessante que algumas variáveis nunca saiam do cache, pois são utilizadas com
grande frequência ou são críticas para o sistema.
Modificador de persistência
Em geral, as variáveis utilizadas dentro das funções perdem seu valor ao término da função. Para
que este valor não se perca podemos utilizar um modificador de persistência: static. Com esse
modificador a variável passa a possuir um endereço fixo de memória dado pelo compilador. Além
disso o compilador não reutiliza este endereço em nenhuma outra parte do código, garantindo
que na próxima vez que a função for chamada o valor continue o mesmo.
// c r i a um c o n t a d o r p e r s i s t e n t e que é
// i n c r e m e n t a d o a cada chamada de f u n ç ã o
i n t ContadorPersistente ( i n t reseta )
{
s t a t i c char variavel_persistente ;
i f ( reseta )
{
variavel_persistente = 0 ;
}
else
{
return ( variavel_persistente++) ;
}
return −1;
}
A soma de dois char, conforme a linha 9, segundo caso pode gerar um problema se ambos
forem muito próximo do valor limite. Por exemplo: 100 + 100 = 200, que não cabe num char,
já que este só permite armazenar valores de -128 à 127.
O terceiro caso (linha 10) está correto, a multiplicação de dois char possui um valor máximo
de 127*127=16.129. O problema é que a multiplicação de dois char gera um outro char, perdendo
informação. É necessário realizar um typecast antes.
O quarto caso (linha 11) pode apresentar um problema de precisão. A divisão de dois inteiros
não armazena parte fracionária. Se isto não for crítico para o sistema está correto. Lembrar que
a divisão de números inteiros é mais rápida que de números fracionários.
O quinto caso (linha 12) pode apresentar um problema de precisão. O resultado da conta de
um número inteiro com um ponto flutuante é um ponto flutuante. Armazenar esse valor num
outro número inteiro gera perda de informação.
O sexto caso (linha 13) apresenta um problema muito comum. A divisão de dois números
inteiros gera um número inteiro. Não importa se armazenaremos o valor numa variável de ponto
flutuante haverá perda de informação pois os operandos são inteiros. Para evitar esse problema
é necessário um typecast.
No sétimo caso (linha 14) pode haver perda de precisão pois o resultado da operação é um
double, e estamos armazenando este valor num float.
O oitavo caso (linha 15) é similar ao sexto. Estamos realizando uma conta com dois números
inteiros esperando que o resultado seja 0,5. Como os operandos são inteiros a expressão será
avaliada como resultante em Zero. Uma boa prática é sempre usar “.0” ou “f” após o número
para indicar operações com vírgula.
pont16 = 40 f / 8 0 . 0 ; // 8 c o r r i g i d o
Devemos tomar cuidado também com comparações envolvendo números com ponto flutuante.
float x = 0 . 1 ;
while ( x != 1 . 1 ) {
printf ( "x = %f\n" , x ) ;
x = x + 0.1;
}
O trecho de código acima apresenta um loop infinito. Como existem restrições de precisão nos
números de ponto flutuante (float e double) nem todos os números são representados fielmente.
Os erros de arredondamento podem fazer com que a condição (x !=1.1) nunca seja satisfeita.
Sempre que houver a necessidade de comparação com números de ponto flutuante utilizar maior,
menor ou variações.
float x = 0 . 1 ;
while ( x < 1 . 1 ) {
printf ( "x = %f\n" , x ) ;
x = x + 0.1;
}
Apesar de sutis estes tipos de erro podem causar um mau funcionamento do sistema. Na
Figura 1.10 é apresentado um erro gerado através de um loop infinito.
pequeno, apenas o necessário para inserir uma instrução de pulo e o endereço onde está a função
principal. Este espaço é conhecido como posição de reset. Existem ainda outros espaços de
memória similares a este que, geralmente, são alocados próximos. O conjunto destes espaços é
conhecido como vetor de interrupção (Figura 1.11).
Endereço Instrução
0x00 Pulo
0x01 0x8A
0x02 Pulo
0x03 0x55
0x04 ...
0x55 Limpa A
0x56 A recebe
0x57 30
0x58 Testa A
0x59 ...
0x8A A recebe
0x8B 50
0x8C Salva em
0x8D Porta B
0x8E ...
compiladores alocam a função main() em algum lugar da memória onde haja espaço disponível.
Depois disso dispõem de uma instrução de pulo para o primeiro endereço de memória, onde foi
alocada a função main.
Outra coisa interessante é que para sistemas embarcados a função principal não recebe nem
retorna nada. Como ela é a primeira a ser chamada não há como enviar algum valor por parâ-
metro. Ela também não retorna nada pois ao término desta o sistema não está mais operativo.
Em geral sistemas embarcados são projetados para começarem a funcionar assim que ligados e
apenas parar sua tarefa quando desligados. Como todas as funcionalidades são chamadas dentro
da função main()1 espera-se que o programa continue executando as instruções dentro dela até
ser desligado ou receber um comando para desligar. Este comportamento pode ser obtido através
de um loop infinito. Abaixo estão as duas alternativas mais utilizadas.
É muito comum necessitar que o microcontrolador fique um tempo sem fazer nada. Uma maneira
de atingir esse objetivo é utilizar um laço FOR 2 .
unsigned char i ;
f o r ( i =0; i < 1 0 ; i++) ;
Notar que não estamos utilizando os colchetes. Logo após fechar os parênteses já existe um
ponto e vírgula. Para entender como esse procedimento funciona, e estimar o tempo de espera é
preciso entender como o compilador traduz essa função para assembler.
1
Em sistemas mais complexos algumas tarefas são executadas independentemente da função principal, tendo
sua execução controlada através de interrupções.
2
Este método não é aconselhado em sistemas de maior porte.
Percebemos pelo código acima que para realizar um for precisamos de 3 passos de inicialização.
Cada iteração exige 2 passos: uma comparação e um “pulo” 3 , totalizando 3 ciclos de inicialização
e 3 ciclos de interação.
Se temos um processador trabalhando a 8 MHz, cada instrução é executada em 0.5µs.4 Para
termos um tempo de espera de 0.5s precisamos de 1 milhão de instruções. Se colocarmos loops
encadeados podemos multiplicar a quantidade de instruções que serão executadas. Para obtermos
um valor de 1 milhão de instruções devemos utilizar pelo menos 3 loops encadeados. Os valores
dos loops são obtidos de maneira iterativa.
unsigned char i , j , k ;
f o r ( i =0; i < 3 4 ; i++) // 3 + 34 ∗ ( 3 0 . 0 0 3 + 3) = 1 . 0 2 0 . 2 0 7 i n s t r u ç õ e s
{
f o r ( j =0; j < 1 0 0 ; j++) // 3 + 100 ∗ (297 + 3) = 3 0 . 0 0 3 i n s t r u ç õ e s
{
f o r ( k =0; k < 9 8 ; k++) ; // 3 + 98 ∗ ( 3 ) = 297 i n s t r u ç õ e s
}
}
O código acima foi projetado para gerar um atraso de tempo de meio segundo. Compilando
e realizando testes práticos podemos confirmar que o tempo real é aproximadamente 0.51 (s).
Esta discrepância acontece porque agora temos 3 loops encadeados e cada qual com sua variável
de controle. Deste modo o compilador precisa salvar e carregar cada variável para realizar a
comparação.
Percebemos assim que para conhecer corretamente o funcionamento do sistema é necessário,
em algumas situações, abrir o código em assembler gerado pelo compilador para entender como
este é executado. Nem sempre o compilador toma as mesmas decisões que nós. Além disso ele
pode gerar otimizações no código. Existem dois tipos de otimização: uma visando diminuir o
tempo de execução do sistema, deixando-o mais rápido e outra que reduz o tamanho do código
final, poupando espaço na memória.
A seguir apresentamos um exemplo de função que gera delays com tempo parametrizado.
Nos sistemas microcontrolados, existem algumas variáveis onde cada bit tem uma interpretação
3
Este valor só é valido quando estamos trabalhando com variáveis char. Se utilizarmos variáveis int o código
em assembler será diferente e teremos que realizar uma nova análise.
4
Para 8MHz, 1 ciclo = 0.125µs. No PIC, cada instrução precisa de 4 ciclos de clock, portanto 0.5µs.
ou funcionalidade diferente. Por isso é necessário realizar algumas operações que modifiquem
apenas os bits desejados, mantendo o restante dos bits da variável inalterados.
As operações da linguagem C que nos permitem trabalhar com as variáveis, levando em conta
os valores individuais de cada bit, são chamadas de bitwise operation.
É importante ressaltar que as operações de bitwise possuem funcionalidade semelhante a suas
respectivas operações lógicas. A diferença é que a lógica opera em cima da variável como um
todo5 enquanto a bitwise opera bit à bit.
NOT
A operação NOT lógica retorna ’1’ (um) se o valor for ’0’ (zero) e ’0’ se o valor for ’1’.
A !A
0 1
1 0
A operação bitwise NOT (operador ˜) executa uma NOT lógica. Isso significa que a operação é
realizada para cada um dos bits da variável, não mais para a variável como um todo. Na tabela
seguinte é apresentada a diferença entre as duas operações.
result = ~A ;
char A = 1 2 ; result = ! A ; // r e s u l t = 243
// A = 0 b00001100 // r e s u l t = 0 // A = 0 b00001100
// r = 0 b11110011
AND
A operação AND lógica (operador &&) retorna 0 se algum dos valores for zero, e 1 se os dois
valores forem diferentes de zero.
A B A&&B
0 0 0
0 1 0
1 0 0
1 1 1
A operação bitwise AND (operador &) executa uma AND lógica para cada par de bits e coloca
o resultado na posição correspondente:
result = A & B ;
char A = 8;
// r e s u l t = 0
// A = 0 b00001000 result = A && B ;
// A = 0 b00001000
char B = 5; // r e s u l t = 1
// B = 0 b00000101
// B = 0 b00000101
// r = 0 b00000000
5
Lembrar que para linguagem C uma variável com valor 0 (zero) representa falso, e qualquer outro valor
representa verdadeiro.
OR
A operação OR lógica (operador ||) retorna 1 se algum dos valores for diferente de zero, e 0 se
os dois valores forem zero.
A B A||B
0 0 0
0 1 1
1 0 1
1 1 1
A operação bitwise OR (operador |) executa uma OR lógica para cada par de bits e coloca o
resultado na posição correspondente:
result = A | B ;
char A = 8;
// r e s u l t = 13
// A = 0 b00001000 result = A | | B ;
// A = 0 b00001000
char B = 5; // r e s u l t = 1
// B = 0 b00000101
// B = 0 b00000101
// r = 0 b00001101
XOR
A operação XOR não possui correspondente lógica na linguagem C. Esta operação pode ser
representada como A XOR B = (A && !B)||(!A && B)
A B A⊕B
0 0 0
0 1 1
1 0 1
1 1 0
A operação bitwise XOR (operador ˆ) executa uma XOR lógica para cada par de bits e coloca
o resultado na posição correspondente:
Shift
A operação shift desloca os bits para a esquerda (operador <<) ou direita (operador >>). É
necessário indicar quantas casas serão deslocadas.
Para variáveis unsigned e inteiras, esta operação funciona como a multiplicação/divisão por
potência de dois. Cada shift multiplica/divide por 2 o valor. Esta é uma prática muito comum
para evitar a divisão que na maioria dos sistemas embarcados é uma operação cara do ponto de
vista de tempo de processamento.
Não utilizar esta operação com o intuito de multiplicar/dividir variáveis com ponto fixo ou
flutuante nem variáveis sinalizadas (signed).
Em diversas ocasiões é necessário que trabalhemos com os bits de maneira individual, prin-
cipalmente quando estes bits representam saídas ou entradas digitais, por exemplo chaves ou
leds.
Supondo que temos 8 leds ligados ao microcontrolador. Cada led é representado através de 1
bit de uma variável. Para ligarmos ou desligarmos apenas um led por vez, não alterando o valor
dos demais, devemos nos utilizar de alguns passos de álgebra digital.
Se a operação OR for executada com a máscara criada, o resultado apresentará valor 1 na posição
X e manterá os valores antigos para as demais posições. Exemplo: Ligar apenas o bit 2 da variável
PORTD
// d e f i n e ' s para p o r t a s de e n t r a d a e s a í d a
#define PORTD ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF83 )
#define TRISD ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF95 )
// i n i c i o do programa
void main ( void )
{
char mascara ; // v a r i á v e l que guarda a máscara
TRISD = 0 x00 ; // c o n f i g u r a a p o r t a D como s a í d a
PORTD = 0 x00 ; // l i g a t o d o s os l e d s ( l ó g i c a n e g a t i v a )
// l i g a o p r i m e i r o b i t da v a r i á v e l
mascara = 1 ; // b i t = 0 b00000001
// r o t a c i o n a −s e a v a r i á v e l para que o b i t 1 c h e g u e na p o s i ç ã o d e s e j a d a
mascara = mascara << 2 ; // b i t = 0 b00000100
// L i g a r o b i t 2 , d e s l i g a n d o o 3o l e d
PORTD = PORTD | mascara ;
//mantém o s i s t e m a l i g a d o i n d e f i n i d a m e n t e
for ( ; ; ) ;
}
Se a operação AND for executada com a máscara criada, o resultado apresentará valor 0 na
posição X e manterá os valores antigos para as demais posições. Exemplo: Desligar apenas o bit
2 da variável PORTD.
// d e f i n e ' s para p o r t a s de e n t r a d a e s a í d a
#define PORTD ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF83 )
#define TRISD ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF95 )
// i n i c i o do programa
void main ( void )
{
char mascara ; // v a r i á v e l que guarda a máscara
TRISD = 0 x00 ; // c o n f i g u r a a p o r t a D como s a í d a
PORTD = 0 xFF ; // d e s l i g a t o d o s os l e d s ( l ó g i c a n e g a t i v a )
// l i g a o p r i m e i r o b i t da v a r i á v e l
mascara = 1 ; // mascara = 0 b00000001
// r o t a c i o n a −s e a v a r i á v e l para que o b i t 1 c h e g u e na p o s i ç ã o d e s e j a d a
mascara = mascara << 2 ; // mascara = 0 b00000100
// i n v e r t e −s e os v a l o r e s de cada b i t
mascara = ~mascara ; // mascara = 0 b11111011
// D e s l i g a o b i t 2 , l i g a n d o o 3o l e d
PORTD = PORTD & mascara ;
//mantém o s i s t e m a l i g a d o i n d e f i n i d a m e n t e
for ( ; ; ) ;
}
É importante notar que geramos a máscara de maneira idêntica àquela utilizada no caso
anterior, onde todos os valores são zero e apenas o desejado é um. Depois realizamos a inversão
dos valores. Este procedimento é realizado desta maneira porque não sabemos o tamanho da
palavra a ser utilizada no microcontrolador: 8 ou 16 bits. Mesmo assim devemos garantir que
todos os bits obtenham o valor correto, o que é garantido pela operação de negação. A opção de
inicializar a variável com apenas um zero e rotacionar pode não funcionar pois, na maioria dos
sistemas, a função de rotação insere zeros à medida que os bits são deslocados e precisamos que
apenas um valor seja zero.
Se a operação XOR for executada com a máscara criada, o valor na posição X será trocado, de
zero para um ou de um para zero. Exemplo: Trocar o bit 2 e 6 da variável PORTD
// d e f i n e ' s para p o r t a s de e n t r a d a e s a í d a
#define PORTD ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF83 )
#define TRISD ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF95 )
// i n i c i o do programa
void main ( void )
{
char mascara ; // v a r i á v e l que guarda a mascara
TRISD = 0 x00 ; // c o n f i g u r a a p o r t a D como s a í d a
PORTD = 0 xF0 ; // d e s l i g a t o d o s os 4 p r i m e i r o s l e d s ( l ó g i c a n e g a t i v a )
// l i g a o p r i m e i r o b i t da v a r i á v e l
mascara = 1 ; // mascara = 0 b00000001
// r o t a c i o n a −s e a v a r i á v e l para que o b i t 1 c h e g u e na p o s i ç ã o d e s e j a d a
mascara = mascara << 2 ; // mascara = 0 b00000100
// L i g a o b i t 2 , d e s l i g a n d o o 3o l e d
PORTD = PORTD ^ mascara ;
// l i g a o p r i m e i r o b i t da v a r i á v e l
mascara = 1 ; // mascara = 0 b00000001
// r o t a c i o n a −s e a v a r i á v e l para que o b i t 1 c h e g u e na p o s i ç ã o d e s e j a d a
mascara = mascara << 6 ; // mascara = 0 b01000000
// D e s l i g a o b i t 6 , l i g a n d o o 7o l e d
PORTD = PORTD ^ mascara ;
//mantém o s i s t e m a l i g a d o i n d e f i n i d a m e n t e
for ( ; ; ) ;
}
Realizamos então uma operação AND com a variável. O resultado será zero se o bit X, da
variável original, for zero. Se o bit da variável original for um a resposta será diferente de zero6 .
Exemplo: Testar o bit 2 da variável PORTD
// d e f i n e ' s para p o r t a s de e n t r a d a e s a í d a
#define PORTD ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF83 )
#define TRISD ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF95 )
// i n i c i o do programa
void main ( void )
{
char mascara ; // v a r i á v e l que guarda a mascara
char teste ;
TRISD = 0 x00 ; // c o n f i g u r a a p o r t a D como s a í d a
teste = 0 x00 ; // d e s l i g a t o d o s os b i t s
// r o d a r d e p o i s o mesmo programa com os b i t s l i g a d o s .
// t e s t e = 0 x f f ;
// c r i a uma v a r i á v e l onde APENAS o p r i m e i r o b i t é 1
mascara = 1 ; // mascara = 0 b00000001
// r o t a c i o n a −s e a v a r i á v e l para que o b i t 1 c h e g u e na p o s i ç ã o d e s e j a d a
mascara = mascara << 2 ; // mascara = 0 b00000100
// V e r i f i c a apenas o b i t 2
i f ( teste & mascara )
{
PORTD = 0 x00 ; // s e o r e s u l t a d o f o r v e r d a d e i r o l i g a t o d o s os l e d s
6
A maioria dos compiladores C adotam uma variável com valor diferente de zero como sendo verdadeiro.
}
else
{
PORTD = 0 xff ; // s e o r e s u l t a d o f o r f a l s o d e s l i g a t o d o s os l e d s
}
//mantém o s i s t e m a l i g a d o i n d e f i n i d a m e n t e
for ( ; ; ) ;
}
char bit = 2 ;
char mascara ;
mascara = 1 << bit ;
arg = arg | mascara ;
Passo a Passo //em 1 l i n h a
arg = arg | (1<<bit ) ;
// ou
arg |= (1<<bit ) ;
// Ligando o b i t 2 da p o r t a D
PORTD = PORTD | (1<<2) ;
Exemplo de uso // ou
PORTD |= (1<<2) ;
char bit = 2 ;
char mascara ;
mascara = 1 << bit ;
arg = arg & ~mascara ;
Passo a Passo //em 1 l i n h a
arg = arg & ~(1<<bit ) ;
// ou
arg &= ~(1<<bit ) ;
// D e s l i g a n d o o b i t 2 da p o r t a D
PORTD = PORTD & ~(1<<2) ;
Exemplo de uso // ou
PORTD &= ~(1<<2) ;
char bit = 2 ;
char mascara ;
mascara = 1 << bit ;
arg = arg ^ mascara ;
Passo a Passo //em 1 l i n h a
arg = arg ^ (1<<bit ) ;
// ou
arg ^= (1<<bit ) ;
// Trocando o v a l o r do b i t 2 da p o r t a D
PORTD = PORTD ^ (1<<2) ;
Exemplo de uso // ou
PORTD ^= (1<<2) ;
char bit = 2 ;
char mascara ;
mascara = 1 << bit ;
Passo a Passo i f ( arg & mascara )
//em 1 l i n h a
i f ( arg & (1<<bit ) )
// Testando o b i t 2 da p o r t a D
i f ( PORTD | (1<<2) )
Exemplo de uso {
// . . .
}
// Testando o b i t 2 da p o r t a D
i f ( BitTst ( PORTD , 2 ) )
Exemplo de uso com de- {
fine // . . .
}
Externalizar as informações
A primeira necessidade é conhecer o que está acontecendo em teu sistema. Na programação
tradicional para desktop é comum utilizarmos de mensagens no console avisando o estado do
programa.
Devemos ter em mente onde é necessário colocar estes alertas e lembrar de retirá-los do código
final.
Para a placa em questão utilizaremos o barramento de leds que está ligado à porta D. A ope-
ração deste dispositivo será estudada posteriormente em detalhes. Por enquanto basta sabermos
que cada bit da variável PORTD está ligada a um led diferente. Por causa da construção física
da placa, o led é aceso com valor 0 (zero) e desligado com o valor 1 (um). Além disso temos que
configurar a porta D. Isto é feito iniciando a variável TRISD com o valor 0x008 .
// d e f i n e ' s para p o r t a s de e n t r a d a e s a í d a
#define PORTD ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF83 )
#define TRISD ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF95 )
// i n i c i o do programa
void main ( void )
{
// c o n f i g u r a n d o t o d o s os p i n o s como s a í d a s
TRISD = 0 x00 ;
PORTD = 0 xFF ; // d e s l i g a t o d o s os l e d s
// l i g a apenas o b i t 1 .
BitClr ( PORTD , 1 ) ;
//mantém o s i s t e m a l i g a d o i n d e f i n i d a m e n t e
for ( ; ; ) ;
7
Mais informações sobre debug de sistemas embarcados referir ao artigo “The ten secrets of embedded debug-
ging” de Stan Schneider e Lori Fraleigh
8
As variáveis PORTD e TRISD são definidas como unsigned char e possuem portanto 8 bits.
Devemos utilizar os leds como sinais de aviso para entendermos o funcionamento do programa.
Isto pode ser feito através das seguintes ideias: “Se passar desta parte liga o led X”, “Se entrar
no IF liga o led Y, se não entrar liga o led Z”, “Assim que sair do loop liga o led W”.
Programação incremental
Ao invés de escrever todo o código e tentar compilar, é interessante realizar testes incrementais.
A cada alteração no código realizar um novo teste. Evitar alterar o código em muitos lugares
simultaneamente, no caso de aparecer um erro fica mais difícil saber onde ele está.
Otimização de código
Apenas se preocupe com otimização se estiver tendo problemas com o cumprimento de tarefas.
Mesmo assim considere em migrar para uma plataforma mais poderosa. Sistemas embarcados
preconizam segurança e não velocidade.
Caso seja necessário otimizar o código analise antes o local de realizar a otimização. Não
adianta otimizar uma função grande se ela é chamada apenas uma vez. Utilize-se de ferramentas
do tipo profiler sempre que possível. Isto evita a perda de tempo e auxilia o programador a
visualizar a real necessidade de otimização de código.
// a q u i tem um monte de c ó d i g o . . .
i f ( PORTB >= 5 ) //PORTB não d e v e r i a s e r um v a l o r maior que 5 .
{
BitClr ( PORTD , 3 ) ; // l i g a o l e d 3
for ( ; ; ) ; // t r a v a o programa
}
// a q u i c o n t i n u a com um monte de c ó d i g o . . .
// c r i a a v a r i á v e l a num e n d e r e ç o de memória a s e r
// d e c i d i d o p e l o c o m p i l a d o r
int a = 0 ;
a = a + 1;
printf ( a ) ; // imprime o v a l o r 1
printf ( &a ) ; // imprime o e n d e r e ç o de a ( por exemplo 157821)
Conhecer o endereço de uma variável é muito útil quando queremos criar um ponteiro para
ela.
Ponteiro é uma variável que, ao invés de armazenar valores, armazena endereços de memória.
Através do ponteiro é possível manipular o que está dentro do lugar apontado por ele.
Para definir um ponteiro também precisamos indicar ao compilador um tipo. A diferença é
que o tipo indica “quanto” cabe no local apontado pelo ponteiro e não o próprio ponteiro.
Sintaxe:
Exemplo:
i n t ∗ apint ;
f l o a t ∗ apfloat ;
Deve-se tomar cuidado, pois nos exemplos acima, apint e apfloat são variáveis que armazenam
endereços de memória e não valores tipo int ou float. O lugar APONTADO pela variável apint é
que armazena um inteiro, do mesmo modo que o lugar apontado por apfloat armazena um valor
fracionário.
Se quisermos manipular o valor do endereço utilizaremos apint e apfloat mas se quisermos
manipular o valor que esta dentro deste endereço devemos usar um asterisco antes do nome da
variável. Exemplo:
apfloat = 3 . 2 ;
∗ apfloat = 3 . 2 ;
A primeira instrução indica ao compilador que queremos que o ponteiro apfloat aponte para
o endereço de memória número 3.2, que não existe, gerando um erro. Se quisermos guardar o
valor 3.2 no endereço de memória apontado por apfloat devemos utilizar a segunda expressão.
Para trabalhar com ponteiros é preciso muito cuidado. Ao ser definido, um ponteiro tem como
conteúdo não um endereço, mas algo indefinido. Se tentarmos usar o ponteiro assim mesmo,
corremos o risco de que o conteúdo do ponteiro seja interpretado como o endereço de algum local
da memória vital para outro programa ou até mesmo para o funcionamento da máquina. Neste
caso podemos provocar danos no programa, nos dados, ou mesmo travar a máquina.
É necessário tomar cuidado ao inicializar os ponteiros. O valor atribuído a eles deve ser
realmente um endereço disponível na memória.
Por exemplo, podemos criar um ponteiro que aponta para o endereço de uma variável já
definida:
// d e f i n i n d o a v a r i á v e l i v a r
i n t ivar ;
// d e f i n i n d o o p o n t e i r o i p t r
i n t ∗ iptr ;
// o p o n t e i r o i p t r r e c e b e o v a l o r do e n d e r e ç o da v a r i á v e l i v a r
iptr = &ivar ;
// as próximas l i n h a s são e q u i v a l e n t e s
ivar = 4 2 1 ;
∗ iptr = 4 2 1 ;
Com sistemas embarcados existem alguns endereços de memória que possuem características
especiais. Estes endereços possuem registros de configuração, interfaces com o meio externo e
variáveis importantes para o projetista. É pelo meio da utilização de ponteiros que é possível
acessar tais endereços de maneira simples, através da linguagem C.
Arquitetura de microcontroladores
36
37 Arquitetura de microcontroladores
tamanho_da_palavra ∗ 2^ tamanho_do_endereco
8 ∗ 2^8 = 2048 bytes ou 2 kbytes
O termo possibilidade foi usado pois, apesar de poder alcançar toda essa extensão, nem
sempre existe memória física para armazenamento. Podemos imaginar a memória como um
armário. Um armário com 6 suportes pode abrigar até 6 gavetas. Depende do marceneiro fabricar
e colocar as gavetas neste armário. Podemos até indicar a posição onde queremos guardar algum
objeto, mas se a gaveta não foi colocada não é possível armazenar nada (Figura 2.2).
Suporte Existe
número: gaveta?
1 sim
2 sim
3 não
4 não
5 sim
6 não
Suporte Existe
número: gaveta?
1 Vitrine
2 Gaveta
3 Dispenser
4 Não
5 Gaveta
6 Cofre
Stack 1 0x000
GPR1
... 0x0FF
Stack 31 0x100
GPR2
0x1FF
Interrupção
GPR3
Baixa prioridade 0x0008 0x2FF
Alta prioridade 0x0018 0x300
GPR4
0x3FF
0x0028
Memória EEPROM
0x7FFF
Não implementado ...
0X8000
Não implementado 0xF60
0X1FFFFF SFR
0xFFF
A = 1 . 2 3 4 5 6 x 10 ^ 5
B = 3 . 4 5 6 7 x 10 ^ 4
C = A x B
A = 123456; //C = 4 . 2 6 7 5 0 3 5 5 2 x 10 ^9
B = 34567;
C = A x B; // 1 . C o n v e r t e r para o mesmo e x p o e n t e
//C = 4267503552 // 1 2 . 3 4 5 6 x 10 ^ 4
// 3 . 4 5 6 7 x 10 ^ 4
// 1 . M u l t i p l i c a r os números // 2 . M u l t i p l i c a r os números
// 123456 // e somar a m a n t i s s a
// ∗ 34567 // 1 2 . 3 4 5 6 x 10 ^ 4
// 4267503552 // x 3 . 4 5 6 7 x 10 ^ 4
// 4 2 . 6 7 5 0 3 5 5 2 x 10 ^ 8
// 3 . C o r r i g i r q u a n t i d a d e de c a s a s dec .
// 4 . 2 6 7 5 0 3 5 5 2 x 10 ^ 9
Conhecer quanto tempo o código leva para ser executado permite ao desenvolvedor saber de
maneira determinística qual é a exigência a nível de hardware para o sistema embarcado.
Por exemplo: Um sistema precisa executar 200 operações a cada milésimo de segundo. Cada
operação possuí uma quantidade diferente de tarefas conforme podemos ver na Tabela 2.1.
O total de tarefas a serem realizadas é de 341 tarefas por milissegundo. Isso dá uma quanti-
dade de 341 mil tarefas por segundo. Se cada tarefa é realizada em um ciclo de clock precisamos
de um microcontrolador cujo processador trabalhe no mínimo em 341 kHz.
1
Números fracionários podem ser armazenados de dois modos no ambiente digital. O modo mais comum é o
ponto flutuante que se assemelha à notação científica.
Para funcionarem, todos os microcontroladores devem ser alimentados com tensão contínua.
O valor varia de modelo para modelo. Alguns podem até mesmo aceitar diversos valores. O PIC
18F4550 por exemplo pode ser alimentado com qualquer tensão contínua entre 2 e 5,5 volts.
Para gerar o clock necessário, que definirá a velocidade na qual o processador ira trabalhar,
em geral é utilizado um oscilador a cristal, que possui uma ótima precisão.
Alguns microcontroladores podem dispensar o cristal externo optando por utilizar uma malha
RC interna ao chip. Esta alternativa é muito menos precisa e geralmente não permite valores
muito altos de clock. A vantagem é que sistemas que utilizam malha RC interna como osciladores
primários possuem um custo menor que sistemas que dependem de malhas de oscilação externa,
seja ela excitada por outra malha RC ou por um cristal.
Existem alguns circuitos que não são essenciais para o funcionamento do sistema, mas auxi-
liam muito no desenvolvimento. Entre estes tipos de circuito o mais importante é o que permite
a gravação do programa no próprio circuito. Alguns microcontroladores exigem que o chip seja
retirado do circuito e colocado numa placa especial para gravá-lo e somente depois recolocado
na placa para teste. Este é um procedimento muito trabalhoso e, devido ao desgaste mecânico
inerente, reduz a vida útil do chip.
Para evitar estes problemas, os fabricantes desenvolveram estruturas no chip que permitem
que este seja gravado mesmo estando soldado à placa final. Para isso, basta que o desenvolvedor
disponibilize o contato de alguns pinos com um conector. Este conector será ligado a um gra-
vador que facilitará o trabalho de gravação do programa. Para a família PIC esta tecnologia é
denominada ICSP (in circuit serial programming).
A escolha de qual funcionalidade será utilizada depende do projetista. Em sistemas mais avan-
çados é possível inclusive utilizar mais de uma funcionalidade no mesmo terminal em períodos
alternados, desde que o circuito seja projetado levando esta opção em consideração.
Dos registros apresentados na Figura 2.6, quatro precisam necessariamente ser configurados
para que o sistema possa funcionar. Dois deles tem relação com a configuração do sistema de
clock: um especifica qual é a fonte do sinal de clock, que no caso da placa em questão é um
cristal externo, e o outro indica qual o prescaler a ser usado (PLL).
Além de configurar a frequência básica do clock é necessário desligar o watchdog. Este é
um circuito para aumentar a segurança do sistema embarcado desenvolvido. Para funcionar
corretamente, o programa deve ser preparado para tal finalidade. Ele será explicado em detalhes
na seção . e por isso será mantido desligado nos próximos exemplos.
A última configuração necessária é desabilitar a programação em baixa tensão. Devido às
ligações feitas na placa, deixar esta opção ligada impede o funcionamento da placa enquanto
estiver ligada ao gravador. Abaixo o trecho de código que realiza estas configurações para o
compilador SDCC.
// P l l d e s l i g a d o
code char at 0 x300000 CONFIG1L = 0 x01 ;
// O s c i l a d o r c / c r i s t a l e x t e r n o HS
code char at 0 x300001 CONFIG1H = 0 x0C ;
// Watchdog c o n t r o l a d o por s o f t w a r e
code char at 0 x300003 CONFIG2H = 0 x00 ;
// Sem programação em b a i x a t e n s ã o
code char at 0 x300006 CONFIG4L = 0 x00 ;
// O s c i l a d o r c / c r i s t a l e x t e r n o HS
#pragma c o n f i g FOSC = HS
// P l l d e s l i g a d o
#pragma c o n f i g CPUDIV = OSC1_PLL2
// Watchdog c o n t r o l a d o por s o f t w a r e
#pragma c o n f i g WDT = OFF
// Sem programação em b a i x a t e n s ã o
#pragma c o n f i g LVP = OFF
Notar que as diretivas utilizadas são completamente diferentes, mas realizam o mesmo tra-
balho.
• Barramento de Led's(.)
• Display de 7 segmentos(.)
• Saídas PWM(.)
• Leitura de teclas(.)
• Conversor AD(.)
1
Periféricos que fornecem informações aos usuários ou enviam comandos da a placa eletrônica para o meio
externo
2
Periféricos que recebem informações ou comandos do meio externo
45
46 Programação dos Periféricos
Cada porta esta ligada à dois endereços de memória. O primeiro armazena o valor que
queremos ler do meio externo ou escrever para o meio externo dependendo da configuração. O
segundo endereço realiza essa configuração indicando quais bits serão utilizados para entrada e
quais serão utilizados para saída (Tabela 3.1).
// i n i c i o do programa
void main ( void )
{
// d e f i n i m o s como :
// u n s i g n e d c h a r : p o i s os 8 b i t s r e p r e s e n t a m v a l o r e s
// v o l a t i l e : as v a r i á v e i s podem mudar a q u a l q u e r momento
// near : i n d i c a p o s i c i o n a m e n t o do r e g i s t r o e s t a na memória
v o l a t i l e near unsigned char ∗ PORTD = 0 xF83 ;
v o l a t i l e near unsigned char ∗ TRISD = 0 xF95 ;
// c o n f i g u r a n d o t o d o s os p i n o s como s a í d a s
// 0 = s a í d a ( Output )
// 1 = e n t r a d a ( I n p u t )
∗ TRISD = 0 b00000000 ;
// l i g a apenas os q u a t r o ú l t i m o s l e d s
∗ PORTD = 0 b11110000 ;
//mantém o s i s t e m a l i g a d o i n d e f i n i d a m e n t e
for ( ; ; ) ;
}
Notar que, por serem ponteiros, sempre que precisarmos utilizar o valor de tais variáveis é
necessário que coloquemos o asterisco.
Uma outra maneira de manipular as portas é criar define’s que permitem o uso das portas
como variáveis, sem a necessidade de utilizar ponteiros de modo explícito, nem asteriscos no
código.
// d e f i n e ' s para p o r t a s de e n t r a d a e s a í d a
#define PORTD ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF83 )
#define TRISD ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF95 )
// i n i c i o do programa
void main ( void )
{
// c o n f i g u r a n d o t o d o s os p i n o s como s a í d a s
TRISD = 0 b00000000 ;
// l i g a apenas os q u a t r o ú l t i m o s l e d s
PORTD = 0 b11110000 ;
//mantém o s i s t e m a l i g a d o i n d e f i n i d a m e n t e
for ( ; ; ) ;
}
Como estamos criando um define, é uma boa prática de programação utilizar apenas letras
maiúsculas para diferenciá-lo de uma variável comum.
Notem que usamos dois asteriscos no define. É isto que permite que utilizemos o define como
uma variável qualquer, sem a necessidade de utilizar um asterisco extra em todas as chamadas
da "variável", como no caso dos ponteiros.
A segunda abordagem (com define) é preferida em relação à primeira pois, dependendo do
compilador, gera códigos mais rápidos além de economizar memória. Além disso, permite que a
definição seja feita apenas uma vez e utilizada em todo o programa.
Para a placa que estamos utilizando, a configuração dos terminais do PIC segue conforme a
Tabela 3.2. Esta configuração reflete a opção do autor de acordo com as possibilidades da placa
e também o sistema mínimo para realização de todas as experiências da apostila.
Os terminais não citados na Tabela 3.2 (1, 3, 5, 6, 15, 18, 23 e 24) possuem periféricos
que não serão utilizados neste curso. Os terminais 11 e 31 representam a alimentação positiva.
O comum (terra) está ligado ao 12 e ao 32. O microcontrolador utilizado (18f4550) possui o
encapsulamento DIP. Para outros encapsulamentos favor considerar o datasheet.
Da Tabela 3.2, temos que a porta A possui o primeiro bit como entrada analógica e o terceiro
e sexto como saída digital. Os dois bits digitais servem como controle de ativação do display.
22 RD3/SPP3
25 RC6/TX/CK
RS232
26 RC7/RX/DT/SDO
27 RD4/SPP4
28 RD5/SPP5/P1B Barramento de dados para o
29 RD6/SPP6/P1C LCD/7seg/Led
30 RD7/SPP7/P1D
33 RB0/AN12/INT0/SDI
34 RB1/AN10/INT1/SCK
Saídas para alimentação do teclado
35 RB2/AN8/INT2/VMO
36 RB3/AN9/CCP2/VPO
37 RB4/AN11/KBI0/CSSPP
38 RB5/KBI1/PGM
Entradas para leitura do teclado
39 RB6/KBI2/PGC
40 RB7/KBI3/PGD
A porta B possui os 4 primeiros bits como saídas e os quatro últimos como entrada. Esta
porta serve para leitura da matriz de chaves. É possível realizar a leitura através de interrupção.
A porta C possui o segundo e terceiro bit como saída PWM e o sétimo e oitavo como
comunicação serial.
A porta D é utilizada como barramento de dados. Os valores escritos nela são transmitidos,
simultaneamente, para os leds, os displays de 7 segmentos e o display de LCD.
A porta E possui apenas os 3 primeiros bits configurados como saídas digitais. São utilizados
para controle de ativação dos displays e também como sinais de controle do LCD.
Podemos notar pela Figura 3.2 que existe um jumper (JP1) que habilita ou não o funcio-
namento destes leds. Além disso percebemos que se o jumper estiver encaixado, os led's estão
permanentemente ligados ao 5 volts. Deste modo, para que o led acenda, é necessário colocar o
valor 0 (zero) no respectivo bit da porta D. Quando um dispositivo é ligado com o valor 0 (zero)
e desligado com o valor 1 (um), dizemos que este dispositivo opera com lógica invertida.
Conforme visto é preciso configurar os pinos da porta D como saída, para isso basta escrever
zero em cada um deles no registro TRISD.
Estes displays foram concebidos com o intuito de gerar os dez algarismos romanos: 0, 1, 2,
3, 4, 5, 6, 7, 8, 9, sendo que os algarismos 0, 6, 7 e 9 podem ser representados de mais de uma
maneira.
Além dos algarismos é possível representar apenas algumas letras de modo não ambíguo: as
maiúsculas A, C, E, F, H, J, L, P, S, U, Z e as minúsculas: a, b, c, d, h, i, n, o, r, t, u.
Os displays podem ser do tipo cátodo comum, ou ânodo comum indicando qual o tipo de
ligação dos leds. Contudo, esta diferença não será crítica para este estudo. Na Figura 3.4
podemos visualizar o esquema elétrico e a disposição física de cada led no componente.
Figura 3.4: Diagrama elétrico para display de 7 segmentos com ânodo comum
http://www.hobbyprojects.com/the_diode/seven_segment_display.html
Pela Figura 3.4 podemos notar que para que apareça o número 2 no display é necessário
acender os leds a, b, g, e, d. Se estivermos utilizando um display com cátodo comum, precisamos
colocar um nível alto para ligar o led, ou seja, o led liga com valor 1 (um) e desliga com valor 0
(zero). Isto é também conhecido como lógica positiva. Na Tabela 3.3 são apresentados os valores
em binário e em hexadecimal para cada representação alfanumérica3 . Dentre as letras disponíveis
estão apresentadas apenas os caracteres A, b, C, d, E, F. Estas foram escolhidas por serem as
mais utilizadas para apresentar valores em hexadecimal nos displays. Neste curso utilizaremos a
3
Notar que os valores hexadecimais apresentados servem apenas quando existe uma sequencia na ligação entre
a porta do microcontrolador e os pinos do display. Em alguns sistemas, o display pode ser controlado por duas
portas diferentes, ou possuir alguma alteração na sequencia de ligação. Para tais casos é necessário remontar a
tabela apresentada.
ordem direta apresentada na Tabela 3.3. A utilização de uma ou outra depende da ligação feita
na placa. A Figura 3.5 apresenta o esquema elétrico disponível.
Para simplificar a utilização deste tipo de display é comum criar uma tabela cujas posições
representam o valor de conversão para o display. Conforme pode ser visto no código a seguir.
Multiplexação de displays
Cada display exige 7 ou 8 terminais de controle, caso também seja utilizado o ponto decimal.
Para utilizar 4 displays, por exemplo um relógio com dois dígitos para horas e dois para minutos,
precisaríamos de 32 terminais de saída, o que pode ser um custo4 muito alto para o projeto.
Uma técnica que pode ser utilizada é a multiplexação dos displays. Esta técnica leva em conta
um efeito biológico denominado percepção retiniana. O olho humano é incapaz de perceber
mudanças mais rápidas que 1/30 (s). Outro fator importante é que as imagens mais claras
ficam gravadas na retina devido ao tempo que leva para sensibilizar e dessensibilizar as células
(bastonetes).
Deste modo podemos ligar e desligar rapidamente o display que a imagem continuará na
retina. Se ligarmos cada display, um por vez, sequencialmente, de maneira suficientemente
rápida, teremos a impressão que todos estão ligados. A frequência de “chaveamento” deve ser
mais rápida que 30Hz.
A Figura 3.5 apresenta o circuito com 4 displays multiplexados. Percebemos que os terminais
iguais estão ligados juntos. Percebemos também que os terminais de cátodo comum estão cada
um ligado a uma saída diferente. Com esta arquitetura reduzimos a quantidade de terminais
necessários de 32 para 12, uma economia de 20 terminais.
Mas esta economia tem um custo, o sistema se torna mais complexo pois não podemos ligar
dois displays ao mesmo tempo.
O controle de qual display será ligado é feito através do transistor que permite a ligação do
cátodo comum ao terra, ou o ânodo comum ao VCC (depende do tipo de dispositivo. Para o
correto funcionamento não basta agora acender os leds corretos para formar o número, temos
que seguir um algoritmo mais complexo:
4. desligar o display
6. voltar ao passo 1
4
Microcontroladores com mais terminais possuem um custo superior, mesmo possuindo os mesmos periféricos
internamente.
5
Se a taxa de atualização dos displays for muito baixa, estes vão apresentar uma variação na intensidade, como
se estivessem piscando. Este efeito é chamado de flicker.
Criação da biblioteca
O programa 3.1 apresenta um exemplo de código para criar uma biblioteca para os displays de 7
segmentos. O programa 3.2 apresenta o header da biblioteca. Já o programa 3.3 apresenta uma
demonstração de uso da biblioteca.
5 // i n i c i o do programa
6 void main ( void )
7 {
8 unsigned i n t tempo ;
9 InicializaDisplays ( ) ;
10 MudaDigito ( 0 , 0 ) ;
11 MudaDigito ( 1 , 1 ) ;
12 MudaDigito ( 2 , 2 ) ;
13 MudaDigito ( 3 , 3 ) ;
14 for ( ; ; )
15 {
16 AtualizaDisplay ( ) ;
17 // g a s t a um tempo para e v i t a r o e f e i t o f l i c k e r
18 f o r ( tempo =0; tempo <1000; tempo++) ;
19 }
20 }
Pela Figura 3.6 percebemos que a tensão de saída é igual a VCC quando a chave está desligada
pois não existe corrente circulando no circuito portanto a queda de tensão em R1 é zero. Quando
a chave é pressionada uma corrente flui de VCC para o terra passando por R1. Como não existe
nenhuma outra resistência no circuito toda a tensão fica em cima de R1 e a tensão de saída passa
a ser zero.
Apesar do funcionamento aparentemente simples, este tipo de circuito apresenta um problema
de oscilação do sinal no momento em que a tecla é pressionada. Esta oscilação é conhecida como
bouncing (Figura 3.7).
Estas oscilações indevidas podem gerar acionamentos acidentais, causando mau funciona-
mento do programa. Para evitar isso podemos utilizar técnicas de debounce, por hardware ou
software.
A opção de debounce por hardware pode ser visualizada na Figura 3.8.
Neste circuito, o capacitor desempenha o papel de amortecedor do sinal. Um circuito com um
resistor e um capacitor possui um tempo de atraso para o sinal. Este é o tempo necessário para
carregar o capacitor. Deste modo as alterações rápidas no sinal, devido à oscilação mecânica da
chave, são filtradas e não ocorre o problema dos chaveamentos indevidos conforme pode ser visto
na Figura 3.9. Notar que o nível do sinal filtrado não chega a zero em nenhum momento, devido
à constante de tempo do filtro RC ser maior que o período de debounce.
8MHz) é de 0,56 (µs). Antes de utilizar o valor que estamos lendo na porta em questão devemos
esperar 300 ciclos de clock após alguma mudança para ter certeza que o sinal se estabilizou, ou
seja, a fase de bouncing acabou.
Notar que, no código, o contador é iniciado com o valor 22. Através da análise do assembler
podemos saber que cada ciclo de conferência do sinal possui 14 instruções. Assim é necessário
que o sinal permaneça com o mesmo valor durante 308 ciclos para que a variável valAtual receba
o valor da porta B. Estes valores podem ser determinados empiricamente através de testes com
osciloscópios.
}
else
{
valTemp = PORTB ; // s e mudar , a t u a l i z a o s i s t e m a e r e i n i c i a o tempo
tempo = 2 2 ;
}
}
valAtual = valTemp ; // v a l o r a t u a l i z a d o ;
PORTD = valAtual ; // c o l o c a o v a l o r no barramento de l e d s
}
}
}
}
}
É importante notar que o código acima não apresenta debounce em software para as teclas. É
possível realizar um debounce minimizando o gasto com memória e tempo, representando cada
chave como um bit diferente numa variável. Esta será a abordagem utilizada na geração da
biblioteca para o teclado.
Criação da biblioteca
O programa 3.4 apresenta um exemplo de código para criar uma biblioteca para um teclado de
16 teclas com leitura matricial. O header pode ser visto no programa 3.5. Já o programa 3.6
apresenta uma demonstração de uso da biblioteca.
5 // i n i c i o do programa
6 void main ( void )
7 {
8 InicializaTeclado ( ) ;
9 TRISD = 0 x00 ; // C o n f i g u r a a p o r t a D como s a í d a
10 PORTD = 0 xFF ; // d e s l i g a t o d o s os l e d s
11 while (1==1)
12 {
13 DebounceTeclas ( ) ;
14 PORTD = LerTeclas ( ) ;
15 }
16 }
Este mesmo tipo de display pode ser encontrado em diversas versões com tamanhos e cores
diferentes sendo os mais comuns de 1x8, 2x16 e 4x40. Pode ainda ter 16 ou 14 terminais,
dependendo se existe ou não retro iluminação. Estes terminais são identificados como:
1. Terra 9. Bit 2
Criação da biblioteca
Para facilitar o controle do display, podemos criar três funções, uma para inicialização, uma para
escrever um caractere e a última para enviar um comando. Estas funções estão apresentadas no
programa 3.8, que constitui um exemplo de biblioteca. Além destas três funções é necessário ter
uma função de delay, que garanta um determinado tempo para que as informações sejam lidas
corretamente pelo LCD.
O header desta biblioteca e um exemplo de como usá-la são apresentados nos programas 3.7
e 3.9, respectivamente.
5 // i n i c i o do programa
6 void main ( void )
7 {
8 unsigned i n t i , j ;
9 char msg [ ] = " Hello World !" ;
10 InicializaLCD ( ) ;
11 f o r ( i =0;i <11; i++)
12 {
13 EnviaDados ( msg [ i ] ) ;
14 f o r ( j = 0 ; j < 6 5 0 0 0 ; j++) ;
15 }
16 for ( ; ; ) ;
17 }
RS 232
O protocolo de comunicação RS232 (Recommended Standard 232) é muito utilizado para co-
municação entre dispositivos que transmitem ou recebem pouca quantidade de informação. É
um dos protocolos mais antigos ainda em uso, tendo seu primeiro uso em 1962 para máquinas
eletromecânicas de escrever. O padrão RS232 revisão C é datado de 1969. Em 1986 aparece a
revisão D pela EIA (Electronic Industries Alliance). A versão atual do protocolo é datada de
1997 pela TIA (Telecommunications Industry Association) sendo chamada TIA-232-F.
O procedimento de envio de um valor pela serial através do padrão RS232 pode ser visto
como uma operação de bit-shift.
Por exemplo a letra K: em ASCII é codificada como 7610 e em binário como 110100102 . Na
maioria dos dispositivos primeiro se envia o bit menos significativo. Antes de iniciar a transmissão
dos bits, é enviado um bit de começo, indicando que haverá transmissão a partir daquele instante.
Após isso o bit menos significativo é enviado para a saída do microcontrolador. Realiza-se então
um shift para direita e o “novo” bit menos significativo é “reenviado”. Esta operação é realizada
oito vezes. Após esse procedimento envia-se um bit de parada, que pode ter a duração de um ou
dois bits.
7
Existem comunicações seriais onde o nível de tensão é diferencial, portanto não temos uma referência ou
ground. Além disso a comunicação com apenas 1 par de fios é denominada half-duplex, onde a transmissão e
recepção não acontecem no mesmo instante
A Figura 3.16 apresenta o sinal elétrico8 enviado ao longo do tempo para a letra K. Notar
a região em branco, que se estende entre +3 e -3. Ela indica a região de tensão na qual o
sinal não está definido. Caso a tensão lida esteja entre estes limiares, seja devido à ruídos ou
outros problemas, o sistema de recepção não entenderá a mensagem e os dados serão perdidos
ou corrompidos.
A equação que gera o menor erro é a terceira. Como queremos trabalhar com uma comuni-
cação assíncrona, da Tabela 3.6 obtemos que os bits de configuração devem ser: TXSTA(4) = 0,
BAUDCON(3) = 1 e TXSTA(2) = 1. A seguir temos todo o processo de configuração da porta
serial RS232.
BitClr ( BAUDCON , 0 ) ; // D e s a b i l i t a a u t o d e t e c ç ã o de v e l o c i d a d e
BitSet ( BAUDCON , 3 ) ; // R e g i s t r o de g e r a ç ã o de s i n a l com 16 b i t s
BitClr ( BAUDCON , 6 ) ; // Operação de r e c e p ç ã o e s t a a t i v a
BitClr ( RCSTA , 1 ) ; // D e s a b i l i t a b i t de e r r o de o v e r r u n
BitClr ( RCSTA , 2 ) ; // D e s a b i l i t a b i t e r r o na comunicação
BitClr ( RCSTA , 4 ) ; // H a b i l i t a b i t de r e c e p ç ã o
BitClr ( RCSTA , 6 ) ; // S e l e c i o n a 8 b i t s
BitSet ( RCSTA , 7 ) ; // C o n f i g u r a RX/TX como p i n o s de comunicação
BitSet ( TXSTA , 2 ) ; //Modo de a l t a v e l o c i d a d e h a b i l i t a d o
BitSet ( TXSTA , 3 ) ; // Envia b i t de parada ( b r e a k c h a r a c t e r b i t )
BitClr ( TXSTA , 4 ) ; //Modo a s s í n c r o n o
BitSet ( TXSTA , 5 ) ; // H a b i l i t a t r a n s m i s s ã o
BitClr ( TXSTA , 6 ) ; // S e l e c i o n a 8 b i t s
SPBRGH = 0 x00 ; // C o n f i g u r a para 56 k (SPBRGH|SPBRG = 32)
SPBRG = 0 x22 ; // C o n f i g u r a para 56 k (SPBRGH|SPBRG = 32)
BitSet ( TRISC , 6 ) ; // C o n f i g u r a p i n o de r e c e p ç ã o como e n t r a d a
BitClr ( TRISC , 7 ) ; // C o n f i g u r a p i n o de e n v i o como s a í d a
O procedimento de serialização dos bits é feito de maneira automática pelo hardware. En-
quanto ele está realizando este processo não devemos mexer no registro que armazena o byte a
ser enviado. Por isso devemos verificar se o registro está disponível. Isto é feito através do bit 4
do registro PIR. Quando este valor estiver em 1 basta escrever o valor que desejamos transmitir
no registro TXREG.
{
char resp = 0 ;
i f ( BitTst ( PIR1 , 5 ) ) // V e r i f i c a s e e x i s t e algum v a l o r d i s p o n í v e l
{
resp = RCREG ; // r e t o r n a o v a l o r
}
return resp ; // r e t o r n a z e r o
}
A metodologia apresentada para leitura e escrita de valores é conhecida como pooling. Neste
tipo de abordagem ficamos parados esperando que o valor esteja disponível para leitura/escrita.
Este é o método mais simples para se controlar qualquer tipo de dispositivo. O problema é que
o processador fica travado em uma tarefa gastando tempo que seria útil para realizar outras
operações. A melhor alternativa para resolver este problema é através de interrupções, que serão
abordadas apenas no tópico ..
Criação da biblioteca
O programa 3.10 apresenta um exemplo de código para criar uma biblioteca para comunicação
serial. O arquivo de header é apresentado no programa 3.11 e o exemplo de uso demonstrado no
programa 3.12.
A seguir o arquivo de header.
5 // i n i c i o do programa
6 void main ( void )
7 {
8 unsigned i n t i , j ;
9 char msg [ ] = " Hello World !" ;
10 unsigned char resp ;
11 TRISD = 0 x00 ; // a c e s s o aos l e d s
12 InicializaSerial ( ) ;
13 j =0;
14 for ( ; ; )
15 {
16 // d e l a y
17 f o r ( i = 0 ; i < 6 5 0 0 0 ; i++) ;
18 // e n v i a dados
19 EnviaSerial ( msg [ j ] ) ;
20 j++;
21 i f ( j > 11)
22 {
23 j =0;
24 EnviaSerial ( 1 3 ) ;
25 }
26 // r e c e b e dados
27 resp = RecebeSerial ( ) ;
28 i f ( resp !=0)
29 {
30 PORTD = resp ;
31 }
32 }
33 }
. Conversor AD
Um conversor de analógico para digital é um circuito capaz de transformar um valor de tensão
numa informação binária. O circuito que utilizaremos possui uma precisão de 10 bits, ou seja,
ele é capaz de sentir uma variação de praticamente um milésimo9 da excursão máxima do sinal.
Para a configuração que iremos utilizar, com uma fonte de 5v, isto significa uma sensibilidade
de 4.88mV.
Elementos sensores
A conversão AD é muito utilizada para realizarmos a leitura de sensores. Todo sensor é baseado
num transdutor. Um elemento transdutor é aquele que consegue transformar um tipo de grandeza
em outro, por exemplo uma lâmpada incandescente (Figura 3.17).
Podemos utilizar uma lâmpada incandescente como sensor de tensão: pega-se uma lâmpada
de 220V. Liga-se a lâmpada a uma tomada desconhecida. Se o brilho for forte a tomada possui
220V, se o brilho for de baixa intensidade, a tomada possui 127V. Se a lâmpada não ascender
existe algum problema na fiação, na tomada ou até mesmo na lâmpada. A lâmpada é um
transdutor de tensão para luminosidade.
Para a eletrônica estamos interessados em transdutores cuja saída seja uma variação de
tensão, corrente ou resistência.
Um sistema muito simples de transdutor de ângulo para resistência é o potenciômetro (Fi-
gura 3.18).
Se o potenciômetro estiver alimentado pelos terminais da extremidade, o terminal central fun-
ciona como um divisor de tensão. O valor de saída é proporcional à posição do cursor. Podemos
aproximar o potenciômetro como duas resistências conforme apresentado na Figura 3.19.
Deste modo a tensão aplicada em RL (supondo que RL é muito maior que R2) é:
V S ∗ R2 R2
VRL = = VS ∗ ( )
R1 + R2 RPot
Se na construção do potenciômetro a variação da resistência ao longo da trilha foi feita de modo
constante, a resistência varia de maneira linear com a posição do cursor. Deste modo podemos
utilizar o potenciômetro como um transdutor de ângulo.
Diversas medidas podem ser realizadas utilizando o conceito de divisor de tensão: luminosi-
dade com LDR's, força com strain-gages, deslocamento com potenciômetros lineares, etc.
Existem alguns sensores que possuem circuitos de amplificação e condicionamento do sinal
embutidos no mesmo envólucro que o elemento sensor. A estes tipos de sensores damos a deno-
minação de ativos.
9
Com uma precisão de 10 bits conseguimos representar 21̂0 valores diferentes, ou 1024 valores.
Um sensor ativo possui no mínimo 3 terminais: 2 para alimentação e 1 para saída do sinal.
Um exemplo deste tipo de sensor é o LM35 (Figura 3.20) que é utilizado para monitoramento
de temperatura.
Na Figura 3.21 é apresentado o diagrama de blocos do circuito integrado do LM35. O diodo
é utilizado como unidade sensora de temperatura.
Processo de conversão AD
Existem alguns circuitos que realizam a conversão de um sinal analógico advindo de um trans-
dutor para um sinal digital com uma precisão arbitrária.
A abordagem mais simples é a utilização de comparadores. Cada comparador possui um
nível diferente de tensão de referência. Estes níveis são escolhidos de forma que a representação
binária faça sentido.
Exemplo: Conversão de um valor analógico que varia de zero à cinco volts numa palavra
digital de dois bits.
Para N bits temos N 2 representações diferentes. É interessante então dividir a amplitude
inicial por N 2 divisões iguais. Para N = 2 temos 4 representações de 1.25v cada.
O circuito eletrônico responsável pelas comparações pode ser visualizado na Figura 3.22.
O circuito da Figura 3.22 é conhecido como conversor analógico digital do tipo flash onde
cada nível de tensão possui seu próprio comparador. Existem outras abordagens que minimizam
11 i n t LeValorAD ( void )
12 {
13 unsigned i n t ADvalor ;
14 BitSet ( ADCON0 , 1 ) ; // i n i c i a c o n v e r s ã o
15 while ( BitTst ( ADCON0 , 1 ) ) ; // e s p e r a t e r m i n a r a c o n v e r s ã o ;
16 ADvalor = ADRESH ; // l ê o r e s u l t a d o
17 ADvalor <<= 8 ;
18 ADvalor += ADRESL ;
19 return ADvalor ;
20 }
o uso de conversores (parte mais cara do circuito) mas inserem atraso no processo de conversão.
O atraso depende do tipo de circuito que é implementado.
Criação da biblioteca
Toda conversão leva um determinado tempo que, conforme citado na seção anterior, depende
da arquitetura que estamos utilizando, da qualidade do conversor e, algumas vezes, do valor de
tensão que queremos converter. Para que o microcontrolador realize corretamente a conversão é
necessário seguir os seguintes passos:
1. Configurar o conversor
2. Iniciar a conversão
4. Ler o valor
Suponha uma saída PWM ligada a um resistor. Quando a saída estiver em nível alto existe
a passagem de corrente elétrica e a resistência aquece. Quando estiver em nível baixo a corrente
para. Como a constante térmica do componente é alta, leva-se alguns segundos para que o
resistor aqueça ou esfrie, é possível ajustar a quantidade de energia média com uma frequência
de sinal PWM suficientemente alta.
Em outras palavras, se a frequência do PWM for mais alta do que a carga conseguir enxergar,
quando colocarmos o duty cycle em 50%, a carga irá receber 50% da energia total. Se for um
resistor, podemos controlar a temperatura final deste modo, num motor podemos ajustar a
velocidade de rotação que queremos.
Como citado a frequência do PWM tem que ser suficientemente alta. Esta frequência depende
do circuito implementado no microcontrolador. No caso do PIC18f4550 é calculada segundo a
fórmula abaixo.
FOSC
Freq.PWM =
[(PR2 ) + 1] ∗ 4 ∗ (TMR2 Prescaler )
Com uma frequência de oscilação de 8MHz (disponível na placa) podemos atingir frequências
que variam de 488Hz até 2MHz.
O problema de trabalhar, no caso do PIC, com frequências muito altas é que perdemos
resolução na definição do duty cycle. Por exemplo, para a frequência de PWM em 2MHz com
um clock de 8MHz temos uma resolução de apenas 2 bits. Ou seja, podemos configurar a saída
para 0%, 25%, 50% ou 75% do valor máximo. A resolução pode ser obtida segundo a fórmula
abaixo.
log( FFPWM
OSC
)
Resolução PWM (max ) = bits
log(2)
O PIC18f4550 permite uma resolução de até 10 bits. Com um oscilador principal de 8 MHz
a frequência máxima do PWM para utilizarmos os 10 bits de resolução é 7812,5 Hz. Para uma
resolução de 8 bits a frequência máxima aumenta para 31.25 kHz.
Utilizando a primeira e segunda fórmulas podemos montar a Tabela 3.7.
O duty cycle (em porcentagem) é calculado de acordo com a fórmula abaixo:
Tabela 3.7: Faixa de frequências máximas e mínimas para cada configuração do prescaler
Criação da biblioteca
Para configurar as saídas PWM devemos especificar a frequência de trabalho através de PR2
e TCON2, além do duty cycle em CCPR1L e CCPR2L. No registro TRISC é configurado o
terminal como uma saída e em CCP1CON e CCP2CON definimos que ele deve trabalhar como
um PWM. O prescaler foi configurado para 16 bits de modo a obter a maior faixa de frequência
audível disponível (Tabela 3.7). Notar que é importante realizar primeiro a multiplicação e
somente depois a divisão, para não haver perda de informação. No programa 3.16 é apresentado
um código exemplo de como criar as rotinas de operação do PWM. O header desta biblioteca
é apresentado no programa 3.17. Por fim, o programa 3.18 apresenta um exemplo de utilização
desta biblioteca.
12 // tempo em micro s e g u n d o s
13 void ResetaTimer ( unsigned i n t tempo )
14 {
15 // para p l a c a com 8MHz 1 us = 2 c i c l o s
16 unsigned ciclos = tempo ∗ 2 ;
17 // o v e r f l o w a c o n t e c e com 2^15−1 = 65535 ( max u n s i g n e d i n t )
18 ciclos = 65535 − ciclos ;
19 ciclos −= 1 4 ; // s u b t r a i tempo de o v e r h e a d ( e x p e r i m e n t a l )
20 TMR0H = ( ciclos >> 8 ) ; // s a l v a a p a r t e a l t a
21 TMR0L = ( ciclos & 0 x00FF ) ; // s a l v a a p a r t e b a i x a
22 BitClr ( INTCON , 2 ) ; // l i m p a a f l a g de o v e r f l o w
23 }
. Timer
Nos microcontroladores existem estruturas próprias para realizar a contagem de tempo, estas
estruturas são denominadas Timers.
O PIC18f4550 possui quatro timers. Para utilizarmos a saída PWM temos que configurar o
timer 2, que gera a base de tempo que sera comparada com o duty cycle.
Ao invés de contarmos quantas instruções são necessárias para criar um delay de um deter-
minado tempo, podemos utilizar os timers. Escolhemos o valor de tempo que queremos contar,
inicializamos as variáveis e esperamos acontecer um “overflow” 10 na contagem do timer.
Para trabalhar com o timer precisamos basicamente de uma função de inicialização, uma
para resetar o timer e outra para indicar se o tempo configurado anteriormente já passou. Uma
quarta função “AguardaTimer()”, foi construída para facilitar o desenvolvimento de algumas
rotinas comuns nos programas. Estas rotinas estão implementadas no programa 3.19 cujo header
é apresentado no programa 3.20. O modo de utilizar esta biblioteca é apresentado no programa
3.21.
10
Overflow é conhecido como estouro de variável. Toda variável digital possui um valor máximo, por exemplo
255 para uma variável do tipo unsigned char. Se uma variável unsigned char possui o valor 255 e é acrescida de
1, seu valor passa a ser zero e acontece o estouro ou overflow.
37 //sem som
38 #define v 125000
11
Esta é a máxima frequência possível para o PWM operado com prescaler de 16x.
. Interrupção
Até o momento todos os programas que foram desenvolvidos seguiam um fluxo sequencial sendo
alterado apenas por chamadas de funções, estruturas de decisão ou loop. Um dos problemas de
se utilizar este tipo de estrutura é que alguns periféricos possuem um tempo muito grande para
realizarem sua função como o conversor AD por exemplo. Nesta situação o que fazemos é iniciar
a conversão e ficar monitorando uma variável que indicava quando a conversão tinha terminado.
Esta técnica é conhecida como pooling.
O problema de se realizar a leitura de algum periférico por pooling é que o processador perde
tempo realizando operações desnecessárias checando a variável de controle. Uma alternativa é
utilizar um sistema que, quando a operação desejada estivesse finalizada, nos avisasse para que
pudéssemos tomar uma providência. Este procedimento é chamado de interrupção.
Alguns dispositivos possuem a possibilidade de operarem com interrupções. Quando a con-
dição do dispositivo for satisfeita (fim da conversão para o AD, chegada de informação na serial,
mudança no valor da variável na porta B) ele gera uma interrupção. A interrupção para o
programa no ponto em que ele estiver, salva todos os dados atuais e vai para uma função pré-
definida. Esta função realiza seu trabalho e assim que terminar volta o programa no mesmo
ponto onde estava antes da interrupção.
Dos dispositivos estudados até agora os que geram interrupção são:
• Porta Serial: quando chega alguma informação em RCREG ou quando o buffer de transmissão
TXREG estiver disponível.
• Porta B: quando algum dos bits configurados como entrada altera seu valor.
Para gerenciar a interrupção, deve-se criar uma rotina que irá verificar qual foi hardware que
gerou a interrupção e tomar as providências necessárias. A maneira de declarar que uma deter-
minada função será a responsável pelo tratamento da interrupção depende do compilador.
Para o compilador SDCC basta que coloquemos a expressão “interrupt 1” após o nome da
função.
Para o compilador C18 da Microchip temos que gerar um código em assembler que indicará
qual função será a responsável pela interrupção.
// I n d i c a r a p o s i ç ã o no v e t o r de i n t e r r u p ç õ e s
#pragma code h i g h _ v e c t o r=0x08
void interrupt_at_high_vector ( void )
{
_asm GOTO Interrupcao _endasm
}
#pragma code
#pragma i n t e r r u p t NomeDaFuncao
A função que irá tratar da interrupção não retorna nem recebe nenhum valor.
Existe uma correlação entre o número que vem depois da expressão “interrupt” para o com-
pilador SDCC e o número ao final da expressão “#pragma code high_vector” para o C18. Estes
números representam a posição para a qual o microcontrolador vai quando acontece uma inter-
rupção. Estas posições estão numa área conhecida como vetor de interrupções.
Para o microcontrolador PIC18f4550 este vetor possui três posições importantes: 0x00(0),
0x08(1) e 0x18(2). O compilador C18 usa a posição física e o SDCC o número entre parênteses.
A posição 0 (0x00) representa o endereço que o microcontrolador busca quando este acaba
de ser ligado. É a posição de reset. Geralmente saímos deste vetor e vamos direto para a função
main().
As posições 1 e 2 (0x08,0x18) são reservadas para as interrupções de alta e baixa prioridade,
respectivamente. É necessário que o programador escolha quais dispositivos são de alta e quais são
de baixa prioridade. Existe ainda um modo de compatibilidade com os microcontroladores mais
antigos no qual todos os periféricos são mapeados na primeira interrupção (0x08). Utilizaremos
este modo por questão de facilidade.
Como todos os periféricos estão mapeados na mesma interrupção, a função deve ser capaz de
diferenciar entre as diversas fontes de requisição. Uma maneira de se realizar esta verificação é
através das flags de controle, ou seja, bits que indicam a situação de cada periférico.
O programa 3.23 apresenta uma função que trata de todas as fontes possíveis de interrupção
para o PIC18f4550.
Em geral não é necessário tratar todas as interrupções, apenas aquelas que influenciarão
o sistema. O programa 3.24 apresenta um exemplo de uma função que trata as interrupções
advindas da porta B, do timer 0, da serial e do AD.
Para que a função apresentada no programa 3.24 funcione corretamente devemos inicializar
as interrupções de modo adequado, conforme apresentado no programa 3.25.
3 // i n i c i o do programa
4 void main ( void )
5 {
6 unsigned i n t i ;
7 unsigned char temp ;
8 TRISD=0x00 ;
9 PORTD=0x00 ;
10 BitSet ( WDTCON , 0 ) ; // l i g a o s i s t e m a de watchdog
11 for ( ; ; )
12 {
13 PORTD++;
14 f o r ( i = 0 ; i < 1 0 0 0 0 ; i++)
15 {
16 CLRWTD ( ) ;
17 }
18 }
19 }
. Watchdog
Por algum motivo o software pode travar em algum ponto, seja por um loop infinito ou por
esperar a resposta de algum componente através de pooling de uma variável.
A primeira condição pode ser evitada através de um projeto cuidadoso de software aliado a
uma boa validação. Já a segunda exige que os hardwares adjacentes funcionem corretamente.
Se algum hardware apresenta uma falha e não envia a resposta que o microcontrolador está
esperando, este último irá travar. Nestas situações é possível utilizar o watchdog.
O watchdog é um sistema que visa aumentar a segurança do projeto. Ele funciona como
um temporizador que precisa constantemente ser reiniciado. Caso não seja reiniciado no tempo
exigido, o watchdog reinicia o microcontrolador dando a possibilidade de sair de um loop infinito
ou de um pooling sem resposta.
Para habilitar o watchdog é necessário alterar os registros de configuração, especificamente
o CONFIG2H (0x300002). Outro método consiste em deixar o watchdog desligado no registro e
ligá-lo através de software, como é apresentado no programa 3.26.
Notar o #define criado na primeira linha do programa 3.26. A expressão CLRWDT é o
comando em assembler responsável por resetar o watchdog. As diretivas _asm e _endasm
informam ao compilador que os comandos utilizados devem ser transcritos exatamente iguais
para o arquivo assembler a ser gerado.
Se após ligar o watchdog não realizarmos a operação de reset dele, comentando ou excluindo a
função CLRWTD(), o sistema irá travar tão logo o tempo associado ao watchdog tenha expirado
pela primeira vez, reiniciando o sistema. Como apenas reiniciar não soluciona o problema, pois o
programa criado não terá função para reiniciar o watchdog, o sistema continua sendo reiniciado
indefinidamente.
Arquitetura de desenvolvimento de
software
“Constrained by memory limitations, performance requirements, and
physical and cost considerations, each embedded system design re-
quires a middleware platform tailored precisely to its needs, unused
features occupy precious memory space, while missing capabilities
must be tacked on.” - Dr. Richard Soley
95
96 Arquitetura de desenvolvimento de software
Esta é a estratégia utilizada até agora nos exemplos apresentados. Dentro da função principal é
colocado um loop infinito. Todas as tarefas são chamadas através de funções.
A vantagem de se utilizar esta abordagem é a facilidade de se iniciar um projeto. Para
sistemas maiores começa a ficar complicado coordenar as tarefas e garantir a execução num
tempo determinístico. Outro problema é a modificação/ampliação do software. Geralmente a
inserção de uma função no meio do loop pode gerar erros em outras funções devido a restrições
de tempo dos periféricos associados.
No exemplo acima, a inserção da comunicação serial e os cálculos podem atrapalhar a escrita
no display de sete segmentos, gerando flicker.
Inicio
Ler Atualiza
Teclado Display
Atualiza Escreve
Display Serial
Atualiza
Ler Serial Display
Nota-se que após a fase de inicialização o sistema entra num ciclo, como na abordagem one-
single-loop. Outra peculiaridade é que algumas tarefas podem ser executadas mais de uma vez
para garantir as restrições de tempo. No exemplo a tarefa de atualização dos displays é executada
três vezes.
A transposição de uma máquina de estado para o código em C é realizada através de um
switch-case.
É possível retirar todas as atribuições para a variável slot e colocar no “slot-bottom” a ex-
pressão slot++. A abordagem apresentada foi escolhida por aumentar a robustez do sistema, já
que a variável slot controla todo o fluxo do programa.
A inserção de uma nova tarefa é realizada de maneira simples, basta adicionar outro slot, ou
seja, basta inserir um case/break com a tarefa desejada.
Como a máquina está dentro do loop infinito, a cada vez que o programa passar pelo case,
ele executará apenas um slot. Esta abordagem gera ainda outro efeito. Como pode ser visto
no código, naturalmente surgem duas regiões: “top-slot” e “bottom-slot”. Se algum código for
colocado nesta região ele será executado toda vez, de modo intercalado, entre os slots. Pela
Figura 4.1, percebemos que é exatamente este o comportamento que queremos para a função
AtualizaDisplay(). Deste modo, podemos remodelar o código fazendo esta alteração.
42 } // fim l o o p i n f i n i t o ( ! ? )
43 }
tempo. Notar que o slot 1 (S.1) gasta um tempo de 2.0(ms), o slot 2 de 3.1 (ms) e o slot 3 apenas
1.2 (ms). Já o top-slot consome 0.5 (ms) e o bottom-slot 0.3 (ms).
Top
S.1
S.2
S.3
Bottom
"vago"
0 5 10 15 20 25 30
Podemos notar que para o ciclo do primeiro slot são gastos 0.5+2.0+0.3 = 2.8(ms). Deste
modo o sistema fica “aguardando” na função AguardaTimer() durante 2.2 (ms) sem realizar
nenhum processamento útil. Para o segundo slot temos um tempo "livre"de 5-(0.5+3.1+0.3)=1.1
(ms). O terceiro slot é o que menos consome tempo de processamento, possuindo um tempo livre
de 5-(0.5+1.2+0.3)=3.0 (ms).
Top 1 1 1
S.1 3 3 3
Bottom 1 1 1
"vago" 3 3 3
Top 1 1
S.1 1 2 3 3
Bottom 1 1 1
"vago" 2 2 2
Interr. 1 1 1
Cada interrupção gasta um tempo de 1 (ms) conforme pode ser visto na Figura 4.4. Como
temos um tempo “vago” de 3 (ms) em cada ciclo basta garantir que os eventos que geram a
interrupção não ultrapassem a frequência de 3 eventos a cada 8 (ms).
Anexos
105
106 Anexos
10 // para o c o m p i l a d o r C18
11 //#pragma c o n f i g FOSC = HS // O s c i l a d o r c / c r i s t a l e x t e r n o HS
12 //#pragma c o n f i g CPUDIV = OSC1_PLL2 // P l l d e s l i g a d o
13 //#pragma c o n f i g WDT = OFF // Watchdog c o n t r o l a d o por s o f t w a r e
14 //#pragma c o n f i g LVP = OFF // Sem programação em b a i x a t e n s ã o \\\ h l i n e
. config.h
O arquivo config.h possui as diretivas de compilação para configuração do microcontrolador.
. basico.h
O header basico.h possui o endereço de todos os registros do microcontrolador PIC18f4550 que é
utilizado nesta apostila. Além disso contém alguns define’s importantes como as funções inline
para limpar a flag de watchdog e para manipulação de bits.
• MPLAB ICD 3
Antes de Começar
O dispostivo não deve ser plugado numa porta USB antes de começar a instalação do Driver.
Se você já plugou o dispositivo e apareceu a informação "Novo hardware encontrado", clique
em cancelar. Desligue o dispositivo e continue com os passos a seguir.
Se você já utilizou o setup do windows você instalou os drivers errados. Siga as instruções
de remoção dos drivers antes de prosseguir.
Passo 1
Conecte o dispositivo ao PC usando o cabo USB. Para os dispositivos que exigem alimentação
externa, ligue-a. Se estiver usando um hub USB, tenha certeza que o este possui energia sufici-
ente para alimentar o dispositivo.
Passo 2
A primeira vez que o dispositivo é conectado aparece uma mensagem indicando que o sistema
encontrou um novo hardware. Quando aparecer uma janela, escolha a opção “Localizar e insta-
lar o driver (recomendado)” Nota: Se aparecer uma mensagem perguntando sobre permissão no
Windows 7, clique em sim/continuar.
Passo 3
Escolha a opção: “Procurar o driver no meu computador (avançado)”
Passo 4
Quando aparecer uma janela pedindo para você indicar o caminho, procure em “C:\Arquivos
de programa (x86)\Microchip\MPLAB IDE\Drivers64”. Clique em continuar
Passo 5
A próxima tela irá perguntar se você quer continuar a instalar o dispositivo. Clique em Instalar
para continuar.
Passo 6
A próxima tela indicará que o software foi instalado corretamente. Clique em fechar para termi-
nar a instalação.
Passo 7
Verificar se o driver está instalado e visivel no Gerenciador de dispositivos em “Custom USB
Drivers>Microchip Custom USB Driver” Abra a janela do gerenciador de dispositivos (Iniciar-
>Painel de controle->Sistema->Gerenciador de dispositivos). Se o driver não fora instalado
corretamente, continue na seção de solução de erros (a seguir)
Solução de erros
Se houve algum problema na instalação do driver siga os passos a seguir.
O Windows tentará instalar o driver mesmo se não encontrar o arquivo correto. No gerenci-
ador de dispositivos dentro da opção “Outros dispositivos” você deve encontrar um “Dispositivo
não conhecido”.
Clique com o botão direito no “Dispositivo não conhecido” e selecione a opção “Atualizar o
Driver” do menu.
Na primeira tela de diálogo selecione “Procurar no meu computador pelos arquivos do driver”.
Continuar a partir do passo 4.