Baixe o app para aproveitar ainda mais
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
Compartilhar