Microcontroladores
Autor: Perrotti | Publicado: 2013-09-17T05:20:00-03:00


  Não pretendo fazer uma 
introdução formal aos microcontroladores, minha experiência com eles é 
limitada e essencialmente prática. Além disso, textos introdutórios de 
excelente qualidade sobre o assunto são facilmente encontrados na 
internet. Aqui serão discutidos alguns tópicos práticos relacionados a 
características da programação de microcontroladores que talvez os 
programadores acostumados a programar computadores não estejam 
habituados. O código usado como exemplo usa a biblioteca do Wixel que 
contém o CC2511F32 da Texas Instruments. Mas mesmo para quem usar outro 
microcontrolador, penso que serão de muita ajuda para os iniciantes 
porque os conceitos são basicamente os mesmo para qualquer 
microcontrolador.

Antes de tratar sobre a 
programação é importante conhecer alguns conceitos básicos usados na 
arquitetura dos microcontroladores então sugiro enfaticamente uma 
pesquisa introdutória sobre o assunto. Abaixo alguns links:

Como funcionam os microcontroladores
Introdução aos Microcontroladores
Introdução ao Microcontrolador.
Microcontroladores industriais (apostila)
Introdução aos Microcontroladores (apostila)



Loop infinito
A primeira coisa a saber
 sobre programação de microcontroladores é que o programa está sempre 
rodando dentro de um loop infinito. Quando o microcontrolador é ligado 
um código de inicialização é executado e em seguida o programa entra em 
um loop infinito que só termina quando a energia é cortada. Normalmente 
dentro do loop estão chamadas a funções que monitoram o estado de alguma
 porta, pino, timer ou qualquer periférico disponível e conforme o caso 
executam alguma ação ou retornam sem fazer nada, 

É preciso lembrar que 
essas funções serão chamadas uma vez a cada iteração do loop, mas vão 
(ou deveriam) ocorrer milhares de iterações por segundo. O comportamento
 esperado é que na grande maioria das chamadas essas funções retornem 
sem nenhuma ação, então é uma boa estratégia iniciar a função 
identificando o mais cedo possível essa condição e retornar executando o
 mínimo de código possível.


Funções não bloqueantes
Quanto mais iterações 
por segundo o loop principal conseguir fazer, mais precisas serão as 
medidas feitas nos periféricos e mais imediatas serão as ações de 
resposta do microcontrolador. Por isso é importante evitar escrever 
código que mantenha o fluxo de processamento preso em uma instrução ou 
loop por muito tempo. Por exemplo, instruções que fazem o processamento 
"esperar" determinada quantidade de tempo ( do tipo delay(tempo) ), 
devem ser evitadas. 

Para entender o problema 
de usar usar funções bloqueantes, vamos considerar uma aplicação bem 
simples que envolva alguns pinos. Suponha que o pino P1 deva ser 
monitorado e que o funcionamento normal do sistema exija este pino em 
nível alto. Sempre que o pino P1 ficar em nível baixo, um alarme 
conectado ao pino P2 tem que ser ligado e mantido assim até o pino P1 
voltar ao nível alto. O programa para esta especificação poderia ser 
algo como:



#define mnPin    P1
#define pinAlarm P2
&nbsp;
void main()
{
    // inicializacão
    setDigitalInput(mnPin, HIGH_IMPEDANCE);
    setDigitalOutput(pinAlarm, LOW);
&nbsp;
    // loop principal
    while(1)
    {
        // monitoramento
        pinAlarm = !mnPin ;
    }
}
&nbsp;
Apesar de simples e 
básico, o código acima é extremamente eficiente e preciso. No instante 
em que P1 estiver em nível baixo, o pino de alarme é ativado. Isso será 
verificado milhares de vezes por segundo, então a reação será 
praticamente imediata.

Entretanto, qualquer 
microcontrolador atual pode dar conta de muito mais código do que o 
programa acima, então vamos acrescentar mais uma funcionalidade à 
aplicação: um led de status. A função desse led é indicar que o 
microcontrolador está ligado e funcionando, para isso faremos ele piscar
 em ciclos de 1 segundo, ficando meio segundo apagado e meio segundo 
aceso. Assumindo que seja usado o pino P3 para ativar o led, poderia ser
 feita uma implementação como o programa abaixo:



#define mnPin     P1
#define pinAlarm  P2
#define pinStatus P3
&nbsp;
#define on  1
#define off 0
&nbsp;
void main()
{
    // inicializacão
    setDigitalInput(mnPin, HIGH_IMPEDANCE);
    setDigitalOutput(pinAlarm, LOW);
    setDigitalOutput(pinStatus, LOW);
&nbsp;
    // loop principal
    while(1)
    {
        // monitoramento
        pinAlarm = !mnPin;
&nbsp;
        // led de status
        pinStatus = on;
        delay(500);
        pinStatus = off;
        delay(500);
    }
}
&nbsp;
A função delay() faz com 
que o processador espere a quantidade de milissegundos indicada no 
argumento. A cada iteração do loop principal, o programa agora verifica o
 pino monitorado, em seguida liga o led de status e espera meio segundo.
 A seguir o led de status é desligado e novamente há uma espera de meio 
segundo. Este código faz o que é pedido, mas agora cada iteração demora 
pelo menos um segundo para ser completada. Isso significa que se o pino 
P1 for para o nível baixo logo após ele ser verificado, pode demorar até
 um segundo para que o alarme seja ativado. Essa demora pode ser 
aceitável em algumas aplicações, mas também pode inviabilizar outras. 
Suponha que o alarme signifique uma sobrecarga elétrica no sistema e o 
mesmo sinal que ative o alarme também desligue o dispositivo 
sobrecarregado. Nesse caso, um segundo é tempo mais que suficiente para 
torrar o dispositivo que deveria estar sendo protegido.

Mas então como fazer para piscar o led de um modo não-bloqueante? Veja o código abaixo:



#define mnPin     P1
#define pinAlarm  P2
#define pinStatus P3
&nbsp;
void blinkStatusLed()
{   static uint32 nextToggle = 0;
&nbsp;
    if(getMs() &gt;= nextToggle)
    {
        pinStatus = !pinStatus;
        nextToggle = getMs()+500;
    }
}
&nbsp;
void main()
{
    // inicializacão
    setDigitalInput(mnPin, HIGH_IMPEDANCE);
    setDigitalOutput(pinAlarm, LOW);
    setDigitalOutput(pinStatus, LOW);
&nbsp;
    // loop principal
    while(1)
    {
        // monitoramento
        pinAlarm = !mnPin;
&nbsp;
        // led de status
        blinkStatusLed();
    }
}
&nbsp;
A função getMs() faz 
parte da biblioteca do Wixel e retorna a quantidade de milissegundos 
decorridos desde que o microcontrolador foi ligado. Uma função com essa 
funcionalidade existe nas bibliotecas da maioria dos microcontroladores,
 talvez com outro nome. De qualquer forma, a função permite medir 
intervalos de tempo em milissegundos. 

A nova implementação do 
programa acrescenta a função blinkStatusLed() que cuida de piscar o led 
de status. Veja que não há mais chamadas à função delay(). Em vez disso,
 a variável estática nextToggle armazena quando deverá ser feita a 
próxima mudança de estado do led somando o valor 500 ao valor retornado 
por getMs() cada vez que o led muda de estado. 

A grande diferença entre 
esta implementação e a anterior, é que novamente o pino será monitorado 
milhares de vezes por segundo, já que não existe mais nenhuma espera no 
processamento. Apesar disso, o led irá piscar em ciclos de 1 segundo, 
conforme foi especificado.


Led de status piscante
Uma maneira simples de 
resolver a questão do tópico anterior seria fazer com que o led de 
status fique sempre aceso em vez de piscar. Isso tornaria o código muito
 mais simples. Na verdade, o led poderia ser ativado ainda na 
inicialização e não seria necessário nenhum código dentro do loop 
principal. Isso simplifica o programa, mas retira uma importante 
informação de debug. A função do led de status não é só indicar que o 
microcontrolador está ligado, mas ele também deve informar se a execução
 está seguindo seu fluxo normal ou se ficou presa em algum loop ou 
função. Se o led de status está configurado para piscar e em determinado
 momento ele passa a ficar sempre aceso ou sempre apagado, isso indica 
que o programa pode ter travado em algum ponto e não está mais seguindo o
 fluxo normal de processamento. Um led de status que fique sempre aceso 
não daria essa informação.


Medir tempo com operações bit-a-bit
Como citado, a maioria 
dos microcontroladores oferece em suas bibliotecas uma função que 
informa a quantidade de milissegundos decorridos desde a ativação. O 
valor retornado por essa função é sempre um inteiro positivo. Se 
analisarmos o comportamento dos bits que compõe esse valor em binário, 
vamos perceber que cada bit muda de estado em intervalos de tempo bem 
determinados. Apenas como referência, vamos chamar o bit menos 
significativo de bit 0, o segundo bit menos significativo de bit 1 e 
assim por diante.

Se em determinado momento
 o bit 0 desse valor for 1, no milissegundo seguinte ele será 0 e no 
próximo milissegundo ele será 1 novamente e assim por diante. Então ele 
muda de estado a cada milissegundo. Já o bit 1 só vai mudar de estado a 
cada dois milissegundos, o bit 2 muda de estado a cada 4 milissegundos, o
 bit 3 muda a cada oito milissegundos e assim por diante dobrando o 
intervalo a cada incremento de ordem do bit.

Então se precisarmos 
medir algum intervalo de tempo fixo (por exemplo para a mudança de 
estado de um led de status) e se esse intervalo for próximo de alguma 
potência de dois, é muito mais eficiente, rápido e econômico usar os 
bits do contador de milissegundos do que criar uma variável estática, 
somar uma valor a ela, comparar o valor da variável com o valor do 
contador e etc, como foi sugerido acima.

No exemplo anterior foi 
implementado um código para piscar o led de status a cada meio segundo 
usando uma variável estática. Meio segundo significa 500 milissegundos e
 500 não é uma potência de dois, mas 512 é (2 elevado a 9) e também é um
 valor próximo o suficiente de 500 para ser aceitável como aproximação 
nesta aplicação. Veja abaixo uma nova implementação da aplicação do 
exemplo, agora usando o contador de milissegundos para piscar o led:


#define mnPin     P1
#define pinAlarm  P2
#define pinStatus P3
&nbsp;
void main()
{
    // inicializacão
    setDigitalInput(mnPin, HIGH_IMPEDANCE);
    setDigitalOutput(pinAlarm, LOW);
    setDigitalOutput(pinStatus, LOW);
&nbsp;
    // loop principal
    while(1)
    {
        // monitoramento
        pinAlarm = !mnPin;
&nbsp;
        // led de status
        pinStatus = (getMs() &gt;&gt; 8) &amp; 1;  // intervalos de 512 ms
    }
}
&nbsp;
Operações bit-a-bit 
ocupam pouca memória e são extremamente rápidas, então este código é 
muito mais compacto e eficiente do que a versão anterior que usa a 
função blinkStatusLed(). Além disso, continua sendo não bloqueante.


Heart beat - Batida de coração
Podemos usar um bit do 
contador de milissegundos para medir um intervalo de tempo específico, 
como mostrado no tópico anterior, mas combinando vários bits do contador
 é possível produzir padrões bem interessantes simulando ritmos, por 
exemplo como o ritmo das batidas de um coração. O princípio é o mesmo da
 medição de intervalos, só que agora vamos usar mais de um bit para 
calcular o estado do led de status. Veja o código abaixo: 



uint32 ms;
&nbsp;
ms = getMs();
pinStatus = (ms &gt;&gt; 8) &amp; (ms &gt;&gt; 6) &amp; 1;


Agora, para que o pino de
 status fique em nivel alto, é preciso que o bit 9 e o bit 7 do contador
 estejam ambos com o valor 1. Veja que o bit 9 muda de estado a cada 512
 milissegundos, então quando o esse bit estiver zerado o led ficará 
apagado não importa o estado do bit 7. Já o bit 7 muda de estado a cada 
128 milissegundos, então no período em que o bit 9 assume o valor 1 (a 
cada 512 ms) o bit 7 muda de estado 4 vezes produzindo duas piscadas. 
Então o led vai dar duas piscadas, ficar meio segundo apagado e depois 
mais duas piscadas, etc. O efeito lembra bem uma batida de coração. 
Outros ritmos podem produzidos fazendo combinações diferentes de bits do
 contador de milissegundos.