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.
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:
- leitura das teclas USR_SW1/USR_SW2 com a respectiva atualização dos LEDs D1 e D2 e;
- atualização de um LED que pode ser interpretado como um LED de Keep-Alive.
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.










Debouncing para FPGA: https://opencores.org/project,debouncer_vhdl
Muito bom post!
obrigado!