Introdução

Aqui no Portal Embarcados já tivemos alguns artigos abordando o problema de debouncing de chaves em sistemas embarcados. Nosso colaborador Rodrigo Almeida escreveu um artigo excelente no ano passado descrevendo o que é debouncing e os seus efeitos em sistemas embarcados. E neste artigo demonstraremos a implementação de soft timers para debouncing de teclas.

Para isso usaremos o kit Connected Launchpad, da Texas Instruments, e suas teclas de usuário, que sofrerão o processo de debouncing. Para implementar o time delay utilizamos alguns soft timers, disponibilizados pela biblioteca Soft Timers. Como compilador, utilizamos o MDK Keil 5.0 da ARM.

Hardware Utilizado para Debouncing

A Connected Launchpad é um kit bem interessante, veja na Figura 1. Pensado para o mercado de IoT, ele vem equipado com um microcontrolador TIVA TM4C1294, conectividade ethernet com MAC e PHY integrado, USB 2.0, 4 LEDs de uso geral, 2 chaves de uso geral e conectores para as placas de expansão Booster XL da Texas. Acabei adquirindo esta placa pois o seu preço é extremamente baixo.

Soft Timers para Debouncing: Connected Launchpad
Figura 1 – Connected Launchpad da Texas Instruments

Aplicação de Soft Timers para Debouncing

O projeto demo (veja aqui o seu repositório) que irei demonstrar aqui realiza basicamente duas operações:

Essas duas tarefas são realizadas através de tasks gerenciadas por soft timers.

A função main() tem a função somente de inicializar o hardware, criar os soft timers e entrar em um laço infinito que só serve para manter o programa rodando e aguardando interrupções.

int main()
{   
    uint32_t dwTimerHandle;
    
    SetSystemClock();  /* configure the system clock  */    
    
    TTimerCfgTimeOut( 500 );  /* initialize the ttimer engine  */
    
    BrdLedsInit();  /* initialize the board leds engine */
    BrdKeyInit();   /* initialize the board keys engine */
    
    BrdLedsSetState( USR_LED0, 0 );
    BrdLedsSetState( USR_LED1, 0 );
    BrdLedsSetState( USR_LED2, 0 );
    BrdLedsSetState( USR_LED3, 0 );

    // create a periodic timer to execute TaskMain
    TTimerRegisterCallBack( TTIMER_1MS_INTERVAL, TimerPeriodic, TaskMain, NULL, &dwTimerHandle );
    TTimerStart( dwTimerHandle );

    // create a periodic timer to execute TaskBlink
    TTimerRegisterCallBack( TTIMER_1SEC_INTERVAL, TimerPeriodic, TaskBlink, NULL, &dwTimerHandle );
    TTimerStart( dwTimerHandle );
    
    for( ;; );
}

As tasks iniciadas pela função main() são responsáveis pela operação de alterar o status dos LEDs D1 e D2 quando as teclas USR_SW1 ou USR_SW2 forem pressionadas e também pela função de pisca-pisca realizado no LED D4.

Abaixo segue o código da task TaskMain(), que é a responsável por controlar os LEDs D1 e D2:

uint32_t TaskMain( void* lpParam )
{
    struct STSwStatus
    {
        uint8_t bCurrentStatus;
        uint8_t bPrevStatus;
    };
    
    uint8_t bCounter;
    static uint8_t bLedStatus=0;
    static UsrLedType tUsrLed[] = {USR_LED0, USR_LED1};
    static struct STSwStatus stSwStatus[] = 
    {
        [0] = 
        {
            .bCurrentStatus = 0,
            .bPrevStatus    = 0,
        },
        [1] = 
        {
            .bCurrentStatus = 0,
            .bPrevStatus    = 0,
        }
    };
    
    for ( bCounter = 0; bCounter < GET_ARRAY_LEN( stSwStatus ); bCounter++ )
    {
        stSwStatus[bCounter].bPrevStatus = stSwStatus[bCounter].bCurrentStatus;
        stSwStatus[bCounter].bCurrentStatus = BrdKeyRead( (UsrSwType)bCounter );
        
        if( (stSwStatus[bCounter].bPrevStatus == 0) && (stSwStatus[bCounter].bCurrentStatus == 1) )
        {
            bLedStatus ^= (1<<bCounter);
            BrdLedsSetState( tUsrLed[bCounter], (bLedStatus & (1<<bCounter)) == (1<<bCounter) ); 
        }
    }
    
    return 0;
}

A “mágica” é realizada dentro do módulo BoardKeys (arquivo Board/BoardKeys.c). Ele é responsável por realizar a leitura das teclas e determinar se o seu status é ON ou OFF. Para isso também utilizamos uma task gerenciada por um soft timer que somente realiza a leitura das chaves e armazena o estado em uma struct que é utilizada internamente pelo módulo (struct STSwStatus stSwStatus).

uint32_t UserSwitchTask( void* lpParam )
{
    static  UsrSwType lblButton = USR_SW1;  //! local variable to control the current key to be read
    struct STGpioInputStatus* pSwitchStatus = &stSwitchStatus[lblButton];
    const struct STGpioConfig* pSwitchConfig = &stSwitchConfig[lblButton];
    
    bBusy = 1;

    switch( lblButton )
    {
        case USR_SW1: 
            lblButton = USR_SW2;
            break;
        
        case USR_SW2:
            lblButton = USR_SW1;
            break;
    }

    // save the previous state
    pSwitchStatus->bPrevState = pSwitchStatus->bState;
    
    // get the new switch status
    pSwitchStatus->bState = (GPIOPinRead( pSwitchConfig->dwBASE, pSwitchConfig->dwPin ) ? 0 : 1);

    bBusy = 0;

    return 0;
}

Atentem-se que, como as tasks são executadas em contexto de interrupção, as tarefas executadas por elas devem ser as mais breves possíveis. Se o usuário colocar um loop infinito em um soft timer o sistema não funcionará a contento. Pensem que é um sistema colaborativo.

O código é bastante simples e pode ser portável para qualquer outra arquitetura de microcontrolador, só necessitando reescrever os drivers para os timers e GPIOs.

O código fonte do projeto de debouncing no ambiente Keil está disponível no github.