Buscar

programação embarcada

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 3, do total de 116 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 6, do total de 116 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 9, do total de 116 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Prévia do material em texto

Notas de Aula Programação Embarcada - ELT024 1
Rodrigo Maximiano Antunes de Almeida
Instituto de Engenharia de Sistemas e Tecnologia da Informação,
Universidade Federal de Itajubá,
Minas Gerais,
Brasil
rodrigomax @ unifei.edu.br
29 de Agosto de 2012
1
Licenciado sobre Criative Commons Attribution-NonCommercial-NoDerivs
Conteúdo
1 Introdução 1
. Linguagem C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
. Hardware utilizado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
. Ambiente de programação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Instalação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Configuração do gravador ICD2 . . . . . . . . . . . . . . . . . . . . . . . 4
Criação de um novo projeto . . . . . . . . . . . . . . . . . . . . . . . . . 5
. Indentação e padrão de escrita . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
. Comentários . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
. Arquivos .c e .h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
. Diretivas de compilação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
#include . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
#define . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
#ifdef, #ifndef, #else e #endif . . . . . . . . . . . . . . . . . . . . . . . . 12
. Tipos de dados em C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Representação binária e hexadecimal . . . . . . . . . . . . . . . . . . . . . 14
Modificadores de tamanho e sinal . . . . . . . . . . . . . . . . . . . . . . . 15
Modificadores de acesso . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Modificadores de posicionamento . . . . . . . . . . . . . . . . . . . . . . . 17
Modificador de persistência . . . . . . . . . . . . . . . . . . . . . . . . . . 17
. Operações aritméticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
. Função main() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
. Rotinas de tempo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
. Operações com bits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
NOT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
AND . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
OR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
XOR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Shift . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Ligar um bit (bit set) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Desligar um bit (bit clear) . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Trocar o valor de um bit (bit flip) . . . . . . . . . . . . . . . . . . . . . . 26
Verificar o estado de um bit (bit test) . . . . . . . . . . . . . . . . . . . . 27
Criando funções através de define’s . . . . . . . . . . . . . . . . . . . . . 28
. Debug de sistemas embarcados . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Externalizar as informações . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Programação incremental . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Checar possíveis pontos de Memory-leak . . . . . . . . . . . . . . . . . . . 33
Cuidado com a fragmentação da memória . . . . . . . . . . . . . . . . . . 33
Otimização de código . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Reproduzir e isolar o erro . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
. Ponteiros e endereços de memória . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
i
2 Arquitetura de microcontroladores 36
. Acesso à memória . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
. Clock e tempo de instrução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
. Esquema elétrico e circuitos importantes . . . . . . . . . . . . . . . . . . . . . . . 41
Multiplexação nos terminais do microcontrolador . . . . . . . . . . . . . . 42
. Registros de configuração do microcontrolador . . . . . . . . . . . . . . . . . . . . 43
3 Programação dos Periféricos 45
. Acesso às “portas”do microcontrolador . . . . . . . . . . . . . . . . . . . . . . . . 46
. Configuração dos periféricos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
. Barramento de Led's . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
. Display de 7 segmentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
Multiplexação de displays . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
Criação da biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
. Leitura de teclas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Debounce por software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Arranjo de leitura por matriz . . . . . . . . . . . . . . . . . . . . . . . . . 60
Criação da biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
. Display LCD 2x16 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Criação da biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
. Comunicação serial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
RS 232 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Criação da biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
. Conversor AD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
Elementos sensores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
Processo de conversão AD . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Criação da biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
. Saídas PWM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
Criação da biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
. Timer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
. Reprodução de Sons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
. Interrupção . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
. Watchdog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
4 Arquitetura de desenvolvimento de software 95
. One single loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
. Interrupt control system . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
. Cooperative multitasking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
Fixação de tempo para execução dos slots . . . . . . . . . . . . . . . . . . 102
Utilização do tempo livre para interrupções . . . . . . . . . . . . . . . . . 103
5 Anexos 105
. config.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
. basico.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
. Instalar gravadores/depuradores de PIC em sistemas x64 . . . . . . . . . . . . . . 108
ii
Lista de Figuras
1.1 Camadas de abstração de um sistema operacional . . . . . . . . . . . . . . . . . . 1
1.2 Pesquisa sobre linguagens utilizadas para projetos de software embarcado . . . . 2
1.3 Configuração das ferramentas de compilação . . . . . . . . . . . . . . . . . . . . . 4
1.4 Instalação do ICD2 . . . . . . . . . . . . . . . . . . . . . . . . . . .. . . . . . . . 5
1.5 Resumo das configurações do ICD2 no MPLAB . . . . . . . . . . . . . . . . . . . 6
1.6 Pedido de atualização do firmware do ICD2 . . . . . . . . . . . . . . . . . . . . . 6
1.7 Project Explorer do MPLAB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.8 Problema das Referências Circulares . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.9 Solução das referências circulares com #ifndef . . . . . . . . . . . . . . . . . . . . 14
1.10 Loop infinito de um device driver gerando erro no sistema . . . . . . . . . . . . . 20
1.11 Exemplo de funcionamento do vetor de interrupção . . . . . . . . . . . . . . . . . 20
2.1 Arquitetura do microcontrolador PIC 18F4550 . . . . . . . . . . . . . . . . . . . 37
2.2 Memória como um armário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
2.3 Memória e periféricos como um armário . . . . . . . . . . . . . . . . . . . . . . . 39
2.4 Regiões de memórias disponíveis no PIC18F4550 . . . . . . . . . . . . . . . . . . 39
2.5 Esquema elétrico: Microcontrolador PIC 18F4550 . . . . . . . . . . . . . . . . . . 41
2.6 Registros de configuração do microcontrolador PIC 18F4550 . . . . . . . . . . . . 43
3.1 Registros de configuração dos periféricos do PIC 18F4550 . . . . . . . . . . . . . 48
3.2 Barramento de Led's . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
3.3 Display de 7 Segmentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
3.4 Diagrama elétrico para display de 7 segmentos com ânodo comum . . . . . . . . . 52
3.5 Ligação de 4 displays de 7 segmentos multiplexados . . . . . . . . . . . . . . . . . 53
3.6 Circuito de leitura de chave . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
3.7 Oscilação do sinal no momento do chaveamento . . . . . . . . . . . . . . . . . . . 58
3.8 Circuito de debounce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
3.9 Utilização de filtro RC para debounce do sinal . . . . . . . . . . . . . . . . . . . . 59
3.10 Teclado em arranjo matricial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
3.11 Display Alfanumérico LCD 2x16 . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
3.12 Display Alfanumérico LCD 2x16 - verso . . . . . . . . . . . . . . . . . . . . . . . 65
3.13 Caracteres disponíveis para ROM A00 . . . . . . . . . . . . . . . . . . . . . . . . 66
3.14 Caracteres disponíveis para ROM A02 . . . . . . . . . . . . . . . . . . . . . . . . 67
3.15 Esquemático de ligação do display de LCD . . . . . . . . . . . . . . . . . . . . . . 69
3.16 Sinal serializado para transmissão em RS232 . . . . . . . . . . . . . . . . . . . . . 73
3.17 Lâmpada incandescente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
3.18 Potenciômetro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
3.19 Potenciômetro como divisor de tensão . . . . . . . . . . . . . . . . . . . . . . . . 78
3.20 Circuito integrado LM35 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
3.21 Diagrama de blocos do LM35 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
3.22 Conversor analógico digital de 2 bits . . . . . . . . . . . . . . . . . . . . . . . . . 80
3.23 Sinais PWM com variação do duty cycle . . . . . . . . . . . . . . . . . . . . . . . 83
4.1 Exemplo de máquina de estados . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
iii
4.2 Exemplo da mudança de slots no tempo . . . . . . . . . . . . . . . . . . . . . . . 103
4.3 Linha de tempo de um sistema com 1 slot . . . . . . . . . . . . . . . . . . . . . . 103
4.4 Comportamento da linha de tempo com interrupções . . . . . . . . . . . . . . . . 103
iv
Lista de Tabelas
1.1 Softwares utilizados no curso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Ferramentas utilizadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3 Tipos de dados e faixa de valores . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.4 Representação decimal - binária - hexadecimal . . . . . . . . . . . . . . . . . . . . 15
1.5 Alteração de tamanho e sinal dos tipos básicos . . . . . . . . . . . . . . . . . . . 16
1.6 Operação bit set com define . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
1.7 Operação bit clear com define . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
1.8 Operação bit flip com define . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
1.9 Operação bit test com define . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2.1 Quantidade de operações e tarefas . . . . . . . . . . . . . . . . . . . . . . . . . . 40
3.1 Endereços de memória para as portas do PIC 18F4550 . . . . . . . . . . . . . . . 46
3.2 Tabela de configuração do PIC para as experiências . . . . . . . . . . . . . . . . . 49
3.3 Conversão binário - hexadecimal para displays de 7 segmentos . . . . . . . . . . . 53
3.4 Lista de comandos aceitos pelo o LCD . . . . . . . . . . . . . . . . . . . . . . . . 68
3.5 Taxas de transmissão para diferentes protocolos . . . . . . . . . . . . . . . . . . . 72
3.6 Cálculo do valor da taxa de transmissão da porta serial . . . . . . . . . . . . . . . 74
3.7 Faixa de frequências máximas e mínimas para cada configuração do prescaler . . 84
v
Lista de Programas
1.1 Resumo do disp7seg.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.2 Resumo do disp7seg.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.3 Estrutura de header . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.4 Operações aritméticas com tipos diferentes . . . . . . . . . . . . . . . . . . . . . . 18
3.1 disp7seg.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
3.2 disp7seg.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
3.3 Utilizando a biblioteca disp7seg . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
3.4 teclado.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
3.5 teclado.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
3.6 Exemplo de uso da biblioteca teclado . . . . . . . . . . . . . . . . . . . . . . . . . 63
3.7 lcd.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
3.8 lcd.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
3.9 Exemplo de uso da biblioteca de LCD . . . . . . . . . . . . . . . . . . . . . . . . 71
3.10 serial.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
3.11 serial.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
3.12 Exemplo de uso da biblioteca de comunicação serial . . . . . . . . . . . . . . . . . 76
3.13 adc.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
3.14 adc.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
3.15 Exemplo de uso da biblioteca de conversores AD . . . . . . . . . . . . . . . . . . 82
3.16 pwm.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
3.17 pwm.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
3.18 Exemplo de uso da biblioteca das saídas PWM . . . . . . . . . . . . . . . . . . . 86
3.19 timer.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
3.20 timer.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
3.21 Exemplo de uso da biblioteca de um temporizador . . . . . . . . . . . . . . . . . 88
3.22 Reprodução de sons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
3.23 Fontes de Interrupção . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. . 91
3.24 Tratamento das interrupções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
3.25 Inicialização do sistema com interrupções . . . . . . . . . . . . . . . . . . . . . . 93
3.26 Inicialização do sistema com interrupções . . . . . . . . . . . . . . . . . . . . . . 94
4.1 Exemplo de arquitetura single-loop . . . . . . . . . . . . . . . . . . . . . . . . . . 96
4.2 Problema na sincronia de tempo para o single-loop . . . . . . . . . . . . . . . . . 96
4.3 Exemplo de sistema Interrupt-driven . . . . . . . . . . . . . . . . . . . . . . . . . 97
4.4 Exemplo de sistema Interrupt-driven com base de tempo . . . . . . . . . . . . . . 98
4.5 Exemplo de cooperative multitasking . . . . . . . . . . . . . . . . . . . . . . . . . 100
4.6 Exemplo de cooperative multitasking com uso do top slot . . . . . . . . . . . . . 101
4.7 Exemplo de sistema Cooperative-multitasking com slot temporizado . . . . . . . 102
5.1 config.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
5.2 basico.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
vi
Capítulo 1
Introdução
“The real danger is not that computers will begin to think like men,
but that men will begin to think like computers.” - Sydney J. Harris
Programação para sistemas embarcados exige uma série de cuidados especiais, pois estes sistemas
geralmente possuem restrições de memória e processamento. Por se tratar de sistemas com
funções específicas, as rotinas e técnicas de programação diferem daquelas usadas para projetos
de aplicativos para desktops.
Também é necessário conhecer mais a fundo o hardware que será utilizado, pois cada mi-
croprocessador possui uma arquitetura diferente, com quantidade e tipos de instruções diversos.
Programadores voltados para desktops não precisam se ater tanto a estes itens, pois eles pro-
gramam para um sistema operacional, que realiza o papel de tradutor, disponibilizando uma
interface comum, independente do hardware utilizado(Figura 1.1).
Firmware
Hardware
Sistema Operacional
Aplicação
Figura 1.1: Camadas de abstração de um sistema operacional
Para sistemas embarcados, é necessário programar especificamente para o hardware em ques-
tão. Uma opção para se obter “artificialmente” esta camada de abstração que era gerada pelo
sistema operacional é a utilização de dois itens: um compilador próprio para o componente em
questão e uma biblioteca de funções. O compilador será o responsável por traduzir a linguagem
de alto nível em uma linguagem que o microcontrolador consegue entender. A biblioteca de
funções, ou framework, em geral, é disponibilizada pelos fabricantes do microcontrolador.
. Linguagem C
“C is quirky, flawed, and an enormous success.” - Dennis M. Ritchie
Neste curso será utilizada a linguagem C. Esta é uma linguagem com diversas características que
a tornam uma boa escolha para o desenvolvimento de software embarcado. Apesar de ser uma
linguagem de alto nível, permite ao programador um acesso direto aos dispositivos de hardware.
1
2 Introdução
Também é a escolha da maioria dos programadores e gerentes de projetos no que concerne
ao desenvolvimento de sistemas embarcados como pode ser visto na Figura 1.2.
Figura 1.2: Pesquisa sobre linguagens utilizadas para projetos de software embarcado
Fonte: http://www.embedded.com/design/218600142
A descontinuidade depois de 2004 se dá devido à mudança de metodologia da pesquisa. Antes
de 2005, a pergunta formulada era: “Para o desenvolvimento da sua aplicação embarcada, quais
das linguagens você usou nos últimos 12 meses?”. Em 2005 a pergunta se tornou: “Meu projeto
embarcado atual é programado principalmente em ______”. Múltiplas seleções eram possíveis
antes de 2005, permitindo a soma superior a 100%, sendo o valor médio de 209%, o que implica
que a maioria das pessoas escolheu duas ou mais opções.
O maior impacto na pesquisa pode ser visualizado na linguagem assembler: até 2004, estava
presente em 62% das respostas (na média). O que comprova que praticamente todo projeto de
sistema embarcado exige um pouco de assembler. Do mesmo modo, percebemos que atualmente
poucos projetos são realizados totalmente ou em sua maioria em assembler, uma média de apenas
7%.
. Hardware utilizado
“People who are really serious about software should make their own
hardware.” - Alan Kay
Como o enfoque deste curso é a programação de sistemas embarcados e não a eletrônica, utili-
zaremos um kit de desenvolvimento pronto, baseado num microcontrolador PIC.
Como periféricos disponíveis temos:
• 1 display LCD 2 linhas por 16 caracteres (compatível com HD77480)
• 4 displays de 7 segmentos com barramento de dados compartilhados
• 8 leds ligados ao mesmo barramento dos displays
• 16 mini switches organizadas em formato matricial 4x4
• 1 sensor de temperatura LM35C
• 1 resistência de aquecimento ligada a uma saída PWM
• 1 motor DC tipo ventilador ligado a uma saída PWM
• 1 buzzer ligado a uma saída PWM
Notas de Aula ELT024 - Programação para Sistemas Embarcados
3 Introdução
• 1 canal de comunicação serial padrão RS-232
Cada componente terá seu funcionamento básico explicado para permitir o desenvolvimento de
rotinas para estes.
. Ambiente de programação
“First, solve the problem. Then, write the code.” - John Johnson
O ambiente utilizado será o MPLAB(R). Este é um ambiente de desenvolvimento disponibilizado
pela Microchip(R) gratuitamente. O compilador utilizado será o SDCC, os linkers e assemblers
serão disponibilizados pela biblioteca GPUtils.
Como o foco é a aprendizagem de conceitos sobre programação embarcada, poderá ser uti-
lizada qualquer plataforma de programação e qualquer compilador/linker. Caso seja utilizado
qualquer conjunto de compilador/linker diferentes deve-se prestar atenção apenas nas diretivas
para gravação.
Para a programação em ambiente Linux recomenda-se o uso da suíte PIKLAB 15.10. Este
programa foi desenvolvido para KDE 3.5. Além de permitir a integração com o mesmo compilador
utilizado neste curso permite a programação do microcontrolador utilizando o programador ICD2
via USB.
Instalação
A Tabela 1.1 apresenta os softwares que serão utilizados no curso.
Tabela 1.1: Softwares utilizados no curso
Item Versão Licença
IDE MPLAB 8.50 Proprietário
Compilador SDCC 2.9.00 (win32) GPL
Linker/Assembler GPUtils 0.13.7 (win32) GPL
Plugin MPLAB sdcc-mplab 0.1 GPL
Todos os softwares são gratuitos e estão disponíveis na internet. Para correta instalação
deve-se instalar os softwares segundo a sequência apresentada na Tabela 1.1. Anote o diretório
onde cada software foi instalado.
Após a instalação dos softwares deve-se abrir o arquivo “pic16devices.txt” (de preferência no
wordpad) que foi instalado no diretório do SDCC dentro da pasta “include\pic16” (por padrão
“C:\Arquivos de programas\SDCC\include\pic16”). No windows vista e windows 7 não é possível
editar arquivos de sistema. Neste caso clique no arquivo com o botão direito > Propriedades >
Segurança > Editar > Usuários e selecionar a opção Controle Total, depois clique em ok. Após
isso será possível editar o arquivo. Procure então a seguintes linhas:
name 18f4550
using 18f2455
Trocar a letra “f” minúscula da primeira linha, apenas do 18f4550, para um “F” maiúsculo:
name 18F4550
using 18f2455
Notas de Aula ELT024 - Programação para Sistemas Embarcados
4 Introdução
Figura 1.3: Configuração das ferramentas de compilação
Em seguida abra o programa MPLAB e vá ao menu “Projects -> Set Language Tool Locati-
ons”. Será apresentada uma tela similar a da Figura 1.3.
Selecione a ferramenta “Small Device C Compiler for PIC16 (SDCC16)”. Expanda a opção
“Executables”. A ferramenta “gpasm” é obtida no diretório “bin” dentro de onde foi instalado
o GPUtils, por padrão: “C:\Arquivos de programas\gputils\bin”.Para as opções sdcc16 e sdcc
link deve-se escolher o arquivo “sdcc.exe”, que é encontrado no diretório “bin” dentro do diretório
onde foi instalado o SDCC por padrão: “C:\Arquivos de programas\SDCC\bin\”. Clicar em
“OK”. A Tabela 1.2 apresenta um resumo destas opções.
Tabela 1.2: Ferramentas utilizadas
Executables Nome do arquivo Localização
gpasm gpasm.exe C:\Arquivos de programas\gputils\bin\
sdcc link sdcc.exe C:\Arquivos de programas\SDCC\bin\
sdcc16 sdcc.exe C:\Arquivos de programas\SDCC\bin\
Após estes passos a suíte MPLAB está pronta para trabalhar com o compilador SDCC+GPUtils.
Configuração do gravador ICD2
Após instalar o MPLAB já é possível fazer a instalação e configuração do gravador ou depurador
ICD2. Conecte-o a qualquer porta USB e aguarde a tela de instalação do Windows. Em algumas
versões do windows pode acontecer de você ser perguntado se deseja instalar um software não
assinado digitalmente, certifique-se que a versão do firmware é pelo menos 1.0.0.0 da fabricante
Microchip, conforme pode ser visto na Figura 1.4 e avance.
Após o termino da instalação abra o programa MPLAB para configurar o gravador ou depu-
rador. Vá ao menu “Programmer -> Select Programmer -> MPLAB ICD 2”. Vá novamente ao
menu “Programmer” mas desta vez escolha a opção “ MPLAB ICD 2 Setup Wizard”.
Notas de Aula ELT024 - Programação para Sistemas Embarcados
5 Introdução
Figura 1.4: Instalação do ICD2
No wizard, escolha a comunicação como USB e depois diga que a placa possui alimentação
independente “Target has own power supply”. Deixe as outras opções na seleção padrão. Antes
de clicar em concluir verifique ao final se o resumo se parece com o da Figura 1.5.
Na primeira vez que o computador se conectar ao ICD2 é possível que o MPLAB precise
atualizar o firmware do ICD2 conforme o aviso que pode ser visto na Figura 1.6.
Criação de um novo projeto
Recomenda-se a utilização do assistente disponível para a criação de um novo projeto (menu
Project -> Project Wizard). Ele irá questionar sobre (entre parênteses os valores adotados neste
curso):
1. O microcontrolador a ser utilizado (PIC18F4550)
2. A suíte de compilação (SDCC 16)
3. O diretório e nome do projeto
4. Arquivos já existentes cujo programador deseja incluir no projeto
Após estes passos o projeto estará criado. Caso a lista de arquivos do projeto não esteja
visível vá ao menu View -> Project.
Para a criação de um novo arquivo vá até o menu File -> New. Neste novo arquivo digite
alguma coisa e salve-o. Caso seja o arquivo que conterá a função principal (main) é costume
salvá-lo com o nome de “main.c”.
A cada novo arquivo criado é necessário inseri-lo no projeto. Para isso deve-se clicar na pasta
correspondente ao tipo de arquivo que se deseja incluir e em seguida “Add Files” como pode ser
visualizado na Figura 1.7.
A programação para sistemas embarcados possui diversas características diferentes da progra-
mação voltada para desktop. Do mesmo modo, existem alguns conceitos que geralmente não são
Notas de Aula ELT024 - Programação para Sistemas Embarcados
6 Introdução
Figura 1.5: Resumo das configurações do ICD2 no MPLAB
Figura 1.6: Pedido de atualização do firmware do ICD2
Notas de Aula ELT024 - Programação para Sistemas Embarcados
7 Introdução
Figura 1.7: Project Explorer do MPLAB
explorados nos cursos de linguagens de programação em C, mas que são essenciais para o bom
desenvolvimento deste curso. Estes conceitos serão explanados neste capítulo.
. Indentação e padrão de escrita
“Good programmers use their brains, but good guidelines save us
having to think out every case.” - Francis Glassborow
É fundamental obedecer a um padrão para escrita de programas, de modo que a visualização do
código seja facilitada.
Na língua portuguesa utilizamos parágrafos para delimitar blocos de frases que possuem a
mesma ideia. Em linguagem C estes blocos são delimitados por chaves “{” e “}”.
Para demonstrar ao leitor que um parágrafo começou utilizamos um recuo à direita na pri-
meira linha. Quando é necessário realizar uma citação de itens coloca-se cada um destes itens
numa linha recuada à direita, algumas vezes com um identificador como um traço “-” ou seta
“->” para facilitar a identificação visual.
Com esse mesmo intuito, os recuos e espaçamentos são utilizados para que o código seja mais
facilmente entendido.
Como todo bloco de comandos é iniciado e terminado com uma chave, tornou-se comum que
estas (as chaves) estejam no mesmo nível e todo código interno a elas seja deslocado à direita. Se
existir um segundo bloco interno ao primeiro, este deve ser deslocado duas vezes para indicar a
hierarquia no fluxo do programa. Segue abaixo um exemplo de um mesmo código com diferença
apenas na indentação.
Notas de Aula ELT024 - Programação para Sistemas Embarcados
8 Introdução
Código indentado Código não indentado
1 void main (void )
{
unsigned int i ;
unsigned int temp ;
unsigned int teclanova=0;
InicializaSerial ( ) ;
InicializaDisplays ( ) ;
InicializaLCD ( ) ;
InicializaAD ( ) ;
for ( ; ; )
{
AtualizaDisplay ( ) ;
i f ( teclanova != Tecla )
{
teclanova = Tecla ;
for (i=0;i<16;i++)
{
i f ( BitTst ( Tecla , i ) )
{
EnviaDados (i+48) ;
}
}
}
for (i = 0 ; i < 1000 ; i++);
}
}
void main (void )
{
unsigned int i ;
unsigned int temp ;
unsigned int teclanova=0;
InicializaSerial ( ) ;
InicializaDisplays ( ) ;
InicializaLCD ( ) ;
InicializaAD ( ) ;
for ( ; ; )
{
AtualizaDisplay ( ) ;
i f ( teclanova != Tecla )
{
teclanova = Tecla ;
for (i=0;i<16;i++)
{
i f ( BitTst ( Tecla , i ) )
{
EnviaDados (i+48) ;
}
}
}
for (i = 0 ; i < 1000 ; i++);
}
}
Podemos notar pelo código anterior que aquele que possui indentação facilita na verificação
de quais instruções/rotinas estão subordinadas às demais.
Outra característica de padronização está na criação de nomes de funções e de variáveis. Pela
linguagem C uma função ou variável pode ter qualquer nome desde que: seja iniciada por uma
letra, maiúscula ou minúscula, e os demais caracteres sejam letras, números ou underscore “_”.
A linguagem C permite também que sejam declaradas duas variáveis com mesmo nome caso
possuam letras diferentes apenas quanto caixa (maiúscula ou minúscula). Por exemplo: “var” e
“vAr” são variáveis distintas, o que pode gerar erro no desenvolvimento do programa causando
dúvidas e erros de digitação.
Por isso convenciona-se que os nomes de variáveis sejam escritos apenas em minúsculas.
Quando o nome é composto, se utiliza uma maiúscula para diferenciá-los como, por exemplo, as
variáveis “contPos” e “contTotal”.
Nomes de função serão escritos com a primeira letra maiúscula e no caso de nome composto,
cada inicial será grafada em maiúsculo: “InicializaTeclado()”, “ParaSistema()”.
Tags de definições (utilizados em conjunto com a diretiva #define) serão grafados exclusiva-
mente em maiúsculo: “NUMERODEVOLTAS”, “CONSTGRAVITACIONAL”.
Cada chave será colocada numa única linha, conforme exemplo anterior, evitando-se constru-
ções do tipo:
i f ( PORTA == 0x30 ) { PORTB = 0x10 ; }
Ou
i f ( PORTA == 0x30 ) {
PORTB = 0x10 ; }
As regras apresentadas visam fornecer uma identidade visual ao código. Tais regras não são
absolutas, servem apenas para o contexto desta apostila. Em geral, cada instituição ou projeto
Notas de Aula ELT024 - Programação para Sistemas Embarcados
9 Introdução
possui seu próprio conjunto de normas. É importante ter conhecimento deste conjunto e aplicá-lo
em seu código.
O estilo adotado nesta apostila é conhecido também como estilo “Allman”, “bsd” (no emacs)
ou ANSI, já que todos os documentos do padrão ANSI C utilizam este estilo. Apesar disto o
padrão ANSI C não especifica um estilo para ser usado.
. Comentários
“If the code and the comments disagree, then both are probably
wrong.” - Norm Schryer
Comentáriossão textos que introduzimos no meio do programa fonte com a intenção de torná-
lo mais claro. É uma boa prática em programação inserir comentários no meio dos nossos
programas. Pode-se comentar apenas uma linha usando o símbolo “//” (duas barras). Para
comentar mais de uma linha usa-se o símbolo “/*” (barra e asterisco) antes do comentário e “*/”
(asterisco e barra) para indicar o final do comentário.
#include <s td i o . h>
#define DIST 260 // d i s t an c i a en t re SP e I t a
int main ( int argc , char∗ argv [ ] )
{
/∗ es s e programa serve para
mostrar como se in s e r e comentários ∗/
printf ("São Paulo está %d Km de Itajubá" , DIST ) ;
return 0 ;
}
. Arquivos .c e .h
Na programação em linguagem C utilizamos dois tipos de arquivos com funções distintas. Toda
implementação de código é feita no arquivo com extensão “.c” (code). É nele que criamos as
funções, definimos as variáveis e realizamos a programação do código. Se existem dois arquivos
“.c” no projeto e queremos que um deles possa usar as funções do outro arquivo, é necessário
realizar um #include.
Os arquivos “.h” (header) tem como função ser um espelho dos arquivos “.c” disponibilizando
as funções de um arquivo “.c” para serem utilizadas em outros arquivos. Nele colocamos todos
os protótipos das funções que queremos que os outros arquivos usem.
Se quisermos que uma função só possa ser utilizada dentro do próprio arquivo, por motivo
de segurança ou organização, basta declarar seu protótipo APENAS no arquivo “.c”.
Se for necessário que um arquivo leia e/ou grave numa variável de outro arquivo é recomen-
dado criar funções específicas para tal finalidade.
O programa 1.1 apresenta um exemplo de um arquivo de código “.c” e o programa 1.2 apre-
senta o respectivo arquivo de header “.h”.
Podemos notar que no arquivo “.h” a função AtualizaDisplay() não está presente, deste modo
ela não estará disponível para os outros arquivos. Podemos notar também que para ler ou
gravar a variável “digito” é necessário utilizar as funções MudarDigito() e LerDigito(). Notar que
não existe acesso direto às variáveis. Este tipo de abordagem insere atrasos no processamento
devido a um efeito conhecido como overhead de funções, podendo inclusive causar travamentos
no sistema caso não exista espaço suficiente no stack.
. Diretivas de compilação
As diretivas de compilação são instruções que são dadas ao compilador. Elas não serão executa-
das. Todas as diretivas de compilação começam com um sinal #, conhecido como jogo da velha
ou hash.
Notas de Aula ELT024 - Programação para Sistemas Embarcados
10 Introdução
Programa 1.1: Resumo do disp7seg.c
1 // v a r i á v e l usada apenas dentro de s t e arqu ivo
2 stat ic char temp ;
3 // v a r i á v e l que será usada também fora do arqu ivo
4 stat ic char valor ;
5 // funções usadas dentro e fora do arqu ivo
6 void MudaDigito (char val )
7 {
8 valor = val ;
9 }
10 char LerDigito (void )
11 {
12 return valor ;
13 }
14 void InicializaDisplays (void )
15 {
16 // código da função
17 }
18 // função usada apenas dentro de s t e arqu ivo
19 void AtualizaDisplay (void )
20 {
21 // código da função
22 }
Programa 1.2: Resumo do disp7seg.h
1 #ifndef VAR_H
2 #define VAR_H
3 void MudaDigito (char val ) ;
4 char LerDigito (void ) ;
5 void InicializaDisplays (void ) ;
6 #endif //VAR_H
Notas de Aula ELT024 - Programação para Sistemas Embarcados
11 Introdução
#include
A diretiva de compilação #include é a responsável por permitir que o programador utilize no seu
código funções que foram implementadas em outros arquivos, seja por ele próprio ou por outras
pessoas. Não é necessário possuir o código fonte das funções que se deseja utilizar. É necessário
apenas de um arquivo que indique os protótipos das funções (como elas devem ser chamadas) e
possuir a função disponível em sua forma compilada.
Em geral um arquivo que possui apenas protótipos de funções é denominado de “Header” e
possui a extensão “.h”.
#define
Outra diretiva muito conhecida é a #define. Geralmente é utilizada para definir uma constante,
mas pode ser utilizada para que o código fonte seja modificado antes de ser compilado.
Original Compilado Resultado na Tela
#define CONST 15
void main (void )
{
printf ("%d" , CONST ∗ 3) ;
}
void main (void )
{
printf ("%d" , 15 ∗ 3) ;
}
45
Função Original Opções de uso com o #define Resultado na Tela
void MostraSaidaPadrao ( )
{
#ifdef PADRAO Serial
char ∗ msg = "SERIAL" ;
#else
char ∗ msg = "LCD" ;
#endif
printf ( msg ) ;
}
#include <s td i o . h>
#define PADRAO Se r i a l
void main (void )
{
MostraSaidaPadrao ( ) ;
}
SERIAL
#include <s td i o . h>
#define PADRAO LCD
void main (void )
{
MostraSaidaPadrao ( ) ;
}
LCD
Pelo código apresentado percebemos que a mesma função MostraSaidaPadrao(), apresenta re-
sultados diferentes dependendo de como foi definida a opção PADRAO.
Os define’s também ajudam a facilitar a localização dos dispositivos e ajustar as configurações
no microcontrolador. Todo periférico possui um ou mais endereços para os quais ele responde.
Estes endereços podem variar inclusive dentro de uma mesma família. Por exemplo: o endereço
da porta D (onde estão ligados os leds) é 0xF83. Para ligar ou desligar um led é preciso alterar
o valor que esta dentro do endereço 0xF83. Para facilitar este procedimento, é definido um
ponteiro para este endereço e rotulado com o nome PORTD. Definir OFF como 0 e ON como 1
facilita a leitura do código.
Notas de Aula ELT024 - Programação para Sistemas Embarcados
12 Introdução
#ifdef, #ifndef, #else e #endif
As diretivas #ifdef, #ifndef, #else e #endif são muito utilizadas quando queremos gerar dois
programas que diferem apenas num pequeno pedaço de código. Por exemplo dois sistemas de
controle de temperatura. O primeiro possui um display de LCD, capaz de mostrar a temperatura
textualmente. O segundo sistema executa a mesma função que o primeiro, mas é um dispositivo
mais barato, portanto possui apenas um led indicativo de sobretemperatura. O código pode ser
escrito da seguinte maneira
void ImprimirTemp (char valor )
{
#ifdef LCD
Imprime_LCD ( valor )
#else
i f ( valor > 30)
{
led = 1 ;
}
else
{
led = 0 ;
}
#endif //LCD
}
No momento da compilação o pré-compilador irá verificar se a “tag” LCD foi definida em
algum lugar. Em caso positivo o pré-compilador irá deixar tudo que estiver entre o #ifdef e o
#else e retirará tudo que está entre o #else e o #endif.
Outra função muito utilizada destas diretivas é para evitar a referência circular. Supondo dois
arquivos, um responsável pela comunicação serial (serial.h) e o segundo responsável pelo controle
de temperatura (temp.h). O projeto exige que a temperatura possa ser controlada pela porta
serial e toda vez que a temperatura passar de um determinado patamar deve ser enviado um alerta
pela porta serial. O arquivo da porta serial (serial.h) tem as seguintes funções, apresentadas a
seguir.
char LerSerial (void ) ;
void EnviaSerial (char val ) ;
O arquivo de controle da temperatura (temp.h) possui as funções apresentadas a seguir.
char LerTemperatura (void ) ;
void AjustaCalor (char val ) ;
Toda vez que a função LerTemperatura() for chamada, ela deve fazer um teste e se o valor for
maior que um patamar chamar a função EnviaSerial() com o código 0x30. Para isso o arquivo
temp.h deve incluir o arquivo serial.h.
#include "serial.h"
char LerTemperatura (void ) ;
void AjustaCalor (char val ) ;
Toda vez que a função LerSerial() receber um valor, ela deve chamar a função AjustaCalor()
e repassar esse valor. Para isso o arquivo serial.h deve incluir o arquivo temp.h
#include "temp.h"
char LerSerial (void ) ;
void EnviaSerial (char val ) ;
Notas de Aula ELT024 - Programação para Sistemas Embarcados
13 Introdução
Programa 1.3: Estruturade header
1 #ifndef TAG_CONTROLE
2 #define TAG_CONTROLE
3 // todo o conteúdo do arqu ivo vem aqui .
5 #endif //TAG_CONTROLE
O problema é que deste modo é criada uma referência circular sem fim: o compilador lê o
arquivo serial.h e percebe que tem que inserir o arquivo temp.h. Inserindo o arquivo temp.h
percebe que tem que inserir o arquivo serial.h, conforme pode ser visto na Figura 1.8.
#include “serial.h”
char LerTemperatura(void);
void AjustaCalor(char val);
temp.h
#include “temp.h”
char LerSerial(void);
void EnviaSerial(char val);
serial.h
#include “serial.h”
char LerTemperatura(void);
void AjustaCalor(char val);
temp.h
Figura 1.8: Problema das Referências Circulares
A solução é criar um dispositivo que permita que o conteúdo do arquivo seja lido apenas uma
vez. Este dispositivo é implementado através da estrutura apresentada no programa 1.3.
Segundo o código acima, o conteúdo que estiver entre o #ifndef e o #endif, só será mantido
se a tag “TAG_CONTROLE” NÃO estiver definida. Como isto é verdade durante a primeira
leitura, o pré-compilador lê o arquivo normalmente. Se acontecer uma referência cíclica, na
segunda vez que o arquivo for lido, a tag “TAG_CONTROLE” já estará definida impedindo
assim que o processo cíclico continue, conforme pode ser visto na Figura 1.9.
Geralmente se utiliza como tag de controle o nome do arquivo. Esta tag deve ser única para
cada arquivo.
. Tipos de dados em C
“19 Jan 2038 at 3:14:07 AM. The end of the world according to Unix
(232 seconds after Jan 1st 1970)” - Unix date system
O tipo de uma variável, informa a quantidade de memória, em bytes, que esta irá ocupar e como
esta deve ser interpretada: com ou sem fração (vírgula). Os tipos básicos de dados na linguagem
Notas de Aula ELT024 - Programação para Sistemas Embarcados
14 Introdução
#infdef TEMP_H
 #define TEMP_H
 #include “serial.h”
 char LerTemperatura(void);
 void AjustaCalor(char val);
#endif
temp.h
#infdef SERIAL_H
 #define SERIAL_H
 #include “temp.h”
 char LerSerial(void);
 void EnviaSerial(char val);
#endif
serial.h
#infdef TEMP_H
 //tag já definida,
 //pula o conteúdo
#endif
temp.h
Figura 1.9: Solução das referências circulares com #ifndef
C são apresentados na Tabela 1.3.
Tabela 1.3: Tipos de dados e faixa de valores
Tipo Bits Bytes Faixa de valores
char 8 1 -127 à 127
int 16 2 -32.768 à 32.767
float 32 4 3,4 x 10-38 à 3,4 x 1038
double 64 8 3,4 x 10-308 à 3,4 x 10308
Podemos notar que as variáveis que possuem maior tamanho podem armazenar valores mai-
ores. Notamos também que apenas os tipos float e double possuem casas decimais.
Representação binária e hexadecimal
A grande maioria dos processadores trabalha com dados binários, ou seja, aqueles que apenas
assumem valores 0 ou 1. Por isso os tipos apresentados anteriormente podem ser representados
utilizando a base 2. Um valor do tipo char que possui 8 bits será representado por um número
de 8 algarismos, todos 0 (zeros) ou 1 (uns). Para realizarmos a conversão de um número na base
decimal para a base 2 podemos seguir o seguinte algoritmo:
1. Dividir o número por 2
2. Anotar o valor do resto (0 ou 1)
3. Se o valor é maior que 0 voltar ao número 1
4. Escrever os valores obtidos através do passo 2 de trás para frente.
Notas de Aula ELT024 - Programação para Sistemas Embarcados
15 Introdução
5. Apresentar o resultado
Por exemplo o número 18.
18/2 = 9, resto 0
9/2 = 4, resto 1
4/2 = 2, resto 0
2/2 = 1, resto 0
1/2 = 0, resto 1
Lendo do último resultado para o primeiro temos que
1810 = 100102
Devido a grande utilização de números binários na programação de baixo nível é muito comum
escrevemos estes números na base 16 ou hexadecimal. A vantagem de escrever o número nesta
base é que existe uma conversão simples de binário para hexadecimal e o número resultante
ocupa bem menos espaço na tela.
A base hexadecimal possui 16 "unidades"diferentes. Como existem apenas 10 algarismos no
sistema de numeração arábico (0, 1, 2, 3, 4, 5, 6, 7, 8, 9) utilizamos 6 letras para complementá-los
(A, B, C, D, E, F). A conversão entre valores binários, decimais e hexadecimais é apresentada
na Tabela 1.4.
Tabela 1.4: Representação decimal – binária - hexadecimal
Decimal Binário Hexadecimal Decimal Binário Hexadecimal
0 0000 0 8 1000 8
1 0001 1 9 1001 9
2 0010 2 10 1010 A
3 0011 3 11 1011 B
4 0100 4 12 1100 C
5 0101 5 13 1101 D
6 0110 6 14 1110 E
7 0111 7 15 1111 F
Para converter de binário para hexadecimal basta dividir o número em grupos de 4 em 4, da
esquerda para a direita, e utilizar a tabela acima.
Por exemplo o número 18. Sabemos que este número em binário é representado por 100102.
Separando o número de 4 em 4 algarismos temos:
1-0010
Pela tabela:
12 = 116
00102 = 216.
Logo:
100102. = 1216.
Modificadores de tamanho e sinal
Um modificador de tipo altera o significado dos tipos base e produz um novo tipo. Existem
quatro tipos de modificadores, dois para o tamanho (long e short) e dois para sinal (unsigned
e signed). Um tipo declarado com o modificador long pode ter tamanho MAIOR ou IGUAL
ao tipo original. Um tipo declarado como short deve ter tamanho MENOR ou IGUAL ao tipo
original. A decisão cabe ao compilador utilizado.
Os tipos declarados como signed possuem um bit reservado para o sinal, deste o valor máximo
que podem atingir é menor. Os tipos declarados como unsigned não podem assumir valores
Notas de Aula ELT024 - Programação para Sistemas Embarcados
16 Introdução
negativos, em compensação podem atingir o dobro do valor de um tipo signed. Na Tabela 1.5
são apresentadas algumas variações possíveis.
Tabela 1.5: Alteração de tamanho e sinal dos tipos básicos
Tipo Bytes Excursão máxima
unsigned char 1 0 à 255
signed char 1 -128 à 127
unsigned int 2 0 à 65.535
signed int 2 -32.768 à 32.767
long int 4 -2.147.483.648 à 2.147.483.647
unsigned long int 4 0 à 4.294.967.295
short int 2 -32.768 à 32.767
Na linguagem C, por padrão os tipos são sinalizados, ou seja, possuem parte positiva e
negativa. Por isso é raro encontrar o modificador signed.
Modificadores de acesso
Durante o processo de compilação, existe uma etapa de otimização do programa. Durante esta
etapa, o compilador pode retirar partes do código ou desfazer loops com períodos fixos. Por
exemplo o código abaixo:
#define X (∗( near unsigned char∗) 0xF83 )
void main (void )
{
while (X !=X ) ;
}
Quando compilado apresenta o seguinte código em assembler:
// S t a r t i n g pCode b l o c k
S_Teste__main code
_main :
. line 19 // Teste . c wh i l e (X!=X) ;
RETURN
Enquanto a variável “x” for diferente de “x” o programa não sai do loop. O compilador
entende que esta condição nunca irá acontecer e elimina o loop do código final como podemos
ver no código gerado, a rotina de return está logo após a inicialização do programa _main. Para
variáveis comuns o valor só é alterado em atribuições diretas de valor ou de outras variáveis: (x
= 4;) ou (x = y;).
Entretanto existe uma condição onde a variável x pode alterar seu valor independentemente
do programa. Se esta variável representar um endereço de memória associado a um periférico
físico, seu valor pode mudar independentemente do fluxo do programa. Para indicar esta situação
ao programa utilizamos a palavra reservada volatile.
#define X (∗( volat i le near unsigned char∗) 0xF83 )
void main (void )
{
while (X !=X ) ;
}
Gerando o código em assembler descrito abaixo:
Notas de Aula ELT024 - Programação para Sistemas Embarcados
17 Introdução
// S t a r t i n g pCode b l o c k
S_Teste__main code
_main :
_00105_DS_ :
. line 19 // Teste . c wh i l e (X != X) ;
MOVLW 0x83 // primeira par te do endereço
MOVWF r0x00
MOVLW 0x0f // segunda par te do endereço
MOVWF r0x01MOVFF r0x00 , FSR0L
MOVFF r0x01 , FSR0H
MOVFF INDF0 , r0x00 // r e a l i z a pr imeira l e i t u r a
MOVLW 0x83 // primeira par te do endereço
MOVWF r0x01
MOVLW 0x0f // segunda par te do endereço
MOVWF r0x02
MOVFF r0x01 , FSR0L
MOVFF r0x02 , FSR0H
MOVFF INDF0 , r0x01 // r e a l i z a segunda l e i t u r a
MOVF r0x00 , W
XORWF r0x01 , W
BNZ _00105_DS_ // f a z o t e s t e para i gua l dade
RETURN
Podemos perceber que, deste modo, o compilador é forçado a ler a variável x duas vezes e realizar
o teste para ver se ela permanece com o mesmo valor.
Em algumas situações é necessário indicar que algumas variáveis não podem receber valores
pelo programa. Para isto utilizamos a palavra reservada const. Utilizamos este modificador
para indicar que a variável representa um local que apenas pode ser lido e não modificado, por
exemplo uma porta para entrada de dados. Nesta situação é comum utilizar as palavras volatile
e const junto.
#define X (∗( volat i le const near unsigned char∗) 0xF83 )
// i n i c i o do programa
void main (void )
{
X = 3 ;
}
Se tentarmos compilar este código aparecerá a seguinte mensagem de erro:
Teste . c : error 33 : Attempt to assign value to a constant variable (=)
Modificadores de posicionamento
As variáveis podem ser declaradas utilizando os modificadores near e far. Estes modificadores
indicam ao compilador em qual região de memória devem ser colocadas as variáveis.
A região near geralmente se refere à “zero page”. É uma região mais fácil de ser acessada. A
região far exige mais tempo para executar a mesma função que a near.
Podemos pensar nestas regiões como a memória RAM e a memória Cache do computador.
A segunda é mais rápida, mas possui um alto custo e por isso geralmente é menor. Em algumas
situações é interessante que algumas variáveis nunca saiam do cache, pois são utilizadas com
grande frequência ou são críticas para o sistema.
Modificador de persistência
Em geral, as variáveis utilizadas dentro das funções perdem seu valor ao término da função. Para
que este valor não se perca podemos utilizar um modificador de persistência: static. Com esse
modificador a variável passa a possuir um endereço fixo de memória dado pelo compilador. Além
Notas de Aula ELT024 - Programação para Sistemas Embarcados
18 Introdução
Programa 1.4: Operações aritméticas com tipos diferentes
1 void main (void )
2 {
3 char var08 ;
4 int var16 ;
5 long int var32 ;
6 f loat pont16 ;
7 double pont32 ;
8 var8 = var8 + var16 ; // 1
9 var8 = var8 + var8 ; // 2
10 var16 = var8 ∗ var8 ; // 3
11 var32 = var32 / var16 ; // 4
12 var32 = pont32 ∗ var32 ; // 5
13 pont16 = var8 / var16 ; // 6
14 pont16 = pont32 ∗ var32 ; // 7
15 pont16 = 40 / 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 p e r s i s t e n t e que é
// incrementado a cada chamada de função
int ContadorPersistente ( int reseta )
{
stat ic char variavel_persistente ;
i f ( reseta )
{
variavel_persistente = 0 ;
}
else
{
return ( variavel_persistente++);
}
return −1;
}
. Operações aritméticas
“If people do not believe that mathematics is simple, it is only be-
cause they do not realize how complicated life is.” - John Louis von
Neumann
Um cuidado a se tomar, na programação em C para sistemas embarcados, é o resultado de
operações aritméticas. Por padrão na linguagem C o resultado de uma operação aritmética
possui tamanho igual ao maior operando. Observando o Programa 1.4 notamos alguns exemplos.
No caso 1 (linha 8) uma variável char somada a um int gera como resultado um int (maior
operando). Não é possível armazenar esse resultado num char, haverá perda de informação.
var32 = var8 + var16 ; // 1 c o r r i g i d o
A soma de dois char, conforme a linha 9, segundo caso pode gerar um problema se ambos
forem muito próximo do valor limite. Por exemplo: 100 + 100 = 200, que não cabe num char,
já que este só permite armazenar valores de -128 à 127.
var16 = var8 + var8 ; // 2 c o r r i g i d o
Notas de Aula ELT024 - Programação para Sistemas Embarcados
19 Introdução
O terceiro caso (linha 10) está correto, a multiplicação de dois char possui um valor máximo
de 127*127=16.129. O problema é que a multiplicação de dois char gera um outro char, perdendo
informação. É necessário realizar um typecast antes.
var16 = (( int ) var8 ) ∗ var8 ; // 3 c o r r i g i d o
O quarto caso (linha 11) pode apresentar um problema de precisão. A divisão de dois inteiros
não armazena parte fracionária. Se isto não for crítico para o sistema está correto. Lembrar que
a divisão de números inteiros é mais rápida que de números fracionários.
O quinto caso (linha 12) pode apresentar um problema de precisão. O resultado da conta de
um número inteiro com um ponto flutuante é um ponto flutuante. Armazenar esse valor num
outro número inteiro gera perda de informação.
O sexto caso (linha 13) apresenta um problema muito comum. A divisão de dois números
inteiros gera um número inteiro. Não importa se armazenaremos o valor numa variável de ponto
flutuante haverá perda de informação pois os operandos são inteiros. Para evitar esse problema
é necessário um typecast.
pont16 = (( f loat ) var8 ) / var16 ; // 6 c o r r i g i d o
No sétimo caso (linha 14) pode haver perda de precisão pois o resultado da operação é um
double, e estamos armazenando este valor num float.
O oitavo caso (linha 15) é similar ao sexto. Estamos realizando uma conta com dois números
inteiros esperando que o resultado seja 0,5. Como os operandos são inteiros a expressão será
avaliada como resultante em Zero. Uma boa prática é sempre usar “.0” ou “f” após o número
para indicar operações com vírgula.
pont16 = 40f / 8 0 . 0 ; // 8 c o r r i g i d o
Devemos tomar cuidado também com comparações envolvendo números com ponto flutuante.
f loat x = 0 . 1 ;
while (x != 1 . 1 ) {
printf ("x = %f\n" , x ) ;
x = x + 0 . 1 ;
}
O trecho de código acima apresenta um loop infinito. Como existem restrições de precisão nos
números de ponto flutuante (float e double) nem todos os números são representados fielmente.
Os erros de arredondamento podem fazer com que a condição (x !=1.1) nunca seja satisfeita.
Sempre que houver a necessidade de comparação com números de ponto flutuante utilizar maior,
menor ou variações.
f loat x = 0 . 1 ;
while (x < 1 . 1 ) {
printf ("x = %f\n" , x ) ;
x = x + 0 . 1 ;
}
Apesar de sutis estes tipos de erro podem causar um mau funcionamento do sistema. Na
Figura 1.10 é apresentado um erro gerado através de um loop infinito.
. Função main()
Todo sistema necessita de iniciar em algum lugar. Em geral, os microcontroladores, assim que
ligados, procuram por suas instruções no primeiro ou último endereço de memória, dependendo
da arquitetura utilizada. O espaço de memória disponível neste endereço é geralmente muito
Notas de Aula ELT024 - Programação para Sistemas Embarcados
20 Introdução
Figura 1.10: Loop infinito de um device driver gerando erro no sistema
pequeno, apenas o necessário para inserir uma instrução de pulo e o endereço onde está a função
principal. Este espaço é conhecido como posição de reset. Existem ainda outros espaços de
memória similares a este que, geralmente, são alocados próximos. O conjunto destes espaços é
conhecido como vetor de interrupção (Figura 1.11).
0x58 Testa A
0x57 30
0x56 A recebe
0x55 Limpa A
0x59 ...
0x8D Porta B
0x8C Salva em
0x8B 50
0x8A A recebe
0x8E ...
0x03 0x55
0x02 Pulo
0x01 0x8A
0x04 ...
0x00 Pulo
Endereço Instrução
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 SistemasEmbarcados
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 )
{
// aqui 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 infinito. Abaixo estão as duas alternativas mais utilizadas.
void main (void )
{
for ( ; ; )
{
// aqui entra o
// código p r i n c i p a l
}
}
void main (void )
{
while (1 )
{
// aqui entra o
// código p r i n c i p a l
}
}
. Rotinas de tempo
“Time is an illusion, lunchtime doubly so.” - Ford Prefect
É muito comum necessitar que o microcontrolador fique um tempo sem fazer nada. Uma maneira
de atingir esse objetivo é utilizar um laço FOR2.
unsigned char i ;
for (i=0; i < 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ódigo em assemb ler e q u i v a l e n t e à f o r ( i =0; i <10; i++);
MOVF r0x00 , W // i n i c i a l i z a W com 0 (1 c i c l o )
SUBLW 0x0a // co loca o va l o r 10 (0 x0a ) no r e g i s t r o W (1 c i c l o )
MOVWF r0x00 //muda o va l o r de W para F (1 c i c l o )
_00107_DS_ :
DECFSZ r0x00 , F //decrementa F, se F > 0 executa a próxima l i nha (1 c i c l o )
BRA _00107_DS_ //" pu la " para o luga r marcado como _00107_DS_ (2 c i c l o s )
1Em sistemas mais complexos algumas tarefas são executadas independentemente da função principal, tendo
sua execução controlada através de interrupções.
2Este 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=0; i < 34 ; i++) //3 + 34 ∗ (30.003 + 3) = 1.020 .207 i n s t r u çõ e s
{
for (j=0; j < 100 ; j++) //3 + 100 ∗ (297 + 3) = 30.003 i n s t r u çõ e s
{
for (k=0; k < 98 ; k++); // 3 + 98 ∗ (3) = 297 in s t r u çõ e s
}
}
O código acima foi projetado para gerar um atraso de tempo de meio segundo. Compilando
e realizando testes práticos podemos confirmar que o tempo real é aproximadamente 0.51 (s).
Esta discrepância acontece porque agora temos 3 loops encadeados e cada qual com sua variável
de controle. Deste modo o compilador precisa salvar e carregar cada variável para realizar a
comparação.
Percebemos assim que para conhecer corretamente o funcionamento do sistema é necessário,
em algumas situações, abrir o código em assembler gerado pelo compilador para entender como
este é executado. Nem sempre o compilador toma as mesmas decisões que nós. Além disso ele
pode gerar otimizações no código. Existem dois tipos de otimização: uma visando diminuir o
tempo de execução do sistema, deixando-o mais rápido e outra que reduz o tamanho do código
final, poupando espaço na memória.
A seguir apresentamos um exemplo de função que gera delays com tempo parametrizado.
void delay (unsigned int DL )
{
unsigned char i , j , k ;
while (DL−−) // executa DL veze s .
{
for (i=0; i < 34 ; i++) //3 + 34 ∗ (30.003 + 3) = 1.020 .207 i n s t r u çõ e s
{
for (j=0; j < 100 ; j++) //3 + 100 ∗ (297 + 3) = 30.003 i n s t r u çõ e s
{
for (k=0; k < 98 ; k++); // 3 + 98 ∗ (3) = 297 in s t r u çõ e s
}
}
}
}
. Operações com bits
“All of the books in the world contain no more information than is
broadcast as video in a single large American city in a single year.
Not all bits have equal value.” - Carl Sagan
Nos sistemas microcontrolados, existem algumas variáveis onde cada bit tem uma interpretação
3Este valor só é valido quando estamos trabalhando com variáveis char. Se utilizarmos variáveis int o código
em assembler será diferente e teremos que realizar uma nova análise.
4Para 8MHz, 1 ciclo = 0.125µs. No PIC, cada instrução precisa de 4 ciclos de clock, portanto 0.5µs.
Notas de Aula ELT024 - Programação para Sistemas Embarcados
23 Introdução
ou funcionalidade diferente. Por isso é necessário realizar algumas operações que modifiquem
apenas os bits desejados, mantendo o restante dos bits da variável inalterados.
As operações da linguagem C que nos permitem trabalhar com as variáveis, levando em conta
os valores individuais de cada bit, são chamadas de bitwise operation.
É importante ressaltar que as operações de bitwise possuem funcionalidade semelhante a suas
respectivas operações lógicas. A diferença é que a lógica opera em cima da variável como um
todo5 enquanto a bitwise opera bit à bit.
NOT
A operação NOT lógica retorna ’1’ (um) se o valor for ’0’ (zero) e ’0’ se o valor for ’1’.
A !A
0 1
1 0
A operação bitwise NOT (operador ˜) executa uma NOT lógica. Isso significa que a operação é
realizada para cada um dos bits da variável, não mais para a variável como um todo. Na tabela
seguinte é apresentada a diferença entre as duas operações.
Declaração Lógico Bitwise
char A = 12 ;
// A = 0b00001100
result = ! A ;
// r e s u l t = 0
result = ~A ;
// r e s u l t = 243
// A = 0b00001100
// r = 0b11110011
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 ;
// A = 0b00001000
char B = 5 ;
// B = 0b00000101
result = A && B ;
// r e s u l t = 1
result = A & B ;
// r e s u l t = 0
// A = 0b00001000
// B = 0b00000101
// r = 0b00000000
5Lembrar 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 ;
// A = 0b00001000
char B = 5 ;
// B = 0b00000101
result = A | | B ;
// r e s u l t = 1
result = A | B ;
// r e s u l t = 13
// A = 0b00001000
// B = 0b00000101
// r = 0b00001101
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 BA ⊕ 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 ;
// A = 0b00001000
char B = 5 ;
// B = 0b00000101
// não e x i s t e em C
result = A ^ B ;
// r e s u l t = 13
// A = 0b00001000
// B = 0b00000101
// r = 0b00001101
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
char A = 8 ;
// A = 0b00001000
result = A << 2 ;
// r e s u l t = 32
// A = 0b00001000
// r = 0b00100000
result = A >> 2 ;
// r e s u l t = 2
// A = 0b00001000
// r = 0b00000010
Para variáveis unsigned e inteiras, esta operação funciona como a multiplicação/divisão por
potência de dois. Cada shift multiplica/divide por 2 o valor. Esta é uma prática muito comum
para evitar a divisão que na maioria dos sistemas embarcados é uma operação cara do ponto de
vista de tempo de processamento.
Não utilizar esta operação com o intuito de multiplicar/dividir variáveis com ponto fixo ou
flutuante nem variáveis sinalizadas (signed).
Em diversas ocasiões é necessário que trabalhemos com os bits de maneira individual, prin-
cipalmente quando estes bits representam saídas ou entradas digitais, por exemplo chaves ou
leds.
Supondo que temos 8 leds ligados ao microcontrolador. Cada led é representado através de 1
bit de uma variável. Para ligarmos ou desligarmos apenas um led por vez, não alterando o valor
dos demais, devemos nos utilizar de alguns passos de álgebra digital.
Ligar um bit (bit set)
Para ligar apenas um bit, utilizaremos uma operação OU. Supondo dois operandos A e B. Se A
é 1 o resultado de (A | B) é 1 independente de B. Se A é 0 o resultado é igual ao valor de B.
Se o objetivo é ligar apenas o bit da posição X devemos criar um valor onde todas as posições
são 0's com exceção da posição desejada. Para uma máscara binária de N bits temos (N>=X):
Posição N . . . X+1 X X-1 . . . 0
Valor 0 ... 0 1 0 ... 0
Se a operação OR for executada com a máscara criada, o resultado apresentará valor 1 na posição
X e manterá os valores antigos para as demais posições. Exemplo: Ligar apenas o bit 2 da variável
PORTD
// de f i n e ' s para por ta s de entrada e sa ída
#define PORTD (∗( volat i le near unsigned char∗) 0xF83 )
#define TRISD (∗( volat i le near unsigned char∗) 0xF95 )
// i n i c i o do programa
void main (void )
{
char mascara ; // v a r i á v e l que guarda a máscara
TRISD = 0x00 ; // con f i gura a por ta D como sa ída
PORTD = 0x00 ; // l i g a todos os l e d s ( l ó g i c a nega t i va )
// l i g a o pr imeiro b i t da v a r i á v e l
mascara = 1 ; // b i t = 0b00000001
// rotac iona−se a v a r i á v e l para que o b i t 1 chegue na pos ição dese jada
mascara = mascara << 2 ; // b i t = 0b00000100
// Ligar o b i t 2 , d e s l i g ando o 3o l e d
PORTD = PORTD | mascara ;
//mantém o s is tema l i g a d o inde f in idamente
for ( ; ; ) ;
}
Notas de Aula ELT024 - Programação para Sistemas Embarcados
26 Introdução
Desligar um bit (bit clear)
Para desligar apenas um bit o procedimento é similar ao utilizado para ligar. Ao invés de utilizar-
mos uma operação OU, utilizaremos uma operação AND. A operação AND tem a característica
de, dados A e B valores binários, se A é 1, a resposta de (A & B) será o próprio valor de B, se a
A=0, a resposta é zero, independente de B.
Novamente é necessário gerar uma máscara. Mas para esta situação ela deve possuir todos
os bits iguais a um com exceção de X, o bit que queremos desligar.
posição N . . . X+1 X X-1 . . . 0
Valor 1 ... 1 0 1 ... 1
Se a operação AND for executada com a máscara criada, o resultado apresentará valor 0 na
posição X e manterá os valores antigos para as demais posições. Exemplo: Desligar apenas o bit
2 da variável PORTD.
// de f i n e ' s para por ta s de entrada e sa ída
#define PORTD (∗( volat i le near unsigned char∗) 0xF83 )
#define TRISD (∗( volat i le near unsigned char∗) 0xF95 )
// i n i c i o do programa
void main (void )
{
char mascara ; // v a r i á v e l que guarda a máscara
TRISD = 0x00 ; // con f i gura a por ta D como sa ída
PORTD = 0xFF ; // d e s l i g a todos os l e d s ( l ó g i c a nega t i va )
// l i g a o pr imeiro b i t da v a r i á v e l
mascara = 1 ; // mascara = 0b00000001
// rotac iona−se a v a r i á v e l para que o b i t 1 chegue na pos ição dese jada
mascara = mascara << 2 ; // mascara = 0b00000100
// inve r t e−se os v a l o r e s de cada b i t
mascara = ~mascara ; // mascara = 0b11111011
// Des l i ga o b i t 2 , l i g ando o 3o l e d
PORTD = PORTD & mascara ;
//mantém o s is tema l i g a d o inde f in idamente
for ( ; ; ) ;
}
É importante notar que geramos a máscara de maneira idêntica àquela utilizada no caso
anterior, onde todos os valores são zero e apenas o desejado é um. Depois realizamos a inversão
dos valores. Este procedimento é realizado desta maneira porque não sabemos o tamanho da
palavra a ser utilizada no microcontrolador: 8 ou 16 bits. Mesmo assim devemos garantir que
todos os bits obtenham o valor correto, o que é garantido pela operação de negação. A opção de
inicializar a variável com apenas um zero e rotacionar pode não funcionar pois, na maioria dos
sistemas, a função de rotação insere zeros à medida que os bits são deslocados e precisamos que
apenas um valor seja zero.
Trocar o valor de um bit (bit flip)
Para trocar o valor de um bit utilizaremos como artifício algébrico a operação XOR. Dado duas
variáveis binárias A e B , se A é 1, o valor resultante de A XOR B é o oposto do valor de B, se
A=0, a resposta se mantém igual ao valor de B.
Podemos perceber que para trocar o valor de apenas um bit a máscara será idêntica àquela
utilizada para ligar um bit:
posição N . . . X+1 X X-1 . . . 0
Valor 0 ... 0 1 0 ... 0
Se a operação XOR for executada com a máscara criada, o valor na posição X será trocado, de
zero para um ou de um para zero. Exemplo: Trocar o bit 2 e 6 da variável PORTD
Notas de Aula ELT024 - Programação para Sistemas Embarcados
27 Introdução
// de f i n e ' s para por ta s de entrada e sa ída
#define PORTD (∗( volat i le near unsigned char∗) 0xF83 )
#define TRISD (∗( volat i le near unsigned char∗) 0xF95 )
// i n i c i o do programa
void main (void )
{
char mascara ; // v a r i á v e l que guarda a mascara
TRISD = 0x00 ; // con f i gura a por ta D como sa ída
PORTD = 0xF0 ; // d e s l i g a todos os 4 pr imeiros l e d s ( l ó g i c a nega t i va )
// l i g a o pr imeiro b i t da v a r i á v e l
mascara = 1 ; // mascara = 0b00000001
// rotac iona−se a v a r i á v e l para que o b i t 1 chegue na pos ição dese jada
mascara = mascara << 2 ; // mascara = 0b00000100
//Liga o b i t 2 , d e s l i g ando o 3o l e d
PORTD = PORTD ^ mascara ;
// l i g a o pr imeiro b i t da v a r i á v e l
mascara = 1 ; // mascara = 0b00000001
// rotac iona−se a v a r i á v e l para que o b i t 1 chegue na pos ição dese jada
mascara = mascara << 6 ; // mascara = 0b01000000
// Des l i ga o b i t 6 , l i g ando o 7o l e d
PORTD = PORTD ^ mascara ;
//mantém o s is tema l i g a d o inde f in idamente
for ( ; ; ) ;
}
Percebemos através do exemplo que a utilização do procedimento apresentado troca o valor
do bit escolhido. Foi utilizado o mesmo procedimento duas vezes. Na primeira, um bit foi ligado
e, na segunda, outro foi desligado.
Verificar o estado de um bit (bit test)
Para verificar se o bit X está um utilizaremos novamente a mesma máscara utilizada para bit set
e bit toggle:
posição N . . . X+1 X X-1 . . . 0
Valor 0 ... 0 1 0 ... 0
Realizamos então uma operação AND com a variável. O resultado será zero se o bit X, da
variáveloriginal, for zero. Se o bit da variável original for um a resposta será diferente de zero6.
Exemplo: Testar o bit 2 da variável PORTD
// de f i n e ' s para por ta s de entrada e sa ída
#define PORTD (∗( volat i le near unsigned char∗) 0xF83 )
#define TRISD (∗( volat i le near unsigned char∗) 0xF95 )
// i n i c i o do programa
void main (void )
{
char mascara ; // v a r i á v e l que guarda a mascara
char teste ;
TRISD = 0x00 ; // con f i gura a por ta D como sa ída
teste = 0x00 ; // d e s l i g a todos os b i t s
// rodar depo i s o mesmo programa com os b i t s l i g a d o s .
// t e s t e = 0 x f f ;
// c r i a uma v a r i á v e l onde APENAS o primeiro b i t é 1
mascara = 1 ; // mascara = 0b00000001
// rotac iona−se a v a r i á v e l para que o b i t 1 chegue na pos ição dese jada
mascara = mascara << 2 ; // mascara = 0b00000100
// Ver i f i c a apenas o b i t 2
i f ( teste & mascara )
{
PORTD = 0x00 ; // se o r e su l t a do f o r verdade i ro l i g a todos os l e d s
6A 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 = 0xff ; // se o r e su l t a do f o r f a l s o d e s l i g a todos os l e d s
}
//mantém o s is tema l i g a d o inde f in idamente
for ( ; ; ) ;
}
Criando funções através de define’s
Uma opção no uso de define’s é criar funções simples que podem ser escritas em apenas uma
linha. Utilizando um pouco de algebrismo e parênteses, é possível escrever as quatro operações
anteriores numa única linha. De posse desta simplificação podemos criar uma função para facilitar
o uso destas operações através de um define conforme podemos ver nas tabelas 1.6, 1.7, 1.8 e
1.9.
Tabela 1.6: Operação bit set com define
Operação Bit set
Passo a Passo
char bit = 2 ;
char mascara ;
mascara = 1 << bit ;
arg = arg | mascara ;
//em 1 l i nha
arg = arg | (1<<bit ) ;
//ou
arg |= (1<<bit ) ;
Exemplo de uso
//Ligando o b i t 2 da por ta D
PORTD = PORTD | (1<<2) ;
//ou
PORTD |= (1<<2) ;
Com define #define BitSet ( arg , b i t ) ( ( arg ) |= (1<<b i t ) )
Exemplo de uso com de-
fine
//Ligando o b i t 2 da por ta D
BitSet ( PORTD , 2 ) ;
Notas de Aula ELT024 - Programação para Sistemas Embarcados
29 Introdução
Tabela 1.7: Operação bit clear com define
Operação Bit clear
Passo a Passo
char bit = 2 ;
char mascara ;
mascara = 1 << bit ;
arg = arg & ~mascara ;
//em 1 l i nha
arg = arg & ~(1<<bit ) ;
//ou
arg &= ~(1<<bit ) ;
Exemplo de uso
//Des l igando o b i t 2 da porta D
PORTD = PORTD & ~(1<<2) ;
//ou
PORTD &= ~(1<<2) ;
Com define #define BitClr ( arg , b i t ) ( ( arg ) &= ~(1<<b i t ) )
Exemplo de uso com de-
fine
//Des l igando o b i t 2 da porta D
BitClr ( PORTD , 2 ) ;
Notas de Aula ELT024 - Programação para Sistemas Embarcados
30 Introdução
Tabela 1.8: Operação bit flip com define
Operação Bit flip
Passo a Passo
char bit = 2 ;
char mascara ;
mascara = 1 << bit ;
arg = arg ^ mascara ;
//em 1 l i nha
arg = arg ^ (1<<bit ) ;
//ou
arg ^= (1<<bit ) ;
Exemplo de uso
//Trocando o va l o r do b i t 2 da porta D
PORTD = PORTD ^ (1<<2) ;
//ou
PORTD ^= (1<<2) ;
Com define #define BitFlp ( arg , b i t ) ( ( arg ) ^= (1<<b i t ) )
Exemplo de uso com de-
fine
//Trocando o va l o r do b i t 2 da porta D
BitFlp ( PORTD , 2 ) ;
Notas de Aula ELT024 - Programação para Sistemas Embarcados
31 Introdução
Tabela 1.9: Operação bit test com define
Operação Bit test
Passo a Passo
char bit = 2 ;
char mascara ;
mascara = 1 << bit ;
i f ( arg & mascara )
//em 1 l i nha
i f ( arg & (1<<bit ) )
Exemplo de uso
//Testando o b i t 2 da por ta D
i f ( PORTD | (1<<2) )
{
// . . .
}
Com define #define BitTst ( arg , b i t ) ( ( arg ) & (1<<b i t ) )
Exemplo de uso com de-
fine
//Testando o b i t 2 da por ta D
i f ( BitTst ( PORTD , 2 ) )
{
// . . .
}
Notas de Aula ELT024 - Programação para Sistemas Embarcados
32 Introdução
. Debug de sistemas embarcados7
“In the beginner's mind there are many possibilities; in the expert's
mind there are few.” - Shunryu Suzuki
A verificação de sistemas embarcados apresenta algumas restrições e de modo geral não é possível
inferir sobre a operação do sistema sem paralisá-lo. Como este tipo de sistema possui vários
dispositivos agregados, que funcionam independentemente do processador, é necessário utilizar
abordagens diferentes para realizar o debug.
Devemos lembrar que além do software devemos levar em conta possíveis problemas advindos
do hardware. Debounce, tempo de chaveamento, limite do barramento de comunicação são
exemplos de pontos a serem considerados no momento de depuração.
Externalizar as informações
A primeira necessidade é conhecer o que está acontecendo em teu sistema. Na programação
tradicional para desktop é comum utilizarmos de mensagens no console avisando o estado do
programa.
#include "stdio.h"
#include "serial.h"
// i n i c i o do programa
int main ( int argc , char∗ argv [ ] )
{
printf ("Inicializando sistema" ) ;
i f ( CheckForData ( ) )
{
printf ("Chegou informacao" ) ;
}
else
{
printf ("Problemas na comunicacao" ) ;
}
return 0 ;
}
Devemos ter em mente onde é necessário colocar estes alertas e lembrar de retirá-los do código
final.
Para a placa em questão utilizaremos o barramento de leds que está ligado à porta D. A ope-
ração deste dispositivo será estudada posteriormente em detalhes. Por enquanto basta sabermos
que cada bit da variável PORTD está ligada a um led diferente. Por causa da construção física
da placa, o led é aceso com valor 0 (zero) e desligado com o valor 1 (um). Além disso temos que
configurar a porta D. Isto é feito iniciando a variável TRISD com o valor 0x008.
// de f i n e ' s para por ta s de entrada e sa ída
#define PORTD (∗( volat i le near unsigned char∗) 0xF83 )
#define TRISD (∗( volat i le near unsigned char∗) 0xF95 )
// i n i c i o do programa
void main (void )
{
// conf igurando todos os pinos como sa ída s
TRISD = 0x00 ;
PORTD = 0xFF ; // d e s l i g a todos os l e d s
// l i g a apenas o b i t 1 .
BitClr ( PORTD , 1 ) ;
//mantém o s is tema l i g a d o inde f in idamente
for ( ; ; ) ;
7Mais informações sobre debug de sistemas embarcados referir ao artigo “The ten secrets of embedded debug-
ging” de Stan Schneider e Lori Fraleigh
8As variáveis PORTD e TRISD são definidas como unsigned char e possuem portanto 8 bits.
Notas de Aula ELT024 - Programação para Sistemas Embarcados
33 Introdução
}
Devemos utilizar os leds como sinais de aviso para entendermos o funcionamento do programa.
Isto pode ser feito através das seguintes ideias: “Se passar desta parte liga o led X”, “Se entrar
no IF liga o led Y, se não entrar liga o led Z”, “Assim que sair do loop liga o led W”.
Programação incremental
Ao invés de escrever todo o código e tentar compilar, é interessante realizar testes incrementais.
A cada alteração no código realizar um novo teste. Evitar alterar o código em muitos lugares
simultaneamente, no caso de aparecer um erro fica mais difícil saber onde ele está.
Checar possíveis pontos de Memory-leak
Se for necessário realizar alocação dinâmica garantir que todas as alocações são liberadas em
algum ponto do programa.
Cuidado com a fragmentação da memória
Sistemas com grande frequência na alocação/liberação de memória podem fragmentar a memória
até o ponto de inviabilizar os espaços livres disponíveis, eventualmente travando o sistema.
Quando trabalhar com rotinas de nível mais baixo, mais próximo ao hardware, tente utilizar
apenas mapeamento estático de memória.
Otimização de código
Apenas se preocupe com otimização se estiver tendo problemas com o cumprimento de tarefas.
Mesmo assim considere em migrar para uma

Continue navegando