quinta-feira, maio 31, 2012

Controlando um LED com um AVR - Parte II

Nesta parte vamos ver o software do nosso projeto.

Como ambiente de desenvolvimento vou usar o avr-gcc toolchain que já vimos por aqui (partes 1, 2 e 3).

Existem, é claro, várias maneiras de estruturar o nossa programa. Optei por programar o timer para gerar interrupções a cada aproximadamente 16 milisegundos e colocar toda a lógica dentro do tratamento desta interrupção. Desta forma, na maior parte do tempo o processador estará parado em modo de economia de energia (sleep mode).



Para controlar o botão usei uma variável de tipo unsigned char (byte), onde 1 bit indica o estado do botão na leitura anterior e outro o estado após o debounce. Este segundo bit só é alterado após duas leituras consecutivas iguais.

Uma segunda variável do tipo usnigned char armazena o estado do LED: apagado, aceso continuamente ou piscando. A cada pressionamento do botão o estado muda ciclicamente.

Sem mais delongas, eis o código em C:
#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>

// Bits correspondentes aos pinos de E/S
#define BOTAO   _BV(PD2)
#define LED     _BV(PB0)

// Valor para contar (aproximadamente) 50ms
#define TEMPO_50MS  3

// Controle do LED
static enum { APAGADO, ACESO, PISCANDO } ModoLed;

// Controles do estado do botão
#define BOTAO_ANTERIOR 0x01 // este bit indica o estado anterior
#define BOTAO_APERTADO 0x02 // este bit tem o valor c/ "debounce"
static unsigned char ModoBotao;

// Programa Principal
int main (void)
{
    // Pino do LED é saída
    DDRB |= LED;
    PORTB = 0;
    
    // Pino do Botão é entrada com pullup
    DDRD &= ~BOTAO;
    PORTD |= BOTAO;

    // Configura o timer 0
    TCCR0A = 0;                       // Modo normal: overflow a cada 256 contagens
    TCCR0B = _BV(CS01) | _BV(CS00);   // Usar clkIO/64: int a cada 64*256/1000 ms
                                      //   = 16,384 ms
    TIMSK = _BV(TOIE0);               // Interromper no overflow

    // Inicia os nossos controles
    ModoBotao = 0;
    ModoLed = PISCANDO;
    
    // Permite interrupções
    sei ();
    
    // Loop infinito
    for (;;)
    {
        set_sleep_mode(SLEEP_MODE_IDLE);
        sleep_mode();
    }
}

// Tratamento da interrupção do timer 0
ISR (TIMER0_OVF_vect)
{
    static unsigned char cnt = TEMPO_50MS;
    
    // Aguarda 50 ms
    if (--cnt != 0)
        return;
    cnt = TEMPO_50MS;
    
    // Trata o LED
    if (ModoLed == PISCANDO)
        PORTB ^= LED;
    
    // Trata o botão
    if ((PIND & BOTAO) == 0)
    {
        // Botao está apertado
        if (ModoBotao & BOTAO_ANTERIOR)
        {
            if ((ModoBotao & BOTAO_APERTADO) == 0)
            {
                // Acabamos de detectar o aperto
                ModoBotao |= BOTAO_APERTADO;
                switch (ModoLed)
                {
                    case APAGADO:
                        ModoLed = ACESO;
                        PORTB |= LED;
                        break;
                    case ACESO:
                        ModoLed = PISCANDO;
                        break;
                    case PISCANDO:
                        ModoLed = APAGADO;
                        PORTB &= ~LED;
                        break;
                }
            }
        }
        else
        {
            // Vamos aguardar a confirmação
            ModoBotao |= BOTAO_ANTERIOR;
        }
    }
    else
    {
        if (ModoBotao & BOTAO_ANTERIOR)
            ModoBotao &= ~BOTAO_ANTERIOR;   // aguarda confirmar
        else
            ModoBotao &= ~BOTAO_APERTADO;   // confirmado
    }
}

Alguns comentários sobre o código:
  • O include avr/io.h define as constantes relativas ao microcontrolador. O modelo específico é definido nos parâmetros de execução do gcc.
  • A iniciação das portas de entrada e saída digital é minimalista, deixando os pinos não usados da forma como eles ficam após um reset.
  • Para ligar o pullup interno no pino onde está conectado o botão é necessário colocar em 1 o bit correspondente no registrador PORTx.
  • O timer é usado de forma bem simplista. Como a temporização não é crítica, deixei o microcontrolador operando no modo padrão de fábrica, com um clock de 1MHz (oscilador de 8MHz dividido por 8). Este clock é dividido por 64 para gerar uma interrupção a cada 16,384ms. A base de tempo de aproximadamente 50ms corresponde a três interrupções. Uma precisão melhor poderia ser obtida dividindo o clock por um valor menor e contando mais interrupções.
  • Durante a maior parte do tempo o processador está no modo IDLE, que para a CPU mas deixa contadores e interrupções funcionando.
 No próximo post vamos ver o circuito montado na protoboard, como compilar o programa e como gravá-lo na Flash.

Nenhum comentário: