An STM32 Arduino IDE Button Debouncing Framework

What follows is a very basic framework for button debouncing. It works on an STM32 and is demonstrated in the following code on an STM32F446RE Nucleo board running at the default 84MHz. It uses timer #2 (TIM2), but could be modified to use another timer. The code was written primarily for the Arduino IDE and STM32duino core, however it could easily be adapted for other environments.

This demo simply uses the builtin Nucleo button, but additional buttons can be added by expanding the buttons array. The comments in the code should be adequate to explain how it works.

This Arduino IDE code requires the HAL TIM module only, so add a “build_opt.h” file with the following definition:

-DHAL_TIM_MODULE_ONLY

The basic code is included in this demonstration file:

#include <cassert>
#include "clamp.h"

#ifdef __cplusplus
extern "C"
#endif

// Number of buttons used.
constexpr size_t NUM_BUTTONS{1};

// Define our button (built in).
#define USER_BUTTON PC13
#define BLUE_BUTTON 0

// Debounce specifics (modify per system clock MHz).
#define DEBOUNCE_THESHOLD 7
#define COUNT_MIN         0
#define COUNT_MAX         15

// Specifics for each defined button.
typedef struct Button 
{
  GPIO_TypeDef* port = nullptr;
  uint16_t pin = NULL;
  bool state = false;
  bool latch = false; 
  uint8_t count = 0; 
} button_t;

// Array of buttons.
button_t buttons[NUM_BUTTONS];


// Debounce functionality called by timer interrupt.
void debounce() 
{
  assert(NUM_BUTTONS != 0);
  
  // Iterate through all defined buttons.
  for (int i=0; i<NUM_BUTTONS; i++)
  {
    // Check for incomplete button defintion.
    if (buttons[i].port == nullptr)
      continue;

    // Read button pin, adjust count.
    if (buttons[i].count != COUNT_MAX && !HAL_GPIO_ReadPin(buttons[i].port, buttons[i].pin))
      buttons[i].count++;
    else if (buttons[i].count)
      buttons[i].count--;

    // For the paranoid, force min/max value on count.
    //buttons[i].count = clamp<uint8_t>(buttons[i].count, COUNT_MIN, COUNT_MAX);

    // Debounce upon count > threshold value.
    if (buttons[i].count > DEBOUNCE_THESHOLD)
      buttons[i].state = true;
    else 
      buttons[i].state = false;
  }
}

TIM_HandleTypeDef timerInstance;

extern "C" void TIM2_IRQHandler() { HAL_TIM_IRQHandler(&timerInstance); }
extern "C" void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { debounce(); }

void initTimer() 
{
  // TIM2 by default has clock of 84MHz (MCU freq).
  // Set value of prescaler and period, so update event is every 1ms:
  // Update Event (Hz) = timer_clock/((TIM_Prescaler + 1)*(TIM_Period + 1))
  // Update Event (Hz) = 84MHz / ((0 + 1) * (83999 + 1)) = 1ms.
  __TIM2_CLK_ENABLE();

  timerInstance.Instance = TIM2;
  timerInstance.Init.Period = 83999;
  timerInstance.Init.Prescaler = 0;
  timerInstance.Init.CounterMode = TIM_COUNTERMODE_UP;
  timerInstance.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  timerInstance.Init.RepetitionCounter = 0;
  HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(TIM2_IRQn);
  HAL_TIM_Base_Init(&timerInstance);
  HAL_TIM_Base_Start_IT(&timerInstance);
}

void initButtons()
{
  // Define button port/pin and set mode of pin.
  buttons[BLUE_BUTTON].port = GPIOC; 
  buttons[BLUE_BUTTON].pin = GPIO_PIN_13;
  pinMode(USER_BUTTON, INPUT);
}

void setup() 
{
  pinMode(LED_BUILTIN, OUTPUT);
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);

  initButtons();
  initTimer();
}

void loop() 
{
  // Detect a new button press.
  if (buttons[BLUE_BUTTON].state && !buttons[BLUE_BUTTON].latch)
  {
    buttons[BLUE_BUTTON].latch = true;
    // Perform action on button press.
    HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
  }

  // Detect button release.
  if (!buttons[BLUE_BUTTON].state && buttons[BLUE_BUTTON].latch)
    buttons[BLUE_BUTTON].latch = false;
}

Contents of “clamp.h” file:

#include <algorithm>

template <class T> 
inline const T& _max(const T& a, const T& b) { return (a < b) ? b : a; }

template <class T> 
inline const T& _min(const T& a, const T& b) { return !(b < a) ? a : b; }

template <typename T>
inline T clamp(T& val, T lo, T hi) { return _max(lo, _min(hi, val)); }

About Jim Eli

µC experimenter
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a comment