Curso de C com microcontroladores MCF51QE128 e MC9S08QE128 – Parte 3

Curso de C com MCF51QE128 MC9S08QE128

ÍNDICE DE CONTEÚDO

MATERIAL DESENVOLVIDO PARA TREINAMENTOS DURANTE O LANÇAMENTO DA LINHA FLEXIS DA FREESCALE EM 2009.

Uma breve introdução à linguagem C

Fonte de consulta (http://pt.wikipedia.org/wiki/c_(linguagem_de_programaçao))

C é uma linguagem de programação compilada de propósito geral, estruturada, imperativa, procedural, de alto nível, e padronizada, criada em 1972, por Dennis Ritchie, no AT&T Bell Labs, para desenvolver o sistema operacional UNIX (que foi originalmente escrito em Assembly). A linguagem C é classificada de alto nível pela própria definição desse tipo de linguagem. A programação em linguagens de alto nível tem como característica não ser necessário conhecer o processador, ao contrário das linguagens de baixo nível. As linguagens de baixo nível estão fortemente ligadas ao processador. A linguagem C permite acesso de baixo nível com a utilização de código Assembly no meio do código fonte. Assim, o baixo nível é realizado por Assembly e não C. Desde então, espalhou-se por muitos outros sistemas, e tornou-se uma das linguagens de programação mais usadas, e influenciou muitas outras linguagens, especialmente C++, que foi originalmente desenvolvida como uma extensão para C.

História

Ken Thompson e Dennis Ritchie (da esquerda pra direita), os criadores das linguagens B e C, respectivamente.
Figura 1 -  Ken Thompson e Dennis Ritchie (da esquerda pra direita), os criadores das linguagens B e C, respectivamente.

O desenvolvimento inicial de C, ocorreu no AT&T Bell Labs, entre 1969 e 1973. Deu-se o nome “C” à linguagem, porque muitas de suas características derivaram da linguagem B.

C foi originalmente desenvolvido, para a implementação do sistema UNIX (originalmente escrito em PDP-7 Assembly, por Dennis Ritchie e Ken Thompson). Em 1973, com a adição do tipo struct, C tornou-se poderoso o bastante para a maioria das partes do Kernel do UNIX, serem reescritas em C. Este foi um dos primeiros sistemas que foram implementados em uma linguagem, que não o Assembly, sendo exemplos anteriores, os sistemas: Multics (escrito em PL/I) e TRIPOS (escrito em BCPL). Segundo Ritchie, o período mais criativo ocorreu em 1972.

K&R C

Em 1978, Brian Kernighan e Dennis Ritchie publicaram a primeira edição do livro The C Programming Language. Esse livro, conhecido pelos programadores de C, como “K&R”, serviu durante muitos anos como uma especificação informal da linguagem. A versão da linguagem C que ele descreve é usualmente referida como “K&R C”. A segunda edição do livro, cobriu o padrão posterior, o ANSI C. K&R C introduziu as seguintes características na linguagem:

  • Biblioteca padrão de E/S;
  • Tipos de dado struct;
  • Tipos de dado long int;
  • Tipos de dado unsigned int;
  • O operador =+ foi alterado para +=, e =- para -= (o analisador léxico do compilador fazia confusão entre i =+ 10 e i = +10. O mesmo acontecia com =-).

K&R C é freqüentemente considerado a parte mais básica da linguagem, cujo suporte deve ser assegurado por um compilador C. Durante muitos anos, mesmo após a introdução do padrão ANSI C, K&R C foi considerado o “menor denominador comum”, em que programadores de C se apoiavam quando uma portabilidade máxima era desejada, já que nem todos os compiladores eram atualizados o bastante para suportar o padrão ANSI C. Nos anos que se seguiram à publicação do K&R C, algumas características “não-oficiais” foram adicionadas à linguagem, suportadas por compiladores da AT&T e de outros vendedores. Estas incluíam:

  • Funções void e tipos de dados void *;
  • Funções que retornam tipos struct ou union;
  • Campos de nome struct num espaço de nome separado para cada tipo struct;
  • Atribuição a tipos de dados struct;
  • Qualificadores const para criar um objecto só de leitura;
  • Biblioteca padrão, que incorpora grande parte da funcionalidade implementada por vários vendedores;
  • Enumerações.

Cálculos de ponto flutuante em precisão simples (no K&R C, os cálculos intermediários eram feitos sempre em double, porque era mais eficiente na máquina onde a primeira implementação do C foi feita).

ANSI C e ISO C

Durante o final da década de 1970, a linguagem C começou a substituir a linguagem BASIC como a linguagem de programação de microcomputadores mais usada. Durante a década de 1980, foi adaptada para uso no PC IBM, e a sua popularidade começou a aumentar significativamente. Ao mesmo tempo, Bjarne Stroustrup, juntamente com outros nos laboratórios Bell, começou a trabalhar num projecto onde se adicionavam construções de linguagens de programação orientada por objectos à linguagem C. A linguagem que eles produziram, chamada C++, é nos dias de hoje a linguagem de programação de aplicações mais comum no sistema operativo Windows da companhia Microsoft; C permanece mais popular no mundo UNIX.

Em 1983, o instituto norte-americano de padrões (ANSI) formou um comitê, X3J11, para estabelecer uma especificação do padrão da linguagem C. Após um processo longo e árduo, o padrão foi completo em 1989 e ratificado como ANSI X3.159-1989 “Programming Language C”. Esta versão da linguagem é frequentemente referida como ANSI C. Em 1990, o padrão ANSI C, após sofrer umas modificações menores, foi adotado pela Organização Internacional de Padrões (ISO) como ISO/IEC 9899:1990, também conhecido como C89 ou C90. Um dos objetivos do processo de padronização ANSI C foi o de produzir um sobreconjunto do K&R C, incorporando muitas das características não-oficiais subsequentemente introduzidas. Entretanto, muitos programas tinham sido escritos e que não compilavam em certas plataformas, ou com um certo compilador, devido ao uso de bibliotecas de funções não-padrão e ao fato de alguns compiladores não aderirem ao ANSI C.

C99

Após o processo da padronização ANSI, as especificações da linguagem C permaneceram relativamente estáticas por algum tempo, enquanto que a linguagem C++ continuou a evoluir. (em 1995, a Normative Amendment 1 criou uma versão nova da linguagem C mas esta versão raramente é tida em conta.) Contudo, o padrão foi submetido a uma revisão nos finais da década de 1990, levando à publicação da norma ISO 9899:1999 em 1999. Este padrão é geralmente referido como “C99”. O padrão foi adoptado como um padrão ANSI em Março de 2000. As novas características do C99 incluem:

  • funções em linha;
  • remoção de restrições sobre a localização da declaração de variáveis (como em C++);
  • adição de vários tipos de dados novos, incluindo o long long int (para minimizar problemas na transição de 32-bits para 64-bits), um tipo de dados boolean explicito (chamado _Bool) e um tipo complex que representa números complexos;
  • vetores de dados de comprimento variável (o vetor pode ter um tamanho diferente a cada execução de uma função, mas não cresce depois de criado);
  • suporte oficial para comentários de uma linha iniciados por //, emprestados da linguagem C++;
  • várias funções de biblioteca novas, tais como snprintf();
  • vários ficheiros-cabeçalho novos, tais como stdint.h.

O interesse em suportar as características novas de C99 parece depender muito das entidades. Apesar do GCC e vários outros compiladores suportarem grande parte das novas características do C99, os compiladores mantidos pela Microsoft e pela Borland suportam pouquíssimos recursos do C99, e estas duas companhias não parecem estar muito interessadas em adicionar tais funcionalidades, ignorando por completo as normas internacionais. A Microsoft parece preferir dar mais ênfase ao C++.

Visão Geral

C é uma linguagem imperativa e procedural, para implementação de sistemas. Seus pontos de design foram para ele ser compilado, fornecendo acesso de baixo nível à memória e baixos requerimentos do hardware. Também foi desenvolvido para ser uma linguagem de alto nível, para maior reaproveitamento do código. C foi útil para muitas aplicações que foram codificadas originalmente em Assembly.

Essa propriedade não foi acidental; a linguagem C foi criada com o objetivo principal em mente: facilitar a criação de programas extensos com menos erros, recorrendo ao paradigma da programação algorítmica ou procedimental, mas sobrecarregando menos o autor do compilador, cujo trabalho complica-se ao ter de realizar as características complexas da linguagem. Para este fim, a linguagem C possui as seguintes características:

  • Uma linguagem nuclear extremamente simples, com funcionalidades não-essenciais, tais como funções matemáticas ou manuseamento de arquivos, fornecida por um conjunto de bibliotecas de rotinas padronizada;
  • A focalização no paradigma de programação procedimental;
  • Um sistema de tipos simples que evita várias operações que não fazem sentido
  • Uso de uma linguagem de pré-processamento, o pré-processador de C, para tarefas tais como a definição de macros e a inclusão de múltiplos ficheiros de código fonte;
  • Ponteiros dão maior flexibilidade à linguagem;
  • Acesso de baixo-nível, através de inclusões de código Assembly no meio do programa C;
  • Parâmetros que são sempre passados por valor para as funções e nunca por referência (É possível simular a passagem por referência com o uso de ponteiros);
  • Definição do alcance lexical de variáveis;
  • Estruturas de variáveis, (structs), que permitem que dados relacionados sejam combinados e manipulados como um todo.

Algumas características úteis, que faltam em C, podem ser encontradas em outras linguagens, que incluem:

  • Segurança de tipo;
  • Coletor de lixo (mais comum em linguagens interpretadas);
  • Vetores que crescem automaticamente;
  • Classes ou objectos com comportamento (ver orientação a objetos);
  • Closures (a closure is a function that is evaluated in an environment containing one or more bound variables. When called, the function can access these variables);
  • Funções aninhadas;
  • Programação genérica;
  • Sobrecarga de operadores;
  • Meta-programação;
  • Apoio nativo de multithreading e comunicação por rede.

Apesar da lista de características úteis que C não possui, ser longa, isso não tem sido um impedimento à sua aceitação, pois isso permite que novos compiladores de C sejam escritos rapidamente para novas plataformas, e também permite que o programador permaneça sempre em controle do que o programa está a fazer. Isto é o que por várias vezes permite o código de C correr de uma forma mais eficiente que muitas outras linguagens. Tipicamente, só código de Assembly “afinado à mão” é que corre mais rapidamente, pois possui um controle completo da máquina, mas avanços na área de compiladores juntamente com uma nova complexidade nos processadores modernos permitiram que a diferença tenha sido rapidamente eliminada.

Uma consequência da aceitação geral da linguagem C é que frequentemente os compiladores, bibliotecas e até intérpretes de outras linguagens de nível maior sejam eles próprios implementados em C.

C tem como ponto forte, a sua eficiência, e é a linguagem de programação preferida para o desenvolvimento de sistemas e softwares de base, apesar de também ser usada para desenvolver programas de computador. É também muito usada no ensino de ciência da computação, mesmo não tendo sido projetada para estudantes e apresentando algumas dificuldades no seu uso. Outra característica importante de C, é sua proximidade do código de máquina, que permite que um projetista seja capaz de fazer algumas previsões de como o software irá se comportar, ao ser executado.

C tem como ponto fraco, a falta de proteção que dá ao programador. Praticamente tudo que se expressa em um programa em C, pode ser executado, como por exemplo, pedir o vigésimo membro de um vetor com apenas dez membros. Os resultados são muitas vezes totalmente inesperados, e os erros, difíceis de encontrar.

C para Desktop X C para embarcados

O primeiro ponto a ser observado nesta comparação é a quantidade de recursos de memória disponíveis para cada um destes ambientes. É óbvio que sistemas embarcados tem menor quantidade de memória disponível e muitas vezes tem velocidade de processamento bem inferior aos “parrudos” computadores de mesa. Poderíamos citar algumas características que devem ser consideradas em ambientes embarcados:

  • Limitado tamanho para memória de programa ROM;
  • Limitado tamanho de memória RAM;
  • Por consequência das limitações anteriores, seu espaço de pilha também é pequeno;
  • Toda a programação deve ser orientada ao hardware e não simplesmente ao software;
  • A temporização das ações é um fator crítico (rotinas de tratamento de interrupções, tarefas a serem realizadas, etc.);
  • Diferentes tipos de ponteiros existentes (próximo, distante, para ROM, para página única, para múltiplas páginas, etc.);
  • Palavras especiais para o ambiente embarcado (@, interrupt, tiny, ...).

Pode-se concluir que em ambientes embarcados, manipular o hardware é o efeito mais desejado. Não se procura em ambientes embarcados apenas apresentar uma sequência de números ou gráficos coloridos em um display. Muitas vezes o objetivo é colocar um motor para funcionar ou qualquer outra tarefa de tempo real, o que pode necessitar de rotinas de interrupção.

Para que um programa embarcado utilizando linguagem C tenha sucesso ele deve manter o menor código possível e manter a sua eficiência. Para isto, é necessário quebrar alguns regras que são utilizadas na programação focada em desktop.

Em C para desktops, os programadores evitam a todo custo a declaração de variáveis globais. Isto se deve ao fato destas variáveis causarem interação com o hardware do desktop através da BIOS do computador. Já num microcontrolador de 8 bits, você estará escrevendo o programa da BIOS e o sistema operacional do chip! Portanto um acesso global é inevitável!

O uso de ponteiros em C às vezes pode ficar um pouco fora de mão. Ao invés de deixar o compilador simplesmente "tratar" os vários níveis direcionamento dos ponteiros, devemos entender como o compilador manipula os ponteiros e, assim, estruturar o nosso código de para maximizar sua eficiência.

E, finalmente, um dos pecados capitais da programação em linguagem C para desktops deve ser revisto para sistemas embarcados. É difícil admitir isso mas pode haver momentos em que uma instrução GOTO estrategicamente colocada pode resultar em um código mais eficiente.

Em um curso de linguagem C somos orientados do porque de evitar a todo custo a utilização de comandos GOTO ao longo de um código. Isto é porque ele pode rapidamente transformar seu programa em uma espiral "spaghetti", o que resulta em um software que pode ser extremamente difícil de compreender e depurar. Em linguagem C somos ensinados a repensar o problema sempre que nos deparamos com a tentação de usar um GOTO. Mas um GOTO bem documentado pode certamente ser apropriado em determinadas ocasiões.

ANSI C para μCs de 8 e 32 bits

O C ANSI puro não é sempre a melhor opção para sistemas embarcados, porque:

  • O ANSI C tem regras para os tipos de variáveis que matariam todo o desempenho de uma máquina de 8 bits;
  • Sistemas embarcados têm que interagir com o hardware. O ANSI C fornece ferramentas extremamente cruas para tratar registradores de memória em locais fixos;
  • Quase todos os sistemas embarcados usam interrupções;
  • Algumas arquiteturas de MCUs não têm suporte de hardware para um C pilha;
  • Alguns MCUs devem ter múltiplos espaços de memória.

Deve-se utilizar o C padrão, o tanto quanto for possível, no entanto quando ele interferir com a resolução do problema, não hesite em ignorá-lo.

Outra regra a ser quebrada é o uso do ANSI C puro. Se você não é cuidadoso com a declaração dos tipos de suas variáveis e deixa que o compilador tome conta do gerenciamento destes tipos para você, o resultado certamente será um código maior. Isto ocorre principalmente quando você tenta comparar ou trabalhar com variáveis de tamanhos diferentes. Nestes casos, tudo será convertido para o maior tamanho utilizado na operação.

Oficialmente, o C ANSI puro não entende o hardware. Às vezes precisamos usar truque que não pertencem ao ANSI para obter um software que faça o que desejamos. Interrupções são um destes truques. Não há nenhuma norma ANSI C que explique a maneira de lidar com as interrupções.

O ANSI C é uma linguagem baseada em pilha. Ele passa os parâmetros na pilha e mantém as variáveis locais na pilha. Várias arquiteturas de 8-bit do microcontrolador não fornecer uma CPU com um controle necessário de manipulação de pilha, a fim de produzir códigos em linguagem C eficientes.

Compiladores de linguagem C montados para essas arquiteturas usam uma variedade de truques para criar uma pilha artificialmente por software. O resultado final é um software que pode funcionar, mas que é um pouco confuso e onde provavelmente existem características padrão do ANSI C, tais como a reentrada de funções, que o compilador não irá suportar.

Assim, no mínimo, um bom compilador deveria suportar todas as funcionalidades do ANSI C. Extensões são permitidas, mas algumas exclusões podem causar problemas com a portabilidade do código.

E a capacidade de poder escrever funções reentrantes é uma das grandes habilidade linguagem de programação C. A arquitetura da Freescale para os chips HCS08 e V1 foi projetada especificamente para atender as necessidades dos programas e dos compiladores em linguagem C. Estas arquiteturas fornecem o controle de pilha necessário para produzir um código eficiente em C.

O ANSI C também não tem uma forma padrão de endereçamento de memória para chaveamento em bancos. A arquitetura de 16 bits da Freescale HCS12 suporta nativamente esse tipo de abordagem com seu conjunto de opcodes. Mas outras arquiteturas tem a necessidade de fazer o compilador C passar diversos loops a fim de alcançar todos os recursos que estão localizados outros pontos de outros bancos de memória. A lição a ser aprendida aqui é usar o padrão C, sempre que possível. Mas não se deixe que está seja a única forma, o único caminho a seguir. É necessário compreender as extensões da linguagem que estão disponíveis e saber como usá-las.

Assembly versus C?

Um compilador de linguagem C nunca é mais eficiente do que um bom programador de linguagem Assembly.

Porém é muito mais fácil de escrever bom código em C que pode ser convertido para código um eficiente em linguagem Assembly do que escrever um código em Assembly a mão. A linguagem C deve ser encarada como um meio para um fim e não um fim em si mesma.

A conclusão a ser tirada é que a compreensão do hardware a ser utilizado, dos recursos linguísticos disponíveis pode resultar em um melhor código em C. Isto é especialmente verdadeiro para as pequenas arquiteturas de 8 bits.

Dito isto, é muito mais fácil de compreender e escrever uma rotina em C e, em seguida, convertê-lo em linguagem Assembly do que fazer o inverso.

Mas uma desvantagem é que se temos a capacidade de escrever uma rotina malfeita em linguagem C, não haverá compilador, não importa quão bom ele seja, que possa gerar um código de máquina eficiente.

O C não vai salvar uma má programação.

Variáveis, tipos de dados e operadores em C

Palavras reservadas na linguagem C

A linguagem C é do tipo “case sensitive”. E além disto, algumas palavras são reservadas, sendo seu uso limitado apenas as funções ou comandos que elas executam:

O compilador Code Warrior tem também algumas palavras reservadas, o que veremos ao longo deste treinamento.

Tipos de dados

Basicamente apenas 5 tipos de dados que são utilizadas em linguagem C:

  • char – caractere – 8 bits, 1 byte;
  • int – inteiro – 16 bits, 2 bytes;
  • float – ponto flutuante – 32 bits, 4 bytes;
  • double – ponto flutuante de precisão dupla – 64 bits, 8 bytes;
  • void – sem valor.

Cada uma destes tipos pode ainda ter os seguintes modificadores:

  • signed – utilizará o último bit para sinalizar se um dado é positivo ou negativo. Com isto, o dado sempre estará com um tamanho menor em um bit.
  • unsigned – não informa se o dado tem valor positivo ou negativo. Por conseqüência todos os bits do dado podem ser utilizados como informação;
  • short – faz com que o dado passe a ter um tamanho menor do que especificado em sua definição. Por exemplo: utilizar o short int pode fazê-lo assumir o valor de apenas um bit, dependendo do compilador utilizado;
  • long – faz com que o dado passe a ter um tamanho maior do que especificado em sua definição. Por exemplo: utilizar o long int pode fazê-lo assumir o valor de 65536 bits, dependendo do compilador utilizado.

Detalhes sobre números em ponto flutuante

A base numérica no padrão IEEE754 é a binária. Neste padrão são adotados dois formatos para representação de números: precisão simples e precisão dupla. (Na base binária, um dígito binário é denominado bit e um byte é um conjunto de 8 bits).

Ficou estabelecido que no padrão IEEE754, em precisão simples, um número real seria representado por 32 bits, (4 bytes), sendo que:

  • 1 bit é reservado para o sinal do número (positivo ou negativo);
  • 8 bits são reservados para o expoente da base, que é um número inteiro;
  • 23 bits são reservados para a mantissa:

Pelo mesmo padrão IEEE754, em precisão dupla, um número real seria representado por 64 bits, (8 bytes), sendo que:

  • 1 bit é reservado para o sinal do número (positivo ou negativo);
  • 11 bits são reservados para o expoente da base, que é um número inteiro;
  • 52 bits são reservados para a mantissa:

Qualquer valor em ponto flutuante é sempre escrito no seguinte formato:

v = S × M × 2E

Onde:

  • S = 1 − 2 × sinal
  • M = 1 + mantissa × 2-23
  • E = expoente − 127

Como o Code Warrior trata os tipos de dados?

No Code Warrior é possível definir como será tradado cada um dos tipos de dados para o MCU de 8 bits, podendo inclusive fazer com que um dado seja ajustado de modo fora do padrão estabelecido pelo padrão ANSI C. Para isto clique na aba TARGET do projeto, como é mostrado na figura abaixo.

Em seguida dê um duplo clique no target STANDART. Isto fará com que a janela de configurações seja aberta, como mostrado abaixo. Na opção target, selecione o item COMPILER FOR HC08.

Na tela de ajustes clique no botão TYPE SIZE, o que fará com que a janela de ajustes se abra, como mostra a figura abaixo.

Grandes economias no tamanho do código que serão geradas pelo compilador podem ser obtidas através da escolha do tipo correto para cada variável em seu programa:

  • A escolha natural para microcontroladores de 8 bits é que as variáveis tenham 8 bits, ou seja, um byte. Mas na linguagem C a grande preferência é pelo tipo inteiro, de 16 bits.
  • Máquinas de 8 bits conseguem processar dados de 8 bits muito mais eficientemente do que dados de 16 bits.
  • ‘int’ e outros tipos grandes de variáveis deveriam ser utilizados apenas quando o que se quer tratar realmente exija este tamanho.
  • Ponto flutuante e Double são particularmente ineficientes e devem ser evitados toda vez que a performance for importante em um projeto.

Declaração de variáveis

É necessário, durante o fluxo do programa, declarar as variáveis que serão utilizadas de acordo com os tipos de dados mostrados anteriormente. Isto pode ser feito das seguintes maneiras:

TIPO nome_da_variável {,outras_variáveis};
unsigned int tempo;

As variáveis podem ser inicializadas com um determinado valor durante a sua declaração:

unsigned int tempo = 100;

Variáveis locais e globais

Dependendo do local onde é declarada a variável, esta pode assumir uma função global ou local:

  • Variáveis globais são acessíveis de qualquer ponto do programa, por qualquer função, e devem ser declaradas no corpo principal do programa, fora de qualquer função, inclusive fora da função da main;
  • Variáveis locais só podem ser acessadas dentro das funções onde foram criadas. Ao sair desta função, a linguagem C destrói uma variável local e, portanto ela não será acessível por outras funções.

EXEMPLO 01: PROJETO VARIÁVEIS 1

O programa a seguir (EXEMPLO 01) dá uma demonstração de como as variáveis podem ler declaradas localmente ou globalmente:

Comandos printf e scanf no Code Warrior

Alguns comentários importantes sobre o programa EXEMPLO-01:

  • Se você observar no livro original do Fábio Pereira, este exemplo foi feito para utilizar o comando printf. Mas esta função NÃO FUNCIONA NATIVAMENTE COM O CODE WARRIOR!!!
  • O comando printf chama uma biblioteca que emula o envio de dados pela porta serial em qualquer programa de simulação e emulação de microcontroladores. No IAR será possível visualizar estes dados através do Terminal I/O, acessível em: View -> Terminal I/O, onde será aberta a janela mostrada abaixo. Para que ele funcione é necessário incluir a biblioteca padrão stdio.h. Mas lembre-se: isto não está disponível no NATIVAMENTE Code Warrior, DEVENDO SER IMPLEMENTADO ATRAVÉS DE UMA SÉRIE DE COMANDO E EDIÇÃO DE ARQUIVOS.

O formato do comando printf é:

printf (string, variável);
printf (“O número de tentativas foi %d”, contador);

Onde:

  • string: é uma constate de caracteres, sempre declarado entre aspas;
  • variável: é a declaração de uma ou mais variáveis que devem ser impressas juntamente com os caracteres. Para tanto é necessário seguir a formatação %wt, onde w é o número de caracteres que se deseja imprimir e t é uma das seguintes opções:
    • c = caractere;
    • s = string ou caractere;
    • u = inteiro sem sinal;
    • x = inteiro em formato hexadecimal com letras minúsculas;
    • X = inteiro em formato hexadecimal com letras maiúsculas;
    • d = inteiro decimal com sinal;
    • i = inteiro decimal com sinal;
    • o = octal sem sinal;
    • e = ponto flutuante em formato exponencial;
    • f = ponto flutuante em formado decimal;
    • Lx = hexadecimal longo (16 ou 32 bits) com letras minúsculas;
    • LX = hexadecimal longo (16 ou 32 bits) com letras maiúsculas;
    • Lu = decimal longo (16 ou 32 bits) sem sinal;
    • Ld = decimal longo (16 ou 32 bits) com sinal;
    • % = símbolo de porcentagem;

Para que todos os caracteres a serem impressos na janela Terminal I/O não fiquem na mesma linha, são utilizados os caracteres especiais de barra invertida, que são:

  • \b = retrocesso (backspace);
  • \f = alimentação de formulário;
  • \n = nova linha;
  • \r = retorno do carro;
  • \t = tabulação horizontal;
  • \” = aspas duplas;
  • \’ = aspas simples;
  • \0 = nulo;
  • \\ = barra invertida;
  • \v = tabulação vertical;
  • \a = alerta (beep);
  • \N = constante octal, onde N é o número em octal;
  • \xN = constante hexadecimal, onde N é o número em hexadecimal;

Um comando complementar aos printf é o scanf. Enquanto o comando printf, mostrado no exemplo anterior, emula obenvio de dados pela porta serial do software, o scanf emula o recebimento destes dados, também pela porta serial.

Isto também será possível visualizar através do Terminal I/O, acessível no IAR em: View ->
Terminal I/O. Assim como no comando anterior, para que ele funcione também é necessário incluir a biblioteca  padrão stdio.h

O formato do comando scanf é:

scanf (string, variável);
scanf (%d”, contador);

Tendo as mesmas características e controladores vistos no printf.

Comandos prinft e scanf manualmente no Code Warrior

Apesar de não ser reconhecido como um comando nativo do Code Warrior, o manual do simulador deste software traz uma informação referente ao uso do printf, como pode ser visto na figura a seguir.

Trecho do manual do simulador do Code Warrior que cita o comando printf:

Mas aonde este comando pode ser aplicado, já que se você o colocar dentro de seu código ele gerará um erro de compilação? A resposta: dentro da janela de comandos, horas bolas!!!

Note que são utilizadas as mesmas características apresentadas pela linguagem C para a aplicação do comando printf.

Fazendo os comandos prinft e scanf funcionar no Code Warrior

Apesar de não ser reconhecido como um comando nativo do Code Warrior, é possível fazer o comando printf funcionar com os chips de 8 (HSC08) e de 32 (ColdFire) bits. Para tanto é necessário atualizar alguns itens do compilador.

Comandos prinft e scanf para o HSC08

Nos microcontroladores de 8 bits, a função de baixo nível que implementa as entradas e saídas está no arquivo termio.c, geralmente no endereço de instalação do Code Warrior, nas pastas LIB/HC08C/SRC.

Neste arquivo estão as três funções que comandam a entrada e a saída de dados:

TERMIO_GetChar ( ): esta função é utilizada para receber caracteres de um canal de entrada;

TERMIO_PutChar ( ): esta função é utilizada para enviar caracteres para um canal de saída;

TERMIO_Init ( ): esta função inicializa o canal de comunicação.

Para fazer o CodeWarrior enviar e receber dados do terminal IO, é necessário alterar estas três funções, redirecionando os caracteres adequadamente. Após isto feito, é necessário adicionar este arquivo ao seu projeto.

Comandos prinft e scanf para o ColdFire

O primeiro passo para fazer os comandos printf e scanf funcionarem com o ColdFire (32 bits) é editar o arquivo exceptions.c, que está na pasta Startup Code do projeto, como pode ser visto na figura abaixo.

Ao abrir o arquivo exceptions.c, procure a linha que mantém desligado os comandos para o console IO: #define CONSOLE_IO_SUPORT 0, como pode ser visto abaixo.

Altere esta linha para: #define CONSOLE_IO_SUPORT 1, como mostrado abaixo.

Após isto é necessário adicionar o arquivo console_io_cf.c ao seu projeto. A localização deste arquivo pode ser vista na figura abaixo:

Note que este arquivo, o console_io_cf.c, deve ser adicionado na pasta source, juntamente com o arquivo main, como mostra a imagem abaixo:

Quando você for construir seu projeto, clicando do debugging, é necessário que o debugger permita que o comando printf seja executado. Isto é feito através do menu CFMultilinkCyclonePro do debugger, na opção Setup, como pode ser visto abaixo:

Ao clicar em Setup abrirá a janela de configuração, mostrada abaixo. Clique na paleta Debug Options, também mostrada abaixo:

Ai basta clicar na caixa Enable Terminal printf support. Pronto!

Ao executar o EXEMPLO 01, você deve abrir a caixa do terminal IO. Isto é feito clicando no menu Component -> Open, mostrado abaixo:

Dentre as diversas opções de componentes que o debugger oferece, selecione o item Terminal IO, mostrado abaixo:

Basta então executar seu programa, e acompanhar a tela do Terminal IO para ver as mensagens serem impressas, como na figura abaixo:

Declaração de variáveis em MCUs de 8 bits 

Existem três regras básicas para a seleção de tipo de variável que será utilizada em sistemas embarcados com microcontroladores de 8 bits:

• Use o menor tipo possível que permita a você realizar a tarefa desejada.
• Use sempre unsigned type se isto for possível.
• Use casts com as expressões para reduzir os tipos de dados ao menor valor possível.

Obs: uso de cast:

Além disto é totalmente aconselhável o uso de typedefs para obter tamanhos fixos nas varáveis:

• Haverá alterações de acordo com o compilador e com o sistema.
• Isto torna o código invariante, independente do microcontrolador.
• Utilize sempre que for necessário um número fixo de bits para uma determinada
variável.

Evite, sempre que possível, utilizar os típos básicos da linguagem C em seu código: ‘char’, ‘int’, ‘short’, ‘long’.

EXEMPLO 03: CONSEQUÊNCIA DE USO DE VARIÁVEIS 

Para entender as consequências da correta ou incorreta declaração de tipos das variáveis, vamos executar o EXEMPLO 02:

Cabe aqui uma pergunta: como é possível declarar que a variável VarB é um byte se este não é um tipo válido para a linguagem C?

A resposta está no arquivo MC9S08QE128.h, que acompanha a criação de um projeto e que contém as seguintes definições:

Ao executarmos este projeto, devemos atentar para a alocação de memória e definição de tipos que acontece, o que pode ser visto nas janelas MEMORY e DATA1 (que indica a locação de área na  memória RAM) do IDE, como pode ser visto na figura abaixo:

Note que a posição 0100 da memória RAM é alocada para a variável VarB, que é um byte (unsigned char) e foi inicializada já na sua declaração com 11 (hexa).

As duas próximas posições da memória RAM foram alocadas para a variável VarC, que é uma word (unsigned int) e foi inicializada já na sua declaração com 2222 (hexa).

As próximas quatro posições da RAM foram alocada para a variável VarD, que é uma dword (unsigned long) e foi inicializada já na sua declaração com 33333333 (hexa).

Por fim, a próxima posições da memória RAM foi alocada para a variável VarA, que foi declarada como unsigned char e foi não inicializada em sua declaração, rebendo então o valor 0 (hexa) em sua posição de memória.

Após algumas execuções do programa, apertando a tecla F10 no simulador, percebemos que as variáveis são incrementadas nas suas respectivas posições de memória RAM, como é possível visualizar na figura da próxima página.

Agora façamos uma alteração neste projeto, criando o EXEMPLO 03: vamos modificar a declaração das variáveis VarB, VarC e VarD, que são GLOBAIS no programa original, transformando-as em variáveis LOCAIS, do seguinte modo:

Algumas surpresas aparecerão quando executarmos o programa e rodarmos o simulador. Primeiro veja o que acontece com a janela DATA1 (que indica a locação de área na memória RAM):

Aonde foram parar as variáveis (agora LOCAIS) VarB, VarC e VarD ??? Se observarmos a janela MEMORY, perceberemos que foi alocado espaço apenas para a variável VarA, a partir do endereço 0100, já que esta é uma variável GLOBAL. E as demais?

Para matar a charada, precisamos observar a janela DATA2 (que indica a manipulação da pilha (stack)):

Voilà!!! Ai estão elas!!!

Se executamos o programa passo a passo (F10 no simulador), veremos que continuamos utilizando posições da memória RAM para armazenar os valores das variáveis, como mostra a figura abaixo, mas agora esta área é pertencente a pilha (stack) e não precisa ficar alocada exclusivamente para a variável, podendo ser utilizada por outra função.

Modificadores de acesso

Além das declarações como global ou local, as variáveis podem receber dois tipos de modificadores de acesso, que especificam a forma como o compilador irá acessar o conteúdo das variáveis:

  • const: determina que a variável será tratada como uma constante, não podendo ser modificada ou alteradas durante a execução do programa. Faz, então, com que a variável passe a ter um valor fixo, pré-estabelecido durante a declaração da variável.
  • volatile: utilizado para indicar ao compilador que a variável por ele indicada pode ter seu conteúdo alterado a qualquer momento.

Modificadores de armazenamento

Indicam como o compilador irá tratar o armazenamento das variáveis. São quatro tipos:

  • auto: serve para declarar que uma variável é local. Porém, todas as variáveis declaradas internamente já são locais. Com isto, este modificador de armazenamento quase nunca é utilizado.
  • extern: utilizado para fazer referência a outras variáveis globais que foram declaradas em outros módulos do programa. Isto é muito útil quando se tem programas extensos onde módulos cuidam de funcionamentos específicos e precisam passar parâmetros para outras partes do programa.
  • static: indica que a variável ocupará uma posição permanente de memória. Isto também ocorre quando uma variável é declarada de modo global. Mas numa declaração de static, a variável é reconhecida apenas localmente, dentro da função em que foi criada. Se o modificador de armazenamento static for aplicado a uma variável que já é global, isto fará com que ela seja reconhecida apenas pelo programa em que está inserida. Caso haja interação deste programa com outros externos, esta variável não estará acessível.
  • register: faz com que o compilador tente gravar esta variável diretamente em um dos registradores que tem acesso direto a CPU do microcontrolador, ao invés de armazená-la em uma posição de memória RAM. Com isto, ganha-se em velocidade de processamento.

É interessante notar o comportamento das variáveis de acordo com a aplicação dos modificadores e, principalmente, como tirar proveito destes recursos. Podemos dizer que as variáveis tem área de trabalho e local de armazenamento diferenciado, de acordo com suas declarações:

  • Variável Global: armazenamento global e área de trabalho global;
  • Variável estática: armazenamento global e área de trabalho local;
  • Variável local: armazenamento local e área de trabalho local.

Mas para entender melhor como isto acontece, vamos estudar em detalhes alguns destes modificadores e como eles atuam de modo a criar não apenas um código melhor, mas também um código mais compacto.

Detalhes sobre o modificador de armazenamento static

Quando aplicado a uma variável, o modificador de armazenamento static tem duas funções principais:

  • Uma variável declarada como static no corpo de uma função mantém o seu valor entre outras funções que façam uma chamada a esta variável;
  • Uma variável declarada como static em um módulo, mas fora do corpo da função, é acessível para todas as funções daquele módulo.

Isto é muito aplicável para linguagem C em desktops. Mas para sistemas embarcados, o static terá as seguintes aplicações:

  • Encapsulamento de dados persistentes;
  • Modularizar um código (data hiding);
  • Ocultar os processos internos de cada módulo.

Note que as variáveis com o modificador static são armazenadas globalmente, e não utilizam a pilha (stack).

Um exemplo deste comportamento pode ser notado no programa exemplo mostrado na figura da próxima página.

Funções que foram declaradas como static em um módulo somente podem ser chamadas por outras funções daquele mesmo módulo.

Esta prática de uso de static tem as seguintes características:

  • Boa estruturação da prática de programação;
  • Resulta em um código menor e mais rápido.

A principal vantagem é que desde que o compilador conheça o momento exato em que uma função pode chamar uma função com static, ele pode estrategicamente colocar a função static de modo que seja chamada utilizando uma versão curta de uma instrução call ou jump.

Logo mais a frente há um programa de exemplo para entender melhor este conceito.

Note que a “ExternalFunction” é acessível pelo “main” porque ela foi declarada como “extern”. Porém, as “InternalFunction1” e “InternalFunction2” somente podem ser acessadas por outras funções que estão no módulo “stuff.c”.

Utilizar o modificador static dará ao compilador a oportunidade de aplicar uma técnica de otimização que definirá como e quando estas funções serão chamadas. Isto é reconhecidamente mais eficiente em arquiteturas como a do HSC08.

Detalhes sobre o modificador de acesso volatile

Uma variável que recebe um modificador de acesso volatile passa a poder ter seu valor alterado fora do fluxo normal do programa. Isto pode acontecer, em sistemas embarcados, de duas maneiras:

  • Através de uma rotina de interrupção de serviço (ISR);
  • Como consequência de uma ação do hardware.

É considerado uma excelente prática fazer a declaração de todos os registradores que trabalham com periféricos como volatile.

Uma técnica comum para fazer isto é mostrada abaixo:

Outra técnica, que garante um pouco mais de portabilidade ao código é a seguinte:

O exemplo abaixo demonstra um potencial de exigência de hardware para armazenar o mesmo valor para o mesmo local. Mostra também uma maneira de realizar uma "leitura simulada" de um registrador de status, algo que é exigido por muitos periféricos em suas rotinas de interrupção de serviço.

Sem o modificador volatile, essas operações de hardware necessitam ser otimizadas pelo compilador. Usando o modificador volatile, o problema é corrigido.

Detalhes sobre o modificador de acesso const

É seguro supor que uma variável que receba o modificador de acesso const, passe a ser reconhecida como somente leitura. Porém, alguns compiladores criam uma variável real na RAM para armazenar a variável do tipo const.

Assim, após a inicialização do sistema, o valor da variável de apenas leitura é copiado para a RAM. Em sistemas que tenham quantidade limitada de memória RAM, esta pode ser uma pena significativa de capacidade apenas pelo fato de ter colocado as variáveis como o modificador const. Compiladores para sistemas embarcados, como é o caso do Code Warrior, as variáveis const são armazenadas em ROM (ou Flash).

No entanto, apesar da característica de somente leitura, ela ainda é uma variável é acessada pelo programa, como tal, embora o compilador tente proteger as definições const de serem escritas inadvertidamente. Cada variável const deve ser declarada com um valor de inicialização.

Detalhes sobre os modificadores em conjunto

Será que uma variável pode ser tratada como const e volatile ao mesmo tempo? A resposta é sim!

Mas será que isto faz sentido para sistemas embarcados? Sim! E isto se aplica a qualquer local da memória que pode mudar inesperadamente (volatile) mas que é de somente leitura (const).

O exemplo mais óbvio do que estamos falando é o registrador SCI1S1, que controla o estado da máquina SCI, mostrado abaixo:

Este registrador contém flags para sinalizar condições como: TX vazio (TDRE), TX terminado (TC), RX cheio (RDRF), entre outros. Este é um registro volátil, uma vez que estas flags tendem a mudar inesperadamente, dependendo do estado da comunicação serial, e também são de condição somente leitura, pois as flags não podem ser gravados diretamente pelo programa, eles respondem apenas ao estado do módulo SCI.

A melhor declaração de registrar este status é:

Ou num formato que torna o código mais portável:

Operadores em C

Operador atribuição

É utilizado para atribuir um determinado valor a uma variável. Seu símbolo é o “ = ” e alguns exemplos de atribuição são mostrados a seguir:

A atribuição é sempre avaliada da direita para a esquerda. Isto significa que ao final deste programa o valor da variável y será igual a 10.

Operadores aritméticos

Indica ao compilador que ele deve fazer determinada operação aritmética entre duas variáveis. São os seguintes:

O EXEMPLO 04 mostra a consequência de uso dos operadores aritméticos:

Operadores relacionais

São utilizados em testes condicionais para determinar a relação existente entre os dados:

Operadores lógicos

São utilizados para fazer conjunções, disjunções ou negações entre elementos durante um teste condicional.

Operadores lógicos bit a bit

Operadores de memória ou de ponteiro

Os ponteiros serão tratados em detalhes no estudo dos dados avançados. É possível dizer que este é um dos pilares fundamentais da linguagem C e portanto gastaremos algumas horas do nosso treinamento para explicá-los em detalhes. Por hora, vamos apenas conhecer quais são os símbolos utilizados como operadores de memória, ou de ponteiros, e suas funcionalidades. As aplicações serão desenvolvidas mais a frente.

São dois os símbolos:

  • & -> endereço do operando. É um operador unário utilizado para retornar o endereço de memória do seu operando. Isto significa que se escrevermos:

teremos que a variável “endereço_a” conterá o endereço em que está armazenada a variável a.

  • * -> conteúdo do endereço apontado pelo operando. Este é um operador unário utilizado para retornar o conteúdo da posição de memória endereçada pelo operando que segue. Isto significa que se escrevermos:

teremos que o valor armazenado no local apontado pela variável “endereço_a” seja atribuído à variável a.

Outros operadores

Outros operadores, que não se encaixam em nenhum dos tipos citados anteriormente, mas que podem ser utilizados em linguagem C são:

  • ? -> (ponto de interrogação) Operador ternário condicional: pode fazer um teste condicional, substituindo o comando IF. Sua forma geral é:

Ele funciona da seguinte maneira: avalie a expressão1. Se ela for verdadeira, atribua a
variável o valor da expressão2. Caso a expressão1 seja falsa, então a variável recebe o valor da expressão3.

  • , -> (vírgula) Separador de expressões: é utilizado para enfileirar duas ou mais expressões. Sua forma geral é:

Uma aplicação, como a seguir, resulta em y = 5.

  • .  (ponto) Separador de estruturas: é utilizado em estruturas de dados como separador dos elementos e será estudado quando abordarmos os tipos de dados avançados.
    ->  (seta) Ponteiro de elementos de estruturas: é utilizado em estruturas de dados como ponteiro dos elementos e será estudado quando abordarmos os tipos de dados avançados.
    (tipo)  Operador modelagem de dado: é possível forçar que uma expressão seja de determinado tipo utilizando um cast cujo formato geral é:

Onde tipo é qualquer um dos tipos de dados permitidos pela linguagem C e expressão é a que se quer que resulte no tipo especificado. Por exemplo: para realizar um cálculo e obrigar que o resultado seja apresentado em ponto flutuante, pode-se utilizar o seguinte cast:

sizeof -> Retorna o tamanho de uma variável: para ter um melhor controle da quantidade de memória utilizada, pode-se aplicar o operador sizeof a qualquer variável. Isto fará retornar qual o tamanho que esta variável ocupa em bytes.

EXERCÍCIOS: Operadores,tipos e variáveis

EXERCÍCIO 01: Exemplos em 32 bits

Os três exemplos tratados anteriormente funcionam no MCU de 8 bits (MC9S08QE128). Reescreva os três exemplos, criando três novos projetos, de modo que eles sejam executados no MCU de 32 bits (MCF51QE128).

EXERCÍCIO 02: Calculadora básica

Escreva um programa em que encontre o valor de x e y assumindo que seus valores são calculados da seguinte maneira:

Assuma, em seu programa, que a, b, c, d, e e f são variáveis globais e que o resultado esperado para x e y seja armazenado em variáveis locais.

EXERCÍCIO 03: Calculadora de Idade – I

Escreva um programa em que o usuário entre com a data de nascimento no formato na seguinte sequencia:

  • DIA (DD);
  • MÊS (MM);
  • ANO (AAAA).

Na sequencia, o usuário deve entrar com a data de hoje, fornecendo os valores de dia, mês e ano no mesmo formato.

O programa deve, então, calcular a idade do usuário, informando a idade em ANOS, cujo valor deve ser um inteiro.

EXERCÍCIO 04: Calculadora de Idade – II

Modifique o programa anterior de modo que seja mostrado na tela também o valor da idade em DIAS e MESES.

Trabalhando com bits

Em sistemas embarcados é essencial tem o acesso habilitado para que seja feita a modificação de apenas um bit, ou um conjunto de bits, dentro de um byte, dado um determinado endereço, que pode ser de uma porta do MCU.

A flexibilidade oferecida pela linguagem C permite realizar este acesso de diversas maneiras diferentes, como por exemplo:

  • Estruturas para os bits (bits structures): a eficiência deste método irá variar de acordo com o tipo do compilador e geralmente não garantirá a portabilidade de código entre compiladores diferentes e dispositivos diferentes;
  • Determinar um tipo de variável como sendo um bit: é bem eficiente, mas continua com o problema da portabilidade de código entre compiladores diferentes e dispositivos diferentes;
  • Descolamentos e máscaras: oferece uma grande otimização nas operações que envolvem bits e a sua portabilidade e razoavelmente eficiente.

A chave para uma eficiente manipulação de bits está no modo em como estes bits foram declarados. Veja o exemplo abaixo, retirado dos arquivos do Code Warrior:

Com este tipo de declaração é possível fazer uma excelente manipulação de bits durante o seu código, como sugere o exemplo abaixo:

Que bit representa cada pino do μC?

A primeira observação é que grande parte dos pinos de um MCU sempre é compartilhado entre diversos periféricos, como temporizadores sistemas de comunicação serial, interrupções de teclado, etc. Nas famílias de MCUs da Freescale que estudamos neste treinamento, todos os periféricos tem prioridade de acesso aos pinos em relação as funções de I/O. Isto significa dizer que quando um periférico está habilitado, a função de I/O daquele pino está desabilitada, automaticamente.

Quando uma função analógica compartilhada é habilitada em um pino, tanto os buffers de entrada e como os de saída são desabilitados. Um valor 0 é lido por qualquer bit de dados da porta onde o bit é uma entrada (PTxDDn = 0) e buffer de entrada está desativado.

Em geral, quando um pino é compartilhado tanto com a função digital e como com uma função analógica, a função analógica tem prioridade de tal forma que, se ambas as funções analógicas e digitais estão habilitados, a função análogica controla a pino.

Registradores que controlam os I/Os

REGISTRADOR DE DADOS (Port x Data Register PTxDn) Cada bit colocado em um pino do microcontrolador tem seu valor refletido neste registrador. Isto ocorre quando a porta está configurada para entrada, sendo válida as seguintes informações:

  • Bit = 0: A entrada está em nível lógico baixo (0 V).
  • Bit = 1: A entrada está em nível lógico alto (+Vcc). Cada bit escrito neste registrador refletirá em um valor de tensão no pino de saída. Isto ocorre quando a porta está configurada para saída, sendo válida as seguintes informações:
  • Bit = 0: A saída será levada a nível lógico baixo (0 V).
  • Bit = 1: A saída será levada a nível lógico alto (+Vcc).

REGISTRADOR DE DIREÇÃO (Port x Data Direction Registers PTxDDn)

Este registrador indicará se um pino de I/O será utilizado como entrada ou saída, de acordo com a seguinte configuração:

  • Bit = 0: A porta será configurada como entrada.
  • Bit = 1: A porta será configurada como saída. É uma boa prática de programação escrever numa porta do MCU apenas após ter alterado a direção da mesma como saída. Isto garantirá que não será colocado na saída um valor binário antigo.

REGISTRADORES DE PULL UP (Port x Pull up Resistor Enable Registers PTxPEn)

Resistores programáveis de pull-up estão disponíveis nos pinos de todos os terminais de I/O, cuja habilitação ou não é feita do seguinte modo:

  • Bit = 0: Os resistores estarão desabilitados.
  • Bit = 1: Os resistores estarão habilitados. Independente do que foi setado neste registrador, estes resistores são desabilitados quando um pino para a ser controlado por um periférico, já que ele perde as funções de I/O.

REGISTRADORES DE CONTROLE DE SLEW RATE (Port x Slew Rate Enable Registers
PTxSEn)

O controle de slew rate pode ser ativado para cada pino das portas do MCU, definindo qual bits se deseja controlar através deste registrador (PTxSEn). Quando ativado, o controle de slew rate limita a velocidade a que uma saída pode ter uma possível transição, a fim de reduzir as emissões EMC. O controle de slew rate não tem nenhum efeito sobre os pinos que são configurados como entradas e pode ser configurado do seguinte modo:

  • Bit = 0: O controle de slew rate está desabilitado.
  • Bit = 1: O controle de slew rate está habilitado.

REGISTRADORES DE CONTROLE DE DRIVE STRENGTH (Port x Drive Strength Enable Registers PTxDSn)

Um pino de saída pode ser selecionado para ter um aumento na corrente de saída, o que é definindo pelo bit correspondente no registrador (PTxDSn). Quando este aumento é selecionado, um pino será capaz de fornecer ou receber uma quantidade de corrente maior. Mesmo que cada pino de I/O possa ser selecionado como de alta capacidade de corrente, o usuário deve assegurar que a fonte de corrente total e seus limites para o MCU não sejam ultrapassados. Este registrador de seleção destina-se a afetar o comportamento do DC do pino de I/O. No entanto é bom lembrar que o comportamento AC também é afetado. Este aumento na corrente permitirá que um pino tenha uma maior movimentação de carga com a mesma velocidade de comutação que tem um pino como uma baixa comutação de carga. Devido a isso, as emissões EMC podem ser afetadas quando esta função está habilitada em um pino.

Bit = 0: O controle de drive strength está desabilitado.
Bit = 1: O controle de drive strength está habilitado.

Características DC do MC9S08QE128 (8 bits)

Os efeitos da utilização do drive strength pode ser notado nos seguintes parâmetros:

Outros parâmetros de corrente contínua, afetados pela utilização do drive strength utilização a classificação que a Freescale faz em cada uma das etapas de teste, produção e utilização dos seus microcontroladores, de acordo com a tabela a seguir:

Características DC do MCF51QE128 (32 bits)

Declarações de controle e repetição

As declarações de controle e repetição são algumas das estruturas mais importantes da linguagem C. Elas é que garantem um fluxo de programa capaz de tomar decisões e desviar o programa de acordo com determinadas regras. Veremos neste treinamento estas estruturas, definidas pelo padrão ANSI C e como utilizá-las.

  • Seleção (condicional): if e switch;
  • Iteração (laço): while, for e do-while;
  • Desvio (salto): break, continue, goto e return;
  • Rótulo: case, default e label;

Falso e verdadeiro em linguagem C

Diversos destes comandos de controle e repetição acontecem apenas após um teste condicional, onde é verificado o status de VERDADEIRO ou FALSO de uma variável. É necessário entender como o C observa esta condição para compreender perfeitamente como as decisões são tomadas:

  • VERDADEIRO: é considerada uma condição verdadeira para uma variável quando esta tem qualquer valor diferente de zero, inclusive valores negativos.
  • FALSO: é considerada uma condição falsa para uma variável quando esta tem valor
    igual a zero.

Comando de seleção condicional if

EXEMPLOS 04 e 05: uso da seleção condicional if

Outro exemplo de utilização da função if está no programa EXEMPLO 05:

Note que este programa chama algumas funções de configuração, mostradas abaixo:

If – Else – If: comandos aninhados

Uma alternativa: o comando ?

Ele funciona da seguinte maneira: avalie a expressão1. Se ela for verdadeira, atribua a variável o valor da expressão2. Caso a expressão1 seja falsa, então a variável recebe o valor da expressão3.

Comando de seleção condicional switch

O comando switch difere do comando if porque somente pode testar igualdades. O comando if pode avaliar uma expressão lógica ou relacional.

EXEMPLO 07e 08: uso da seleção condicional switch case

Veja um exemplo de utilização da função switch case no programa EXEMPLO 06, que contém os comandos printf e scanf:

Veja outro exemplo de utilização da função switch case no programa EXEMPLO 07, que não contém os comandos printf e scanf:

Comando de iteração (laço) for

EXEMPLOS 08 e 09: uso da iteração (laço) for

Veja no programa EXEMPLO 08 como utilizar o laço for, com o uso do comando printf:

Agora teste o programa EXEMPLO 09 como utilizar o laço for, sem o uso do printf:

Como criar um laço infinito com o for?

Cláusula break no comando for

Como sair de um laço infinito? Execute um comando break dentro do laço. Ele retira o programa da execução infinita. É bom lembrar que quando um comando break é encontrado em qualquer lugar do corpo for, ele causa seu término imediato.

O controle do programa passará então imediatamente para o código que segue o loop.4

Cláusula continue no comando for

Qual o efeito de trocar uma cláusula break por uma cláusula continue? Algumas vezes torna-se necessário “saltar” uma parte do programa. Para isto utilizamos o continue. Isto faz com que o programa:

  • Force a próxima iteração do loop.
  • Pule o código que esteja em seguida.

Comando de iteração (laço) while

EXEMPLO 10: funcionamento do laço while

Veja no programa EXEMPLO 10 como utilizar o laço while:

Como criar um laço infinito com o while?

Comando de iteração (laço) do-while

EXEMPLO 11: funcionamento do laço do-while

Veja no programa EXEMPLO 11 como utilizar o laço do-while:

Usar um laço while (enquanto) ou um laço for (para)?

Os dois laços, while e for, tem várias similaridades. Na grande maioria das situações o mesmo problema poderá ser resolvido tanto com um tipo de laço quanto com o outro. Para uma melhor compreensão do que estamos dizendo, vamos fazer uma equivalência entre os parâmetros que são ajustados para fazer rodar um laço for e os mesmos parâmetros para fazer exatamente o mesmo efeito, mas com um laço while.

A escolha por um ou outro modelo dependerá apenas do estilo do programador. A forma for é mais compacta. Já a forma while permite uma visualização mais fácil de todas as partes envolvidas. Enfim: ambos geram os mesmos efeitos na linguagem C pura e podem ser considerados iguais pelo programador.

Já a sua aplicação em microcontroladores deve ser analisada com cuidado. Dependendo do compilador e da forma como o laço é gerado, pode ser obtido programas em Assembly maiores ou menores para executar a mesma tarefa. A velocidade com que o programa executa o laço também é influenciada pelo estilo de conversão que o compilador fará para cada um deles.

Um excelente exercício é modificar as opções de otimização do IAR (por velocidade ou por tamanho de código) e compilar programas com resultados de saída iguais mas que façam uso de laços diferentes, observando o comportamento do código Assembly gerado pelo compilador.

Todos estes conceitos juntos? Exemplo 12

Veja no programa EXEMPLO 12 todos os conceitos deste capítulo reunidos em um único programa, que usa os comandos printf e scanf:

Comando de desvio (salto) goto

EXERCÍCIOS: Declarações de controle e repetição

Para a série de exercícios a seguir, faremos a manipulação de bits conectados às portas dos microcontroladores. Para que tenhamos sucesso nesta empreitada, é necessário primeiramente entender como as portas podem ser manipuladas. 

Identificação de pinos na DEMOQE128

Identifique no diagrama elétrico da DEMOQE128, mostrado no item 7, as seguintes conexões, envolvendo pinos dos MCUs MC9S08QE128 e MCF51QE128 e seus respectivos hardwares externos:

a) Botão S1 -> pino PTA2;

b) Botão S2 -> pino PTA3;

c) LED1 -> pino PTC0;

d) LED2 -> pino PTC1;

e) LED3 -> pino PTC2;

f) LED4 -> pino PTC3.

Com estas informações escrevam programas em linguagem C de modo que aconteçam as operações solicitadas nos itens a seguir.

EXERCÍCIO 06: 1 botão e 1 led

Ao pressionar o botão S1 deve acender o LED1. Se o botão não estiver pressionado, o
LED1 deve se manter apagado.

EXERCÍCIO 07: 2 botões e 2 leds

Ao pressionar qualquer um dos quatro botões (S1, S2, S3 e S4), os LEDS correspondentes devem ser acesos (LED1, LED2, LED3 e LED4). Se qualquer um dos botões não stiverem
pressionados, os LEDS correspondentes devem se manter apagados.

EXERCÍCIO 08: Contador binário

Escreva um programa que a cada pressionar do botão S1 deve ser incrementada a quantidade de LEDs a serem acesas na porta PTC. Deste modo, após pressionar o botão
S1 por 63 vezes todos os LEDS da porta PTC devem estar acesos. No 64 pressionar de botão, todos os LEDs se apagam e a contagem deve se reiniciar.

EXERCÍCIO 09: 2 botões e 2 leds com temporização simples

Ao pressionar o botão S2 deve apagar o LED1, o que deve acontecer por alguns milissegundos (tempo suficiente para perceber a retenção da informação). Se o botão não
estiver pressionado, o LED1 deve se manter aceso. Ao mesmo tempo, se o botão S1 estiver pressionado o LED2 deve ficar apagado, o que também deve acontecer por alguns milissegundos (tempo suficiente para perceber a retenção da informação). Se o botão não
estiver pressionado, o LED2 deve se manter aceso.

EXERCÍCIO 10: Calculadora de funções

Escreva um programa que encontre o valor de f(x) para a seguinte equação:

f(x) = X 2 – 3X + 2

Fazendo X variar de 0 a 3 em passos de 0,1.

EXERCÍCIO 11: botões e leds com o ColdFire V1

Refaça os exercícios 06 a 09 de modo que eles possam funcionar também no chip  MCF51QE128, de 32 bits.

Outros artigos da série

<< Curso de C com microcontroladores MCF51QE128 e MC9S08QE128 - Parte 2Curso de C com microcontroladores MCF51QE128 e MC9S08QE128 - Parte 4 >>
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Software » Curso de C com microcontroladores MCF51QE128 e MC9S08QE128 - Parte 3
Comentários:
Notificações
Notificar
guest
0 Comentários
Inline Feedbacks
View all comments
Talvez você goste:

Séries



Outros da Série

Menu

WEBINAR
 

Soluções inteligentes para acionamento de MOSFETs/IGBTs com família STDRIVE

Data: 08/10 às 15:00h - Apoio: STMicroelectronics
 
INSCREVA-SE AGORA »



 
close-link