Auto-repeat and debouncing

In this article I show a simple C library I wrote for the STM32 to help debouncing keys. It is completely non-blocking and also includes a nice auto-repeat feature with progressive speed! I wrote this library to complete an assignment for professor Rudy Hofer’s Embedded Programming Principles course at the Conestoga College and now I am sharing it with anyone that might need it.

Figure 1

The library is fairly simple and there are only two functions: initKeyAutoRepeat() and readKeyAutoRepeat(). The first one is used to initialize the internal data structure, which includes the GPIO port, GPIO pin, GPIO state for pressed key, debounce time (in ms) and auto repeat start-time multiplier. These are public parameters, but the structure also includes some internal variables used to track key state and timing. The structure is also typedef so we can easily declare variables to store each key structure.

typedef struct {
	GPIO_TypeDef* GPIOx;
	uint16_t GPIO_Pin;
	GPIO_PinState targetState;
	uint8_t debounceTimeMs;
	uint16_t autoRepeatMult;
	uint32_t tick;
	uint32_t endTime;
	uint16_t autoRepeatTarget;
	uint16_t autoRepeatStart;
	uint8_t keyPressed;
} AutoRepeatKey;

Source code of the initialization function is show below:

void initKeyAutoRepeat(	AutoRepeatKey *keyProp, GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin,
						GPIO_PinState targetState, uint8_t debounceTimeMs, uint16_t autoRepeatMult)
{
	keyProp->GPIOx = GPIOx;
	keyProp->GPIO_Pin = GPIO_Pin;
	keyProp->targetState = targetState;
	keyProp->debounceTimeMs = debounceTimeMs;
	keyProp->autoRepeatMult = autoRepeatMult;
	keyProp->autoRepeatTarget = 0;
	keyProp->endTime = 0;
	keyProp->keyPressed = 0;
	keyProp->tick = 0;
}

And the actual debouncing and repeating function is shown below:

uint8_t readKeyAutoRepeat(AutoRepeatKey *keyProp)
{
	uint8_t result = 0;
	if (!keyProp->endTime) {
		keyProp->tick = HAL_GetTick();
		keyProp->endTime = keyProp->tick + keyProp->debounceTimeMs;
	}
	if (HAL_GPIO_ReadPin(keyProp->GPIOx,keyProp->GPIO_Pin) == keyProp->targetState){
		if (keyProp->tick < keyProp->endTime){
			keyProp->tick = HAL_GetTick();
			if (HAL_GetTick() < keyProp->tick) {
				keyProp->endTime = keyProp->tick + keyProp->debounceTimeMs;
			}
		} else {
			keyProp->endTime = keyProp->tick + keyProp->debounceTimeMs;
			if (!keyProp->keyPressed) {
				result = 1;
				keyProp->keyPressed = 1;
			} else {
				if (keyProp->autoRepeatMult) {
					keyProp->autoRepeatTarget++;
					if (keyProp->autoRepeatTarget >= keyProp->autoRepeatMult) {
						if ((keyProp->autoRepeatMult - keyProp->autoRepeatStart)>1) {
							keyProp->autoRepeatStart += (keyProp->autoRepeatMult - keyProp->autoRepeatStart) / 8;
						}
						keyProp->autoRepeatTarget = keyProp->autoRepeatStart;
						result = 1;
					}
				}
			}
		}
	} else {
		keyProp->tick = HAL_GetTick();
		keyProp->endTime = keyProp->tick + keyProp->debounceTimeMs;
		keyProp->keyPressed = 0;
		keyProp->autoRepeatStart = 0;
		keyProp->autoRepeatTarget = 0;
	}
	return result;
}

Notice it receives a pointer to the key structure and returns a byte (uint8_t) which represents current key state:

  • 0 for key not pressed;
  • 1 for key pressed;

When key is pressed for more than “n” ms (where n = debounce time * auto repeat multiplier), auto repeat mode is engaged and function toggles its return value (from 0 to 1 to 0 and so on) while the key is pressed. An internal mechanism also increases the toggling speed over time, so in the beginning, repeating is slow and gradually speed-up while key is pressed! This way, the application code does not need to be aware of what is going on!

The library also uses the 32-bit tick timer (which operates as a free-running counter) to track all timing regarding debouncing and auto-repeat, it is non-blocking, so it won’t slow down your code while waiting for a key press or wait for a debounce timer to expire!

It uses ST’s CubeMX HAL functions so you can easily port the code to other microcontroller families.

Usage is pretty simple: declare as many key variables as needed, initialize them with initKeyAutoRepeat() and periodically call readKeyAutoRepeat() to check each key state! You need to configure your key pins as GPIO inputs and provide pulling devices (internal or external ones).

Don’t forget to set targetState parameter accordingly! So, if you are using a pull-up device and your key pulls the GPIO to ground when pressed, your targetState should be GPIO_PIN_RESET. If you are using a pull-down device and your key pulls the GPIO do VDD when pressed, your targetState should be GPIO_PIN_SET!!!

AutoRepeatKey keyIncStruct;
AutoRepeatKey keyDecStruct;
...
initKeyAutoRepeat(&keyIncStruct,KEY_INC_GPIO_Port,KEY_INC_Pin,GPIO_PIN_RESET,10,60);
initKeyAutoRepeat(&keyDecStruct,KEY_DEC_GPIO_Port,KEY_DEC_Pin,GPIO_PIN_RESET,10,60);
...
// And then simply read keys within your main loop:

while (1) {
  if (readKeyAutoRepeat(&keyIncStruct)){
    counter++;
    printCounter(counter);
  }
  if (readKeyAutoRepeat(&keyDecStruct)){
    counter--;
    printCounter(counter);
  }
}

Checkout my GitHub repository for a fully working Nucleo32 board (STM32L432) example!

Leave a Reply