quarta-feira, setembro 02, 2015

Timer para Apresentações - Operação e Software

Concluindo a apresentação deste projeto, vou falar como é a operação (que pode ser vista no vídeo abaixo) e como eu estruturei o software.



Obs.: Reparar que refiz a fixação dos botões. Outra alteração foi o reposicionamento do buzzer para o lado de fora, para ficar mais audível.

A operação é bastante simples. Inicialmente, com o relógio parado, os LEDs na extremidade pulsam bem devagar. Através do botão da esquerda se seleciona o tempo dentre as opções programadas no firmware. O botão da direita inicia a contagem decrescente, com os LEDs nas extremidades pulsando mais rápido. O botão da direita pode ser usado para parar momentaneamente a contagem (apertando de novo retoma) e o botão da esquerda interrompe (voltando à situação inicial). Se a contagem chegar a zero, a "bomba" "explode": o buzzer é acionado e os LEDs azuis no interior piscam rapidamente. Neste ponto é preciso apertar o botão esquerdo para voltar ao estado inicial.

O programa principal do software está organizado como uma máquina de estados. Os estados possíveis são RESET, INICIAL, CONTANDO, PAUSA e EXPLOSAO. A mudança entre os estados (exceto de RESET para INICIAL) é controlada pelos botões (quando a tecla é solta) e o evento da contagem chegar a zero. A atualização do display e o acionamento do buzzer na explosão também são feitos pelo programa principal:

// Programa Principal
int main (void)
{
    enum { RESET, INICIAL, CONTANDO, PAUSA, EXPLOSAO } estado;
    uint8_t iVal = 0;
    
    // Inicia o hardware
    iniciaHw();
    
    // Inicia o software
    estado = RESET;
    
    // Permite interrupções
    sei ();
    
    // Eterno equanto dure
    for (;;)
    {
        // Maquina de Estados
        switch (estado)
        {
            case RESET:
                contador = valCont[iVal];
                mostraContador ();
                apagaLedMeio ();
                acendeLedPonta (100, 200);
                estado = INICIAL;
                break;
            case INICIAL:
                if (botaoEsq)
                {
                    while (botaoEsq)
                        ;
                    click();
                    if (++iVal == N_VALORES)
                        iVal = 0;
                    contador = valCont[iVal];
                    mostraContador ();
                }
                if (botaoDir)
                {
                    while (botaoDir)
                        ;
                    click();
                    acendeLedPonta (20, 20);
                    ligaContador (valCont[iVal]);
                    estado = CONTANDO;
                }
                break;
            case CONTANDO:
                if (contAtl)
                {
                    mostraContador();
                    contAtl = FALSE;
                }
                if (contZero)
                {
                    mostraContador();
                    acendeLedMeio (20, 20);
                    PORTA |= BUZZER;
                    estado = EXPLOSAO;
                    break;
                }
                if (botaoEsq)
                {
                    while (botaoEsq)
                        ;
                    click();
                    pausaContador();
                    estado = RESET;
                }
                if (botaoDir)
                {
                    while (botaoDir)
                        ;
                    click();
                    pausaContador();
                    apagaLedPonta();
                    estado = PAUSA;
                }
                break;
            case PAUSA:
                if (botaoEsq)
                {
                    while (botaoEsq)
                        ;
                    click();
                    estado = RESET;
                }
                if (botaoDir)
                {
                    while (botaoDir)
                        ;
                    click();
                    acendeLedPonta (20, 20);
                    contOn = TRUE;
                    estado = CONTANDO;
                }
                break;
            case EXPLOSAO:
                if (botaoEsq)
                {
                    while (botaoEsq)
                        ;
                    click();
                    PORTA &= ~BUZZER;
                    estado = RESET;
                }
                break;
        }
    }
}
A rotina de interrupção do timer é usada para:
  • Verificar o estado das teclas e efetuar o "debounce"
  • Controlar a piscada dos LEDs
  • Decrementar o contador
  • Desligar o buzzer no click das teclas.
// Tratamento da interrupção do timer 0
// ocorre a cada 10ms
ISR (TIM0_COMPA_vect)
{
    static uint8_t debDir = 0, debEsq = 0;
    static uint8_t contSeg = 100;

    // Atualiza estado das teclas
    if ((PINB & TEC1) == 0)
    {
        if (botaoDir)
        {
            debDir = 0;
        }
        else
        {
            if (++debDir == DEBOUNCE)
            {
                botaoDir = TRUE;
                debDir = 0;
            }
        }
    }
    else
    {
        if (botaoDir)
        {
            if (++debDir == DEBOUNCE)
            {
                botaoDir = FALSE;
                debDir = 0;
            }
        }
        else
        {
            debDir = 0;
        }
    }
    if ((PINB & TEC2) == 0)
    {
        if (botaoEsq)
        {
            debEsq = 0;
        }
        else
        {
            if (++debEsq == DEBOUNCE)
            {
                botaoEsq = TRUE;
                debEsq = 0;
            }
        }
    }
    else
    {
        if (botaoEsq != 0)
        {
            if (++debEsq == DEBOUNCE)
            {
                botaoEsq = 0;
                debEsq = 0;
            }
        }
        else
        {
            debEsq = 0;
        }
    }

    // Trata o contador
    if (contOn && (--contSeg == 0))
    {
        contSeg = 100;
        if (contador != 0)
        {
            contAtl = TRUE;
            if (--contador == 0)
                contZero = TRUE;
        }
    }
    
    // Trata os LEDs
    if (cntLedPonta != 0)
    {
        if (--cntLedPonta == 0)
        {
            if ((PORTA & LED1) != 0)
            {
                PORTA &= ~(LED1 | LED2);
                cntLedPonta = tmpOffPonta;
            }
            else
            {
                PORTA |= (LED1 | LED2);
                cntLedPonta = tmpOnPonta;
            }
        }
    }
    if (cntLedMeio != 0)
    {
        if (--cntLedMeio == 0)
        {
            if ((PORTA & LED3) != 0)
            {
                PORTA &= ~(LED3 | LED4);
                cntLedMeio = tmpOffMeio;
            }
            else
            {
                PORTA |= (LED3 | LED4);
                cntLedMeio = tmpOnMeio;
            }
        }
    }
    
    // Click das teclas
    if (cntClick != 0)
    {
        if (--cntClick == 0)
        {
            PORTA &= ~BUZZER;
        }
    }
}

O resto do código (inclusive as rotinas de manipulação do display) podem ser vistas baixando Timer.zip dos arquivos do blog.

O curioso com relação ao software foi que nos primeiros testes nada funcionada direito, o microcontrolador estava muito doido! Depois de muitos testes, descobri que o problema era que quando eu linkava os objetos não estava informando o modelo do microcontrolador. Corrigido isto, funcionou praticamente tudo.

Nenhum comentário: