Você está na página 1de 116

1

Notas de Aula Programação Embarcada - ELT024

Rodrigo Maximiano Antunes de Almeida


Instituto de Engenharia de Sistemas e Tecnologia da Informação,
Universidade Federal de Itajubá,
Minas Gerais,
Brasil
rodrigomax @ unifei.edu.br

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

3 Programação dos Periféricos 45


. Acesso às “portas”do microcontrolador . . . . . . . . . . . . . . . . . . . . . . . . 46
. Configuração dos periféricos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
. Barramento de Led's . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
. Display de 7 segmentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
Multiplexação de displays . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
Criação da biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
. Leitura de teclas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Debounce por software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Arranjo de leitura por matriz . . . . . . . . . . . . . . . . . . . . . . . . . 60
Criação da biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
. Display LCD 2x16 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Criação da biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
. Comunicação serial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
RS 232 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Criação da biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
. Conversor AD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
Elementos sensores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
Processo de conversão AD . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Criação da biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
. Saídas PWM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
Criação da biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
. Timer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
. Reprodução de Sons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
. Interrupção . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
. Watchdog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94

4 Arquitetura de desenvolvimento de software 95


. One single loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
. Interrupt control system . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
. Cooperative multitasking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
Fixação de tempo para execução dos slots . . . . . . . . . . . . . . . . . . 102
Utilização do tempo livre para interrupções . . . . . . . . . . . . . . . . . 103

5 Anexos 105
. config.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
. basico.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
. Instalar gravadores/depuradores de PIC em sistemas x64 . . . . . . . . . . . . . . 108

ii
Lista de Figuras

1.1 Camadas de abstração de um sistema operacional . . . . . . . . . . . . . . . . . . 1


1.2 Pesquisa sobre linguagens utilizadas para projetos de software embarcado . . . . 2
1.3 Configuração das ferramentas de compilação . . . . . . . . . . . . . . . . . . . . . 4
1.4 Instalação do ICD2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.5 Resumo das configurações do ICD2 no MPLAB . . . . . . . . . . . . . . . . . . . 6
1.6 Pedido de atualização do firmware do ICD2 . . . . . . . . . . . . . . . . . . . . . 6
1.7 Project Explorer do MPLAB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.8 Problema das Referências Circulares . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.9 Solução das referências circulares com #ifndef . . . . . . . . . . . . . . . . . . . . 14
1.10 Loop infinito de um device driver gerando erro no sistema . . . . . . . . . . . . . 20
1.11 Exemplo de funcionamento do vetor de interrupção . . . . . . . . . . . . . . . . . 20

2.1 Arquitetura do microcontrolador PIC 18F4550 . . . . . . . . . . . . . . . . . . . 37


2.2 Memória como um armário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
2.3 Memória e periféricos como um armário . . . . . . . . . . . . . . . . . . . . . . . 39
2.4 Regiões de memórias disponíveis no PIC18F4550 . . . . . . . . . . . . . . . . . . 39
2.5 Esquema elétrico: Microcontrolador PIC 18F4550 . . . . . . . . . . . . . . . . . . 41
2.6 Registros de configuração do microcontrolador PIC 18F4550 . . . . . . . . . . . . 43

3.1 Registros de configuração dos periféricos do PIC 18F4550 . . . . . . . . . . . . . 48


3.2 Barramento de Led's . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
3.3 Display de 7 Segmentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
3.4 Diagrama elétrico para display de 7 segmentos com ânodo comum . . . . . . . . . 52
3.5 Ligação de 4 displays de 7 segmentos multiplexados . . . . . . . . . . . . . . . . . 53
3.6 Circuito de leitura de chave . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
3.7 Oscilação do sinal no momento do chaveamento . . . . . . . . . . . . . . . . . . . 58
3.8 Circuito de debounce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
3.9 Utilização de filtro RC para debounce do sinal . . . . . . . . . . . . . . . . . . . . 59
3.10 Teclado em arranjo matricial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
3.11 Display Alfanumérico LCD 2x16 . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
3.12 Display Alfanumérico LCD 2x16 - verso . . . . . . . . . . . . . . . . . . . . . . . 65
3.13 Caracteres disponíveis para ROM A00 . . . . . . . . . . . . . . . . . . . . . . . . 66
3.14 Caracteres disponíveis para ROM A02 . . . . . . . . . . . . . . . . . . . . . . . . 67
3.15 Esquemático de ligação do display de LCD . . . . . . . . . . . . . . . . . . . . . . 69
3.16 Sinal serializado para transmissão em RS232 . . . . . . . . . . . . . . . . . . . . . 73
3.17 Lâmpada incandescente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
3.18 Potenciômetro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
3.19 Potenciômetro como divisor de tensão . . . . . . . . . . . . . . . . . . . . . . . . 78
3.20 Circuito integrado LM35 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
3.21 Diagrama de blocos do LM35 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
3.22 Conversor analógico digital de 2 bits . . . . . . . . . . . . . . . . . . . . . . . . . 80
3.23 Sinais PWM com variação do duty cycle . . . . . . . . . . . . . . . . . . . . . . . 83

4.1 Exemplo de máquina de estados . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99

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

1.1 Softwares utilizados no curso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3


1.2 Ferramentas utilizadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3 Tipos de dados e faixa de valores . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.4 Representação decimal - binária - hexadecimal . . . . . . . . . . . . . . . . . . . . 15
1.5 Alteração de tamanho e sinal dos tipos básicos . . . . . . . . . . . . . . . . . . . 16
1.6 Operação bit set com define . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
1.7 Operação bit clear com define . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
1.8 Operação bit flip com define . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
1.9 Operação bit test com define . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

2.1 Quantidade de operações e tarefas . . . . . . . . . . . . . . . . . . . . . . . . . . 40

3.1 Endereços de memória para as portas do PIC 18F4550 . . . . . . . . . . . . . . . 46


3.2 Tabela de configuração do PIC para as experiências . . . . . . . . . . . . . . . . . 49
3.3 Conversão binário - hexadecimal para displays de 7 segmentos . . . . . . . . . . . 53
3.4 Lista de comandos aceitos pelo o LCD . . . . . . . . . . . . . . . . . . . . . . . . 68
3.5 Taxas de transmissão para diferentes protocolos . . . . . . . . . . . . . . . . . . . 72
3.6 Cálculo do valor da taxa de transmissão da porta serial . . . . . . . . . . . . . . . 74
3.7 Faixa de frequências máximas e mínimas para cada configuração do prescaler . . 84

v
Lista de Programas

1.1 Resumo do disp7seg.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10


1.2 Resumo do disp7seg.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.3 Estrutura de header . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.4 Operações aritméticas com tipos diferentes . . . . . . . . . . . . . . . . . . . . . . 18
3.1 disp7seg.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
3.2 disp7seg.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
3.3 Utilizando a biblioteca disp7seg . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
3.4 teclado.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
3.5 teclado.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
3.6 Exemplo de uso da biblioteca teclado . . . . . . . . . . . . . . . . . . . . . . . . . 63
3.7 lcd.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
3.8 lcd.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
3.9 Exemplo de uso da biblioteca de LCD . . . . . . . . . . . . . . . . . . . . . . . . 71
3.10 serial.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
3.11 serial.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
3.12 Exemplo de uso da biblioteca de comunicação serial . . . . . . . . . . . . . . . . . 76
3.13 adc.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
3.14 adc.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
3.15 Exemplo de uso da biblioteca de conversores AD . . . . . . . . . . . . . . . . . . 82
3.16 pwm.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
3.17 pwm.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
3.18 Exemplo de uso da biblioteca das saídas PWM . . . . . . . . . . . . . . . . . . . 86
3.19 timer.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
3.20 timer.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
3.21 Exemplo de uso da biblioteca de um temporizador . . . . . . . . . . . . . . . . . 88
3.22 Reprodução de sons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
3.23 Fontes de Interrupção . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
3.24 Tratamento das interrupções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
3.25 Inicialização do sistema com interrupções . . . . . . . . . . . . . . . . . . . . . . 93
3.26 Inicialização do sistema com interrupções . . . . . . . . . . . . . . . . . . . . . . 94
4.1 Exemplo de arquitetura single-loop . . . . . . . . . . . . . . . . . . . . . . . . . . 96
4.2 Problema na sincronia de tempo para o single-loop . . . . . . . . . . . . . . . . . 96
4.3 Exemplo de sistema Interrupt-driven . . . . . . . . . . . . . . . . . . . . . . . . . 97
4.4 Exemplo de sistema Interrupt-driven com base de tempo . . . . . . . . . . . . . . 98
4.5 Exemplo de cooperative multitasking . . . . . . . . . . . . . . . . . . . . . . . . . 100
4.6 Exemplo de cooperative multitasking com uso do top slot . . . . . . . . . . . . . 101
4.7 Exemplo de sistema Cooperative-multitasking com slot temporizado . . . . . . . 102
5.1 config.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
5.2 basico.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107

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

Figura 1.1: Camadas de abstração de um sistema operacional

Para sistemas embarcados, é necessário programar especificamente para o hardware em ques-


tão. Uma opção para se obter “artificialmente” esta camada de abstração que era gerada pelo
sistema operacional é a utilização de dois itens: um compilador próprio para o componente em
questão e uma biblioteca de funções. O compilador será o responsável por traduzir a linguagem
de alto nível em uma linguagem que o microcontrolador consegue entender. A biblioteca de
funções, ou framework, em geral, é disponibilizada pelos fabricantes do microcontrolador.

. 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

Também é a escolha da maioria dos programadores e gerentes de projetos no que concerne


ao desenvolvimento de sistemas embarcados como pode ser visto na Figura 1.2.

Figura 1.2: Pesquisa sobre linguagens utilizadas para projetos de software embarcado
Fonte: http://www.embedded.com/design/218600142

A descontinuidade depois de 2004 se dá devido à mudança de metodologia da pesquisa. Antes


de 2005, a pergunta formulada era: “Para o desenvolvimento da sua aplicação embarcada, quais
das linguagens você usou nos últimos 12 meses?”. Em 2005 a pergunta se tornou: “Meu projeto
embarcado atual é programado principalmente em ______”. Múltiplas seleções eram possíveis
antes de 2005, permitindo a soma superior a 100%, sendo o valor médio de 209%, o que implica
que a maioria das pessoas escolheu duas ou mais opções.
O maior impacto na pesquisa pode ser visualizado na linguagem assembler: até 2004, estava
presente em 62% das respostas (na média). O que comprova que praticamente todo projeto de
sistema embarcado exige um pouco de assembler. Do mesmo modo, percebemos que atualmente
poucos projetos são realizados totalmente ou em sua maioria em assembler, uma média de apenas
7%.

. Hardware utilizado


“People who are really serious about software should make their own
hardware.” - Alan Kay

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:

• 1 display LCD 2 linhas por 16 caracteres (compatível com HD77480)

• 4 displays de 7 segmentos com barramento de dados compartilhados

• 8 leds ligados ao mesmo barramento dos displays

• 16 mini switches organizadas em formato matricial 4x4

• 1 sensor de temperatura LM35C

• 1 resistência de aquecimento ligada a uma saída PWM

• 1 motor DC tipo ventilador ligado a uma saída PWM

• 1 buzzer ligado a uma saída PWM

Notas de Aula ELT024 - Programação para Sistemas Embarcados


3 Introdução

• 1 canal de comunicação serial padrão RS-232

Cada componente terá seu funcionamento básico explicado para permitir o desenvolvimento de
rotinas para estes.

. Ambiente de programação


“First, solve the problem. Then, write the code.” - John Johnson

O ambiente utilizado será o MPLAB(R). Este é um ambiente de desenvolvimento disponibilizado


pela Microchip(R) gratuitamente. O compilador utilizado será o SDCC, os linkers e assemblers
serão disponibilizados pela biblioteca GPUtils.
Como o foco é a aprendizagem de conceitos sobre programação embarcada, poderá ser uti-
lizada qualquer plataforma de programação e qualquer compilador/linker. Caso seja utilizado
qualquer conjunto de compilador/linker diferentes deve-se prestar atenção apenas nas diretivas
para gravação.
Para a programação em ambiente Linux recomenda-se o uso da suíte PIKLAB 15.10. Este
programa foi desenvolvido para KDE 3.5. Além de permitir a integração com o mesmo compilador
utilizado neste curso permite a programação do microcontrolador utilizando o programador ICD2
via USB.

Instalação
A Tabela 1.1 apresenta os softwares que serão utilizados no curso.

Tabela 1.1: Softwares utilizados no curso

Item Versão Licença


IDE MPLAB 8.50 Proprietário
Compilador SDCC 2.9.00 (win32) GPL
Linker/Assembler GPUtils 0.13.7 (win32) GPL
Plugin MPLAB sdcc-mplab 0.1 GPL

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

Notas de Aula ELT024 - Programação para Sistemas Embarcados


4 Introdução

Figura 1.3: Configuração das ferramentas de compilação

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.

Tabela 1.2: Ferramentas utilizadas

Executables Nome do arquivo Localização


gpasm gpasm.exe C:\Arquivos de programas\gputils\bin\
sdcc link sdcc.exe C:\Arquivos de programas\SDCC\bin\
sdcc16 sdcc.exe C:\Arquivos de programas\SDCC\bin\

Após estes passos a suíte MPLAB está pronta para trabalhar com o compilador SDCC+GPUtils.

Configuração do gravador ICD2


Após instalar o MPLAB já é possível fazer a instalação e configuração do gravador ou depurador
ICD2. Conecte-o a qualquer porta USB e aguarde a tela de instalação do Windows. Em algumas
versões do windows pode acontecer de você ser perguntado se deseja instalar um software não
assinado digitalmente, certifique-se que a versão do firmware é pelo menos 1.0.0.0 da fabricante
Microchip, conforme pode ser visto na Figura 1.4 e avance.
Após o termino da instalação abra o programa MPLAB para configurar o gravador ou depu-
rador. Vá ao menu “Programmer -> Select Programmer -> MPLAB ICD 2”. Vá novamente ao
menu “Programmer” mas desta vez escolha a opção “ MPLAB ICD 2 Setup Wizard”.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


5 Introdução

Figura 1.4: Instalação do ICD2

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.

Criação de um novo projeto


Recomenda-se a utilização do assistente disponível para a criação de um novo projeto (menu
Project -> Project Wizard). Ele irá questionar sobre (entre parênteses os valores adotados neste
curso):

1. O microcontrolador a ser utilizado (PIC18F4550)

2. A suíte de compilação (SDCC 16)

3. O diretório e nome do projeto

4. Arquivos já existentes cujo programador deseja incluir no projeto

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

Notas de Aula ELT024 - Programação para Sistemas Embarcados


6 Introdução

Figura 1.5: Resumo das configurações do ICD2 no MPLAB

Figura 1.6: Pedido de atualização do firmware do ICD2

Notas de Aula ELT024 - Programação para Sistemas Embarcados


7 Introdução

Figura 1.7: Project Explorer do MPLAB

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.

. Indentação e padrão de escrita


“Good programmers use their brains, but good guidelines save us
having to think out every case.” - Francis Glassborow

É fundamental obedecer a um padrão para escrita de programas, de modo que a visualização do


código seja facilitada.
Na língua portuguesa utilizamos parágrafos para delimitar blocos de frases que possuem a
mesma ideia. Em linguagem C estes blocos são delimitados por chaves “{” e “}”.
Para demonstrar ao leitor que um parágrafo começou utilizamos um recuo à direita na pri-
meira linha. Quando é necessário realizar uma citação de itens coloca-se cada um destes itens
numa linha recuada à direita, algumas vezes com um identificador como um traço “-” ou seta
“->” para facilitar a identificação visual.
Com esse mesmo intuito, os recuos e espaçamentos são utilizados para que o código seja mais
facilmente entendido.
Como todo bloco de comandos é iniciado e terminado com uma chave, tornou-se comum que
estas (as chaves) estejam no mesmo nível e todo código interno a elas seja deslocado à direita. Se
existir um segundo bloco interno ao primeiro, este deve ser deslocado duas vezes para indicar a
hierarquia no fluxo do programa. Segue abaixo um exemplo de um mesmo código com diferença
apenas na indentação.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


8 Introdução

Código indentado Código não indentado

1 void main ( void ) void main ( void )


{ {
unsigned i n t i ; unsigned i n t i ;
unsigned i n t temp ; unsigned i n t temp ;
unsigned i n t teclanova =0; unsigned i n t teclanova =0;
InicializaSerial ( ) ; InicializaSerial ( ) ;
InicializaDisplays ( ) ; InicializaDisplays ( ) ;
InicializaLCD ( ) ; InicializaLCD ( ) ;
InicializaAD ( ) ; InicializaAD ( ) ;
for ( ; ; ) for ( ; ; )
{ {
AtualizaDisplay ( ) ; AtualizaDisplay ( ) ;
i f ( teclanova != Tecla ) i f ( teclanova != Tecla )
{ {
teclanova = Tecla ; teclanova = Tecla ;
f o r ( i =0;i <16; i++) f o r ( i =0;i <16; i++)
{ {
i f ( BitTst ( Tecla , i ) ) i f ( BitTst ( Tecla , i ) )
{ {
EnviaDados ( i+48) ; EnviaDados ( i+48) ;
} }
} }
} }
f o r ( i = 0 ; i < 1 0 0 0 ; i++) ; f o r ( i = 0 ; i < 1 0 0 0 ; i++) ;
} }
} }

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:

i f ( PORTA == 0 x30 ) { PORTB = 0 x10 ; }

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

Notas de Aula ELT024 - Programação para Sistemas Embarcados


9 Introdução

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.

#include <s t d i o . h>


#define DIST 260 // d i s t a n c i a e n t r e SP e I t a
i n t main ( i n t argc , char∗ argv [ ] )
{
/∗ e s s e programa s e r v e para
m os t r ar como s e i n s e r e c o m e n t á r i o s ∗/
printf ( " São Paulo está %d Km de Itajubá " , DIST ) ;
return 0 ;
}

. 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.

. Diretivas de compilação


As diretivas de compilação são instruções que são dadas ao compilador. Elas não serão executa-
das. Todas as diretivas de compilação começam com um sinal #, conhecido como jogo da velha
ou hash.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


10 Introdução

Programa 1.1: Resumo do disp7seg.c


1 // v a r i á v e l usada apenas d e n t r o d e s t e a r q u i v o
2 s t a t i c char temp ;
3 // v a r i á v e l que s e r á usada também f o r a do a r q u i v o
4 s t a t i c char valor ;
5 // f u n ç õ e s u s a d a s d e n t r o e f o r a do a r q u i v o
6 void MudaDigito ( char val )
7 {
8 valor = val ;
9 }
10 char LerDigito ( void )
11 {
12 return valor ;
13 }
14 void InicializaDisplays ( void )
15 {
16 // c ó d i g o da f u n ç ã o
17 }
18 // f u n ç ã o usada apenas d e n t r o d e s t e a r q u i v o
19 void AtualizaDisplay ( void )
20 {
21 // c ó d i g o da f u n ç ã o
22 }

Programa 1.2: Resumo do disp7seg.h


1 #i f n d e f VAR_H
2 #define VAR_H
3 void MudaDigito ( char val ) ;
4 char LerDigito ( void ) ;
5 void InicializaDisplays ( void ) ;
6 #endif //VAR_H

Notas de Aula ELT024 - Programação para Sistemas Embarcados


11 Introdução

#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.

Original Compilado Resultado na Tela

#define CONST 15
void main ( void )
void main ( void )
{
{ 45
printf ( "%d" , 15 ∗ 3 ) ;
printf ( "%d" , CONST ∗ 3 ) ;
}
}

Função Original Opções de uso com o #define Resultado na Tela

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 ) ;
}

#include <s t d i o . h>


#define PADRAO LCD
void main ( void )
LCD
{
MostraSaidaPadrao ( ) ;
}

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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


12 Introdução

#ifdef, #ifndef, #else e #endif


As diretivas #ifdef, #ifndef, #else e #endif são muito utilizadas quando queremos gerar dois
programas que diferem apenas num pequeno pedaço de código. Por exemplo dois sistemas de
controle de temperatura. O primeiro possui um display de LCD, capaz de mostrar a temperatura
textualmente. O segundo sistema executa a mesma função que o primeiro, mas é um dispositivo
mais barato, portanto possui apenas um led indicativo de sobretemperatura. O código pode ser
escrito da seguinte maneira

void ImprimirTemp ( char valor )


{
#ifdef LCD
Imprime_LCD ( valor )
#e l s e
i f ( valor > 3 0 )
{
led = 1 ;
}
else
{
led = 0 ;
}
#endif //LCD
}

No momento da compilação o pré-compilador irá verificar se a “tag” LCD foi definida em


algum lugar. Em caso positivo o pré-compilador irá deixar tudo que estiver entre o #ifdef e o
#else e retirará tudo que está entre o #else e o #endif.
Outra função muito utilizada destas diretivas é para evitar a referência circular. Supondo dois
arquivos, um responsável pela comunicação serial (serial.h) e o segundo responsável pelo controle
de temperatura (temp.h). O projeto exige que a temperatura possa ser controlada pela porta
serial e toda vez que a temperatura passar de um determinado patamar deve ser enviado um alerta
pela porta serial. O arquivo da porta serial (serial.h) tem as seguintes funções, apresentadas a
seguir.

char LerSerial ( void ) ;


void EnviaSerial ( char val ) ;

O arquivo de controle da temperatura (temp.h) possui as funções apresentadas a seguir.

char LerTemperatura ( void ) ;


void AjustaCalor ( char val ) ;

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.

#include " serial .h"


char LerTemperatura ( void ) ;
void AjustaCalor ( char val ) ;

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

#include " temp .h"


char LerSerial ( void ) ;
void EnviaSerial ( char val ) ;

Notas de Aula ELT024 - Programação para Sistemas Embarcados


13 Introdução

Programa 1.3: Estrutura de header


1 #i f n d e f TAG_CONTROLE
2 #define TAG_CONTROLE
3 // t o d o o c o n t e ú d o do a r q u i v o vem a q u i .

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);

Figura 1.8: Problema das Referências Circulares

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.

. Tipos de dados em C


“19 Jan 2038 at 3:14:07 AM. The end of the world according to Unix
(232 seconds after Jan 1st 1970)” - Unix date system
O tipo de uma variável, informa a quantidade de memória, em bytes, que esta irá ocupar e como
esta deve ser interpretada: com ou sem fração (vírgula). Os tipos básicos de dados na linguagem

Notas de Aula ELT024 - Programação para Sistemas Embarcados


14 Introdução

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”

temp.h char LerSerial(void);


void EnviaSerial(char val);
#infdef TEMP_H #endif

//tag já definida,
//pula o conteúdo

#endif

Figura 1.9: Solução das referências circulares com #ifndef

C são apresentados na Tabela 1.3.

Tabela 1.3: Tipos de dados e faixa de valores

Tipo Bits Bytes Faixa de valores


char 8 1 -127 à 127
int 16 2 -32.768 à 32.767
float 32 4 3,4 x 10-38 à 3,4 x 1038
double 64 8 3,4 x 10-308 à 3,4 x 10308

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.

Representação binária e hexadecimal


A grande maioria dos processadores trabalha com dados binários, ou seja, aqueles que apenas
assumem valores 0 ou 1. Por isso os tipos apresentados anteriormente podem ser representados
utilizando a base 2. Um valor do tipo char que possui 8 bits será representado por um número
de 8 algarismos, todos 0 (zeros) ou 1 (uns). Para realizarmos a conversão de um número na base
decimal para a base 2 podemos seguir o seguinte algoritmo:

1. Dividir o número por 2

2. Anotar o valor do resto (0 ou 1)

3. Se o valor é maior que 0 voltar ao número 1

4. Escrever os valores obtidos através do passo 2 de trás para frente.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


15 Introdução

5. Apresentar o resultado

Por exemplo o número 18.


18/2 = 9, resto 0
9/2 = 4, resto 1
4/2 = 2, resto 0
2/2 = 1, resto 0
1/2 = 0, resto 1
Lendo do último resultado para o primeiro temos que
1810 = 100102
Devido a grande utilização de números binários na programação de baixo nível é muito comum
escrevemos estes números na base 16 ou hexadecimal. A vantagem de escrever o número nesta
base é que existe uma conversão simples de binário para hexadecimal e o número resultante
ocupa bem menos espaço na tela.
A base hexadecimal possui 16 "unidades"diferentes. Como existem apenas 10 algarismos no
sistema de numeração arábico (0, 1, 2, 3, 4, 5, 6, 7, 8, 9) utilizamos 6 letras para complementá-los
(A, B, C, D, E, F). A conversão entre valores binários, decimais e hexadecimais é apresentada
na Tabela 1.4.
Tabela 1.4: Representação decimal – binária - hexadecimal

Decimal Binário Hexadecimal Decimal Binário Hexadecimal


0 0000 0 8 1000 8
1 0001 1 9 1001 9
2 0010 2 10 1010 A
3 0011 3 11 1011 B
4 0100 4 12 1100 C
5 0101 5 13 1101 D
6 0110 6 14 1110 E
7 0111 7 15 1111 F

Para converter de binário para hexadecimal basta dividir o número em grupos de 4 em 4, da


esquerda para a direita, e utilizar a tabela acima.
Por exemplo o número 18. Sabemos que este número em binário é representado por 100102 .
Separando o número de 4 em 4 algarismos temos:
1-0010
Pela tabela:
12 = 116
00102 = 216 .
Logo:
100102 . = 1216 .

Modificadores de tamanho e sinal


Um modificador de tipo altera o significado dos tipos base e produz um novo tipo. Existem
quatro tipos de modificadores, dois para o tamanho (long e short) e dois para sinal (unsigned
e signed). Um tipo declarado com o modificador long pode ter tamanho MAIOR ou IGUAL
ao tipo original. Um tipo declarado como short deve ter tamanho MENOR ou IGUAL ao tipo
original. A decisão cabe ao compilador utilizado.
Os tipos declarados como signed possuem um bit reservado para o sinal, deste o valor máximo
que podem atingir é menor. Os tipos declarados como unsigned não podem assumir valores

Notas de Aula ELT024 - Programação para Sistemas Embarcados


16 Introdução

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.

Tabela 1.5: Alteração de tamanho e sinal dos tipos básicos

Tipo Bytes Excursão máxima


unsigned char 1 0 à 255
signed char 1 -128 à 127
unsigned int 2 0 à 65.535
signed int 2 -32.768 à 32.767
long int 4 -2.147.483.648 à 2.147.483.647
unsigned long int 4 0 à 4.294.967.295
short int 2 -32.768 à 32.767

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:

#define X ( ∗ ( n e a r unsigned char ∗ ) 0xF83 )


void main ( void )
{
while ( X!=X ) ;
}

Quando compilado apresenta o seguinte código em assembler:

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

#define X ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF83 )


void main ( void )
{
while ( X!=X ) ;
}

Gerando o código em assembler descrito abaixo:

Notas de Aula ELT024 - Programação para Sistemas Embarcados


17 Introdução

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

#define X ( ∗ ( v o l a t i l e const n e a r unsigned char ∗ ) 0xF83 )


// i n i c i o do programa
void main ( void )
{
X = 3;
}

Se tentarmos compilar este código aparecerá a seguinte mensagem de erro:

Teste . c : error 3 3 : Attempt to assign value to a constant variable (=)

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

Notas de Aula ELT024 - Programação para Sistemas Embarcados


18 Introdução

Programa 1.4: Operações aritméticas com tipos diferentes


1 void main ( void )
2 {
3 char var08 ;
4 i n t var16 ;
5 long i n t var32 ;
6 f l o a t pont16 ;
7 double pont32 ;
8 var8 = var8 + var16 ; // 1
9 var8 = var8 + var8 ; // 2
10 var16 = var8 ∗ var8 ; // 3
11 var32 = var32 / var16 ; // 4
12 var32 = pont32 ∗ var32 ; // 5
13 pont16 = var8 / var16 ; // 6
14 pont16 = pont32 ∗ var32 ; // 7
15 pont16 = 40 / 8 0 ; // 8
16 }

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;
}

. Operações aritméticas


“If people do not believe that mathematics is simple, it is only be-
cause they do not realize how complicated life is.” - John Louis von
Neumann
Um cuidado a se tomar, na programação em C para sistemas embarcados, é o resultado de
operações aritméticas. Por padrão na linguagem C o resultado de uma operação aritmética
possui tamanho igual ao maior operando. Observando o Programa 1.4 notamos alguns exemplos.
No caso 1 (linha 8) uma variável char somada a um int gera como resultado um int (maior
operando). Não é possível armazenar esse resultado num char, haverá perda de informação.

var32 = var8 + var16 ; // 1 c o r r i g i d o

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.

var16 = var8 + var8 ; // 2 c o r r i g i d o

Notas de Aula ELT024 - Programação para Sistemas Embarcados


19 Introdução

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.

var16 = ( ( i n t ) var8 ) ∗ var8 ; // 3 c o r r i g i d o

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.

pont16 = ( ( f l o a t ) var8 ) / var16 ; // 6 c o r r i g i d o

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.

. Função main()


Todo sistema necessita de iniciar em algum lugar. Em geral, os microcontroladores, assim que
ligados, procuram por suas instruções no primeiro ou último endereço de memória, dependendo
da arquitetura utilizada. O espaço de memória disponível neste endereço é geralmente muito

Notas de Aula ELT024 - Programação para Sistemas Embarcados


20 Introdução

Figura 1.10: Loop infinito de um device driver gerando erro no sistema

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

Figura 1.11: Exemplo de funcionamento do vetor de interrupção

A maneira de indicar o ponto de início de um programa depende do compilador. Em geral os

Notas de Aula ELT024 - Programação para Sistemas Embarcados


21 Introdução

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.

void main ( void )


{
// a q u i e n t r a o c ó d i g o do programa
}

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.

void main ( void ) void main ( void )


{ {
for ( ; ; ) while ( 1 )
{ {
// a q u i e n t r a o // a q u i e n t r a o
// c ó d i g o p r i n c i p a l // c ó d i g o p r i n c i p a l
} }
} }

. Rotinas de tempo


“Time is an illusion, lunchtime doubly so.” - Ford Prefect

É 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.

// c ó d i g o em a s s e m b l e r e q u i v a l e n t e à f o r ( i =0; i <10; i ++) ;


MOVF r0x00 , W // i n i c i a l i z a W com 0 (1 c i c l o )
SUBLW 0 x0a // c o l o c a o v a l o r 10 (0 x0a ) no r e g i s t r o W (1 c i c l o )
MOVWF r0x00 //muda o v a l o r de W para F (1 c i c l o )
_00107_DS_ :
DECFSZ r0x00 , F // decrementa F , s e F > 0 e x e c u t a a próxima l i n h a (1 c i c l o )
BRA _00107_DS_ // " p u l a " para o l u g a r marcado como _00107_DS_ (2 c i c l o s )

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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


22 Introdução

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.

void delay ( unsigned i n t DL )


{
unsigned char i , j , k ;
while ( DL−−) // e x e c u t a DL v e z e s .
{
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
}
}
}
}

. Operações com bits


“All of the books in the world contain no more information than is
broadcast as video in a single large American city in a single year.
Not all bits have equal value.” - Carl Sagan

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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


23 Introdução

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.

Declaração Lógico Bitwise

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:

Declaração Lógico Bitwise

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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


24 Introdução

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:

Declaração Lógico Bitwise

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:

Declaração Lógico Bitwise


result = A ^ B ;
char A = 8;
// r e s u l t = 13
// A = 0 b00001000
// não e x i s t e em C // A = 0 b00001000
char B = 5;
// B = 0 b00000101
// B = 0 b00000101
// r = 0 b00001101

Shift
A operação shift desloca os bits para a esquerda (operador <<) ou direita (operador >>). É
necessário indicar quantas casas serão deslocadas.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


25 Introdução

Declaração Shift Esquerda Shift Direita

result = A << 2 ; result = A >> 2 ;


char A = 8 ; // r e s u l t = 32 // r e s u l t = 2
// A = 0 b00001000 // A = 0 b00001000 // A = 0 b00001000
// r = 0 b00100000 // r = 0 b00000010

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.

Ligar um bit (bit set)


Para ligar apenas um bit, utilizaremos uma operação OU. Supondo dois operandos A e B. Se A
é 1 o resultado de (A | B) é 1 independente de B. Se A é 0 o resultado é igual ao valor de B.
Se o objetivo é ligar apenas o bit da posição X devemos criar um valor onde todas as posições
são 0's com exceção da posição desejada. Para uma máscara binária de N bits temos (N>=X):

Posição N . . . X+1 X X-1 . . . 0


Valor 0 ... 0 1 0 ... 0

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 ( ; ; ) ;
}

Notas de Aula ELT024 - Programação para Sistemas Embarcados


26 Introdução

Desligar um bit (bit clear)


Para desligar apenas um bit o procedimento é similar ao utilizado para ligar. Ao invés de utilizar-
mos uma operação OU, utilizaremos uma operação AND. A operação AND tem a característica
de, dados A e B valores binários, se A é 1, a resposta de (A & B) será o próprio valor de B, se a
A=0, a resposta é zero, independente de B.
Novamente é necessário gerar uma máscara. Mas para esta situação ela deve possuir todos
os bits iguais a um com exceção de X, o bit que queremos desligar.

posição N . . . X+1 X X-1 . . . 0


Valor 1 ... 1 0 1 ... 1

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.

Trocar o valor de um bit (bit flip)


Para trocar o valor de um bit utilizaremos como artifício algébrico a operação XOR. Dado duas
variáveis binárias A e B , se A é 1, o valor resultante de A XOR B é o oposto do valor de B, se
A=0, a resposta se mantém igual ao valor de B.
Podemos perceber que para trocar o valor de apenas um bit a máscara será idêntica àquela
utilizada para ligar um bit:

posição N . . . X+1 X X-1 . . . 0


Valor 0 ... 0 1 0 ... 0

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

Notas de Aula ELT024 - Programação para Sistemas Embarcados


27 Introdução

// 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 ( ; ; ) ;
}

Percebemos através do exemplo que a utilização do procedimento apresentado troca o valor


do bit escolhido. Foi utilizado o mesmo procedimento duas vezes. Na primeira, um bit foi ligado
e, na segunda, outro foi desligado.

Verificar o estado de um bit (bit test)


Para verificar se o bit X está um utilizaremos novamente a mesma máscara utilizada para bit set
e bit toggle:

posição N . . . X+1 X X-1 . . . 0


Valor 0 ... 0 1 0 ... 0

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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


28 Introdução

}
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 ( ; ; ) ;
}

Criando funções através de define’s


Uma opção no uso de define’s é criar funções simples que podem ser escritas em apenas uma
linha. Utilizando um pouco de algebrismo e parênteses, é possível escrever as quatro operações
anteriores numa única linha. De posse desta simplificação podemos criar uma função para facilitar
o uso destas operações através de um define conforme podemos ver nas tabelas 1.6, 1.7, 1.8 e
1.9.
Tabela 1.6: Operação bit set com define

Operação Bit set

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) ;

Com define #define B i t S e t ( arg , b i t ) ( ( a r g ) |= (1<< b i t ) )

Exemplo de uso com de- // Ligando o b i t 2 da p o r t a D


fine BitSet ( PORTD , 2 ) ;

Notas de Aula ELT024 - Programação para Sistemas Embarcados


29 Introdução

Tabela 1.7: Operação bit clear com define

Operação Bit clear

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) ;

Com define #define B i t C l r ( arg , b i t ) ( ( a r g ) &= ~(1<< b i t ) )

Exemplo de uso com de- // D e s l i g a n d o o b i t 2 da p o r t a D


fine BitClr ( PORTD , 2 ) ;

Notas de Aula ELT024 - Programação para Sistemas Embarcados


30 Introdução

Tabela 1.8: Operação bit flip com define

Operação Bit flip

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) ;

Com define #define B i t F l p ( arg , b i t ) ( ( a r g ) ^= (1<< b i t ) )

Exemplo de uso com de- // Trocando o v a l o r do b i t 2 da p o r t a D


fine BitFlp ( PORTD , 2 ) ;

Notas de Aula ELT024 - Programação para Sistemas Embarcados


31 Introdução

Tabela 1.9: Operação bit test com define

Operação Bit test

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 {
// . . .
}

Com define #define B i t T s t ( arg , b i t ) ( ( a r g ) & (1<< b i t ) )

// Testando o b i t 2 da p o r t a D
i f ( BitTst ( PORTD , 2 ) )
Exemplo de uso com de- {
fine // . . .
}

Notas de Aula ELT024 - Programação para Sistemas Embarcados


32 Introdução

. Debug de sistemas embarcados7


“In the beginner's mind there are many possibilities; in the expert's
mind there are few.” - Shunryu Suzuki
A verificação de sistemas embarcados apresenta algumas restrições e de modo geral não é possível
inferir sobre a operação do sistema sem paralisá-lo. Como este tipo de sistema possui vários
dispositivos agregados, que funcionam independentemente do processador, é necessário utilizar
abordagens diferentes para realizar o debug.
Devemos lembrar que além do software devemos levar em conta possíveis problemas advindos
do hardware. Debounce, tempo de chaveamento, limite do barramento de comunicação são
exemplos de pontos a serem considerados no momento de depuração.

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.

#include " stdio .h"


#include " serial .h"
// i n i c i o do programa
i n t main ( i n t argc , char∗ argv [ ] )
{
printf ( " Inicializando sistema " ) ;
i f ( CheckForData ( ) )
{
printf ( " Chegou informacao " ) ;
}
else
{
printf ( " Problemas na comunicacao " ) ;
}
return 0 ;
}

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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


33 Introdução

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á.

Checar possíveis pontos de Memory-leak


Se for necessário realizar alocação dinâmica garantir que todas as alocações são liberadas em
algum ponto do programa.

Cuidado com a fragmentação da memória


Sistemas com grande frequência na alocação/liberação de memória podem fragmentar a memória
até o ponto de inviabilizar os espaços livres disponíveis, eventualmente travando o sistema.
Quando trabalhar com rotinas de nível mais baixo, mais próximo ao hardware, tente utilizar
apenas mapeamento estático de memória.

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.

Reproduzir e isolar o erro


Quando houver algum erro deve-se primeiro entender como reproduzi-lo. Não é possível tentar
corrigir o erro se não houver maneira de verificar se ele foi eliminado.
No momento em que se consegue um procedimento de como reproduzir o erro podemos
começar a visualizar onde ele pode estar. A partir deste momento devemos isolar onde o erro
está acontecendo. Uma maneira de se fazer isto em sistemas embarcados é colocar um loop
infinito dentro de um teste, que visa verificar alguma condição de anomalia. Se o sistema entrar
neste teste devemos sinalizar através dos meios disponíveis, ligar/desligar algum led por exemplo.

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

Notas de Aula ELT024 - Programação para Sistemas Embarcados


34 Introdução

. Ponteiros e endereços de memória


“Writing in C or C++ is like running a chain saw with all the safety
guards removed.” - Bob Gray
Toda variável criada é armazenada em algum lugar da memória. Este lugar é definido de maneira
única através de um endereço.
Para conhecermos o endereço de uma variável podemos utilizar o operador &. Cuidado! Este
operador também é utilizado para realização da operação bitwise AND. Exemplo:

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

tipo ∗ nome da variavel ;

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

Notas de Aula ELT024 - Programação para Sistemas Embarcados


35 Introdução

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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


Capítulo 2

Arquitetura de microcontroladores

“Any sufficiently advanced technology is indistinguishable from ma-


gic.” - Arthur C. Clarke

Os microcontroladores são formados basicamente por um processador, memória e periféricos


interligados através de um barramento conforme Figura 2.1.
Em geral, os periféricos são tratados do mesmo modo que a memória, ou seja, para o pro-
cessador não existe diferença se estamos tratando com um valor guardado na memória RAM ou
com o valor da leitura de um conversor analógico digital. Isto acontece porque existem circuitos
eletrônicos que criam um nível de abstração em hardware. Deste modo todos os dispositivos
aparecem como endereços de memória.

36
37 Arquitetura de microcontroladores

Figura 2.1: Arquitetura do microcontrolador PIC 18F4550

Notas de Aula ELT024 - Programação para Sistemas Embarcados


38 Arquitetura de microcontroladores

. Acesso à memória


A quantidade de memória disponível que um microcontrolador pode acessar depende de dois
fatores, os tamanhos das palavras de dados e das palavras de endereço.
O tamanho da palavra de dados representa quantos bits podem ser colocados numa única
posição da memória.
O tamanho da palavra de endereço indica quantas posições de memória o processador con-
segue enxergar.
Por exemplo, um microcontrolador cujo tamanho de dados e o tamanho da palavra de ende-
reço são ambos 8 bits possui uma possibilidade de acessar uma memória de até:

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

Figura 2.2: Memória como um armário

Ao invés de gavetas o marceneiro pode pensar em colocar outros “sistemas de armazenamento”


nos espaços (Figura 2.3). Alguns destes sistemas podem permitir que o usuário enxergue o que
esta dentro mas não mexa. Alguns vão permitir que o usuário coloque coisas mas não retire.
Outros ainda podem permitir que a pessoa retire objetos mas não possa repô-los.
Esta variedade de “sistemas de armazenamento” representam a variedade de tipos de memória
e de interfaces de periféricos que possuímos.
A memória é identificada através de um endereço. Por estarmos tratando de sistemas digitais,
o valor do endereço é codificado em binário. Como visto anteriormente, escrever em binário é
trabalhoso e muito propenso a gerar erros. Visando facilitar esse processo, é comumente adotado
o sistema hexadecimal para indicar o local de memória.
Os dispositivos são então ligados a um determinado número de endereço que passa a identificá-
lo de forma única. O mesmo acontece para a memória RAM e memória ROM. Elas estão ligadas a
uma série de endereços. Se, portanto, é preciso salvar uma variável na memória, tem-se que saber
quais endereços estão ligados à memória RAM. Se quisermos ler alguma informação gravada na
memória ROM, é preciso conhecer a localização exata antes de realizar a leitura.
A porta D do microcontrolador PIC 4550 por exemplo está no endereço 0xF83, destinado aos
registros de função especial ou special function register, SFR Figura 2.4).

Notas de Aula ELT024 - Programação para Sistemas Embarcados


39 Arquitetura de microcontroladores

Suporte Existe
número: gaveta?
1 Vitrine

2 Gaveta

3 Dispenser

4 Não

5 Gaveta

6 Cofre

Figura 2.3: Memória e periféricos como um armário

Stack 1 0x000
GPR1
... 0x0FF
Stack 31 0x100
GPR2
0x1FF
Interrupção

Reset 0x0000 0x200


Vetor de

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

Figura 2.4: Regiões de memórias disponíveis no PIC18F4550

Notas de Aula ELT024 - Programação para Sistemas Embarcados


40 Arquitetura de microcontroladores

. Clock e tempo de instrução


O microcontrolador é capaz de realizar apenas uma tarefa por vez. Estas tarefas são executadas
sempre a intervalos regulares definidos pelo clock do sistema. O clock define então a velocidade
com que o processador trabalha.
Algumas operações são mais demoradas pois são compostas de uma quantidade maior de
tarefas. Por exemplo a soma.
A soma de números inteiros é feita de maneira direta enquanto para somar dois números
fracionários, que estão em notação científica1 , é necessário igualar as potencias antes de realizar
a soma. Por este motivo a segunda operação é mais demorada que a primeira.
Exemplo:

Multiplicação de inteiros Multiplicação de fracionários

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.

Tabela 2.1: Quantidade de operações e tarefas

Operação com: Quantidade Total de tarefas


1 tarefa 104 104
2 tarefas 63 126
3 tarefas 21 63
4 tarefas 12 48
Total 200 341

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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


41 Arquitetura de microcontroladores

. Esquema elétrico e circuitos importantes


Um microcontrolador é representado através de esquema elétrico. O esquema elétrico apresenta
ao projetista todos os componentes e suas ligações conforme apresentado na Figura 2.5.

Figura 2.5: Esquema elétrico: Microcontrolador PIC 18F4550

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).

Notas de Aula ELT024 - Programação para Sistemas Embarcados


42 Arquitetura de microcontroladores

Multiplexação nos terminais do microcontrolador


Conforme pode ser observado na Figura 2.5, alguns pinos/terminais possuem mais de uma des-
crição. Por exemplo o terminal 8, a descrição deste terminal é “RE0/AN5/CK1SPP”. Isto indica
que dependendo da configuração escolhida ele pode ser um terminal:

• de entrada ou saída referente ao primeiro bit da porta E (RE0)

• de leitura analógica pertencente ao quinto conversor analógico - digital (AN5)

• utilizado para enviar um clock externo de comunicação paralela (CK1SPP)

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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


43 Arquitetura de microcontroladores

. Registros de configuração do microcontrolador


A maioria dos terminais dos microcontroladores podem ser configurados para trabalhar de di-
versas maneiras. Esta configuração é realizada através de registros especiais. Estes registros
são posições de memória pré-definidas pelo fabricante. Para conhecer quais são e o que fazem é
preciso recorrer ao datasheet do componente.
Além dos registros de configuração dos terminais, existem registros que indicam como o mi-
crocontrolador deve operar. O microcontrolador PIC18f4550 possui dez registros que controlam
seu modo de operação, velocidade, modo de gravação, etc. Estes registros são apresentados na
Figura 2.6.

Figura 2.6: Registros de configuração do microcontrolador PIC 18F4550

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 ;

A primeira coluna de números indica a posição do registro que armazena as configurações.


A segunda coluna representa os códigos utilizados para configuração. Para conhecer as demais
opções de configurações devemos consultar o manual do usuário.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


44 Arquitetura de microcontroladores

Estas configurações são dependentes do compilador a ser usado. A seguir demonstramos os


códigos necessários para o compilador C18 da Microchip(R).

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

Notas de Aula ELT024 - Programação para Sistemas Embarcados


Capítulo 3

Programação dos Periféricos

“In theory, there is no difference between theory and practice; In


practice, there is.” - Chuck Reid

Periféricos são os componentes, circuitos ou sistemas ligados ao microcontrolador, interna ou


externamente. Eles podem ser utilizados para realizar interface entre o homem e o equipamento
ou incluir funcionalidades extras ao sistema, como comunicação, segurança, etc.
Os periféricos de saída1 que serão abordados neste curso serão:

• Barramento de Led's(.)

• Display de 7 segmentos(.)

• Display LCD 2x16(.)

• Saídas PWM(.)

Entre os periféricos de entrada2 temos:

• Leitura de teclas(.)

• Conversor AD(.)

Além de sistemas de entrada e saída de dados será abordado um dispositivo de comunicação


serial (.), um tratador de Interrupção(.), um Timer(.) e um dispositivo de segurança:
Watchdog(.).

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

. Acesso às “portas” do microcontrolador


O microcontrolador possui “portas” que permitem o interfaceamento do meio externo para o meio
interno. Algumas portas podem trabalhar como receptoras ou transmissoras de sinais. Algumas
podem operar dos dois modos sendo necessário configurá-las antes de sua utilização.
O microcontrolador PIC 18F4550 possui 5 portas

• PORTA: bidirecional com 7 bits

• PORTB: bidirecional com 8 bits

• PORTC: bidirecional com 7 bits

• PORTD: bidirecional com 8 bits

• PORTE: 3 bits bidirecionais e 1 bit apenas entrada

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).

Tabela 3.1: Endereços de memória para as portas do PIC 18F4550

Porta Endereço dos dados Endereço de configuração (TRIS)


PORTA 0xF80 0xF92
PORTB 0xF81 0xF93
PORTC 0xF82 0xF94
PORTD 0xF83 0xF95
PORTE 0xF84 0xF96

Aqui o conceito de ponteiros se faz extremamente necessário. Já que conhecemos os endereços


fixos onde as portas se encontram, podemos criar ponteiros para tais endereços de forma que
possamos utilizar as portas como se fossem variáveis. Exemplo:

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

Notas de Aula ELT024 - Programação para Sistemas Embarcados


47 Programação dos Periféricos

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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


48 Programação dos Periféricos

. Configuração dos periféricos


“Make everything as simple as possible, but not simpler.” - Albert
Einstein
Em geral, os terminais das portas são multiplexados com outros dispositivos. Para saber como
configurar cada porta é preciso conhecer os registros de configuração que atuam sobre a porta
desejada. A Figura 3.1 apresenta todos os registros disponíveis do microcontrolador 18F4550.

Figura 3.1: Registros de configuração dos periféricos do PIC 18F4550

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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


49 Programação dos Periféricos

Tabela 3.2: Tabela de configuração do PIC para as experiências

Terminal Descrição do pino Função


2 RA0/AN0 Potenciômetro / Sensor de Temperatura
4 RA2/AN2/VREF-/CVREF Display 2
7 RA5/AN4/SS/C2OUT Display 1
8 RE0/AN5/CK1SPP RS-LCD / Display 3
9 RE1/AN6/CK2SPP EN-LCD
10 RE2/AN7/OESPP RW-LCD / Display 4
13 OSC1/CLKI
Cristal
14 OSC2/CLKO/RA6
16 RC1/T1OSI/CCP2 Aquecedor
17 RC2/CCP1/P1A Ventilador / Buzzer
19 RD0/SPP0
20 RD1/SPP1 Barramento de dados para o
21 RD2/SPP2 LCD/7seg/Led

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

Notas de Aula ELT024 - Programação para Sistemas Embarcados


50 Programação dos Periféricos

TRISA = 0 b00000001 ; // c o n f i g u r a n d o os t e r m i n a i s como e n t r a d a e s a í d a


ADCON1 = 0 b00001110 ; // apenas o p r i m e i r o t e r m i n a l é a n a l ó g i c o

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.

TRISB = 0 b11110000 ; // c o n f i g u r a n d o os t e r m i n a i s como e n t r a d a e s a í d a


// c o n f i g u r a ç ã o com i n t e r r u p ç ã o h a b i l i t a d a
INTCON = 0 b11000101 ;
INTCON2 = 0 b00000001 ;
// c o n f i g u r a ç ã o sem i n t e r r u p ç ã o
INTCON = 0 b00000000 ;
INTCON2 = 0 b00000001 ;
SPPCFG = 0 b00000000 ; //RB0 e RB4 são c o n t r o l a d o s p e l a p o r t a B e não p e l o SPP

A porta C possui o segundo e terceiro bit como saída PWM e o sétimo e oitavo como
comunicação serial.

TRISC = 0 b10000000 ; // c o n f i g u r a n d o os t e r m i n a i s como s a í d a , apenas RC7 é e n t r a d a


CCP1CON = 0 b00001100 ; // c o n f i g u r a o segundo t e r m i n a l como PWM
CCP2CON = 0 b00001100 ; // c o n f i g u r a o t e r c e i r o t e r m i n a l como PWM
TXTA = 0 b00101100 ; // c o n f i g u r a a t r a n s m i s s ã o de dados da s e r i a l
RCSTA = 0 b10010000 ; // c o n f i g u r a a r e c e p ç ã o de dados da s e r i a l
BAUDCON = 0 b00001000 ; // c o n f i g u r a s i s t e m a de v e l o c i d a d e da s e r i a l
SPBRGH = 0 b00000000 ; // c o n f i g u r a para 56 k
SPBRG = 0 b00100010 ; // c o n f i g u r a para 56 k

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.

TRISD = 0 b00000000 ; // c o n f i g u r a n d o os t e r m i n a i s como s a í d a

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.

TRISE = 0 b00000000 ; // c o n f i g u r a n d o os t e r m i n a i s como s a í d a

Notas de Aula ELT024 - Programação para Sistemas Embarcados


51 Programação dos Periféricos

. Barramento de Led's


Existe na placa utilizada um barramento de 8 bits, onde cada linha possui um led associado. Este
barramento está ligado diretamente com a porta D do microcontrolador conforme Figura 3.2.

Figura 3.2: Barramento de Led's

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.

void main ( void )


{
TRISD = 0 x00 ; // c o n f i g u r a os p i n o s da p o r t a D como s a í d a
PORTD = 0 xF0 ; // l i g a apenas os q u a t r o ú l t i m o s b i t s .
for ( ; ; ) ; //mantém o s i s t e m a num l o o p i n f i n i t o
}

Notas de Aula ELT024 - Programação para Sistemas Embarcados


52 Programação dos Periféricos

. Display de 7 segmentos


Os displays de 7 segmentos (Figura 3.3) são componentes opto eletrônicos utilizados para apre-
sentar informações para o usuário em formato numérico.

Figura 3.3: Display de 7 Segmentos


Fonte: Peter Halasz - http://commons.wikimedia.org/wiki/File:Seven_segment_02_Pengo.jpg

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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


53 Programação dos Periféricos

Tabela 3.3: Conversão binário - hexadecimal para displays de 7 segmentos

Ordem inversa Ordem direta


Display a b c d e f g Hex (0abcdefg) g f e d c b a Hex (0gfedcba)
0 1 1 1 1 1 1 0 7E 0 1 1 1 1 1 1 3F
1 0 1 1 0 0 0 0 30 0 0 0 0 1 1 0 06
2 1 1 0 1 1 0 1 6D 1 0 1 1 0 1 1 5B
3 1 1 1 1 0 0 1 79 1 0 0 1 1 1 1 4F
4 0 1 1 0 0 1 1 33 1 1 0 0 1 1 0 66
5 1 0 1 1 0 1 1 5B 1 1 0 1 1 0 1 6D
6 1 0 1 1 1 1 1 5F 1 1 1 1 1 0 1 7D
7 1 1 1 0 0 0 0 70 0 0 0 0 1 1 1 07
8 1 1 1 1 1 1 1 7F 1 1 1 1 1 1 1 7F
9 1 1 1 1 0 1 1 7B 1 1 0 1 1 1 1 6F
A 1 1 1 0 1 1 1 77 1 1 1 0 1 1 1 77
b 1 0 1 1 1 1 0 5E 1 1 1 1 1 0 0 7C
C 1 0 0 1 1 1 1 4F 0 1 1 1 0 0 1 39
d 0 1 1 1 1 0 1 3D 1 0 1 1 1 1 0 5E
E 1 0 0 1 1 1 1 4F 1 1 1 1 0 0 1 79
F 1 0 0 0 1 1 1 47 1 1 1 0 0 0 1 71

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.

Figura 3.5: Ligação de 4 displays de 7 segmentos multiplexados

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.

void main ( void )


{

Notas de Aula ELT024 - Programação para Sistemas Embarcados


54 Programação dos Periféricos

// v e t o r que armazena a c o n v e r s ã o dos a l g a r i s m o s para o d i s p l a y 7 s e g


const char conv [ ] = {0 x3F , 0 x06 , 0 x5B , 0 x4F , 0 x66 , 0 x6D , 0 x7D , 0 x07 ,
0 x7F , 0 x6F , 0 x77 , 0 x7C , 0 x39 , 0 x5E , 0 x79 , 0 x71 } ;
unsigned i n t var , time ;
TRISD = 0 x00 ;
TRISA = 0 x00 ;
PORTA = 0 xFF ;
f o r ( var = 0 ; var < 1 6 ; var++)
{
// c o l o c a os c a r a c t e r e s em s e q u ê n c i a na s a í d a
PORTD = conv [ var ] ;
// apenas para c o n t a r tempo
f o r ( time = 0 ; time < 6 5 0 0 0 ; time++) ;
}
}

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:

1. colocar no barramento de dados o valor a ser mostrado no display X

2. ligar o display X através da linha de comando

3. esperar um tempo adequado para evitar flicker5

4. desligar o display

5. escolher o próximo display (X+1)

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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


55 Programação dos Periféricos

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.

Programa 3.1: disp7seg.c


1 #include " disp7seg .h"
2 // v e t o r para armazenar a c o n v e r s ã o do ←֓
display
3 s t a t i c const char valor [ ] = {0 x3F , ←֓ 40 void AtualizaDisplay ( void )
0 x06 , 0 x5B , 0 x4F , 0 x66 , 0 x6D , ←֓ 41 {
0 x7D , 0 x07 , 0 x7F , 0 x6F , 0 x77 , ←֓ 42 // d e s l i g a t o d o s os d i s p l a y s
0 x7C , 0 x39 , 0 x5E , 0 x79 , 0 x71 } ; 43 PORTA = 0 x00 ;
4 // i n d i c a o d i s p l a y a t u a l 44 PORTE = 0 x00 ;
5 s t a t i c char display ; 45 // d e s l i g a t o d o s os l e d s
6 // v a l o r e s dos d i s p l a y s 46 PORTD = 0 x00 ;
7 s t a t i c char v0 , v1 , v2 , v3 ; 47 // l i g a apenas o d i s p l a y da v e z
8 void MudaDigito ( char val , char pos ) 48 switch ( display )
9 { 49 {
10 i f ( pos == 0 ) 50 case 0 :
11 { 51 PORTD = valor [ v0 ] ;
12 v0 = val ; 52 BitSet ( PORTA , 5 ) ;
13 } 53 display = 1 ;
14 i f ( pos == 1 ) 54 break ;
15 { 55 case 1 :
16 v1 = val ; 56 PORTD = valor [ v1 ] ;
17 } 57 BitSet ( PORTA , 2 ) ;
18 i f ( pos == 2 ) 58 display = 2 ;
19 { 59 break ;
20 v2 = val ; 60 case 2 :
21 } 61 PORTD = valor [ v2 ] ;
22 i f ( pos == 3 ) 62 BitSet ( PORTE , 0 ) ;
23 { 63 display = 3 ;
24 v3 = val ; 64 break ;
25 } 65 case 3 :
26 } 66 PORTD = valor [ v3 ] ;
67 BitSet ( PORTE , 2 ) ;
28 void InicializaDisplays ( void ) 68 display = 0 ;
29 { 69 break ;
30 // c o n f i g u r a ç ã o dos p i n o s de c o n t r o l e 70 default :
31 BitClr ( TRISA , 2 ) ; 71 display = 0 ;
32 BitClr ( TRISA , 5 ) ; 72 break ;
33 BitClr ( TRISE , 0 ) ; 73 }
34 BitClr ( TRISE , 2 ) ; 74 }
35 // apenas AN0 é a n a l ó g i c o
36 ADCON1 = 0 x0E ;
37 // Porta de dados
38 TRISD = 0 x00 ;
39 }

Notas de Aula ELT024 - Programação para Sistemas Embarcados


56 Programação dos Periféricos

Programa 3.2: disp7seg.h


1 #i f n d e f DISP7SEG_H
2 #define DISP7SEG_H
3 void MudaDigito ( char val , char pos ) ;
4 void AtualizaDisplay ( void ) ;
5 void InicializaDisplays ( void ) ;
6 #endif

Programa 3.3: Utilizando a biblioteca disp7seg


1 #include " basico .h"
2 #include " config .h"
3 #include " disp7seg .h"

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 }

Notas de Aula ELT024 - Programação para Sistemas Embarcados


57 Programação dos Periféricos

. Leitura de teclas


Para realizar a leitura de uma tecla é necessário criar um circuito que realize a leitura de um
sinal elétrico para o valor zero e outro para o valor um. Os níveis de tensão associados dependem
muito dos circuitos envolvidos. Os níveis mais comuns são os compatíveis com TTL, onde o zero
lógico é representado por 0v (zero volts) e o um lógico é representado por 5v (cinco volts).
Uma maneira de se obter este funcionamento é com o uso de uma chave ligada ao VCC e um
pull-down ou uma chave ligada ao terra (GND) e um pull-up.

Figura 3.6: Circuito de leitura de chave


http://www.labbookpages.co.uk/electronics/debounce.html - Dr. Andrew Greensted

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.

Debounce por software


O debounce por software em geral é utilizado em situações onde se deseja aumentar a robustez
de uma entrada que já possua um debounce por hardware ou reduzir o custo da placa utilizando
apenas a solução por software. A grande desvantagem deste tipo de sistema é inserir um atraso
na detecção da informação.
Para realizar o debounce por software precisamos ter uma noção do tempo que a chave
precisa para estabilizar. Da Figura 3.7 temos que este tempo, para uma determinada chave é de
aproximadamente 150 (µs). Um ciclo de clock do sistema em questão (PIC18f4550 com cristal de

Notas de Aula ELT024 - Programação para Sistemas Embarcados


58 Programação dos Periféricos

Figura 3.7: Oscilação do sinal no momento do chaveamento


http://www.labbookpages.co.uk/electronics/debounce.html - Dr. Andrew Greensted

Figura 3.8: Circuito de debounce


http://www.ikalogic.com/debouncing.php - Ibrahim Kamal

Notas de Aula ELT024 - Programação para Sistemas Embarcados


59 Programação dos Periféricos

Figura 3.9: Utilização de filtro RC para debounce do sinal


http://www.labbookpages.co.uk/electronics/debounce.html – A. Greensted (modificado)

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.

void main ( void )


{
unsigned char valTemp ;
unsigned char valAtual ;
unsigned char tempo ;
TRISB = 0 xF0 ; //mantém os 4 ú l t i m o s b i t s como e n t r a d a
TRISD = 0 x00 ; // C o n f i g u r a a p o r t a D como s a í d a
PORTB = 0 x00 ; // l i g a os 4 p r i m e i r o s b i t s
BitClr ( INTCON2 , 7 ) ; // h a b i l i t a p u l l −up
ADCON1 = 0 b00001110 ; // c o n f i g u r a t o d o s os b i t s da p o r t a B como d i g i t a i s
for ( ; ; )
{
// aguarda uma mudança na p o r t a B
while ( valAtual==PORTB ) ;
// quando a c o n t e c e r alguma mudança , c o n t a um tempo pra v e r s e é permanente
valTemp = PORTB ;
tempo = 2 2 ;
while ( tempo > 0 )
{
i f ( valTemp == PORTB ) // s e não mudar c o n t i n u a a c o n t a r
{
tempo −−;
6
Lembrar que cada ciclo do PIC necessita de 4 ciclos de clock externo

Notas de Aula ELT024 - Programação para Sistemas Embarcados


60 Programação dos Periféricos

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

Arranjo de leitura por matriz


Para cada tecla/botão que é colocado no projeto, é necessário um terminal do microcontrolador.
Para um teclado maior é possível que o microcontrolador não possua terminais disponíveis em
quantidade suficiente. Do mesmo modo que no caso dos displays é possível multiplexar as
chaves aumentando a quantidade de chave por terminal. Novamente, este ganho, em termos de
hardware, aumenta a complexidade para o software e juntamente com este, o custo em termos
de tempo de computação.
Uma das técnicas mais eficientes para a geração deste teclado é o arranjo em formato matri-
cial. Com esta configuração podemos, com N terminais, ler até (N/2)2 chaves.
Conforme podemos ver na Figura 3.10, cada chave pode ser identificada unicamente pela
sua posição (linha, coluna). Os terminais ligados às linhas serão configurados como entrada,
que servirão para ler os valores das teclas. Os terminais ligados às colunas serão configurados
como saídas, fornecendo energia para as chaves. A leitura é realizada então por um processo
conhecido como varredura: liga-se uma coluna por vez e verifica-se quais chaves daquela coluna
estão ligadas.

void main ( void )


{
unsigned char i , j ;
unsigned char chave [ 2 ] [ 4 ] = { { 0 , 0 , 0 , 0 } , { 0 , 0 , 0 , 0 } } ;
INTCON2 &= 0 x7F ; // h a b i l i t a p u l l −up
ADCON1 = 0 b00001110 ; // apenas AN0 é a n a l ó g i c o ,
TRISB = 0 xF0 ; // os 4 ú l t i m o s b i t s são e n t r a d a
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 ;
for ( ; ; )
{
f o r ( i = 0 ; i < 2 ; i++)
{
PORTB = 0 xff ; // " d e s l i g a " t o d a s as c o l u n a s
f o r ( j = 0 ; j < 1 0 0 ; j++) ;
BitClr ( PORTB , i ) ; // " l i g a " o b i t da c o l u n a c o r r e s p o n d e n t e
// g a s t a tempo para g a r a n t i r que o p i n o a t i n g i u o n í v e l a l t o
f o r ( j = 0 ; j < 1 0 0 ; j++) ;
// r e a l i z a o t e s t e para cada b i t e a t u a l i z a a m a t r i z .
f o r ( j = 0 ; j < 4 ; j++)
{
i f ( ! BitTst ( PORTB , j+4) )
{
chave [ i ] [ j ] = 1 ;
BitSet ( PORTD , j+4∗i ) ;
}
else
{
chave [ i ] [ j ] = 0 ;
BitClr ( PORTD , j+4∗i ) ;
}
}

Notas de Aula ELT024 - Programação para Sistemas Embarcados


61 Programação dos Periféricos

Figura 3.10: Teclado em arranjo matricial

Notas de Aula ELT024 - Programação para Sistemas Embarcados


62 Programação dos Periféricos

Programa 3.4: teclado.c


1 #include " teclado .h"
2 #include " basico .h"
3 s t a t i c unsigned in t valor = 0 x0000 ;
4 unsigned i n t LerTeclas ( void ) {
5 return valor ;
6 }
7 void DebounceTeclas ( void ) {
8 unsigned char i , j ;
9 s t a t i c unsigned char tempo ;
10 s t a t i c unsigned in t valorNovo = 0 x0000 ;
11 s t a t i c unsigned in t valorAntigo = 0 x0000 ;
12 f o r ( i = 0 ; i < 4 ; i++){
13 PORTB |= 0 x0F ; // d e s l i g a t o d a s as c o l u n a s
14 BitClr ( PORTB , ( i ) ) ; // l i g a a c o l u n a c o r r e s p o n d e n t e
15 // g a s t a tempo sem r e a l i z a r f u n ç ã o n e c e s s á r i o para g a r a n t i r que o p i n o ←֓
atingiu o nivel alto
16 f o r ( j =0;j <100; j++) ;
17 // r e a l i z a o t e s t e para cada b i t e a t u a l i z a a v a r i á v e l
18 f o r ( j = 0 ; j < 4 ; j++) {
19 i f ( ! BitTst ( PORTB , j+4) ) {
20 BitSet ( valorNovo , ( i ∗ 4 )+j ) ;
21 } else {
22 BitClr ( valorNovo , ( i ∗ 4 )+j ) ;
23 }
24 }
25 }
26 i f ( valorAntigo == valorNovo ) {
27 tempo −−;
28 } else {
29 tempo = 1 0 ;
30 valorAntigo = valorNovo ;
31 }
32 i f ( tempo == 0 ) {
33 valor = valorAntigo ;
34 }
35 }

37 void InicializaTeclado ( void ) {


38 TRISB = 0 xF0 ; // q u a t r o e n t r a d a s e q u a t r o s a í d a s
39 INTCON2 &= 0 x7F ; // h a b i l i t a p u l l −up
40 ADCON1 = 0 b00001110 ; // apenas AN0 é a n a l ó g i c o
41 SPPCFG = 0 x00 ;
42 }

}
}
}

É 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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


63 Programação dos Periféricos

Programa 3.5: teclado.h


1 #i f n d e f TECLADO_H
2 #define TECLADO_H

4 unsigned i n t LerTeclas ( void ) ;


5 void DebounceTeclas ( void ) ;
6 void InicializaTeclado ( void ) ;
7 #endif //TECLADO_H

Programa 3.6: Exemplo de uso da biblioteca teclado


1 #include " basico .h"
2 #include " config .h"
3 #include " teclado .h"

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 }

Notas de Aula ELT024 - Programação para Sistemas Embarcados


64 Programação dos Periféricos

. Display LCD 2x16


O display de LCD utilizado neste curso possui duas linhas por 16 colunas de caracteres, compatí-
vel com o HD44780 que é um padrão muito utilizado na indústria. Na Figura 3.11 é apresentado
um modelo genérico deste tipo de display. A Figura 3.12 apresenta o verso do display com os
terminais expostos.

Figura 3.11: Display Alfanumérico LCD 2x16

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

2. VCC (+5V) 10. Bit 3

3. Ajuste do contraste 11. Bit 4

4. Seleção de registro(RS) 12. Bit 5

5. Read/Write (RW) 13. Bit 6

6. Clock, Enable (EN) 14. Bit 7

7. Bit 0 15. Backlight + (opcional)

8. Bit 1 16. Backlight Gnd (opcional)

Trabalharemos apenas com 11 terminais: os 3 de controle do display (RS,RW,EN) e os 8


para o barramento de dados. Este tipo de display possui, integrado, um microcontrolador para
realizar as rotinas de manutenção do estado do display, controle da luminosidade e interface com

Notas de Aula ELT024 - Programação para Sistemas Embarcados


65 Programação dos Periféricos

Figura 3.12: Display Alfanumérico LCD 2x16 - verso

o restante do sistema eletrônico. A comunicação é realizada através de um barramento paralelo


de 8 bits, por onde são enviados os dados/comandos.
O terminal RS indica ao display se o barramento contém um comando a ser executado (0)
ou uma informação para ser exibida (1).
O terminal RW indica ao display se ele receberá um valor (0) ou se estamos requisitando
alguma informação (1).
O terminal EN indica ao display que ele pode ler/executar o que está no barramento de
dados.
Atenção, o display utilizado apresenta os terminais colocados de maneira não sequencial,
conforme pode ser visto na Figura 3.12. Deste modo não é qualquer display que é compatível.
As informações são enviadas através da codificação ASCII, sendo que os caracteres de 0 à
127 são padronizados. Os caracteres de 128 à 255 dependem do fabricante do display. É possível
também criar algumas representações, símbolos definidos pelo usuário e armazenar na memória
interna do display. Para um display com a ROM do tipo A00 temos os caracteres definidos na
Figura 3.13. Para a ROM A02 temos a Figura 3.14.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


66 Programação dos Periféricos

Figura 3.13: Caracteres disponíveis para ROM A00


http://www.sparkfun.com/datasheets/LCD/HD44780.pdf - Datasheet Hitachi

Notas de Aula ELT024 - Programação para Sistemas Embarcados


67 Programação dos Periféricos

Figura 3.14: Caracteres disponíveis para ROM A02


http://www.sparkfun.com/datasheets/LCD/HD44780.pdf - Datasheet Hitachi

Notas de Aula ELT024 - Programação para Sistemas Embarcados


68 Programação dos Periféricos

Os comandos reconhecidos pelo display são apresentados na Tabela 3.4.

Tabela 3.4: Lista de comandos aceitos pelo o LCD

Instrução RS RW Barramento de dados (bit) Tempo


7 6 5 4 3 2 1 0
Limpa todo o display e configura o endereço
0 0 0 0 0 0 0 0 0 1 37 us
para 0.
Configura o endereço para 0. Retorna o dis-
play para o início se houve alguma operação 0 0 0 0 0 0 0 0 1 - 1.52 ms
de shift.
Configura a movimentação do cursor e o modo
0 0 0 0 0 0 0 1 ID S 37 us
de shift do display
Configura o display (D) inteiro para desligado
ou ligado, cursor (C) ligado ou desligado e 0 0 0 0 0 0 1 D C B 37 us
“blinking” (B) do cursor.
Move o cursor e/ou o display sem alterar o
0 0 0 0 0 1 SC RL - - 37 us
conteúdo
Configura o tamanho da palavra (DL), nú-
0 0 0 0 1 DL N F - - 37 us
mero de linhas (N) e fonte dos caracteres (F)
Desloca o cursor para a posição desejada: li-
0 0 1 X 0 0 Coluna 37 us
nha e coluna.
Verifica se o display está disponível ou se esta
0 1 BF - - - - - - - 10 us
ocupado com alguma operação interna.
Definições das opções
ID: 1 – Incrementa, 0 – Decrementa N: 1 – 2 linhas, 0 – 1 linha
S: 1 – O display acompanha o deslocamento F: 1 – 5x10 pontos, 0 – 5x8 pontos
SC: 1 – Desloca o display, 0 – Desloca o cursor BF: 1 – Ocupado, 0 – Disponível
RL: 1 – Move para direita, 0 – Move para esquerda X: 1 – 2a linha, 0 – 1a linha
DL: 1 – 8 bits, 0 – 4 bits Coluna: nible que indica a coluna
http://www.sparkfun.com/datasheets/LCD/HD44780.pdf - Datasheet Hitachi (modificado)

Os terminais de dados estão ligados à porta D, juntamente com o display de 7 segmentos e


barramento de dados. Para estes dispositivos funcionarem em conjunto é necessário multiplexa-
los no tempo. Os terminais de controle estão ligados à porta E conforme o esquema apresentado
na Figura 3.15.

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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


69 Programação dos Periféricos

Figura 3.15: Esquemático de ligação do display de LCD

Programa 3.7: lcd.h


1 #i f n d e f LCD_H
2 #define LCD_H
3 void EnviaComando ( char cmd ) ;
4 void EnviaDados ( char valor ) ;
5 void InicializaLCD ( void ) ;
6 #endif //LCD_H

Notas de Aula ELT024 - Programação para Sistemas Embarcados


70 Programação dos Periféricos

Programa 3.8: lcd.c


1 #include " lcd .h"
2 #include " basico .h"
3 #define RS 0
4 #define EN 1 47 void EnviaDados ( char valor )
5 #define RW 2 48 {
49 // dados
7 void InicializaLCD ( void ) 50 BitSet ( PORTE , RS ) ;
8 { 51 // h a b i l i t a e s c r i t a
9 // I n i c i a l i z a o LCD 52 BitClr ( PORTE , RW ) ;
10 Delay2ms ( ) ; 53 PORTD = valor ;
11 Delay2ms ( ) ; 54 // h a b i l i t a l e i t u r a
12 Delay2ms ( ) ; 55 BitSet ( PORTE , EN ) ;
13 Delay2ms ( ) ; 56 Delay40us ( ) ;
14 Delay2ms ( ) ; 57 // t e r m i n a l e i t u r a
15 // c o n f i g . de d i r e ç ã o (E/S ) 58 BitClr ( PORTE , EN ) ;
16 BitClr ( TRISE , RS ) ; //RS 59 // d e i x a em n í v e l b a i x o
17 BitClr ( TRISE , EN ) ; //EN 60 BitClr ( PORTE , RS ) ;
18 BitClr ( TRISE , RW ) ; //RW 61 // d e i x a em n í v e l b a i x o
19 TRISD = 0 x00 ; // dados 62 BitClr ( PORTE , RW ) ;
20 ADCON1 = 0 b00001110 ; 63 Delay40us ( ) ;
21 // c o n f i g u r a o d i s p l a y 64 }
22 // 8 b i t s , 2 l i n h a s , 5 x8
23 EnviaComando ( 0 x38 ) ; 66 void EnviaComando ( char cmd )
24 //modo i n c r e m e n t a l 67 {
25 EnviaComando ( 0 x06 ) ; 68 // comando
26 // d i s p l a y , c u r s o r e b l i n k i n g on 69 BitClr ( PORTE , RS ) ;
27 EnviaComando ( 0 x0F ) ; 70 // h a b i l i t a e s c r i t a
28 // z e r a c o n t a d o r e s i n t e r n o s 71 BitClr ( PORTE , RW ) ;
29 EnviaComando ( 0 x03 ) ; 72 PORTD = cmd ;
30 // l i m p a r d i s p l a y 73 // h a b i l i t a l e i t u r a
31 EnviaComando ( 0 x01 ) ; 74 BitSet ( PORTE , EN ) ;
32 // p o s i ç ã o i n i c i a l 75 Delay2ms ( ) ;
33 EnviaComando ( 0 x80 ) ; 76 // t e r m i n a l e i t u r a
34 } 77 BitClr ( PORTE , EN ) ;
78 // d e i x a em n í v e l b a i x o
36 void Delay40us ( void ) { 79 BitClr ( PORTE , RS ) ;
37 unsigned char i ; 80 // d e i x a em n í v e l b a i x o
38 f o r ( i =0; i < 2 5 ; i++) ; 81 BitClr ( PORTE , RW ) ;
39 } 82 Delay2ms ( ) ;
83 }
41 void Delay2ms ( void ) {
42 unsigned char i ;
43 f o r ( i =0; i < 2 0 0 ; i++){
44 Delay40us ( ) ;
45 }
46 }

Notas de Aula ELT024 - Programação para Sistemas Embarcados


71 Programação dos Periféricos

Programa 3.9: Exemplo de uso da biblioteca de LCD


1 #include " basico .h"
2 #include " config .h"
3 #include " lcd .h"

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 }

Notas de Aula ELT024 - Programação para Sistemas Embarcados


72 Programação dos Periféricos

. Comunicação serial


Em geral a comunicação entre dois dispositivos eletrônicos é realizada de modo serial, isto é, as
informações são passadas bit à bit do transmissor para o receptor. Este tipo de comunicação
possui algumas vantagens em relação à comunicação paralela, na qual a palavra (byte) é enviada
toda de uma vez.
A primeira é a simplificação do hardware. Como os dados são enviados um a um, é necessário
apenas 1 par de fios, um para comunicação/transferência dos dados e um para referência da tensão
do sinal 7 .
A segunda é a maior taxa de transmissão, o que a primeira vista é inconsistente já que num
mesmo ciclo de clock a comunicação paralela envia mais de um bit, enquanto a serial apenas um.
Este fato acontece pois para frequências muito altas pode existir atraso entre um fio e outro se os
cabos da comunicação paralela possuírem qualquer diferença. Além disso existe o problema do
crosstalking, onde o campo magnético gerado por um cabo induz uma pequena tensão no outro
cabo, atrapalhando a comunicação. Estes problemas aumentam com a frequência limitando
assim a máxima transferência possível pelo barramento paralelo. É este o motivo que levou os
projetistas de hardware a desenvolverem o protocolo SATA, em detrimento ao IDE/ATA, para
comunicação entre o HD e a placa mãe conforme pode ser visto na Tabela 3.5.

Tabela 3.5: Taxas de transmissão para diferentes protocolos

Protocolo Taxa (Mbit/s) Taxa (Mb/s)


ATA 33 (Ultra DMA) 264 33
ATA 66 (Ultra DMA) 528 66
ATA 100 (Ultra DMA) 800 100
ATA 133 (Ultra DMA) 1064 133
Serial ATA (SATA-150) 1200 150
Serial ATA 2 (SATA-300) 2400 300
Serial ATA 3 (SATA-600) 4800 600

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

Notas de Aula ELT024 - Programação para Sistemas Embarcados


73 Programação dos Periféricos

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.

Figura 3.16: Sinal serializado para transmissão em RS232

Para o correto funcionamento do protocolo devemos garantir compatibilidade no nível físico


(do hardware) e lógico (no software).
Para o hardware basta compatibilizar o tipo de conector, os níveis de tensão e a pinagem dos
conectores.
Para o nível de software temos que definir a codificação utilizada (ASCII, UTF-8, etc),
especificar o fluxo de caracteres (quantidade de bits por caractere, tamanho do start/stop bit,
paridade) e a taxa de transmissão desejada.
Estas configurações são realizadas através de 5 registros TXSTA, RCSTA, BAUDCON, SP-
BRGH e SPBRG.
Os registros TXSTA e RCSTA são responsáveis por configurar o meio de transmissão: pre-
sença/ausência de bit de parada, tamanho da palavra de um caractere, transmissão síncrona/as-
síncrona.
O registro BAUDCON é responsável por configurar o controle de velocidade de transmissão.
Os registros SPBRGH e SPBRG representam o byte mais alto e o mais baixo da palavra de
16 bits que indica a taxa de transmissão.
A taxa de transmissão pode ser calculada segundo a Tabela 3.6.
Como visto na Tabela 3.6 existem três fórmulas diferentes para calcular a taxa de transmissão.
A melhor maneira de configurar a taxa de transmissão da porta serial é verificar qual dos métodos
gera o menor erro para uma dada taxa de transmissão.
Por exemplo, queremos uma taxa de transmissão de 57,6 kbps. A frequência disponível é um
cristal de 8MHz. Usando as três fórmulas chegamos aos seguintes valores:

• n1 = 1, F232 = 62.500, err = -7,64%

• n2 = 8, F232 = 55.555, err = 3,63%


8
Para o protocolo RS232 o nível alto ou 1 (um) é aquele com tensões positivas entre +3 e +15. O nível lógico
baixo ou 0 (zero) é interpretado entre -3 e -15 volts.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


74 Programação dos Periféricos

Tabela 3.6: Cálculo do valor da taxa de transmissão da porta serial

Bits de Configuração Precisão Taxa de transmissão


TXSTA:4 BAUDCON:3 TXSTA:2
FOSC
0 0 0 8bits F232 =
[64 ∗ (n + 1)]
0 0 1 8bits FOSC
F232 =
0 1 0 16bits [16 ∗ (n + 1)]
0 1 1 16bits
FOSC
1 0 x 8bits F232 =
[4 ∗ (n + 1)]
1 1 x 16bits
x – não importa, n – valor do par SPBRGH:SPBRG

• n3 = 32, F232 = 57.142, err = 0,79%

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.

void EnviaSerial ( unsigned char c )


{
while ( ! BitTst ( PIR1 , 4 ) ) ; // aguarda o r e g i s t r o f i c a r d i s p o n í v e l
TXREG=c ; // c o l o c a o v a l o r para s e r e n v i a d o
}

O processo de desserialização também é realizado de modo automático pelo hardware do


dispositivo. Assim que um byte estiver disponível o sistema seta o bit 5 do registro PIR1 e
podemos então ler o valor disponível no registro RCREG.

unsigned char RecebeSerial ( void )

Notas de Aula ELT024 - Programação para Sistemas Embarcados


75 Programação dos Periféricos

Programa 3.10: serial.c


1 #include " serial .h"
2 #include " basico .h"

4 void EnviaSerial ( unsigned char c )


5 {
6 while ( ! BitTst ( PIR1 , 4 ) ) ; // aguarda o r e g i s t r o f i c a r d i s p o n í v e l
7 TXREG=c ; // c o l o c a o v a l o r para s e r e n v i a d o
8 }

10 unsigned char RecebeSerial ( void )


11 {
12 char resp = 0 ;
13 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
14 {
15 resp = RCREG ; // r e t o r n a o v a l o r
16 }
17 return resp ; // r e t o r n a z e r o
18 }

20 void InicializaSerial ( void )


21 {
22 TXSTA = 0 b00101100 ; // c o n f i g u r a a t r a n s m i s s ã o de dados da s e r i a l
23 RCSTA = 0 b10010000 ; // c o n f i g u r a a r e c e p ç ã o de dados da s e r i a l
24 BAUDCON = 0 b00001000 ; // c o n f i g u r a s i s t e m a de v e l o c i d a d e da s e r i a l
25 SPBRGH = 0 b00000000 ; // c o n f i g u r a para 56 k
26 SPBRG = 0 b00100010 ; // c o n f i g u r a para 56 k
27 BitSet ( TRISC , 6 ) ; // p i n o de r e c e p ç ã o de dados
28 BitClr ( TRISC , 7 ) ; // p i n o de e n v i o de dados
29 }

{
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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


76 Programação dos Periféricos

Programa 3.11: serial.h


1 #i f n d e f SERIAL_H
2 #define SERIAL_H
3 void EnviaSerial ( unsigned char c ) ;
4 unsigned char RecebeSerial ( void ) ;
5 void InicializaSerial ( void ) ;
6 #endif //SERIAL_H

Programa 3.12: Exemplo de uso da biblioteca de comunicação serial


1 #include " basico .h"
2 #include " config .h"
3 #include " serial .h"

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 }

Notas de Aula ELT024 - Programação para Sistemas Embarcados


77 Programação dos Periféricos

. 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).

Figura 3.17: Lâmpada incandescente

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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


78 Programação dos Periféricos

Figura 3.18: Potenciômetro

Figura 3.19: Potenciômetro como divisor de tensão


http://en.wikipedia.org/wiki/File:Potentiometer_with_load.png

Figura 3.20: Circuito integrado LM35

Notas de Aula ELT024 - Programação para Sistemas Embarcados


79 Programação dos Periféricos

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.

Figura 3.21: Diagrama de blocos do LM35

Quando polarizado através de uma corrente constante, havendo mudança de temperatura a


tensão em cima do diodo muda. Os dois amplificadores e as respectivas realimentações estão
inseridas no circuito para amplificar e estabilizar as variações de tensão.

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.

Representação binária com 2 bits Valor em tensão


00 0.625v
01 1.875v
10 3.125v
11 4.375v

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

Notas de Aula ELT024 - Programação para Sistemas Embarcados


80 Programação dos Periféricos

Figura 3.22: Conversor analógico digital de 2 bits


http://en.wikipedia.org/wiki/File:Flash_ADC.png - Jon Guerber

Notas de Aula ELT024 - Programação para Sistemas Embarcados


81 Programação dos Periféricos

Programa 3.13: adc.c


1 #include " adc .h"
2 #include " basico .h"
3 void InicializaAD ( void )
4 {
5 BitSet ( TRISA , 0 ) ; // s e t a o b i t 0 como e n t r a d a
6 ADCON0 = 0 b00000001 ; // s e l e c i o n a o c a n a l 0 e l i g a o ad
7 ADCON1 = 0 b00001110 ; // apenas AN0 é a n a l ó g i c o , a r e f e r ê n c i a é b a s e a d a na f o n t e
8 ADCON2 = 0 b10101010 ; //FOSC /32 , Alinhamento à d i r e i t a e tempo de conv = 12 TAD
9 }

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 }

Programa 3.14: adc.h


1 #i f n d e f ADC_H
2 #define ADC_H
3 void InicializaAD ( void ) ;
4 i n t LeValorAD ( void ) ;
5 #endif //ADC_H

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

3. Monitorar o final da conversão

4. Ler o valor

Os programas 3.13 e 3.14 apresentam os arquivos de código e header de uma biblioteca


exemplo para conversores analógicos para digital no microcontrolador PIC. O programa 3.15
apresenta um código exemplificando o uso da biblioteca criada.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


82 Programação dos Periféricos

Programa 3.15: Exemplo de uso da biblioteca de conversores AD


1 #include " basico .h"
2 #include " config .h"
3 #include " disp7seg .h"
4 #include " adc .h"
5 // i n i c i o do programa
6 void main ( void )
7 {
8 unsigned i n t i ;
9 i n t temp = 0 ;
10 InicializaDisplays ( ) ;
11 InicializaAD ( ) ;
12 for ( ; ; )
13 {
14 temp = LeValorAD ( ) ;
15 temp %= 1 0 0 0 0 ;
16 MudaDigito ( temp / 1 0 0 0 , 3 ) ;
17 temp %= 1 0 0 0 ;
18 MudaDigito ( temp / 100 , 2 ) ;
19 temp %= 1 0 0 ;
20 MudaDigito ( temp / 10 ,1) ;
21 temp %= 1 0 ;
22 MudaDigito ( temp ,0) ;
23 AtualizaDisplay ( ) ;
24 f o r ( i = 0 ; i < 1 0 0 0 ; i++) ;
25 }
26 }

Notas de Aula ELT024 - Programação para Sistemas Embarcados


83 Programação dos Periféricos

. Saídas PWM


As saídas PWM são saídas digitais que possuem um chaveamento acoplado. O sinal muda seu
estado de positivo para zero várias vezes por segundo. A porcentagem do tempo que este sinal
permanece em nível Alto define o ciclo de trabalho, ou duty cycle, da saída. A Figura 3.23
apresenta 3 sinais PWM com a mesma frequência mas com duty cycle diferentes.

Figura 3.23: Sinais PWM com variação do duty cycle

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:

Notas de Aula ELT024 - Programação para Sistemas Embarcados


84 Programação dos Periféricos

Tabela 3.7: Faixa de frequências máximas e mínimas para cada configuração do prescaler

Prescaler Freq. máxima (PR2 = 0) Freq. mínima (PR2 = 0)


1 2.000.000 7.812,5
4 500.000 1.953,2
16 125.000 488,3

[CCPRxL : CCPxCON (5 : 4)]


DutyCycle PWM =
(PR2 + 1) ∗ 4

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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


85 Programação dos Periféricos

Programa 3.16: pwm.c


1 #include " pwm .h"
2 #include " basico .h"

4 void SetaPWM1 ( unsigned char porcento )


5 {
6 // f ó r m u l a do d u t y c y c l e :
7 // DC_porcento = V / ( ( PR2+1) ∗ 4 ;
8 //V = DC/100 ∗ (PR2+1) ∗ 4 = DC ∗ (PR2+1) /25
9 unsigned i n t val = ( ( unsigned i n t ) porcento ) ∗ ( PR2 +1) ;
10 val = val / 2 5 ;
11 // g a r a n t e que tem apenas 10 b i t s
12 val &= 0 x03ff ;
13 // os 8 p r i m e i r o s b i t s são c o l o c a d o s no CCPR1L
14 CCPR1L = val >> 2 ;
15 // os ú l t i m o s d o i s são c o l o c a d o s na p o s i ç ã o 5 e 4 do CCP1CON
16 CCP1CON |= ( val & 0 x0003 ) << 4 ;
17 }

19 void SetaPWM2 ( unsigned char porcento )


20 {
21 // 100 ∗ 256 = 2 5 . 6 0 0
22 unsigned i n t val = porcento ∗ PR2 ;
23 val /= 2 5 ;
24 // g a r a n t e que tem apenas 10 b i t s
25 val &= 0 x03ff ;
26 // os 8 p r i m e i r o s b i t s são c o l o c a d o s no CCPR1L
27 CCPR2L = val >> 2 ;
28 // os ú l t i m o s d o i s são c o l o c a d o s na p o s i ç ã o 5 e 4 do CCP1CON
29 CCP2CON |= ( val & 0 x0003 ) << 4 ;
30 }

32 void SetaFreqPWM ( unsigned i n t freq )


33 {
34 //PR2 = f o s c /( fpwm∗4∗ p r e s c a l e r )−1 = ( 8 0 0 0 0 0 0 / ( f r e q ∗4∗16) ) − 1
35 PR2 = ( 1 2 5 0 0 0 / ( freq ) ) − 1 ;
36 }

38 void InicializaPWM ( void )


39 {
40 BitClr ( TRISC , 2 ) ; // c o n f i g u r a os p i n o s como s a í d a s
41 BitClr ( TRISC , 3 ) ;
42 T2CON |= 0 b00000011 ; // c o n f i g u r a o p r e s c a l e do t i m e r 2 para 1 : 1 6
43 BitSet ( T2CON , 2 ) ; // L i g a o t i m e r 2
44 CCP1CON |= 0 b00001100 ; // c o n f i g u r a CCP1 como um PWM
45 CCP2CON |= 0 b00001100 ; // c o n f i g u r a CCP2 como um PWM
46 }

Programa 3.17: pwm.h


1 #i f n d e f PWM_H
2 #define PWM_H
3 void SetaPWM1 ( unsigned char porcento ) ;
4 void SetaPWM2 ( unsigned char porcento ) ;
5 void SetaFreqPWM ( unsigned i n t freq ) ;
6 void InicializaPWM ( void ) ;
7 #endif //PWM_H

Notas de Aula ELT024 - Programação para Sistemas Embarcados


86 Programação dos Periféricos

Programa 3.18: Exemplo de uso da biblioteca das saídas PWM


1 #include " config .h"
2 #include " basico .h"
3 #include " pwm .h"
4 #include " adc .h"
5 // i n i c i o do programa
6 void main ( void )
7 {
8 i n t temp ;
9 InicializaPWM ( ) ;
10 InicializaAD ( ) ;
11 for ( ; ; ) {
12 temp = LeValorAD ( ) ;
13 // a j u s t a n d o a f r e q u ê n c i a de acordo com e n t r a d a a n a l ó g i c a
14 SetaFreqPWM ( temp ) ;
15 // a j u s t a n d o o duty−c y c l e para 50%
16 SetaPWM1 ( 5 0 ) ;
17 }
18 }

Notas de Aula ELT024 - Programação para Sistemas Embarcados


87 Programação dos Periféricos

Programa 3.19: timer.c


1 #include " basico .h"
2 #include " timer .h"
3 char FimTimer ( void )
4 {
5 return BitTst ( INTCON , 2 ) ;
6 }
7 void AguardaTimer ( void )
8 {
9 while ( ! BitTst ( INTCON , 2 ) ) ;
10 }

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 }

25 void InicializaTimer ( void )


26 {
27 T0CON = 0 b00001000 ; // c o n f i g u r a t i m e r 0 sem p r e s c a l e r
28 BitSet ( T0CON , 7 ) ; // l i g a o t i m e r 0
29 }

. 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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


88 Programação dos Periféricos

Programa 3.20: timer.h


1 #i f n d e f TIMER_H
2 #define TIMER_H
3 char FimTimer ( void ) ;
4 void AguardaTimer ( void ) ;
5 // tempo em micro s e g u n d o s
6 void ResetaTimer ( unsigned i n t tempo ) ;
7 void InicializaTimer ( void ) ;
8 #endif //TIMER_H

Programa 3.21: Exemplo de uso da biblioteca de um temporizador


1 // i n i c i o do programa
2 void main ( void )
3 {
4 unsigned i n t cont ;
5 TRISD=0x00 ;
6 InicializaTimer ( ) ;
7 ResetaTimer ( 1 0 0 0 0 ) ;
8 cont = 0 ;
9 for ( ; ; )
10 {
11 AguardaTimer ( ) ;
12 ResetaTimer ( 1 0 0 0 0 ) ;
13 cont ++;
14 i f ( cont >= 5 0 ) // 50 ∗ 10ms = 0 ,5 s
15 {
16 PORTD ^= 0 xFF ;
17 cont = 0 ;
18 }
19 }
20 }

Notas de Aula ELT024 - Programação para Sistemas Embarcados


89 Programação dos Periféricos

. Reprodução de Sons


Se ligarmos à saída PWM um auto-falante é possível reproduzir sons. Conhecendo a frequência
de cada uma das notas musicais e a duração destas é possível reproduzir uma música. Para
reproduzir o tempo com uma precisão melhor podemos utilizar o TIMER0 como unidade de
tempo.
Conforme visto na seção ., o PWM utilizado na placa consegue reproduzir as frequências
audíveis a partir de 488,3Hz. Por isso escolhemos começar a escala musical a partir do C5 (Dó
Tenor) que possui a frequência de 523. A segunda escala possui o dobro da frequência (uma
oitava acima). Para reproduzir a ausência de som escolhemos a frequência de 125.000 Hz11 , que
é inaudível. Isto simplifica o programa.

Programa 3.22: Reprodução de sons


1 #include " config .h"
2 #include " basico .h" 39 // i n i c i o do programa
3 #include " pwm .h" 40 void main ( void )
4 #include " timer .h" 41 {
42 unsigned char cont =0;
6 // f r e q u ê n c i a das 43 unsigned char pos =0;
7 // n o t a s m u s i c a i s 44 // I m p e r i a l March (SW E p i s o d e V)
8 #define C 523 45 unsigned char tempo [ ] = { 5 0 , 1 0 , 5 0 , 1 0 , 5 0 , 1 0 , ←֓
9 #define CS 554 5 0 , 5 , 2 5 , 5 , 5 0 , 5 , 5 0 , 5 , 2 5 , 5 , 5 0 , 5 0 , 5 0 , ←֓
10 #define D 587 1 0 , 5 0 , 1 0 , 5 0 , 1 0 , 5 0 , 5 , 2 5 , 5 , 5 0 , 5 , 5 0 , ←֓
11 #define DS 622 5 , 2 5 , 5 , 5 0 , 5 0 , 1 0 0 , 5 , 2 5 , 5 , 2 5 , 1 0 , 1 0 0 , ←֓
12 #define E 659 5 , 50 , 5 , 25 , 2 , 10 , 2 , 10 , 2 , 100 , 250};
13 #define F 698 46 unsigned i n t notas [ ] = {G , v , G , v , G , v , E , v , B , ←֓
14 #define FS 740 v , G , v , E , v , B , v , G , v , D2S , v , D2S , v , ←֓
15 #define G 784 D2S , v , E2 , v , B , v , FS , v , E , v , B , v , G , v , ←֓
16 #define GS 830 G2S , v , G , v , G , v , G2S , v , G2 , v , F2S , v , F2 , ←֓
17 #define A 880 v , E2 , v , F2S , v } ;
18 #define AS 932 47 InicializaPWM ( ) ;
19 #define B 987 48 InicializaTimer ( ) ;
49 SetaFreqPWM ( notas [ 0 ] ) ;
50 SetaPWM1 ( 5 0 ) ; // g a r a n t e duty−c y c l e de 50%
22 // segunda o i t a v a 51 for ( ; ; )
23 #define C2 C∗2 52 {
24 #define C2S CS∗2 53 AguardaTimer ( ) ;
25 #define D2 D∗2 54 ResetaTimer ( 1 0 0 0 0 ) ;
26 #define D2S DS∗2 55 cont ++;
27 #define E2 E∗2 56 i f ( cont >= tempo [ pos ] )
28 #define F2 F∗2 57 {
29 #define F2S FS∗2 58 pos++;
30 #define G2 G∗2 59 SetaFreqPWM ( notas [ pos ] ) ;
31 #define G2S GS∗2 60 SetaPWM1 ( 5 0 ) ;
32 #define A2 A∗2 61 cont =0;
33 #define A2S AS∗2 62 }
34 #define B2 B∗2 63 }
64 }

37 //sem som
38 #define v 125000

11
Esta é a máxima frequência possível para o PWM operado com prescaler de 16x.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


90 Programação dos Periféricos

. 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.

• Conversor AD: quando o resultado da conversão estiver disponível para leitura.

• Porta B: quando algum dos bits configurados como entrada altera seu valor.

• Timer 0: quando acontece overflow em seu contador

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.

void NomeDaFuncao ( void ) interrupt 1


{
// c ó d i g 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.

void NomeDaFuncao ( void )


{
// c ó d i g 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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


91 Programação dos Periféricos

Programa 3.23: Fontes de Interrupção


1 void Interrupcao ( void ) interrupt 1
2 {
3 // não é n e c e s s á r i o u t i l i z a r t o d o s os i f ' s apenas a q u e l e s
4 // das i n t e r r u p ç õ e s d e s e j a d a s
5 i f ( BitTst ( PIR1 , 0 ) ) { /∗ c ó d i g o ∗/ } // F l a g de o v e r f l o w do TIMER1
6 i f ( BitTst ( PIR1 , 1 ) ) { /∗ c ó d i g o ∗/ } // F l a g de comparação do TIMER2 com PR2
7 i f ( BitTst ( PIR1 , 2 ) ) { /∗ c ó d i g o ∗/ } // F l a g de comparação do CCP1
8 i f ( BitTst ( PIR1 , 3 ) ) { /∗ c ó d i g o ∗/ } // F l a g de fim de o p e r a ç ã o na p o r t a ←֓
paralela
9 i f ( BitTst ( PIR1 , 4 ) ) { /∗ c ó d i g o ∗/ } // F l a g de fim de t r a n s m i s s ã o da S e r i a l
10 i f ( BitTst ( PIR1 , 5 ) ) { /∗ c ó d i g o ∗/ } // F l a g de r e c e p ç ã o da S e r i a l
11 i f ( BitTst ( PIR1 , 6 ) ) { /∗ c ó d i g o ∗/ } // F l a g de fim de c o n v e r s ã o do AD
12 i f ( BitTst ( PIR1 , 7 ) ) { /∗ c ó d i g o ∗/ } // F l a g de l e i t u r a / e s c r i t a da p o r t a ←֓
paralela
13 i f ( BitTst ( PIR2 , 0 ) ) { /∗ c ó d i g o ∗/ } // F l a g de comparação do CCP2
14 i f ( BitTst ( PIR2 , 1 ) ) { /∗ c ó d i g o ∗/ } // F l a g de o v e r f l o w do TIMER3
15 i f ( BitTst ( PIR2 , 2 ) ) { /∗ c ó d i g o ∗/ } // F l a g de c o n d i ç ã o de Tensão A l t a / Baixa
16 i f ( BitTst ( PIR2 , 3 ) ) { /∗ c ó d i g o ∗/ } // F l a g de d e t e c ç ã o de c o l i s ã o no ←֓
barramento
17 i f ( BitTst ( PIR2 , 4 ) ) { /∗ c ó d i g o ∗/ } // F l a g de fim e s c r i t a na memoria f l a s h
18 i f ( BitTst ( PIR2 , 5 ) ) { /∗ c ó d i g o ∗/ } // F l a g de i n t e r r u p ç ã o da USB
19 i f ( BitTst ( PIR2 , 6 ) ) { /∗ c ó d i g o ∗/ } // F l a g de mudança na e n t r a d a de ←֓
comparação
20 i f ( BitTst ( PIR2 , 7 ) ) { /∗ c ó d i g o ∗/ } // F l a g de f a l h a no o s c i l a d o r
21 i f ( BitTst ( INTCON , 0 ) ) { /∗ c ó d i g o ∗/ } // F l a g de mudança na PORTA B
22 i f ( BitTst ( INTCON , 1 ) ) { /∗ c ó d i g o ∗/ } // F l a g de i n t e r r u p ç ã o e x t e r n a INT0
23 i f ( BitTst ( INTCON , 2 ) ) { /∗ c ó d i g o ∗/ } // F l a g de o v e r f l o w no TIMER0
24 i f ( BitTst ( INTCON3 , 0 ) ) { /∗ c ó d i g o ∗/ } // F l a g de i n t e r r u p ç ã o e x t e r n a INT1
25 i f ( BitTst ( INTCON3 , 1 ) ) { /∗ c ó d i g o ∗/ } // F l a g de i n t e r r u p ç ã o e x t e r n a INT2
26 }

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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


92 Programação dos Periféricos

Programa 3.24: Tratamento das interrupções


1 s t a t i c unsigned in t ADvalor ;
2 s t a t i c unsigned char Serial ;
3 s t a t i c unsigned in t Tecla ;
4 void Interrupcao ( void ) interrupt 1
5 {
6 char i , j ;
7 i f ( BitTst ( PIR1 , 6 ) ) //AD : fim de c o n v e r s ã o
8 {
9 ADvalor = ADRESH ; // l ê o r e s u l t a d o
10 ADvalor <<= 8 ;
11 ADvalor += ADRESL ;
12 BitClr ( PIR1 , 6 ) ; // l i m p a a f l a g
13 }
14 i f ( BitTst ( PIR1 , 5 ) ) // S e r i a l : r e c e p ç ã o
15 {
16 // B i t C l r ( PIR1 , 5 ) ;
17 Serial = RCREG ; // l i m p a s o z i n h o quando l ê
18 }
19 i f ( BitTst ( INTCON , 0 ) ) //PORTA B : mudou v a l o r
20 {
21 f o r ( i = 0 ; i < 4 ; i++){
22 PORTB |= 0 xFF ;
23 BitClr ( PORTB , ( i ) ) ;
24 f o r ( j =0;j <10; j++) ;
25 f o r ( j = 0 ; j < 4 ; j++){
26 i f ( ! BitTst ( PORTB , j+4) ) {
27 BitSet ( Tecla , ( i ∗ 4 )+j ) ;
28 } else {
29 BitClr ( Tecla , ( i ∗ 4 )+j ) ;
30 }
31 }
32 }
33 PORTB = 0 x00 ;
34 BitClr ( INTCON , 0 ) ;
35 }
36 i f ( BitTst ( INTCON , 2 ) ) //TIMER0 : O v e r f l o w
37 {
38 // tempo maximo de i n t e r r u p ç ã o do t i m e r 0
39 BitClr ( INTCON , 2 ) ; // l i m p a a f l a g
40 TMR0H = 0 x00 ; // r e i n i c i a c o n t a d o r de tempo
41 TMR0L = 0 x00 ; // r e i n i c i a c o n t a d o r de tempo
42 ADCON0 |= 0 b00000010 ; // i n i c i a c o n v e r s ã o
43 }
44 }

Notas de Aula ELT024 - Programação para Sistemas Embarcados


93 Programação dos Periféricos

Programa 3.25: Inicialização do sistema com interrupções


1 void main ( void )
2 {
3 unsigned i n t i , temp , teclanova =0;
4 // c o n f i g u r a n d o t o d a s as i n t e r r u p ç õ e s
5 TRISD = 0 x00 ;
6 TRISB = 0 xF0 ; //mantém os 4 ú l t i m o s b i t s como e n t r a d a
7 PORTB = 0 x00 ; //mantém l i g a d a s as 4 c o l u n a s
8 InicializaSerial ( ) ;
9 InicializaDisplays ( ) ;
10 InicializaLCD ( ) ;
11 InicializaAD ( ) ;
12 InicializaTimer ( ) ;
13 BitClr ( RCON , 7 ) ; // d e s a b i l i t a IPEN ( modo de c o m p a t i b i l i d a d e )
14 BitSet ( PIE1 , 6 ) ; // l i g a a i n t e r r u p ç ã o para o AD
15 BitSet ( PIE1 , 5 ) ; // l i g a a i n t e r r u p ç ã o para a r e c e p ç ã o na s e r i a l
16 BitSet ( INTCON , 5 ) ; // l i g a a i n t e r r u p ç ã o para o t i m e r 0
17 BitSet ( INTCON , 3 ) ; // l i g a a i n t e r r u p ç ã o para a p o r t a B
18 BitSet ( INTCON , 7 ) ; // h a b i l i t a t o d a s as i n t e r r u p ç õ e s g l o b a i s
19 BitSet ( INTCON , 6 ) ; // h a b i l i t a t o d a s as i n t e r r u p ç õ e s de p e r i f é r i c o s
20 for ( ; ; ) {
21 AtualizaDisplay ( ) ;
22 temp = ADvalor ;
23 temp %=10000;
24 MudaDigito ( temp / 1 0 0 0 , 3 ) ;
25 temp %=1000;
26 MudaDigito ( temp / 1 0 0 , 2 ) ;
27 temp %=100;
28 MudaDigito ( temp / 1 0 , 1 ) ;
29 temp %=10;
30 MudaDigito ( temp , 0 ) ;
31 i f ( teclanova != Tecla ) {
32 teclanova = Tecla ;
33 f o r ( i =0;i <16; i++){
34 i f ( BitTst ( Tecla , i ) ) {
35 EnviaDados ( i+48) ;
36 }
37 }
38 }
39 f o r ( i = 0 ; i < 1 0 0 0 ; i++) ;
40 }
41 }

Notas de Aula ELT024 - Programação para Sistemas Embarcados


94 Programação dos Periféricos

Programa 3.26: Inicialização do sistema com interrupções


1 #define CLRWTD( ) _asm CLRWDT _endasm

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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


Capítulo 4

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

No desenvolvimento de um sistema de maior porte, é importante definir o tipo de arquitetura


que iremos utilizar. Neste capítulo apresentamos três abordagens possíveis. A escolha deve ser
baseada no tipo de dispositivo a ser desenvolvido, na complexidade do sistema, na possibilidade
de gerar subprodutos e dos requisitos de tempo.

95
96 Arquitetura de desenvolvimento de software

Programa 4.1: Exemplo de arquitetura single-loop


1 // s e ç ã o de i n c l u d e s
2 #include " basico .h"
3 #include " config .h"
4 #include " teclado .h"
5 #include " disp7seg .h"
6 // f u n ç ã o p r i n c i p a l
7 void main ( void )
8 {
9 // d e c l a r a ç ã o das v a r i á v e i s
10 i n t ia , ib , ic ;
11 f l o a t fa , fb , fc ;
12 // i n i c i a l i z a ç ã o dos p e r i f é r i c o s
13 InicializaTeclado ( ) ;
14 InicializaDisplays ( ) ;
15 // l o o p p r i n c i p a l
16 for ( ; ; )
17 {
18 // chamada das t a r e f a s
19 DebounceTeclas ( ) ;
20 ia = LerTeclas ( ) ;
21 ImprimeDisplay ( ia ) ; // tem que s e r e x e c u t a d o p e l o menos a cada 10(ms)
22 }
23 }

Programa 4.2: Problema na sincronia de tempo para o single-loop


1 // l o o p p r i n c i p a l
2 for ( ; ; )
3 {
4 // chamada das t a r e f a s
5 DebounceTeclas ( ) ;
6 ia = LerTeclas ( ) ;
7 ImprimeDisplay ( ia ) ; // tem que s e r e x e c u t a d o p e l o menos a cada 10(ms)
8 ic = RecebeSerial ( ) ;
9 fa = 2 . 0 ∗ ic / 3 . 1 4 ;
10 EnviaSerial ( fa & 0 x00FF ) ;
11 EnviaSerial ( fa >> 8 ) ;
12 }

. One single loop


“1 Infinite Loop, Cupertino, CA 95014.” - Endereço da Apple

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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


97 Arquitetura de desenvolvimento de software

Programa 4.3: Exemplo de sistema Interrupt-driven


1 i n t ia ;
2 // t r a t a m e n t o do t e c l a d o v i a i n t e r r u p ç ã o
3 void Interrupcao ( void ) interrupt 1
4 {
5 i f ( BitTst ( INTCON , 0 ) ) //PORTA B : mudou v a l o r
6 {
7 ia = LerTeclas ( ) ;
8 }
9 }

11 void main ( void )


12 {
13 // i n i c i a l i z a ç ã o dos p e r i f é r i c o s
14 InicializaTeclado ( ) ;
15 InicializaDisplay ( ) ;
16 // i n i c i a l i z a ç ã o da i n t e r r u p ç ã o
17 BitClr ( RCON , 7 ) ; // d e s a b i l i t a IPEN ( modo de c o m p a t i b i l i d a d e )
18 BitSet ( INTCON , 3 ) ; // l i g a a i n t e r r u p ç ã o para a p o r t a B
19 BitSet ( INTCON , 7 ) ; // h a b i l i t a t o d a s as i n t e r r u p ç õ e s g l o b a i s
20 BitSet ( INTCON , 6 ) ; // h a b i l i t a t o d a s as i n t e r r u p ç õ e s de p e r i f é r i c o s
21 f o r ( ; ; ) // l o o p p r i n c i p a l
22 {
23 // chamada das t a r e f a s
24 ImprimeDisplay ( ia ) ;
25 }
26 }

. Interrupt control system


Uma parte dos desenvolvedores de sistemas embarcados, que possuem restrições de tempo de
atendimento mais rigorosos, optam por garantir estas restrições através de interrupções.
Na maioria dos sistemas microcontroladores, as interrupções são atendidas num tempo muito
curto, cerca de alguns ciclos de instrução, o que para a maioria dos sistemas é suficiente. Deve-se,
entretanto, tomar cuidado com a quantidade de periféricos que geram interrupções e a prioridade
dada a cada um deles.
Outra abordagem muito utilizada é a geração de uma interrupção com tempo fixo, por exem-
plo a cada 5ms.
A grande vantagem da abordagem citada é que a inserção de mais código dentro do loop
principal não atrapalha a velocidade com que o display é atualizado, que está fixo em 5(ms).

Notas de Aula ELT024 - Programação para Sistemas Embarcados


98 Arquitetura de desenvolvimento de software

Programa 4.4: Exemplo de sistema Interrupt-driven com base de tempo


1 i n t ia ;
2 // e x i t e apenas uma f o n t e de i n t e r r u p ç ã o : Timer 0
3 void Interrupcao ( void ) interrupt 1
4 {
5 ResetaTimer ( 5 0 0 0 ) ; // r e i n i c i a t i m e r para próxima i n t e r r u p ç ã o
6 ImprimeDisplay ( ia ) ;
7 BitSet ( INTCON , 5 ) ; // r e l i g a a i n t e r r u p ç ã o para o t i m e r 0
8 }

10 void main ( void )


11 {
12 // i n i c i a l i z a ç ã o dos p e r i f é r i c o s
13 InicializaTeclado ( ) ;
14 InicializaDisplay ( ) ;
15 InicializaTimer ( ) ;
16 // i n i c i a l i z a ç ã o da i n t e r r u p ç ã o
17 BitClr ( RCON , 7 ) ; // d e s a b i l i t a IPEN ( modo de c o m p a t i b i l i d a d e )
18 BitSet ( INTCON , 5 ) ; // l i g a a i n t e r r u p ç ã o para o t i m e r 0
19 BitSet ( INTCON , 7 ) ; // h a b i l i t a t o d a s as i n t e r r u p ç õ e s g l o b a i s
20 BitSet ( INTCON , 6 ) ; // h a b i l i t a t o d a s as i n t e r r u p ç õ e s de p e r i f é r i c o s
21 ResetaTimer ( 5 0 0 0 ) ;
22 f o r ( ; ; ) // l o o p p r i n c i p a l
23 {
24 DebounceTeclas ( ) :
25 ia = LerTeclas ( ) ;
26 }
27 }

Notas de Aula ELT024 - Programação para Sistemas Embarcados


99 Arquitetura de desenvolvimento de software

. Cooperative multitasking


Em computação, multitarefa ou multitasking é um processo pelo qual diferentes tarefas compar-
tilham um mesmo recurso, seja ele memória, processamento ou qualquer periférico disponível.
Uma maneira de realizar este compartilhamento é através de uma divisão do tempo: a tarefa
A possui um intervalo ao final do qual deve ceder os recursos para a tarefa B. Quando a mudança
de tarefa é feita pela própria tarefa, o sistema é dito cooperativo. Quando existe um sistema
externo que realiza essa troca o sistema é denominado preemptivo.
Se a mudança de tarefas for extremamente rápida o efeito resultante, para o ser humano,
é de que todas as tarefas estão sendo executadas simultaneamente. Uma das maneiras de se
obter este tipo de operação é através da criação de uma máquina de estados, como mostrado na
Figura 4.1.

Inicio

Ler Atualiza
Teclado Display

Atualiza Escreve
Display Serial

Atualiza
Ler Serial Display

Figura 4.1: Exemplo de máquina de estados

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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


100 Arquitetura de desenvolvimento de software

Programa 4.5: Exemplo de cooperative multitasking


1 void main ( void )
2 {
3 // d e c l a r a ç ã o das v a r i á v e i s
4 char slot ;
5 // f u n ç õ e s de i n i c i a l i z a ç ã o
6 InicializaSerial ( ) ;
7 InicializaTeclado ( ) ;
8 InicializaDisplay ( ) ;
9 f o r ( ; ; ) { // i n i c i o do l o o p i n f i n i t o
10 // ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ i n í c i o do top−s l o t ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗
11 // ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ fim do top−s l o t ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗

13 // ∗∗∗∗∗∗∗∗∗∗∗ i n í c i o da máquina de e s t a d o ∗∗∗∗∗∗∗∗∗∗∗∗


14 switch ( slot ) {
15 case 0 :
16 LeTeclado ( ) ;
17 slot = 1 ;
18 break ;
19 case 1 :
20 AtualizaDisplay ( ) ;
21 slot = 2 ;
22 break ;
23 case 2 :
24 RecebeSerial ( ) ;
25 slot = 3 ;
26 break ;
27 case 3 :
28 AtualizaDisplay ( ) ;
29 slot = 4 ;
30 break ;
31 case 4 :
32 EnviaSerial ( ) ;
33 slot = 5 ;
34 break ;
35 case 5 :
36 AtualizaDisplay ( ) ;
37 slot = 0 ;
38 break ;
39 default :
40 slot = 0 ;
41 break ;
42 }
43 // ∗∗∗∗∗∗∗∗∗∗∗∗ fim da máquina de e s t a d o ∗∗∗∗∗∗∗∗∗∗∗∗∗∗

45 // ∗∗∗∗∗∗∗∗∗∗∗∗∗∗ i n í c i o do bottom−s l o t ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗


46 // ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ fim do bottom−s l o t ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗
47 } // fim l o o p i n f i n i t o ( ! ? )
48 }

Notas de Aula ELT024 - Programação para Sistemas Embarcados


101 Arquitetura de desenvolvimento de software

Programa 4.6: Exemplo de cooperative multitasking com uso do top slot


1 void main ( void )
2 {
3 // d e c l a r a ç ã o das v a r i á v e i s
4 char slot ;
5 // f u n ç õ e s de i n i c i a l i z a ç ã o
6 InicializaSerial ( ) ;
7 InicializaTeclado ( ) ;
8 InicializaDisplay ( ) ;
9 f o r ( ; ; ) // i n i c i o do l o o p i n f i n i t o
10 {
11 // ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ i n í c i o do top−s l o t ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗
12 AtualizaDisplay ( ) ;
13 // ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ fim do top−s l o t ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗

16 // ∗∗∗∗∗∗∗∗∗∗∗ i n í c i o da máquina de e s t a d o ∗∗∗∗∗∗∗∗∗∗∗∗


17 switch ( slot )
18 {
19 case 0 :
20 LeTeclado ( ) ;
21 slot = 1 ;
22 break ;
23 case 1 :
24 RecebeSerial ( ) ;
25 slot = 2 ;
26 break ;
27 case 2 :
28 EnviaSerial ( ) ;
29 slot = 0 ;
30 break ;
31 default :
32 slot = 0 ;
33 break ;
34 }
35 // ∗∗∗∗∗∗∗∗∗∗∗∗ fim da máquina de e s t a d o ∗∗∗∗∗∗∗∗∗∗∗∗∗∗

38 // ∗∗∗∗∗∗∗∗∗∗∗∗∗∗ i n í c i o do bottom−s l o t ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗

40 // ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ fim do bottom−s l o t ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗

42 } // fim l o o p i n f i n i t o ( ! ? )
43 }

Notas de Aula ELT024 - Programação para Sistemas Embarcados


102 Arquitetura de desenvolvimento de software

Programa 4.7: Exemplo de sistema Cooperative-multitasking com slot temporizado


1 void main ( void )
2 {
3 // d e c l a r a ç ã o das v a r i á v e i s
4 char slot ;
5 // f u n ç õ e s de i n i c i a l i z a ç ã o
6 InicializaDisplay ( ) ;
7 InicializaTimer ( ) ;
8 f o r ( ; ; ) // i n i c i o do l o o p i n f i n i t o
9 {
10 // ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ i n í c i o do top−s l o t ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗
11 ResetaTimer ( 5 0 0 0 ) ; // 5 ms para cada s l o t
12 AtualizaDisplay ( ) ;
13 // ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ fim do top−s l o t ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗

16 // ∗∗∗∗∗∗∗∗∗∗∗ i n í c i o da máquina de e s t a d o ∗∗∗∗∗∗∗∗∗∗∗∗


17 switch ( slot )
18 {
19 case 0 :
20 LeTeclado ( ) ;
21 slot = 1 ;
22 break ;
23 case 1 :
24 RecebeSerial ( ) ;
25 slot = 2 ;
26 break ;
27 case 2 :
28 EnviaSerial ( ) ;
29 slot = 0 ;
30 break ;
31 default :
32 slot = 0 ;
33 break ;
34 }
35 // ∗∗∗∗∗∗∗∗∗∗∗∗ fim da máquina de e s t a d o ∗∗∗∗∗∗∗∗∗∗∗∗∗∗

38 // ∗∗∗∗∗∗∗∗∗∗∗∗∗∗ i n í c i o do bottom−s l o t ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗

40 // ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗ fim do bottom−s l o t ∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗


41 AguardaTimer ( ) ;
42 } // fim l o o p i n f i n i t o ( ! ? )
43 }

Fixação de tempo para execução dos slots


Do modo apresentado até agora, assim que uma função termina, o sistema automaticamente
passa para a próxima tarefa. Uma característica desejada é que estas funções possuam um
tempo determinado para funcionar. Deste modo, todo o sistema se torna mais previsível.
A maneira mais simples de realizar este procedimento é criar uma rotina de tempo. Toda vez
que um slot terminar, o sistema ficará aguardando o tempo escolhido para reiniciar o sistema.
No exemplo apresentado é inserida a função AguardaTimer() no bottom-slot de modo que a
próxima função só executará quando passar os 5 (ms).
Como este é um modo simples de implementar um sistema multitarefa podemos notar que
se a função ultrapassar 5 (ms) todo o cronograma será afetado. É necessário então garantir que
todo e cada slot será executado em menos de 5 (ms). Isto deve ser feito através de testes de
bancada.
Na Figura 4.2 está um exemplo de como um sistema com 3 slots se comporta ao longo do

Notas de Aula ELT024 - Programação para Sistemas Embarcados


103 Arquitetura de desenvolvimento de software

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

Figura 4.2: Exemplo da mudança de slots no tempo

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).

Utilização do tempo livre para interrupções


Conforme visto anteriormente, dependendo do tempo escolhido para o slot e do “tamanho” da
função, podem existir espaços vagos na linha de tempo do processador. A Figura 4.3 apresenta
uma linha de tempo de um sistema que possui apenas 1 slot. Já a Figura 4.4 demonstra o mesmo
sistema sendo interrompido através de interrupções assíncronas.

Top 1 1 1
S.1 3 3 3
Bottom 1 1 1
"vago" 3 3 3

Figura 4.3: Linha de tempo de um sistema com 1 slot

Top 1 1
S.1 1 2 3 3
Bottom 1 1 1
"vago" 2 2 2
Interr. 1 1 1

Figura 4.4: Comportamento da linha de tempo com interrupções

Notas de Aula ELT024 - Programação para Sistemas Embarcados


104 Arquitetura de desenvolvimento de software

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).

Notas de Aula ELT024 - Programação para Sistemas Embarcados


Capítulo 5

Anexos

105
106 Anexos

Programa 5.1: config.h


1 // para o c o m p i l a d o r SDCC + GPUtils
2 #i f n d e f CONFIG_H
3 #define CONFIG_H
4 code char at 0 x300000 CONFIG1L = 0 x01 ; // Pll desligado
5 code char at 0 x300001 CONFIG1H = 0 x0C ; // 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
6 code char at 0 x300003 CONFIG2H = 0 x00 ; // Watchdog c o n t r o l a d o por s o f t w a r e
7 code char at 0 x300006 CONFIG4L = 0 x00 ; // Sem programação em b a i x a t e n s ã o
8 #endif //CONFIG_H

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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


107 Anexos

Programa 5.2: basico.h


1 // f u n ç ã o para l i m p a r o watchdog
2 #define CLRWTD( ) _asm CLRWDT _endasm
3 // f u n ç õ e s de b i t
4 #define B i t S e t ( arg , b i t ) ( ( a r g ) |= (1<< b i t ) )
5 #define B i t C l r ( arg , b i t ) ( ( a r g ) &= ~(1<< b i t ) )
6 #define B i t F l p ( arg , b i t ) ( ( a r g ) ^= (1<< b i t ) )
7 #define B i t T s t ( arg , b i t ) ( ( a r g ) & (1<< b i t ) )
8 // d e f i n e ' s para r e g i s t r o s e s p e c i a i s
9 #define PORTA ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF80 )
10 #define PORTB ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF81 )
11 #define PORTC ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF82 )
12 #define PORTD ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF83 )
13 #define PORTE ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF84 )
14 #define TRISA ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF92 )
15 #define TRISB ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF93 )
16 #define TRISC ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF94 )
17 #define TRISD ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF95 )
18 #define TRISE ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF96 )
19 #define INTCON ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xFF2 )
20 #define INTCON2 ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xFF1 )
21 #define PIE1 ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF9D)
22 #define PIR1 ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF9E )
23 #define TMR0L ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xFD6)
24 #define TMR0H ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xFD7)
25 #define T0CON ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xFD5)
26 #define SPPCON ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF65 )
27 #define SPPCFG ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xF63 )
28 #define ADCON2 ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xFC0 )
29 #define ADCON1 ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xFC1 )
30 #define ADCON0 ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xFC2 )
31 #define ADRESL ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xFC3 )
32 #define ADRESH ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xFC4 )
33 #define RCSTA ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xFAB)
34 #define TXSTA ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xFAC)
35 #define TXREG ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xFAD)
36 #define RCREG ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xFAE)
37 #define SPBRG ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xFAF)
38 #define SPBRGH ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xFB0 )
39 #define BAUDCON ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xFB8 )
40 #define RCON ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xFD0)
41 #define WDTCON ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xFD1)
42 #define T2CON ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xFCA)
43 #define PR2 ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xFCB)
44 #define CCP2CON ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xFBA)
45 #define CCPR2L ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xFBB)
46 #define CCP1CON ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xFBD)
47 #define CCPR1L ( ∗ ( v o l a t i l e n e a r unsigned char ∗ ) 0xFBE)

. 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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados


108 Anexos

. Instalar gravadores/depuradores de PIC em sistemas x64


Os passos a seguir devem ser seguidos para instalar os device drivers corretamente em sistemas
operacionais de 64 bits. Atualmente apenas os seguintes aparelhos são suportados:

• MPLAB REAL ICE in-circuit emulator

• MPLAB ICE 2000 with USB converter

• MPLAB ICD 2 (P/N 10-00397)

• MPLAB ICD 3

• MPLAB PM3 (P/N 10-00398)

• PIC32MX Starter Kit

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)

Notas de Aula ELT024 - Programação para Sistemas Embarcados


109 Anexos

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.

Notas de Aula ELT024 - Programação para Sistemas Embarcados

Você também pode gostar