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.
Nenhum comentário:
Postar um comentário