Вы находитесь на странице: 1из 121

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 Ita jubá,

Minas Gerais,

Brasil

rodrigomax @ unifei.edu.br

13 de Dezembro de 2013

1 Licenciado sobre Criative Commons Attribution-NonCommercial-NoDerivs


Conteúdo
1 Introdução 1
1.1 Linguagem C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Hardware utilizado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 Ambiente de programação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Instalação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Conguração do gravador ICD2 . . . . . . . . . . . . . . . . . . . . . . . 4
Criação de um novo projeto . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.4 Indentação e padrão de escrita . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.5 Comentários . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.6 Arquivos .c e .h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.7 Diretivas de compilação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
#include . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
#dene . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
#ifdef, #ifndef, #else e #endif . . . . . . . . . . . . . . . . . . . . . . . . 12
1.8 Tipos de dados em C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Representação binária e hexadecimal . . . . . . . . . . . . . . . . . . . . . 14
Modicadores de tamanho e sinal . . . . . . . . . . . . . . . . . . . . . . . 15
Modicadores de acesso . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Modicadores de posicionamento . . . . . . . . . . . . . . . . . . . . . . . 17
Modicador de persistência . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.9 Operações aritméticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
1.10 Função main() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.11 Rotinas de tempo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.12 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 ip) . . . . . . . . . . . . . . . . . . . . . . 26
Vericar o estado de um bit (bit test) . . . . . . . . . . . . . . . . . . . . 27
Criando funções através de dene's . . . . . . . . . . . . . . . . . . . . . 28
1.13 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
1.14 Ponteiros e endereços de memória . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

i
2 Arquitetura de microcontroladores 36
2.1 Acesso à memória . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
2.2 Clock e tempo de instrução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
2.3 Esquema elétrico e circuitos importantes . . . . . . . . . . . . . . . . . . . . . . . 41
Multiplexação nos terminais do microcontrolador . . . . . . . . . . . . . . 42
2.4 Registros de conguração do microcontrolador . . . . . . . . . . . . . . . . . . . . 43

3 Programação dos Periféricos 45


3.1 Acesso às portasdo microcontrolador . . . . . . . . . . . . . . . . . . . . . . . . 46
3.2 Conguração dos periféricos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.3 Barramento de Led's . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
3.4 Display de 7 segmentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
Multiplexação de displays . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
Criação da biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
3.5 Leitura de teclas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Debounce por software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Arranjo de leitura por matriz . . . . . . . . . . . . . . . . . . . . . . . . . 60
Criação da biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
3.6 Display LCD 2x16 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Criação da biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
3.7 Comunicação serial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
I2C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
RS 232 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
Criação da biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
3.8 Conversor AD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
Elementos sensores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
Processo de conversão AD . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
Criação da biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
3.9 Saídas PWM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
Criação da biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
3.10 Timer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
3.11 Reprodução de Sons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
3.12 Interrupção . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
3.13 Watchdog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99

4 Arquitetura de desenvolvimento de software 100


4.1 One single loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
4.2 Interrupt control system . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
4.3 Cooperative multitasking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
Fixação de tempo para execução dos slots . . . . . . . . . . . . . . . . . . 107
Utilização do tempo livre para interrupções . . . . . . . . . . . . . . . . . 108

5 Anexos 110
5.1 cong.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
5.2 basico.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
5.3 Instalar gravadores/depuradores de PIC em sistemas x64 . . . . . . . . . . . . . . 113

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 Conguração das ferramentas de compilação . . . . . . . . . . . . . . . . . . . . . 4
1.4 Instalação do ICD2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.5 Resumo das congurações do ICD2 no MPLAB . . . . . . . . . . . . . . . . . . . 6
1.6 Pedido de atualização do rmware 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 innito 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 PIC 18f4550 . . . . . . . . . . . . . . . . . . 39
2.5 Esquema elétrico: Microcontrolador PIC 18f4550 . . . . . . . . . . . . . . . . . . 41
2.6 Registros de conguração do microcontrolador PIC 18f4550 . . . . . . . . . . . . 43

3.1 Registros de conguraçã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 ltro 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 . . . . . . . . . . . . . . . . . . . . . . . 64
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 Barramento I2C com vários dispositivos . . . . . . . . . . . . . . . . . . . . . . . 73
3.17 Conexão com coletor aberto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
3.18 Envio de dados para o HT1380 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
3.19 Byte de comando do HT1380 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
3.20 Registros internos do HT1380 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
3.21 Sinal serializado para transmissão em RS232 . . . . . . . . . . . . . . . . . . . . . 77
3.22 Lâmpada incandescente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
3.23 Potenciômetro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
3.24 Potenciômetro como divisor de tensão . . . . . . . . . . . . . . . . . . . . . . . . 83
3.25 Circuito integrado LM35 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83

iii
3.26 Diagrama de blocos do LM35 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
3.27 Denição de faixa de valores para AD de 2 bits . . . . . . . . . . . . . . . . . . . 85
3.28 Conversor analógico digital de 2 bits . . . . . . . . . . . . . . . . . . . . . . . . . 85
3.29 Sinais PWM com variação do duty cycle . . . . . . . . . . . . . . . . . . . . . . . 88

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


4.2 Exemplo da mudança de slots no tempo . . . . . . . . . . . . . . . . . . . . . . . 108
4.3 Linha de tempo de um sistema com 1 slot . . . . . . . . . . . . . . . . . . . . . . 108
4.4 Comportamento da linha de tempo com interrupções . . . . . . . . . . . . . . . . 108

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 dene . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
1.7 Operação bit clear com dene . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
1.8 Operação bit ip com dene . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
1.9 Operação bit test com dene . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 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 conguraçã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 . . . . . . . . . . . . . . . 78
3.7 Faixa de frequências máximas e mínimas para cada conguração do prescaler . . 89

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 Write RTC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
3.11 Read RTC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
3.12 Rotinas de escrita e leitura no RTC . . . . . . . . . . . . . . . . . . . . . . . . . . 76
3.13 Rotinas de conversão BCD x inteiro . . . . . . . . . . . . . . . . . . . . . . . . . 77
3.14 serial.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
3.15 serial.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
3.16 Exemplo de uso da biblioteca de comunicação serial . . . . . . . . . . . . . . . . . 81
3.17 adc.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
3.18 adc.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
3.19 Exemplo de uso da biblioteca de conversores AD . . . . . . . . . . . . . . . . . . 87
3.20 pwm.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
3.21 pwm.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
3.22 Exemplo de uso da biblioteca das saídas PWM . . . . . . . . . . . . . . . . . . . 91
3.23 timer.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
3.24 timer.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
3.25 Exemplo de uso da biblioteca de um temporizador . . . . . . . . . . . . . . . . . 93
3.26 Reprodução de sons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
3.27 Fontes de Interrupção . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
3.28 Tratamento das interrupções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
3.29 Inicialização do sistema com interrupções . . . . . . . . . . . . . . . . . . . . . . 98
3.30 Inicialização do sistema com interrupções . . . . . . . . . . . . . . . . . . . . . . 99
4.1 Exemplo de arquitetura single-loop . . . . . . . . . . . . . . . . . . . . . . . . . . 101
4.2 Problema na sincronia de tempo para o single-loop . . . . . . . . . . . . . . . . . 101
4.3 Exemplo de sistema Interrupt-driven . . . . . . . . . . . . . . . . . . . . . . . . . 102
4.4 Exemplo de sistema Interrupt-driven com base de tempo . . . . . . . . . . . . . . 103
4.5 Exemplo de cooperative multitasking . . . . . . . . . . . . . . . . . . . . . . . . . 105
4.6 Exemplo de cooperative multitasking com uso do top slot . . . . . . . . . . . . . 106
4.7 Exemplo de sistema Cooperative-multitasking com slot temporizado . . . . . . . 107
5.1 cong.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
5.2 basico.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112

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ícas, 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 especicamente para o hardware em ques-


tão. Uma opção para se obter articialmente 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.

1.1 Linguagem C

C is quirky, awed, 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%.

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

1.3 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: Conguraçã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.

Conguração do gravador ICD2


Após instalar o MPLAB já é possível fazer a instalação e conguraçã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, certique-se que a versão do rmware é 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 congurar 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 verique ao nal 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 rmware 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 (PIC 18f4550)

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 congurações do ICD2 no MPLAB

Figura 1.6: Pedido de atualização do rmware 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.

1.4 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 identicador como um traço - ou seta
-> para facilitar a identicaçã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 uxo 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 int unsigned int i
{
i;
unsigned int unsigned int temp
;
temp ;
unsigned int unsigned int teclanova
;
teclanova =0; =0;
InicializaSerial ( ) ; InicializaSerial ( ) ;
InicializaDisplays ( ) ; InicializaDisplays ( ) ;
InicializaLCD ( ) ; InicializaLCD ( ) ;
InicializaAD ( ) ; InicializaAD ( ) ;
for (;;) for (;;)
{ {
AtualizaDisplay ( ) ; AtualizaDisplay ( ) ;
if ( teclanova != Tecla ) if ( teclanova != Tecla )
{ {
teclanova = Tecla ; teclanova = Tecla ;
for( i =0; i < 1 6 ; i++) for( i =0; i < 1 6 ; i++)
{
if
{
( BitTst ( Tecla , i) ) if ( BitTst ( Tecla , i) )
{ {
EnviaDados ( i +48) ; EnviaDados ( i +48) ;
} }
} }

for
} }
(i = 0; i < 1000; i++) ; for (i = 0; i < 1000; i++) ;
} }
} }

Podemos notar pelo código anterior que aquele que possui indentação facilita na vericaçã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 denições (utilizados em conjunto com a diretiva #dene) 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:

if ( PORTA == 0 x30 ) { PORTB = 0 x10 ; }

Ou

if ( 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 especica um estilo para ser usado.

1.5 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 nal do comentário.

#include
#define
< s t d i o . h>

int main int


DIST 260
char *
// distancia entre SP e Ita
( argc , argv [ ] )
{
/* esse programa serve para
mostrar como se insere comentários */
printf ( " São Paulo está a %d Km de Itajubá " , DIST ) ;
return 0;
}

1.6 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, denimos 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ícas para tal nalidade.
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 MudaDigito() 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 suciente no stack.

1.7 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
static char temp
// v a r i á v e l usada apenas dentro deste arquivo
2 ;
3
static char valor
// v a r i á v e l que será usada também fora do arquivo
4 ;
5
void MudaDigito char
// f u n ç õ e s usadas dentro e fora do arquivo
6 ( val )
7 {
8 valor = val ;
9
char LerDigito void
}
10 ( )
11 {
12 return valor ;
13 }
14 void InicializaDisplays void ( )
15 {
16 // c ó d i g o da função
17 }
18
void void
// f u n ç ã o usada apenas dentro deste arquivo
19 AtualizaDisplay ( )
20 {
21 // c ó d i g o da função
22 }

Programa 1.2: Resumo do disp7seg.h


1 #ifndef VAR_H
2 define VAR_H
#
3 void MudaDigito char val
char LerDigito void
( );
4
void InicializaDisplays void
( );
5
#endif
( );
6 //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.

#dene
Outra diretiva muito conhecida é a #dene. Geralmente é utilizada para denir uma constante,
mas pode ser utilizada para que o código fonte seja modicado antes de ser compilado.

Original Compilado Resultado na Tela

#define void void


void main void
CONST 1 5
main ( )
( )
{
{ 45
printf ( "%d" , 15 * 3) ;
printf ( "%d" , CONST * 3) ;
}
}

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

void MostraSaidaPadrao ( )
{
#include
#define
< s t d i o . h>
#ifdef PADRAO Serial
char * msg = " SERIAL " ;
void main void
PADRAO S e r i a l

#else ( )
SERIAL
char * msg = " LCD " ;
{
MostraSaidaPadrao ( ) ;
#endif
}
printf ( msg ) ;
}

#include
#define
< s t d i o . h>

void main void


PADRAO LCD
( )
LCD
{
MostraSaidaPadrao ( ) ;
}

Pelo código apresentado percebemos que a mesma função MostraSaidaPadrao(), apresenta re-
sultados diferentes dependendo de como foi denida a opção PADRAO.
Os dene's também ajudam a facilitar a localização dos dispositivos e ajustar as conguraçõ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, é denido um
ponteiro para este endereço e rotulado com o nome PORTD. Denir 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 )
#else
if ( valor > 3 0 )
{
led = 1;
}
else
{
led = 0;
}
#endif //LCD
}

No momento da compilação o pré-compilador irá vericar se a tag LCD foi denida 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 char
);
EnviaSerial ( val ) ;

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

char LerTemperatura ( void


void char val
);
AjustaCalor ( );

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 #ifndef TAG_CONTROLE
2 #define TAG_CONTROLE
3 // t o d o o conteúdo do arquivo vem aqui .

5 #endif //TAG_CONTROLE

O problema é que deste modo é criada uma referência circular sem m: 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 denida. 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á denida 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.

1.8 Tipos de dados em C

19 Jan 2038 at 3:14:07 AM. The end of the world according to Unix
(2
32
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
#ifndef TEMP_H
#define TEMP_H
#include “serial.h”

char LerTemperatura(void);
void AjustaCalor(char val); serial.h
#endif
#ifndef SERIAL_H
#define SERIAL_H
#include “temp.h”

temp.h char LerSerial(void);


void EnviaSerial(char val);
#ifndef 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 -128 à 127


int 16 2 -32.768 à 32.767
oat 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 oat 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
escrevermos 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


direita para a esquerda, 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 .

Modicadores de tamanho e sinal


Um modicador de tipo altera o signicado dos tipos base e produz um novo tipo. Existem
quatro tipos de modicadores, dois para o tamanho (long e short) e dois para sinal (unsigned
e signed). Um tipo declarado com o modicador 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 modo o valor
máximo que podem atingir é menor. Os tipos declarados como unsigned não podem assumir

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


16 Introdução

valores 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 modicador signed.

Modicadores 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 xos. Por
exemplo o código abaixo:

#define * unsigned char *


void main void
X ( ( near ) 0 xF83 )
( )
{
while X X ( != );
}

Quando compilado apresenta o seguinte código em assembler:

// Starting pCode block


S_Teste__main code
_main :
. line 19 // T e s t e . c while (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 nal. Como é possível
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 uxo do programa. Para indicar esta situação
ao programa utilizamos a palavra reservada volatile.

#define * volatile unsigned char *


void main void
X ( ( near ) 0 xF83 )
( )
{
while X X ( != );
}

Gerando o código em assembler descrito abaixo:

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


17 Introdução

// Starting pCode block


S_Teste__main code
_main :
_00105_DS_ :
. line 19 // Teste . c while (X != X) ;
MOVLW 0 x83 // p r i m e i r a parte do endereço
MOVWF r0x00
MOVLW 0 x0f // s e g u n d a parte do endereço
MOVWF r0x01
MOVFF r0x00 , FSR0L
MOVFF r0x01 , FSR0H
MOVFF INDF0 , r0x00 // r e a l i z a primeira leitura
MOVLW 0 x83 // p r i m e i r a parte do endereço
MOVWF r0x01
MOVLW 0 x0f // s e g u n d a parte do endereço
MOVWF r0x02
MOVFF r0x01 , FSR0L
MOVFF r0x02 , FSR0H
MOVFF INDF0 , r0x01 // r e a l i z a segunda leitura
MOVF r0x00 , W
XORWF r0x01 , W
BNZ _00105_DS_ // f a z o teste para igualdade
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 modicador
para indicar que a variável representa um local que apenas pode ser lido e não modicado, por
exemplo uma porta para entrada de dados. Nesta situação é comum utilizar as palavras volatile
e const junto.

#define * volatile const


X ( ( near unsigned char * ) 0 xF83 )

void main void


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

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

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

Modicadores de posicionamento
As variáveis podem ser declaradas utilizando os modicadores near e far. Estes modicadores
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.

Modicador 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 modicador de persistência: static. Com esse
modicador a variável passa a possuir um endereço xo 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
int var16
;
4
long int var32
;
5
float pont16
;
6
double pont32
;
7 ;
8 var08 = var08 + var16 ; // 1
9 var08 = var08 + var08 ; // 2
10 var16 = var08 * var08 ; // 3
11 var32 = var32 / var16 ; // 4
12 var32 = pont32 * var32 ; // 5
13 pont16 = var08 / var16 ; // 6
14 pont16 = pont32 * var32 ; // 7
15 pont16 = 40 / 80; // 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 contador persistente que é

int ContadorPersistente int reseta


// i n c r e m e n t a d o a cada chamada de função
( )
{
static char variável_persistente
if reseta
;
( )
{
variável_persistente = 0;
}
else
{
return ( variável_persistente ++) ;
}
return − 1;
}

1.9 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 corrigido

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 corrigido

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 = (( int ) var8 ) * var8 ; // 3 corrigido

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 utuante é um ponto utuante. 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
utuante haverá perda de informação pois os operandos são inteiros. Para evitar esse problema
é necessário um typecast.

pont16 = (( float ) var8 ) / var16 ; // 6 corrigido

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 oat.
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 / 80.0; // 8 corrigido

Devemos tomar cuidado também com comparações envolvendo números com ponto utuante.

float x
while
= 0.1;
(x != 1.1) {
printf ( "x = %f\n" , x ) ;
x = x + 0.1;
}

O trecho de código acima apresenta um loop innito. Como existem restrições de precisão nos
números de ponto utuante (oat e double) nem todos os números são representados elmente.
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 utuante utilizar maior,
menor ou variações.

float x
while
= 0.1;
(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 innito.

1.10 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 innito 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 entra o código 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 innito. Abaixo estão as duas alternativas mais utilizadas.

void main void ( ) void main void ( )


{
for
{
(;;) while (1)
{ {
// a q u i entra o // a q u i entra o
// c ó d i g o principal // c ó d i g o principal
} }
} }

1.11 Rotinas de tempo

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

É muito comum necessitar que o microcontrolador que um tempo sem fazer nada. Uma maneira
de atingir esse objetivo é utilizar um laço FOR 2 .

unsigned char i;
for i i ( =0; < 10; 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 assembler equivalente à 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 ciclo )
SUBLW 0 x0a // c o l o c a o valor 10 (0 x0a ) no r e g i s t r o W (1 ciclo )
MOVWF r0x00 // muda o valor de W para F (1 ciclo )
_00107_DS_ :
DECFSZ r0x00 , F // d e c r e m e n t a F, se F > 0 executa a próxima linha (1 ciclo )
BRA _00107_DS_ // " p u l a " para o lugar marcado como _00107_DS_ (2 ciclos )

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;
for i i
,
( =0; < 34; i++) // 3 + 34 * (30.003 + 3) = 1.020.207 instruções
{
for j j ( =0; < 100; j++) // 3 + 1 0 0 * (297 + 3) = 30.003 instruções
{
for k k ( =0; < 98; k++) ; // 3 + 98 * (3) = 297 instruções
}
}

O código acima foi projetado para gerar um atraso de tempo de meio segundo. Compilando
e realizando testes práticos podemos conrmar 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
nal, poupando espaço na memória.
A seguir apresentamos um exemplo de função que gera delays com tempo parametrizado.

void delay unsigned int ( DL )


{
unsigned char i j k
while DL −−
, , ;
( ) // e x e c u t a DL vezes .
{
for i i i ( =0; < 34; ++) // 3 + 34 * (30.003 + 3) = 1.020.207 instruções
{
for j j ( =0; < 100; j++) // 3 + 1 0 0 * (297 + 3) = 30.003 instruções
{
for k k ( =0; < 98; k++) ; // 3 + 98 * (3) = 297 instruções
}
}
}
}

1.12 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ó é válido 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 modiquem
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 signica 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 = 12; result = ! A; // result = 243
// A = 0 b 0 0 0 0 1 1 0 0 // result = 0 // A = 0 b 0 0 0 0 1 1 0 0
// 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

char A = 8;
result
//
=
result
A
= 0
& B;
result A B;
char
// A = 0 b 0 0 0 0 1 0 0 0 = &&
// A = 0 b 0 0 0 0 1 0 0 0
B = 5; // result = 1
// B = 0 b 0 0 0 0 0 1 0 1
// B = 0 b 0 0 0 0 0 1 0 1
// 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

char A = 8;
result
// result
= A
= 13
| B;
result A B;
char
// A = 0 b 0 0 0 0 1 0 0 0 = ||
// A = 0 b 0 0 0 0 1 0 0 0
B = 5; // result = 1
// B = 0 b 0 0 0 0 0 1 0 1
// B = 0 b 0 0 0 0 0 1 0 1
// 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

char A = 8;
result
// result
= A
= 13
^ B;

char
// A = 0 b 0 0 0 0 1 0 0 0
// não existe em C // A = 0 b 0 0 0 0 1 0 0 0
B = 5;
// B = 0 b 0 0 0 0 0 1 0 1
// B = 0 b 0 0 0 0 0 1 0 1
// 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 result A
char
= << 2; = >> 2;
A = 8; // result = 32 // result = 2
// A = 0 b 0 0 0 0 1 0 0 0 // A = 0 b 0 0 0 0 1 0 0 0 // A = 0 b 0 0 0 0 1 0 0 0
// 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 xo ou
utuante 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.
Suponha, por exemplo, que um sistema possua 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

#define volatile unsigned char *


// d e f i n e ' s para portas de entrada e saída
PORTD ( * (
#define volatile
near
unsigned char *
) 0 xF83 )
TRISD (*( near ) 0 xF95 )

void main void


// i n í c i o do programa
( )
{
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 porta D como saída
PORTD = 0 x00 ; // l i g a todos os leds ( lógica negativa )
// l i g a o primeiro bit da variável
mascara = 1; // bit = 0 b00000001
// r o t a c i o n a −s e a variável para que o bit 1 chegue na posição desejada
mascara = mascara << 2; // bit = 0 b00000100
// L i g a r o bit 2, desligando o 3o led
PORTD = PORTD | mascara ;

for
// mantém o sistema ligado indefinidamente
(;;) ;
}

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

#define volatile unsigned char *


// d e f i n e ' s para portas de entrada e saída
PORTD ( * (
#define volatile
near
unsigned char *
) 0 xF83 )
TRISD (*( near ) 0 xF95 )

void main void


// i n í c i o do programa
( )
{
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 porta D como saída
PORTD = 0 xFF ; // d e s l i g a todos os leds ( lógica negativa )
// l i g a o primeiro bit da variável
mascara = 1; // mascara = 0 b00000001
// r o t a c i o n a −s e a variável para que o bit 1 chegue na posição desejada
mascara mascara << 2 ; // m a s c a r a =
= 0 b00000100
// i n v e r t e − s e o s v a l o r e s d e c a d a b i t
mascara = ~ mascara ; // m a s c a r a = 0 b11111011
// D e s l i g a o bit 2, ligando o 3o led
PORTD = PORTD & mascara ;

for
// mantém o sistema ligado indefinidamente
(;;) ;
}

É 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 ip)


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

#define volatile unsigned char *


// d e f i n e ' s para portas de entrada e saída
PORTD ( * (
#define volatile
near
unsigned char *
) 0 xF83 )
TRISD (*( near ) 0 xF95 )

void main void


// i n í c i o do programa
( )
{
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 porta D como saída
PORTD = 0 xF0 ; // d e s l i g a todos os 4 primeiros leds ( lógica negativa )
// l i g a o primeiro bit da variável
mascara = 1; // mascara = 0 b00000001
// r o t a c i o n a −s e a variável para que o bit 1 chegue na posição desejada
mascara = mascara << 2; // mascara = 0 b00000100
// L i g a o bit 2, desligando o 3o led
PORTD = PORTD ^ mascara ;
// l i g a o primeiro bit da variável
mascara = 1; // mascara = 0 b00000001
// r o t a c i o n a −s e a variável para que o bit 1 chegue na posição desejada
mascara = mascara << 6; // mascara = 0 b01000000
// D e s l i g a o bit 6, ligando o 7o led
PORTD = PORTD ^ mascara ;

for
// mantém o sistema ligado indefinidamente
(;;) ;
}

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.

Vericar o estado de um bit (bit test)


Para vericar se o bit X está com o valor 1 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 1 a resposta será diferente de 06 .
Exemplo: Testar o bit 2 da variável PORTD

#define volatile unsigned char *


// d e f i n e ' s para portas de entrada e saída
PORTD ( * (
#define volatile
near
unsigned char *
) 0 xF83 )
TRISD (*( near ) 0 xF95 )

void main void


// i n í c i o do programa
( )
{
char mascara
char teste
; // v a r i á v e l que guarda a mascara
;
TRISD = 0 x00 ; // c o n f i g u r a a porta D como saída
teste = 0 x00 ; // d e s l i g a todos os bits
// r o d a r depois o mesmo programa com os bits ligados .
// t e s t e = 0xff ;
// cria uma variável o n d e APENAS o primeiro bit é 1
mascara = 1; // mascara = 0 b00000001
// r o t a c i o n a −s e a variável para que o bit 1 chegue na posição desejada
mascara = mascara << 2; // mascara = 0 b00000100

if
// V e r i f i c a apenas o bit 2
( teste & mascara )
{
PORTD = 0 x00 ; // s e o resultado for verdadeiro liga todos os leds

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 resultado for falso desliga todos os leds
}

for
// mantém o sistema ligado indefinidamente
(;;) ;
}

Criando funções através de dene's


Uma opção no uso de dene'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 simplicação podemos criar uma função para facilitar
o uso destas operações através de um dene conforme podemos ver nas tabelas 1.6, 1.7, 1.8 e
1.9.
Tabela 1.6: Operação bit set com dene

Operação Bit set

char bit = 2 ;
char mascara ;
mascara = 1 << bit ;
arg = arg | mascara ;
Passo a Passo //em 1 linha
arg = arg | (1<< bit ) ;
// ou
arg |= (1<< bit ) ;

// L i g a n d o o bit 2 da porta D
PORTD = PORTD | (1<<2) ;
Exemplo de uso // ou
PORTD |= (1<<2) ;

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

Exemplo de uso com de- // L i g a n d o o b i t 2 da porta D

ne BitSet ( PORTD , 2 ) ;

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


29 Introdução

Tabela 1.7: Operação bit clear com dene

Operação Bit clear

char bit = 2 ;
char mascara ;
mascara = 1 << bit ;
arg = arg & ~ mascara ;
Passo a Passo //em 1 linha
arg = arg & ~(1<< bit ) ;
// ou
arg &= ~(1<< bit ) ;

// D e s l i g a n d o o bit 2 da porta D
PORTD = PORTD & ~(1<<2) ;
Exemplo de uso // ou
PORTD &= ~(1<<2) ;

Com dene #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 porta D

ne BitClr ( PORTD , 2 ) ;

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


30 Introdução

Tabela 1.8: Operação bit ip com dene

Operação Bit ip

char bit = 2 ;
char mascara ;
mascara = 1 << bit ;
arg = arg ^ mascara ;
Passo a Passo //em 1 linha
arg = arg ^ (1<< bit ) ;
// ou
arg ^= (1<< bit ) ;

// T r o c a n d o o valor do bit 2 da porta D


PORTD = PORTD ^ (1<<2) ;
Exemplo de uso // ou
PORTD ^= (1<<2) ;

Com dene #define BitFlp ( arg , b i t ) ( ( a r g ) ^= (1<< b i t ) )

Exemplo de uso com de- // T r o c a n d o o v a l o r do bit 2 da porta D

ne BitFlp ( PORTD , 2 ) ;

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


31 Introdução

Tabela 1.9: Operação bit test com dene

Operação Bit test

char bit
char mascara
= 2;
;
mascara bit
if arg mascara
= 1 << ;
Passo a Passo ( & )

if arg
//em 1 linha
( bit& (1<< ))

if
// T e s t a n d o o bit 2 da porta D
( PORTD | (1<<2) )

Exemplo de uso {
// . . .
}

Com dene #define BitTst ( arg , b i t ) ( ( a r g ) & (1<< b i t ) )

if
// T e s t a n d o o bit 2 da porta D
( BitTst ( PORTD , 2 ) )
Exemplo de uso com de- {
ne // . . .
}

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


32 Introdução

1.13 Debug de sistemas embarcados7

In the beginner's mind there are many possibilities; in the expert's
mind there are few. - Shunryu Suzuki
A vericaçã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"
int main int argc char *
// i n í c i o do programa
( , argv [ ] )
{
printf ( " Inicializando sistema " ) ;
if ( 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
nal.
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
congurar a porta D. Isto é feito iniciando a variável TRISD com o valor 0x008 .

#define volatile unsigned char *


// d e f i n e ' s para portas de entrada e saída
PORTD ( * (
#define volatile
near
unsigned char *
) 0 xF83 )
TRISD (*( near ) 0 xF95 )

void main void


// i n í c i o do programa
( )
{
// c o n f i g u r a n d o todos os pinos como saídas
TRISD = 0 x00 ;
PORTD = 0 xFF ; // d e s l i g a todos os leds
// l i g a apenas o bit 1.
BitClr ( PORTD , 1 ) ;

for
// mantém o sistema ligado indefinidamente
(;;) ;

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 denidas 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 ca 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 sejam 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 proler 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 vericar 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
innito dentro de um teste, que visa vericar 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.

if
// aqui tem um monte de código . . .
( PORTB >= 5 ) //PORTB não deveria ser um valor maior que 5.
{
BitClr ( PORTD , 3 )
for
; // l i g a o led 3
(;;) ; // t r a v a o programa
}
// aqui continua com um monte de código . . .

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


34 Introdução

1.14 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 é denido 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 variável a num endereço de memória a ser

int
// d e c i d i d o pelo compilador
a = 0;
a = a + 1;
printf ( a ); // i m p r i m e o valor 1
printf ( &a ); // i m p r i m e o endereç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 denir 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 variável ;

Exemplo:

int * apint
float * apfloat
;
;

Deve-se tomar cuidado, pois nos exemplos acima, apint e apoat são variáveis que armazenam
endereços de memória e não valores tipo int ou oat. O lugar APONTADO pela variável apint é
que armazena um inteiro, do mesmo modo que o lugar apontado por apoat armazena um valor
fracionário.
Se quisermos manipular o valor do endereço utilizaremos apint e apoat 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 apoat 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 apoat devemos utilizar a segunda expressão.
Para trabalhar com ponteiros é preciso muito cuidado. Ao ser denido, um ponteiro tem como
conteúdo não um endereço, mas algo indenido. 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á
denida:

// d e f i n i n d o a variável ivar

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


35 Introdução

int ivar ;

int
// d e f i n i n d o o ponteiro iptr
* iptr ;
// o ponteiro iptr recebe o valor do endereço da variável ivar
iptr = &ivar ;
// as próximas linhas são equivalentes
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 conguraçã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 suciently 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

2.1 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 = 2 0 4 8 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 é identicada através de um endereço. Por estarmos tratando de sistemas digitais,
o valor do endereço é codicado 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 identicá-
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 PIC 18f4550

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


40 Arquitetura de microcontroladores

2.2 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 denidos pelo clock do sistema. O clock dene 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íca1 , é necessário igualar as potências 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.23456 x 10 ^ 5
B = 3.4567 x 10 ^ 4
C = A x B
A = 123456; //C = 4.267503552 x 10 ^9
B = 34567;
C = A x B; // 1 . Converter para o mesmo expoente
//C = 4 2 6 7 5 0 3 5 5 2 // 12.3456 x 10 ^ 4
// 3.4567 x 10 ^ 4
// 1 . Multiplicar os núm er os // 2 . Multiplicar os núm er os
// 123456 // e somar a mantissa
// * 34567 // 12.3456 x 10 ^ 4
// 4267503552 // x 3.4567 x 10 ^ 4
// 42.67503552 x 10 ^ 8
// 3 . Corrigir quantidade de casas dec .
// 4.267503552 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 possui 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 utuante que se assemelha à notação cientíca.

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


41 Arquitetura de microcontroladores

2.3 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 denirá a velocidade na qual o processador irá 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 nal. 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 conguraçã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

2.4 Registros de conguração do microcontrolador

A maioria dos terminais dos microcontroladores podem ser congurados para trabalhar de di-
versas maneiras. Esta conguração é realizada através de registros especiais. Estes registros
são posições de memória pré-denidas pelo fabricante. Para conhecer quais são e o que fazem é
preciso recorrer ao datasheet do componente.
Além dos registros de conguração dos terminais, existem registros que indicam como o mi-
crocontrolador deve operar. O microcontrolador PIC 18f4550 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 conguração do microcontrolador PIC 18f4550

Dos registros apresentados na Figura 2.6, quatro precisam necessariamente ser congurados
para que o sistema possa funcionar. Dois deles tem relação com a conguração do sistema de
clock: um especica 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 congurar 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 nalidade. Ele será explicado em detalhes
na seção 3.13 e por isso será mantido desligado nos próximos exemplos.
A última conguraçã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 congurações para o
compilador SDCC.

char
// Pll desligado
code at 0 x300000 CONFIG1L = 0 x01 ;

char
// Oscilador c/ cristal externo HS
code at 0 x300001 CONFIG1H = 0 x0C ;

char
// Watchdog controlado por software
code at 0 x300003 CONFIG2H = 0 x00 ;

char
// Sem programação em baixa tensão
code at 0 x300006 CONFIG4L = 0 x00 ;

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


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

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


44 Arquitetura de microcontroladores

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


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

#pragma
// Oscilador c/ cristal externo HS
config FOSC = HS

#pragma
// Pll desligado
config CPUDIV = OSC1_PLL2

#pragma
// Watchdog controlado por software
c o n f i g WDT = OFF

#pragma
// Sem programação em baixa tensão
config 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 dierence 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(3.3)
ˆ Display de 7 segmentos(3.4)
ˆ Display LCD 2x16(3.6)
ˆ Saídas PWM(3.9)

Entre os periféricos de entrada2 temos:

ˆ Leitura de teclas(3.5)
ˆ Conversor AD(3.8)

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


serial (3.7), um tratador de Interrupção(3.12), um Timer(3.10) e um dispositivo de segurança:
Watchdog(3.13).

1
Periféricos que fornecem informações aos usuários ou enviam comandos da 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

3.1 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 congurá-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 está 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 conguração. O
segundo endereço realiza essa conguraçã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 conguraçã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


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

void void
// i n í c i o do programa
main ( )
{
// Para que o ponteiro para a porta D e Tris D funcione
// e l e s são definidos como :
// a ) unsigned char : pois os 8 bits representam valores
// b ) volatile : as variáveis podem mudar a qualquer momento

volatile unsigned char


// c ) near : indica que o registro está na memória de dados
near * PORTD = 0 xF83 ;
volatile near unsigned char * TRISD = 0 xF95 ;
// c o n f i g u r a n d o todos os pinos como saídas
// 0 = saída ( Output )
// 1 = entrada ( Input )
* TRISD = 0 b00000000 ;
// l i g a apenas os quatro últimos leds
* PORTD = 0 b11110000 ;

for
// mantém o sistema ligado indefinidamente
(;;) ;
}

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

#define volatile unsigned char *


// d e f i n e ' s para portas de entrada e saída
PORTD ( * (
#define volatile
near
unsigned char *
) 0 xF83 )
TRISD (*( near ) 0 xF95 )

void main void


// i n í c i o do programa
( )
{
// c o n f i g u r a n d o todos os pinos como saídas
TRISD = 0 b00000000 ;
// l i g a apenas os quatro últimos leds
PORTD = 0 b11110000 ;

for
// mantém o sistema ligado indefinidamente
(;;) ;
}

Como estamos criando um dene, é 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 dene. É isto que permite que utilizemos o dene 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 dene) é 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
deniçã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

3.2 Conguraçã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
congurar cada porta é preciso conhecer os registros de conguraçã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 conguração dos periféricos do PIC 18f4550

Para a placa que estamos utilizando, a conguração dos terminais do PIC segue conforme a
Tabela 3.2. Esta conguração reete 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 conguraçã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

14 OSC2 /CLKO/RA6
Cristal

16 CCP2
RC1/T1OSI/ Aquecedor

17 CCP1
RC2/ /P1A Ventilador / Buzzer

19 RD0 /SPP0

20 RD1 /SPP1 Barramento de dados para o

21 RD2 /SPP2
LCD/7seg/Led

22 RD3 /SPP3

25 TX
RC6/ /CK

26 RX
RC7/ /DT/SDO
RS232

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

RB2
Saídas para alimentação do teclado
35 /AN8/INT2/VMO

36 RB3 /AN9/CCP2/VPO

37 RB4 /AN11/KBI0/CSSPP

38 RB5 /KBI1/PGM

RB6
Entradas para leitura do teclado
39 /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 terminais como entrada e saída


ADCON1 = 0 b00001110 ; // a p e n a s o primeiro terminal é analógico

A porta B possui os 4 primeiros bits como saída 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 terminais como entrada e saída


// c o n f i g u r a ç ã o com interrupção habilitada
INTCON = 0 b11000101 ;
INTCON2 = 0 b00000001 ;
// c o n f i g u r a ç ã o sem interrupção
INTCON = 0 b00000000 ;
INTCON2 = 0 b00000001 ;
SPPCFG = 0 b00000000 ; //RB0 e RB4 são controlados pela porta B e não pelo 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 terminais como saída , apenas RC7 é entrada


CCP1CON = 0 b00001100 ; // c o n f i g u r a o segundo terminal como PWM
CCP2CON = 0 b00001100 ; // c o n f i g u r a o terceiro terminal como PWM
TXTA = 0 b00101100 ; // c o n f i g u r a a transmissão de dados da serial
RCSTA = 0 b10010000 ; // c o n f i g u r a a recepção de dados da serial
BAUDCON = 0 b00001000 ; // c o n f i g u r a sistema de velocidade da serial
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 terminais como saída

A porta E possui apenas os 3 primeiros bits congurados 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 terminais como saída

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


51 Programação dos Periféricos

3.3 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 congurar 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 pinos da porta D como saída
PORTD = 0 xF0 ;
for
// l i g a apenas os quatro últimos bits .
(;;) ; // mantém o sistema num loop infinito
}

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


52 Programação dos Periféricos

3.4 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 arábicos: 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. 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 sequência 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 sequência 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 simplicar 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

const char conv


// v e t o r que armazena a conversão dos algarismos para o display 7 seg
[] = { 0 x3F , 0 x06 , 0 x5B , 0 x4F , 0 x66 , 0 x6D , 0 x7D , 0 x07 ,
x7F 0 x6F , 0 x77 , 0 x7C , 0 x39 , 0 x5E , 0 x79 , 0 x71 } ;
unsigned int var
0 ,
, time ;
TRISD = 0 x00 ;
TRISA = 0 x00 ;
PORTA = 0 xFF ;
for ( var = 0 ; var < 16; var ++)
{
// c o l o c a os caracteres em sequência na saída
PORTD = conv [ var ] ;

for
// a p e n a s para contar tempo
( time = 0 ; time < 65000; 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
cam 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 sucientemente
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 icker5

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

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 conversão do ←-

3 static const char


display
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 todos os displays

0 x7C , 0 x39 , 0 x5E , 0 x79 , 0 x71 } ; 43 PORTA = 0 x00 ;

4 44 PORTE = 0 x00 ;

static char display


// i n d i c a o display atual
5 ; 45 // d e s l i g a todos os leds

6 46 PORTD = 0 x00 ;

static char v0 v1 v2 v3
// v a l o r e s dos displays
47
switch
7 // l i g a apenas o display da vez

void MudaDigito char val char


, , , ;
8 ( , pos ) 48 ( display )
49 {
9
10
{
if pos ( == 0 ) 50 case 0:

11 { 51 PORTD = valor [ v0 ] ;
12 v0 = val ; 52 BitSet ( PORTA , 5 ) ;
53 display = 1 ;
13
if
}
( pos == 1 ) 54 break
case
14 ;

15 { 55 1:

16 v1 = val ; 56 PORTD = valor [ v1 ] ;


17 } 57 BitSet ( PORTA , 2 ) ;
18 if ( pos == 2 ) 58
59
display = 2 ;
break
case
19 { ;

20 v2 = val ; 60 2:

21 } 61 PORTD = valor [ v2 ] ;
22 if ( pos == 3 ) 62
63
BitSet ( PORTE , 0 ) ;
display = 3 ;
23 {
v3 val ; 64 break
case
24 = ;

25 } 65 3:

26 } 66 PORTD = valor [ v3 ] ;
67 BitSet ( PORTE , 2 ) ;
28 void InicializaDisplays ( void ) 68
69
display = 0 ;
break
default
29 { ;

30 // c o n f i g u r a ç ã o dos pinos de controle 70 :

BitClr ( TRISA , 2 ) 71 display


break
31 ; = 0;

32 BitClr ( TRISA , 5 ) ; 72 ;

33 BitClr ( TRISE , 0 ) ; 73 }

34 BitClr ( TRISE , 2 ) ; 74 }

35 // a p e n a s AN0 é analógico
36 ADCON1 = 0 x0E ;
37 // P o r t a 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 #ifndef DISP7SEG_H
2 define DISP7SEG_H
#
3 void MudaDigito char val char pos ) ;
void AtualizaDisplay void
( ,
4
void InicializaDisplays void
( );
5
#endif
( );
6

Programa 3.3: Utilizando a biblioteca disp7seg


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

5
void main void
// i n í c i o do programa
6 ( )
7 {
8 unsigned int 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
for
// g a s t a um t e m p o para evitar o efeito flicker
18 ( tempo =0; tempo < 1 0 0 0 ; tempo ++) ;
19 }
20 }

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


57 Programação dos Periféricos

3.5 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.
Não havendo circulação de corrente no circuito a queda de tensão em R1 é zero.
Quando a chave é pressionada uma corrente ui de VCC para o terra passando por R1. Como
não existe nenhuma outra resistência no circuito, toda a tensão ca em cima de R1. Deste modo
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 ltradas e não ocorre o problema dos chaveamentos indevidos, como pode ser visto
na Figura 3.9. Notar que o nível do sinal ltrado não chega a zero em nenhum momento, devido
à constante de tempo do ltro 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 (PIC 18f4550 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 ltro RC para debounce do sinal


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

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 o s 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 o s 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 o s b i t s da p o r t a B
for
como digitais
(;;)
{

while
// a g u a r d a uma mudança na porta B
( valAtual==PORTB ) ;
// q u a n d o acontecer alguma mudança , conta um t e m p o pra ver se é permanente
valTemp = PORTB ;
tempo = 2 2 ;
while ( tempo > 0 )
{
if ( valTemp == PORTB ) // se não mudar continua a contar
{
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 ; // se mudar , atualiza o sistema e reinicia o tempo
tempo = 22;
}
}
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 b a r r a m e n t o de leds
}
}

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 suciente. 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 ecientes para a geração deste teclado é o arranjo em formato matri-
cial. Com esta conguração podemos, com N terminais, ler até (N/2)2 chaves.
Conforme podemos ver na Figura 3.10, cada chave pode ser identicada unicamente pela
sua posição (linha, coluna). Os terminais ligados às linhas serão congurados como entrada,
que servirão para ler os valores das teclas. Os terminais ligados às colunas serão congurados
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 verica-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 ; // a p e n a s AN0 é a n a l ó g i c o ,
TRISB = 0 xF0 ; // o s 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 (;;)
{
for (i = 0; i < 2; i++)
{
PORTB = 0 xff ;
for
// " d e s l i g a " t o d a s a s c o l u n a s
( 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

for j
// g a s t a tempo para garantir que o pino atingiu o nível alto
( = 0; j < 100; j++) ;

for j
// r e a l i z a o teste para cada bit e atualiza a matriz .
( = 0; j < 4; j++)
{
if (! 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 static unsigned int valor x0000
unsigned int LerTeclas void
= 0 ;
4
return valor
( ){
5 ;
6 }
7 void DebounceTeclas void
unsigned char i j
( ){
8
static unsigned char tempo
, ;
9
static unsigned int valorNovo
;
10 = 0 x0000 ;
11 static unsigned int valorAntigo = 0 x0000 ;
12 for i i
( i = 0; < 4; ++){
13 PORTB |= 0 x0F ; // d e s l i g a todas as colunas
14 BitClr ( PORTB , ( i ) ) ; // l i g a a coluna correspondente
15 // g a s t a tempo sem realizar função necessário para garantir que o pino ←-

for j j
atingiu o nível alto
16 ( j =0; < 1 0 0 ; ++) ;
17
for j
// r e a l i z a o teste para cada bit e atualiza a variável
18 j j
if BitTst PORTB j
( = 0; < 4; ++) {
19 (! ( , +4) ) {
20 BitSet valorNovo i * 4 )+j ) ;
else
( ,(
21 } {
22 BitClr ( valorNovo ,( i * 4 )+j ) ;
23 }
24 }
25 }
26 if valorAntigo
( == valorNovo ) {
27 tempo −−
else
;
28 } {
29 tempo = 1 0 ;
30 valorAntigo = valorNovo ;
31 }
32 if ( 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 saídas
39 INTCON2 &= 0 x7F ; // h a b i l i t a p u l l −up
40 ADCON1 = 0 b00001110 ; // a p e n a s 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 #ifndef TECLADO_H
2 #define TECLADO_H

4 unsigned int LerTeclas void


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

Programa 3.6: Exemplo de uso da biblioteca teclado


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

5
void void
// i n í c i o do programa
6 main ( )
7 {
8 InicializaTeclado ( ) ;
9 TRISD = 0 x00 ; // C o n f i g u r a a p o r t a D como saída
10 PORTD = 0 xFF ; // d e s l i g a t o d o s o s 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

3.6 Display LCD 2x16

O display de LCD utilizado neste curso possui duas linhas por 16 colunas de caracteres, compa-
tível com o HD44780. 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

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

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 identicados como:

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


65 Programação dos Periféricos

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
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 codicaçã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 denidos pelo usuário e armazenar na memória
interna do display. Para um display com a ROM do tipo A00 temos os caracteres denidos 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 congura o endereço


0 0 0 0 0 0 0 0 0 1 37 us
para 0.

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

Congura a movimentação do cursor e o modo


0 0 0 0 0 0 0 1 ID S 37 us
de shift do display

Congura 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

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

Verica se o display está disponível ou se está


0 1 BF - - - - - - - 10 us
ocupado com alguma operação interna.

Deniçõ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 (modicado)

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 #ifndef LCD_H
2 #define LCD_H
3 void EnviaComando char cmd
void EnviaDados char valor
( );
4
void InicializaLCD void
( );
5
#endif
( );
6 //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
#define
RS 0
4
5 #define
EN 1
RW 2
47 void EnviaDados ( char valor )
48 {

7 void InicializaLCD void ( )


49
50
// d a d o s
BitSet ( PORTE , RS ) ;
8 {
51 // habilita escrita
9 // Inicializa o LCD
52 BitClr ( PORTE , RW ) ;
10 Delay2ms ( ) ; 53 PORTD = valor ;
11 Delay2ms ( ) ; 54 // h a b i l i t a leitura
12 Delay2ms ( ) ; 55 BitSet ( PORTE , EN ) ;
13 Delay2ms ( ) ; 56 Delay40us ( ) ;
14 Delay2ms ( ) ; 57 // t e r m i n a leitura
15 // config . de direção (E/ S )
58 BitClr ( PORTE , EN ) ;
16 BitClr ( TRISE , RS ) ; //RS 59 // d e i x a em nível baixo
17 BitClr ( TRISE , EN ) ; //EN 60 BitClr ( PORTE , RS ) ;
18 BitClr ( TRISE , RW ) ; //RW 61 // d e i x a em nível baixo
19 TRISD = 0 x00 ; // d a d o s
62 BitClr ( PORTE , RW ) ;
20 ADCON1 = 0 b00001110 ; 63 Delay40us ( ) ;
21 // c o n f i g u r a o display
64 }
22 // 8 b i t s , 2 linhas , 5 x8
23
24
EnviaComando ( 0 x38 ) ;
// modo incremental
66 void EnviaComando ( char cmd )
67 {
25 EnviaComando ( 0 x06 ) ; 68 // comando
26 // d i s p l a y , c u r s o r e blinking on
69 BitClr ( PORTE , RS ) ;
27 EnviaComando ( 0 x0F ) ; 70 // habilita escrita
28 // z e r a contadores internos
71 BitClr ( PORTE , RW ) ;
29 EnviaComando ( 0 x03 ) ; 72 PORTD = cmd ;
30 // l i m p a r display
73 // h a b i l i t a leitura
31 EnviaComando ( 0 x01 ) ; 74 BitSet ( PORTE , EN ) ;
32 // p o s i ç ã o inicial
75 Delay2ms ( ) ;
33 EnviaComando ( 0 x80 ) ; 76 // t e r m i n a leitura
34 }
77 BitClr ( PORTE , EN ) ;
36 void Delay40us void 78 // d e i x a em nível baixo

unsigned char i
( ){
79 BitClr ( PORTE , RS ) ;
37
for i i
;
80 // d e i x a em nível baixo
38 ( =0; i < 25; ++) ;
81 BitClr ( PORTE , RW ) ;
39 }
82 Delay2ms ( ) ;
41 void Delay2ms void 83 }

unsigned char i
( ){
42
for i i
;
43 ( =0; < 200; 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
void main void
// i n í c i o do programa
6 ( )
7 {
8 unsigned int i j
char msg
, ;
9 " Hello
[] = World !" ;
10 InicializaLCD
for i i i
() ;
11 ( =0; < 1 1 ; ++)
12 {
13 EnviaDados ( msg [ i ] ) ;
14 for ( 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

3.7 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 simplicação do hardware. Como os dados são enviados um a um, a quantidade
de os envolvidos na transmissão é menor.
A segunda é a maior taxa de transmissão, o que a primeira vista pode parecer inconsistente
já que a comunicação paralela envia mais de um bit ao mesmo tempo. Para frequências muito
altas nem sempre o envio das informações são sincronizadas em todos os os. Existe também
o problema do crosstalking, onde o campo magnético gerado por um cabo induz uma tensão
no cabo adjacente, 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 (bits/s) Taxa (bytes/s)

Serial MIDI 31.25 kbit/s 3.9 kB/s


Serial EIA-232 max. 230.4 kbit/s 28.8 kB/s
Serial UART max 2.7648 Mbit/s 345.6 kB/s
I2c 3.4 Mbit/s 425 kB/s
Serial EIA-422 max. 10 Mbit/s 1.25 MB/s
SPI Bus (Up to 100MHz) 100 Mbit/s 12.5 MB/s
USB super speed (USB 3.0) 5 Gbit/s 625 MB/s
HDMI v. 1.3 10.2 Gbit/s 1.275 GB/s
Ultra DMA ATA 133 (16 bits) 1,064 Mbit/s 133 MB/s
Serial ATA 3 (SATA-600) 6,000 Mbit/s 600 MB/s
Ultra-640 SCSI (16 bits) 5,120 Mbit/s 640 MB/s
Serial Attached SCSI (SAS) 3 9,600 Mbit/s 1,200 MB/s

Para sistemas embarcados em geral não existe grande necessidade de altas taxas de trans-
missão mas de sistemas de baixo custo e conáveis. Existem diversas alternativas entre elas
estudaremos duas das mais comuns: RS232 e I2C.

I2C
O protocolo I2C foi desenvolvido pela Phillips na década de 1980 para permitir que componentes
eletrônicos de uma mesma placa pudessem se comunicar de modo simples e fácil. O protocolo
inicialmente foi desenvolvido para uma comunicação de 100kbps. A versão 4.0 de 2012 7 especica
velocidades de até 5MHz.
Este é um protocolo serial síncrono, ou seja, o clock é enviado junto com o sinal, possibilitando
ao dispositivo receptor saber o momento certo de ler os sinais no barramento. Isto permite o
desenvolvimento de um sistema ou dispositivo mais simples e consequentemente mais barato.
A especicação normatiza um protocolo do tipo mestre/escravo e permite mais de um mestre
ou escravo no barramento. Para isto cada um dos dispositivos possue um registro de 7 ou 10
7
I2C Specication Version 4.0: http://www.nxp.com/documents/user_manual/UM10204.pdf

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


73 Programação dos Periféricos

bits que permite identicá-lo de maneira única. Deste modo é possível construir um sistema
integrado com baixo custo de conexão. A gura 3.16 mostra um exemplo de um barramento com
mais de dois componentes no mesmo barramento.

Figura 3.16: Barramento I2C com vários dispositivos

Isto só é possível por causa da estrura eletrônica escolhida para as conexões: coletor aberto.
Esta estrutura permite que mais de um dispositivo se conecte ao barramento sem causar curtos.
Se um componente está ligado e outro desligado a estrutura evita que aconteça um curto e o
sinal no barramento permanece com nível baixo, gura 3.17.

Figura 3.17: Conexão com coletor aberto

O envio ou a recepção de dados são sempre iniciados pelo mestre. O primeiro bit informa se
o o mestre deseja ler/escrever um valor do o componente. Os próximos sete bits são o endereço
do dispositivo.
Após o envio do primeiro byte o dispositivo envia um bit indicando que recebeu o comando
corretamente e está pronto para o próximo byte. Se a operação for de escrita, o próximo byte é
enviado pelo mestre, se a operação for de leitura o byte é enviado pelo escravo.
Todas essas operações são sincronizadas pelo clock do mestre, mesmo quando o byte é enviado
pelo escravo. Segundo a norma do protocolo o valor na linha de dados deve ser sempre válido
quando a linha de clock estiver alta.
Deste modo é muito simples, e também bastante comum, implementar o protocolo inteiro em
software.

HT1380
O dispositivo HT1380 é um relógio de tempo real (RTC) com um protocolo proprietário baseado
no I2C. Um RTC é um dispositivo especializado em manter a contagem de tempo para longos
períodos de tempo.

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


74 Programação dos Periféricos

Além dos dois terminais do protocolo (clock e dados) este RTC possui um terminal para ha-
bilitar/desabilitar a comunicação do chip. A comunicação também não exige um bit de resposta
como no I2C. A gura 3.18 apresenta a forma de onda que deve ser gerada para a comunicação
entre os dispositivos.

Figura 3.18: Envio de dados para o HT1380

O byte de comando é composto de 1 bit indicando leitura ou escrita, três bits indicando qual
é o registro de interesse e 1 bit para habilitar/desabilitar o clock interno do chip conforme a
gura 3.19.

Figura 3.19: Byte de comando do HT1380

O RTC possui 8 registros internos, com endereços de 0b000 à 0b111. Os valores destes
endereços estão codicados em BCD para facilitar a passagem dos valores. Estes registros são
apresentados na gura 3.20.

Figura 3.20: Registros internos do HT1380

A implementação em software da rotina para enviar um byte é bastante simples:

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


75 Programação dos Periféricos

Programa 3.10: Write RTC

2 void writeByte char dados


unsigned char i
( ) {
3 ;
4
for i
// Escreve o comando
5 ( i
= 0; i < 8; ++) {
6
if dados x01
// A n a l i s a o bit a ser transmitido
7 ( & 0 ) {
8 RTC_IO_ON
else
() ;
9 } {
10 RTC_IO_OFF ( ) ;
11 }
12 // p e g a o próximo bit
13 dados >>= 1 ;
14 // g e r a ç ã o do pulso de clock
15 RTC_SCLK_ON ( ) ;
16 DELAY ( ) ;
17 // i n f o valida
18 RTC_SCLK_OFF ( ) ;
19 DELAY ( ) ;
20 }
21 }

Programa 3.11: Read RTC


1 unsigned char readByte
unsigned char i
() {
2
unsigned char dados
;
3 ;
4 dados
for i
= 0;
5 ( i = 0; i < 8; ++) {
6 RTC_SCLK_ON ( ) ;
7 DELAY ( ) ;
8 // informação válida
9
if
// RTC_IO_STATUS( ) ; // l e r duas vezes , problema do HW d e leitura
10 ( RTC_IO_STATUS ( ) ) {
11 dados |= 0 x80 ;
12 }
13 dados >>= 1 ;
14 RTC_SCLK_OFF ( ) ;
15 DELAY ( ) ;
16 }
17 return dados ;
18 }

1. Colocar o valor do bit a ser enviado na linha de DADOS

2. Gerar um pulso de clock na linha de CLOCK

3. Se houver mais bits para enviar voltar ao passo 1

O código que implementa a rotina acima está na listagem 3.10.


A recepção de valores possui apenas uma diferença. Como o valor dos dados é gerado pelo
escravo, a leitura deve ser feita com o clock em valor alto conforme pode ser observado no trecho
de código 3.11.
Como citado anteriormente, para enviar ou ler um valor é necessário escrever um byte indi-
cando qual é o registro a ser lido. Para facilitar essa tarefa duas funções foram desenvolvidas,

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


76 Programação dos Periféricos

Programa 3.12: Rotinas de escrita e leitura no RTC

2 void ht1380write ( char addr , char dados ) {


3 RTC_RESET_OFF ( ) ;
4 RTC_SCLK_OFF ( ) ;
5 RTC_IO_OFF ( ) ;
6 // l i g a o RTC
7 RTC_RESET_ON ( ) ;

9 addr <<= 1 ;
10 addr |= 0 x80 ; // w r i t e
11 writeByte ( addr ) ;
12 writeByte ( dados ) ;

14 RTC_RESET_OFF ( ) ;
15 RTC_SCLK_OFF ( ) ;
16 RTC_IO_OFF ( ) ;
17 }

19 char ht1380read char addr


unsigned char dados
( ) {
20 ;
21 RTC_RESET_OFF ( ) ;
22 RTC_SCLK_OFF ( ) ;
23 RTC_IO_OFF ( ) ;

25 RTC_RESET_ON ( ) ;
26 addr <<= 1 ;
27 addr |= 0 x81 ; // r e a d
28 writeByte ( addr ) ;
29 // muda pino para ler sinais
30 RTC_IO_IN ( ) ;
31 DELAY ( ) ; DELAY ( ) ; DELAY ( ) ; DELAY ( ) ;
32 dados = readByte ( ) ;

34 RTC_RESET_OFF ( ) ;
35 RTC_SCLK_OFF ( ) ;
36 RTC_IO_OFF ( ) ;
37 RTC_IO_OUT ( ) ;
38 return dados ;
39 }

para leitura e escrita do RTC e são apresentadas no código 3.12. Note que na escrita é necessário
inverter a direção do terminal de dados, de saída para entrada.
Os registros deste RTC armazenam 8 valores em bcd: segundos, minutos, dias, meses, anos,
dia do mês e um registro de proteção para escrita. BCD é um formato utilizado em sistemas
eletrônicos para armazenar valores decimais em variáveis binárias (bcd = binary codded decimal ).
A conversão de BCD para decimal é simples, basta se utiliza da aritmética binária separando-se
os digitos e multiplicando a dezena por dez antes de somá-la à unidade. Para a conversão de
decimal para BCD é necessário primeiro separar a dezena do valor da unidade de depois colocar
os dois valores defasados de 4 bits numa variável binária. O código 3.13 apresenta duas funções
de conversão e acesso ao registro de segundos.

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

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


77 Programação dos Periféricos

Programa 3.13: Rotinas de conversão BCD x inteiro


1 char rtcGetSeconds void
char value
( ) {
2 ;
3 value ht1380read
return value
= (0) ; // read seconds
4 ((( >> 4 ) &0 x07 ) * 1 0 + ( value & 0 x0f ) ) ;
5 }
6 void rtcPutSeconds char ( seconds ) {
7 ht1380write ( 0 , ( seconds % 1 0 ) | (( seconds / 1 0 ) << 4 ) ) ;
8 }

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 é codicada como 7610 e em binário como 110100102 . Na
maioria dos dispositivos primeiro se envia o bit menos signicativo. 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 signicativo é enviado para a saída do microcontrolador. Realiza-se então
um shift para direita e o novo bit menos signicativo é 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.
A Figura 3.21 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á denido. 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.21: Sinal serializado para transmissão em RS232

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


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


78 Programação dos Periféricos

(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 denir a codicação utilizada (ASCII, UTF-8, etc),
especicar o uxo de caracteres (quantidade de bits por caractere, tamanho do start/stop bit,
paridade) e a taxa de transmissão desejada.
Estas conguraçõ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 congurar 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 congurar 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.

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

Bits de Conguraçã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

Como visto na Tabela 3.6 existem três fórmulas diferentes para calcular a taxa de transmissão.
A melhor maneira de congurar a taxa de transmissão da porta serial é vericar 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%
ˆ 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 conguração devem ser: TXSTA(4) = 0,
BAUDCON(3) = 1 e TXSTA(2) = 1. A seguir temos todo o processo de conguração da porta
serial RS232.

BitClr ( BAUDCON , 0 ) ; // D e s a b i l i t a auto detecção de velocidade


BitSet ( BAUDCON , 3 ) ; // R e g i s t r o de geração de sinal com 16 bits
BitClr ( BAUDCON , 6 ) ; // O p e r a ç ã o de recepção ativa
BitClr ( RCSTA , 1 ) ; // D e s a b i l i t a bit de erro de overrun
BitClr ( RCSTA , 2 ) ; // D e s a b i l i t a bit erro na comunicação
BitClr ( RCSTA , 4 ) ; // H a b i l i t a bit de recepção
BitClr ( RCSTA , 6 ) ; // S e l e c i o n a 8 bits

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


79 Programação dos Periféricos

BitSet ( RCSTA , 7 ) ; // C o n f i g u r a RX/TX como pinos de comunicação


BitSet ( TXSTA , 2 ) ; //Modo de alta velocidade habilitado
BitSet ( TXSTA , 3 ) ; // E n v i a bit de parada ( break character bit )
BitClr ( TXSTA , 4 ) ; //Modo assíncrono
BitSet ( TXSTA , 5 ) ; // H a b i l i t a transmissão
BitClr ( TXSTA , 6 ) ; // S e l e c i o n a 8 bits
SPBRGH = 0 x00 ; // C o n f i g u r a para 56 k (SPBRGH | SPBRG = 3 2 )
SPBRG = 0 x22 ; // C o n f i g u r a para 56 k (SPBRGH | SPBRG = 3 2 )
BitSet ( TRISC , 6 ) ; // C o n f i g u r a pino de recepção como entrada
BitClr ( TRISC , 7 ) ; // C o n f i g u r a pino de envio como saída

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 vericar 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) ) ; // a g u a r d a o registro ficar disponível
TXREG=c ; // c o l o c a o valor para ser enviado
}

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


{
char resp
if BitTst PIR1
= 0;
( ( ,5) ) // V e r i f i c a se existe algum valor disponível
{
resp = RCREG ; // r e t o r n a o valor
}
return resp ; // r e t o r n a zero
}

A metodologia apresentada para leitura e escrita de valores é conhecida como pooling. Neste
tipo de abordagem camos 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 ca 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 3.12.

Criação da biblioteca
O programa 3.14 apresenta um exemplo de código para criar uma biblioteca para comunicação
serial. O arquivo de header é apresentado no programa 3.15 e o exemplo de uso demonstrado no
programa 3.16.
A seguir o arquivo de header.

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


80 Programação dos Periféricos

Programa 3.14: serial.c


1 #include " serial .h"
2 #include " basico .h"
4 void EnviaSerial unsigned char ( c)
5 {
6 while BitTst PIR1
(! ( ,4) ) ; // a g u a r d a o registro ficar disponível
7 TXREG=c ; // c o l o c a o valor para ser enviado
8 }

10 unsigned char RecebeSerial void ( )


11 {
12 char resp
if BitTst PIR1
= 0;
13 ( ( ,5) ) // V e r i f i c a se existe algum valor disponível
14 {
15 resp = RCREG ; // r e t o r n a o valor
16 }
17 return resp ; // r e t o r n a zero
18 }

20 void InicializaSerial ( void )


21 {
22 TXSTA = 0 b00101100 ; // c o n f i g u r a a transmissão de dados da serial
23 RCSTA = 0 b10010000 ; // c o n f i g u r a a recepção de dados da serial
24 BAUDCON = 0 b00001000 ; // c o n f i g u r a sistema de velocidade da serial
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 recepção de dados
28 BitClr ( TRISC , 7 ) ; // p i n o de envio de dados
29 }

Programa 3.15: serial.h


1 #ifndef SERIAL_H
2 define SERIAL_H
#
3 void EnviaSerial unsigned char c
unsigned char RecebeSerial void
( );
4
void InicializaSerial void
( );
5
#endif
( );
6 //SERIAL_H

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


81 Programação dos Periféricos

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


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

5
void main void
// i n í c i o do programa
6 ( )
7 {
8 unsigned int i j
char msg
, ;
9 " Hello World !" ;
unsigned char resp
[] =
10 ;
11 TRISD = 0 x00 ; // a c e s s o aos leds
12 InicializaSerial ( ) ;
13 j =0;
14 for (;;)
15 {
16
for
// d e l a y
17 (i = 0; i < 65000; i++) ;
18 // e n v i a dados
19 EnviaSerial ( msg [ j ] ) ;
20 j ++;
21 if ( j > 11)
22 {
23 j =0;
24 EnviaSerial ( 1 3 ) ;
25 }
26 // r e c e b e dados
27 resp RecebeSerial ( ) ;
if
=
28 ( resp ! = 0 )
29 {
30 PORTD = resp ;
31 }
32 }
33 }

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


82 Programação dos Periféricos

3.8 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 conguração que iremos utilizar, com uma fonte de 5v, isto signica 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.22).

Figura 3.22: 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 açã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.23).
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.24.
Deste modo a tensão aplicada em RL (supondo que RL é muito maior que R2) é:

VS ∗ 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-gauges, deslocamento com potenciômetros lineares, etc.
Existem alguns sensores que possuem circuitos de amplicaçã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 210 valores diferentes, ou 1024 valores.

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


83 Programação dos Periféricos

Figura 3.23: Potenciômetro

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


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

Figura 3.25: Circuito integrado LM35

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


84 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.25) que é utilizado para monitoramento
de temperatura.
Na Figura 3.26 é apresentado o diagrama de blocos do circuito integrado do LM35. O diodo
é utilizado como unidade sensora de temperatura.

Figura 3.26: 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 amplicadores e as respectivas realimentações estão
inseridas no circuito para amplicar 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 2N representações diferentes. É interessante então dividir a amplitude
inicial por 2N divisões iguais. Para N = 2 temos 4 representações de 1.25v cada. É comum
nestes comparadores que a primeira tensão possua um oset.

Representação binária com 2 bits Valor em tensão Valor em Tensão com oset
00 0.000 0.625v
01 1.250 1.875v
10 2.500 3.125v
11 3.750 4.375v

A Figura 3.27 apresenta as faixas de valores e da necessidade de oset para uma faixa mais
representativa dos valores reais.

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


85 Programação dos Periféricos

5
11 4,375
4

3 10 3,125

2 01 1,875

1
00 0,675

Intervalos Limites de
de valor comparação

Figura 3.27: Denição de faixa de valores para AD de 2 bits

Figura 3.28: 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


86 Programação dos Periféricos

Programa 3.17: adc.c


1 #include " adc .h"
2 #include " basico .h"
3 void InicializaAD void ( )
4 {
5 BitSet ( TRISA , 0 ) ; // s e t a o bit 0 como entrada
6 ADCON0 = 0 b00000001 ; // s e l e c i o n a o canal 0 e liga o ad
7 ADCON1 = 0 b00001110 ; // a p e n a s AN0 é analógico , a referência é baseada na fonte
8 ADCON2 = 0 b10101010 ; //FOSC /32 , Alinhamento à direita e tempo de c o n v = 12 TAD
9 }

11 int LeValorAD void ( )


12 {
13 unsigned int ADvalor ;
14 BitSet ADCON0
while BitTst ADCON0
( ,1) ; // i n i c i a conversão
15 ( ( , 1 ) ) ; // espera terminar a conversão ;
16 ADvalor = ADRESH ; // lê o resultado
17 ADvalor <<= 8 ;
18 ADvalor += ADRESL ;
19 return ADvalor ;
20 }

Programa 3.18: adc.h


1 #ifndef ADC_H
2 define ADC_H
#
3 void InicializaAD void
int LeValorAD void
( );
4
#endif
( );
5 //ADC_H

O circuito eletrônico responsável pelas comparações pode ser visualizado na Figura 3.28.
O circuito da Figura 3.28 é conhecido como conversor analógico digital do tipo ash onde
cada nível de tensão possui seu próprio comparador. Existem outras abordagens que minimizam
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. Congurar o conversor;

2. Iniciar a conversão;

3. Monitorar o nal da conversão;

4. Ler o valor.

Os programas 3.17 e 3.18 apresentam os arquivos de código e header de uma biblioteca


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

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


87 Programação dos Periféricos

Programa 3.19: 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
void main void
// i n í c i o do programa
6 ( )
7 {
8 unsigned int i
int temp
;
9 = 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 / 1 0 0 , 2 ) ;
19 temp %= 1 0 0 ;
20 MudaDigito ( temp / 1 0 , 1 ) ;
21 temp %= 1 0 ;
22 MudaDigito ( temp ,0) ;
23 AtualizaDisplay ( ) ;
24 for( i = 0 ; i < 1 0 0 0 ; i++) ;
25 }
26 }

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


88 Programação dos Periféricos

3.9 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 dene o ciclo de trabalho, ou duty cycle, da saída. A Figura 3.29
apresenta 3 sinais PWM com a mesma frequência mas com duty cycle diferentes.

Figura 3.29: 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. Assim é possível ajustar a quantidade de energia média dado uma
frequência sucientemente alta10 de sinal do PWM.
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 nal deste modo, num motor podemos ajustar a
velocidade de rotação que queremos.
Como citado, a frequência do PWM tem que ser sucientemente alta. Esta frequência de-
pende do circuito implementado no microcontrolador. No caso do PIC 18f4550 é 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 deniçã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 congurar 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 PIC 18f4550 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
10
Para ser considerada sucientemente alta a frequência do PWM deve possuir um valor mais alto que a maior
constante de tempo do sistema, de modo que este não perceba a oscilação. um fator de 10 vezes em geral é
suciente para que o sistema não sinta esse impacto. No entanto cada caso deve ser analisado em particular

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


89 Programação dos Periféricos

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.

Tabela 3.7: Faixa de frequências máximas e mínimas para cada conguraçã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

O duty cycle (em porcentagem) é calculado de acordo com a fórmula abaixo:

[CCPRxL : CCPxCON (5 : 4)]


DutyCycle PWM =
(PR2 + 1) ∗ 4

Criação da biblioteca
Para congurar as saídas PWM devemos especicar a frequência de trabalho através de PR2
e TCON2, além do duty cycle em CCPR1L e CCPR2L. No registro TRISC é congurado o
terminal como uma saída e em CCP1CON e CCP2CON denimos que ele deve trabalhar como
um PWM. O prescaler foi congurado 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.20 é apresentado
um código exemplo de como criar as rotinas de operação do PWM. O header desta biblioteca
é apresentado no programa 3.21. Por m, o programa 3.22 apresenta um exemplo de utilização
desta biblioteca.

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


90 Programação dos Periféricos

Programa 3.20: 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 duty cycle :
7 // DC_porcento = V / ( ( PR2+1) * 4 ;
8
unsigned int unsigned int
//V = DC/ 1 0 0 * ( PR2+1) * 4 = DC * ( PR2+1) /25
9 val = (( ) porcento ) * ( PR2 +1) ;
10 val = val / 25;
11 // g a r a n t e que tem apenas 10 bits
12 val &= 0 x03ff ;
13 // o s 8 primeiros bits são colocados no CCPR1L
14 CCPR1L = val >> 2;
15 // o s últimos dois são colocados na posição 5 e 4 do CCP1CON
16 CCP1CON |= ( val & 0 x0003 ) << 4;
17 }

19 void SetaPWM2 ( unsigned char porcento )


20 {
21
unsigned int
// 1 0 0 * 256 = 25.600
22 val = porcento * PR2 ;
23 val /= 25;
24 // g a r a n t e que tem apenas 10 bits
25 val &= 0 x03ff ;
26 // o s 8 primeiros bits são colocados no CCPR1L
27 CCPR2L = val >> 2;
28 // o s últimos dois são colocados na posição 5 e 4 do CCP1CON
29 CCP2CON |= ( val & 0 x0003 ) << 4;
30 }

32 void SetaFreqPWM ( unsigned int freq )


33 {
34 //PR2 = f o s c / ( fpwm * 4 * p r e s c a l e r ) −1 = (8000000/( 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 , 1 ) ; // c o n f i g u r a os pinos como saídas
41 BitClr ( TRISC , 2 ) ;
42 T2CON |= 0 b00000011 ; // c o n f i g u r a o prescale do timer 2 para 1:16
43 BitSet ( T2CON , 2 ) ; // L i g a o timer 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.21: pwm.h


1 #ifndef PWM_H
2 define PWM_H
#
3 void SetaPWM1 unsigned char porcento
void SetaPWM2 unsigned char porcento
( );
4
void SetaFreqPWM unsigned int freq
( );
5
void InicializaPWM void
( );
6
#endif
( );
7 //PWM_H

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


91 Programação dos Periféricos

Programa 3.22: 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
void main void
// i n í c i o do programa
6 ( )
7 {
8 int temp ;
9 InicializaPWM ( ) ;
10 InicializaAD ( ) ;
11 for (;;) {
12 temp = LeValorAD ( ) ;
13 // a j u s t a n d o a frequência de acordo com entrada analógica
14 SetaFreqPWM ( temp ) ;
15 // a j u s t a n d o o d u t y −c y c l e para 50%
16 SetaPWM1 ( 5 0 ) ;
17 }
18 }

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


92 Programação dos Periféricos

Programa 3.23: 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
void unsigned int
// t e m p o em micro segundos
13 ResetaTimer ( tempo )
14 {
15
unsigned char
// p a r a placa com 8MHz 1 us = 2 ciclos
16 ciclos tempo * 2 ;
=
17 // o v e r f l o w a c o n t e c e com 2^15 − 1 = 6 5 5 3 5 ( max u n s i g n e d i n t )
18 ciclos = 6 5 5 3 5 − ciclos ;
19 ciclos −= 1 4 ; // s u b t r a i t e m p o d e 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 d e 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 timer 0 sem prescaler
28 BitSet ( T0CON , 7 ) ; // l i g a o t i m e r 0
29 }

3.10 Timer

Nos microcontroladores existem estruturas próprias para realizar a contagem de tempo, estas
estruturas são denominadas Timers.
O PIC 18f4550 possui quatro timers. Para utilizarmos a saída PWM temos que congurar o
timer 2, que gera a base de tempo que será 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 overow 11 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 congurado 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.23 cujo header
é apresentado no programa 3.24. O modo de utilizar esta biblioteca é apresentado no programa
3.25.

11
Overow é 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 overow.

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


93 Programação dos Periféricos

Programa 3.24: timer.h


1 #ifndef TIMER_H
2 define TIMER_H
#
3 char FimTimer void
void AguardaTimer void
( );
4 ( );
5
void ResetaTimer unsigned int
// t e m p o em micro segundos ( us )
6 tempo ) ;
void InicializaTimer void
(
7
#endif
( );
8 //TIMER_H

Programa 3.25: Exemplo de uso da biblioteca de um temporizador


1
void main void
// i n í c i o do programa
2 ( )
3 {
4 unsigned int cont ;
5 TRISD =0 x00 ;
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 if ( 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


94 Programação dos Periféricos

3.11 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 3.9, 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 Hz. 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 Hz12 , que
é inaudível. Isto simplica o programa.

Programa 3.26: Reprodução de sons


1 #include " config .h"
2 #include " basico .h" 39
void main void
// i n í c i o do programa

3 #include " pwm .h" 40 ( )

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;
44
unsigned char
7 // I m p e r i a l March (SW E p i s o d e V)

#define
// n o t a s musicais
8 45 tempo [ ] = {50 , 10 , 50 , 10 , 50 , 10 , ←-
#define
C 523
9 50 , 5, 25 , 5, 50 , 5, 50 , 5, 25 , 5, 50 , 50 , 50 , ←-
#define
CS 5 5 4
10 10 , 50 , 10 , 50 , 10 , 50 , 5, 25 , 5, 50 , 5, 5 0 , ←-

#define
D 587
11 5, 25 , 5, 50 , 50 , 100 , 5, 25 , 5, 25 , 10 , 1 0 0 , ←-

#define
DS 6 2 2

unsigned int
12 5, 50 , 5, 25 , 2, 10 , 2, 10 , 2, 100 , 250};

#define
E 659
13 46 notas [ ] = { G , v , G , v , G , v , E , v , B , ←-
#define
F 698
14 v , G , v , E , v , B , v , G , v , D2S , v , D2S , v , ←-
#define
FS 740
15 D2S , v , E2 , v , B , v , FS , v , E , v , B , v , G , v , ←-
#define
G 784
16 G2S , v , G , v , G , v , G2S , v , G2 , v , F2S , v , F2 , ←-
#define
GS 8 3 0
17 v , E2 , v , F2S , v } ;
#define
A 880
18 47 InicializaPWM ( ) ;
#define
AS 9 3 2
19 B 987 48 InicializaTimer ( ) ;
49 SetaFreqPWM ( notas [ 0 ] ) ;
50 SetaPWM1 ( 5 0 ) ; // g a r a n t e d u t y − c y c l e d e 50%
22 51 for (;;)

#define
// s e g u n d a oitava
23 C* 2 52 {

#define
C2
24 C2S CS * 2 53 AguardaTimer ( ) ;
25 #define D* 2 54 ResetaTimer ( 1 0 0 0 0 ) ;
#define
D2
D2S DS * 2 55 cont ++;
26
27 #define E* 2 56 if ( cont >= tempo [ pos ] )

#define
E2
28 F* 2 57 {

#define
F2
29 F2S FS * 2 58 pos ++;
30 #define G* 2 59 SetaFreqPWM ( notas [ pos ] ) ;

#define
G2
31 G2S GS* 2 60 SetaPWM1 ( 5 0 ) ;
32 #define A* 2 61 cont =0;
#define
A2
33 A2S AS * 2 62 }

34 #define B2 B* 2 63
64 }
}

37
#define
// sem som
38 v 125000

12
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


95 Programação dos Periféricos

3.12 Interrupção

Até o momento todos os programas que foram desenvolvidos seguiam um uxo 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 car 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 nalizada, 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 (m 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é-
denida. 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 buer de trans-
missão TXREG estiver disponível.

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

ˆ Porta B: quando algum dos bits congurados como entrada altera seu valor.

ˆ Timer 0: quando acontece overow em seu contador.

Para gerenciar a interrupção, deve-se criar uma rotina que irá vericar qual foi o hardware
que gerou a interrupção e tomar as providências necessárias. A maneira de declarar que uma
determinada 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 ...
}

#pragma
// I n d i c a r a posição no vetor de interrupções

void interrupt_at_high_vector void


code h i g h _ v e c t o r =0x 0 8
( )
{
_asm GOTO Interrupcao _endasm

#pragma
}

#pragma
code
interrupt 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


96 Programação dos Periféricos

Programa 3.27: Fontes de Interrupção


1 void Interrupcao ( void ) interrupt 1
2 {
3 // não é necessário utilizar todos os if 's , apenas
4
if
// aqueles das interrupções desejadas
5 ( BitTst ( PIR1 , 0 ) )
if
{ /* c ó d i g o */ } // F l a g de overflow do TIMER1
6 ( BitTst ( PIR1 , 1 ) )
if
{ /* c ó d i g o */ } // F l a g de comparação do TIMER2 com PR2
7 ( BitTst ( PIR1 , 2 ) )
if
{ /* c ó d i g o */ } // F l a g de comparação do CCP1
8 ( BitTst ( PIR1 , 3 ) ) { /* c ó d i g o */ } // F l a g de fim de operação na porta ←-

if
paralela
9 ( BitTst ( PIR1 , 4 ) )
if
{ /* c ó d i g o */ } // F l a g de fim de transmissão da Serial
10 ( BitTst ( PIR1 , 5 ) )
if
{ /* c ó d i g o */ } // F l a g de recepção da Serial
11 ( BitTst ( PIR1 , 6 ) )
if
{ /* c ó d i g o */ } // F l a g de fim de conversão do AD
12 ( BitTst ( PIR1 , 7 ) ) { /* c ó d i g o */ } // F l a g de leitura /escrita da porta ←-

if
paralela
13 ( BitTst ( PIR2 , 0 ) )
if
{ /* c ó d i g o */ } // F l a g de comparação do CCP2
14 ( BitTst ( PIR2 , 1 ) )
if
{ /* c ó d i g o */ } // F l a g de overflow do TIMER3
15 ( BitTst ( PIR2 , 2 ) )
if
{ /* c ó d i g o */ } // F l a g de condição de Tensão A l t a / Baixa
16 ( BitTst ( PIR2 , 3 ) ) { /* c ó d i g o */ } // F l a g de detecção de colisão no ←-

if
barramento
17 ( BitTst ( PIR2 , 4 ) )
if
{ /* c ó d i g o */ } // F l a g de fim escrita na memória flash
18 ( BitTst ( PIR2 , 5 ) )
if
{ /* c ó d i g o */ } // F l a g de interrupção da USB
19 ( BitTst ( PIR2 , 6 ) ) { /* c ó d i g o */ } // F l a g de mudança na entrada de ←-

if
comparação
20 ( BitTst ( PIR2 , 7 ) )
if
{ /* c ó d i g o */ } // F l a g de falha no oscilador
21 ( BitTst ( INTCON , 0 ) )
if
{ /* c ó d i g o */ } // F l a g de mudança na PORTA B
22 ( BitTst ( INTCON , 1 ) )
if
{ /* c ó d i g o */ } // F l a g de interrupção externa INT0
23 ( BitTst ( INTCON , 2 ) )
if
{ /* c ó d i g o */ } // F l a g de overflow no TIMER0
24 ( BitTst ( INTCON3 , 0 ) )
if
{ /* c ó d i g o */ } // F l a g de interrupção externa INT1
25 ( BitTst ( INTCON3 , 1 ) ) { /* c ó d i g o */ } // F l a g de interrupção externa 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 nal 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 PIC 18f4550 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 vericação é
através das ags de controle, ou seja, bits que indicam a situação de cada periférico.
O programa 3.27 apresenta uma função que trata de todas as fontes possíveis de interrupção
para o PIC 18f4550.
Em geral não é necessário tratar todas as interrupções, apenas aquelas que inuenciarão
o sistema. O programa 3.28 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.28 funcione corretamente devemos inicializar
as interrupções de modo adequado, conforme apresentado no programa 3.29.

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


97 Programação dos Periféricos

Programa 3.28: Tratamento das interrupções


1 static unsigned int ADvalor
static unsigned char Serial
;
2
static unsigned int Tecla
;
3
void Interrupcao void interrupt
;
4 ( ) 1
5 {
6 char i j
if BitTst PIR1
, ;
7 ( ( ,6) ) //AD : fim de conversã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 if ( BitTst ( PIR1 , 5 ) ) // S e r i a l : recepção
15 {
16 // B i t C l r ( PIR1 , 5 ) ;
17 Serial = RCREG ; // l i m p a sozinho quando lê
18 }
19 if ( BitTst ( INTCON , 0 ) ) //PORTA B : mudou valor
20 {
21 for ( i = 0 ; i < 4 ; i++){
22 PORTB |= 0 xFF ;
23 BitClr ( PORTB , ( i ) ) ;
24 for ( j =0; j < 1 0 ; j++) ;
25 for ( j = 0 ; j < 4 ; j++){
26 if ( ! 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 if ( BitTst ( INTCON , 2 ) ) //TIMER0 : Overflow
37 {
38 // t e m p o máximo de interrupção do timer 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 d e t e m p o
41 TMR0L = 0 x00 ; // r e i n i c i a c o n t a d o r d e t e m p o
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


98 Programação dos Periféricos

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


1 void main void ( )
2 {
3 unsigned int i , temp , teclanova =0;
4 // c o n f i g u r a n d o todas as interrupções
5 TRISD = 0 x00 ;
6 TRISB = 0 xF0 ; // mantém o s 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 a s 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 d e 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 p a r a o AD
15 BitSet ( PIE1 , 5 ) ; // l i g a a i n t e r r u p ç ã o p a r a 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 p a r a 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 p a r a a p o r t a B
18 BitSet ( INTCON , 7 ) ; // h a b i l i t a t o d a s a s 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 a s i n t e r r u p ç õ e s d e 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 if ( teclanova != Tecla ) {
32 teclanova = Tecla ;
33 for
( i =0; i < 1 6 ; i++){
34 if ( BitTst ( Tecla , i ) ) {
35 EnviaDados ( i +48) ;
36 }
37 }
38 }
39 for (i = 0; i < 1000; i++) ;
40 }
41 }

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


99 Programação dos Periféricos

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


1 #define CLRWTD( ) _asm CLRWDT _endasm

3
void main void
// i n í c i o do programa
4 ( )
5 {
6 unsigned int i
unsigned char temp
;
7 ;
8 TRISD =0 x00 ;
9 PORTD =0 x00 ;
10 BitSet ( WDTCON , 0 )
for
; // l i g a o sistema de watchdog
11 (;;)
12 {
13 PORTD ++;
14 for(i = 0; i < 10000; i++)
15 {
16 CLRWTD ( ) ;
17 }
18 }
19 }

3.13 Watchdog

Por algum motivo o software pode travar em algum ponto, seja por um loop innito 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 innito
ou de um pooling sem resposta.
Para habilitar o watchdog é necessário alterar os registros de conguração, especicamente
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.30.
Notar o #dene criado na primeira linha do programa 3.30. 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
indenidamente.

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

100
101 Arquitetura de desenvolvimento de software

Programa 4.1: Exemplo de arquitetura single-loop


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

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


1
for
// l o o p principal
2 (;;)
3 {
4 // chamada das tarefas
5 DebounceTeclas ( ) ;
6 ia = LerTeclas ( ) ;
7 ImprimeDisplay ( ia ) ; // tem que ser executado pelo menos a cada 1 0 ( ms )
8 ic = RecebeSerial ( ) ;
9 fa = 2 . 0 * ic / 3 . 1 4 ;
10 EnviaSerial ( fa & 0 x00FF ) ;
11 EnviaSerial ( fa >> 8 ) ;
12 }

4.1 One single loop

1 Innite 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 innito. 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 car complicado coordenar as tarefas e garantir a execução num
tempo determinístico. Outro problema é a modicaçã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 icker.

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


102 Arquitetura de desenvolvimento de software

Programa 4.3: Exemplo de sistema Interrupt-driven


1 int ia ;
2
void Interrupcao void
// t r a t a m e n t o do teclado via interrupção
3 ( ) interrupt 1
4 {
5 if BitTst INTCON
( ( ,0) ) //PORTA B : mudou valor
6 {
7 ia = LerTeclas ( ) ;
8 }
9 }

11 void main ( void )


12 {
13 // i n i c i a l i z a ç ã o dos periféricos
14 InicializaTeclado ( ) ;
15 InicializaDisplays ( ) ;
16 // i n i c i a l i z a ç ã o da interrupção
17 BitClr ( RCON , 7 ) ; // d e s a b i l i t a IPEN ( modo d e 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 p a r a a p o r t a B
19 BitSet ( INTCON , 7 ) ; // h a b i l i t a t o d a s a s 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 a s i n t e r r u p ç õ e s d e p e r i f é r i c o s
21 for ( ; ; ) // l o o p principal
22 {
23 // chamada das tarefas
24 ImprimeDisplay ( ia ) ;
25 }
26 }

4.2 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 é suciente. 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 xo, 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á xo em 5(ms).

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


103 Arquitetura de desenvolvimento de software

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


1 int ia ;
2
void Interrupcao void
// e x i s t e apenas uma fonte de interrupção : Timer 0
3 ( ) interrupt 1
4 {
5 ResetaTimer ( 5 0 0 0 ) ; // r e i n i c i a t i m e r p a r a p r ó x i m a 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 p a r a 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 periféricos
13 InicializaTeclado ( ) ;
14 InicializaDisplay ( ) ;
15 InicializaTimer ( ) ;
16 // i n i c i a l i z a ç ã o da interrupção
17 BitClr ( RCON , 7 ) ; // d e s a b i l i t a IPEN ( modo de compatibilidade )
18 BitSet ( INTCON , 5 ) ; // l i g a a interrupção para o timer 0
19 BitSet ( INTCON , 7 ) ; // h a b i l i t a todas as interrupções globais
20 BitSet ( INTCON , 6 ) ; // h a b i l i t a todas as interrupções de periféricos
21 ResetaTimer ( 5 0 0 0 ) ;
22 for (;;) // l o o p principal
23 {
24 DebounceTeclas ( ) :
25 ia = LerTeclas ( ) ;
26 }
27 }

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


104 Arquitetura de desenvolvimento de software

4.3 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 nal 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 uxo 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 innito, 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


105 Arquitetura de desenvolvimento de software

Programa 4.5: Exemplo de cooperative multitasking


1 void main ( void )
2 {
3
char
// d e c l a r a ç ã o das variáveis
4 slot ;
5 // f u n ç õ e s de inicialização
6 InicializaSerial ( ) ;
7 InicializaTeclado ( ) ;
8 InicializaDisplay ( ) ;
9 for (;;){ // i n í c i o do loop infinito
10 // * * * * * * * * * * * * * * * início do top−s l o t ******************
11 // * * * * * * * * * * * * * * * * fim do top−s l o t ******************

13
switch
// * * * * * * * * * * * início da máquina de estado ************
14 ( slot ) {
15 case 0:
16 ProcessaTeclado ( ) ;
17 slot = 1 ;
18 break
case
;
19 1:
20 AtualizaDisplay ( ) ;
21 slot = 2 ;
22 break
case
;
23 2:
24 RecebeSerial ( ) ;
25 slot = 3 ;
26 break
case
;
27 3:
28 AtualizaDisplay ( ) ;
29 slot = 4 ;
30 break
case
;
31 4:
32 EnviaSerial ( ) ;
33 slot = 5 ;
34 break
case
;
35 5:
36 AtualizaDisplay ( ) ;
37 slot = 0 ;
38 break
default
;
39 :
40 slot
break
= 0;
41 ;
42 }
43 // * * * * * * * * * * * * fim da máquina de estado **************

45 // * * * * * * * * * * * * * * início do bottom−s l o t *****************


46 // * * * * * * * * * * * * * * * fim do bottom−s l o t *****************
47 } // f i m loop infinito (!?)
48 }

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


106 Arquitetura de desenvolvimento de software

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


1 void main ( void )
2 {
3
char
// d e c l a r a ç ã o das variáveis
4 slot ;
5 // f u n ç õ e s de inicialização
6 InicializaSerial ( ) ;
7 InicializaTeclado ( ) ;
8 InicializaDisplay ( ) ;
9 for (;;) // i n í c i o do loop infinito
10 {
11 // * * * * * * * * * * * * * * * início do top−s l o t ******************
12 AtualizaDisplay ( ) ;
13 // * * * * * * * * * * * * * * * * fim do top−s l o t ******************

16
switch
// * * * * * * * * * * * início da máquina de estado ************
17 ( slot )
18 {
19 case 0:
20 ProcessaTeclado ( ) ;
21 slot = 1 ;
22 break
case
;
23 1:
24 RecebeSerial ( ) ;
25 slot = 2 ;
26 break
case
;
27 2:
28 EnviaSerial ( ) ;
29 slot = 0 ;
30 break
default
;
31 :
32 slot
break
= 0;
33 ;
34 }
35 // * * * * * * * * * * * * fim da máquina de estado **************

38 // * * * * * * * * * * * * * * início do bottom−s l o t *****************

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

42 } // f i m loop infinito (!?)


43 }

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


107 Arquitetura de desenvolvimento de software

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


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

16
switch
// * * * * * * * * * * * início da máquina de estado ************
17 ( slot )
18 {
19 case 0:
20 ProcessaTeclado ( ) ;
21 slot = 1 ;
22 break
case
;
23 1:
24 RecebeSerial ( ) ;
25 slot = 2 ;
26 break
case
;
27 2:
28 EnviaSerial ( ) ;
29 slot = 0 ;
30 break
default
;
31 :
32 slot
break
= 0;
33 ;
34 }
35 // * * * * * * * * * * * * fim da máquina de estado **************

38 // * * * * * * * * * * * * * * início do bottom−s l o t *****************

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


41 AguardaTimer ( ) ;
42 } // f i m loop infinito (!?)
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 cará 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


108 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 ca 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


109 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

110
111 Anexos

Programa 5.1: cong.h


1
#ifndef
// p a r a o compilador SDCC + G P U t i l s
2 CONFIG_H
3 #define CONFIG_H
4 code char at 0 x300000 CONFIG1L = 0 x01 ;
char
// Pll desligado
5 code at 0 x300001 CONFIG1H = 0 x0C ;
char
// Oscilador c/ cristal externo HS
6 code at 0 x300003 CONFIG2H = 0 x00 ;
char
// Watchdog controlado por software
7 code at 0 x300006 CONFIG4L = 0 x00 ;
#endif
// Sem programação em baixa tensão
8 //CONFIG_H

10 // p a r a o compilador C18
11 //#pragma config FOSC = HS // Oscilador c/ cristal externo HS
12 //#pragma config CPUDIV = OSC1_PLL2 // Pll desligado
13 //#pragma c o n f i g WDT = OFF // Watchdog controlado por software
14 //#pragma config LVP = OFF // Sem programação em baixa t e n s ã o \\\ h l i n e

5.1 cong.h

O arquivo cong.h possui as diretivas de compilação para conguração do microcontrolador.

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


112 Anexos

Programa 5.2: basico.h


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

5.2 basico.h

O header basico.h possui o endereço de todos os registros do microcontrolador PIC 18f4550 que
é utilizado nesta apostila. Além disso contém alguns dene's importantes como as funções inline
para limpar a ag de watchdog e para manipulação de bits.

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


113 Anexos

5.3 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, provavelmente os drivers errados foram instalados.
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 este possui energia suciente
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 instalar
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
Vericar se o driver está instalado e visível 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


114 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

Вам также может понравиться