🛠 Interruption EXTI + anti-rebond par timer
Objectives
Replace blocking debounce with a true event-driven design: a press on PA0 triggers an external interrupt (EXTI0), a general-purpose 16-bit timer running at 1 kHz confirms the press 20 ms later, and the core spends its time in WFI (wait for interrupt). You will use the official SPL (GD32F10x Firmware Library V2.2.x) for: RCU, GPIO, AFIO/exti_source, EXTI, TIMER, and NVIC. This is the foundation of any responsive firmware on Cortex-M3.
Hardware
- GD32F103C8T6 / CBT6 board compatible with Blue Pill, with soldered HXTAL 8 MHz crystal (the SystemInit in the SPL pack configures the PLL for 108 MHz from 8 MHz)
- Button on PA0 (press = HIGH, pull-down), LED on PC13 (active-LOW) — pinout verified from the datasheet
- SWD programmer (ST-Link) + OpenOCD compatible with GD32 (tool-openocd-gd32) or GD-Link
- SPL V2.2.x (mirror CommunityGD32Cores/gigadevice-firmware-and-docs) + ARM GCC toolchain or Keil (with the GD32 flash algorithm, not STM32!)
Complete Code
/* exti_debounce.c — GD32F103C8, SPL V2.2.x. 108 MHz (SystemInit du pack). */
#include "gd32f10x.h"
static volatile uint32_t ticks_ms = 0;
static volatile uint8_t flag_appui = 0;
static bool led_on = false;
void EXTI0_1_IRQHandler(void)
{
if (exti_interrupt_flag_get(EXTI_0) == SET) {
exti_interrupt_flag_clear(EXTI_0); /* sinon re-entrée infinie */
flag_appui = 1; /* signaler, ne pas traiter */
}
}
void TIMER2_IRQHandler(void)
{
if (timer_interrupt_flag_get(TIMER2, TIMER_INT_FLAG_UP) == SET) {
timer_interrupt_flag_clear(TIMER2, TIMER_INT_FLAG_UP);
ticks_ms++;
if (flag_appui) {
flag_appui = 0;
/* confirmation 20 ms apres l'event : le bouton est encore appuye */
if (ticks_ms >= 20 && gpio_input_bit_get(GPIOA, GPIO_PIN_0) == SET) {
led_on = !led_on;
gpio_bit_write(GPIOC, GPIO_PIN_13, led_on ? RESET : SET);
ticks_ms = 0;
}
}
}
}
int main(void)
{
timer_parameter_struct tp;
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_GPIOC);
rcu_periph_clock_enable(RCU_AF); /* AFIO : selection source EXTI */
rcu_periph_clock_enable(RCU_TIMER2);
gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13);
gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_0);
/* TIMER2 : 108 MHz / (107+1) / (999+1) = 1 kHz */
timer_deinit(TIMER2);
tp.prescaler = 107;
tp.alignedmode = TIMER_COUNTER_EDGE;
tp.counterdirection = TIMER_COUNTER_UP;
tp.period = 999;
tp.clockdivision = TIMER_CKDIV_DIV1;
tp.repetitioncounter = 0;
timer_init(TIMER2, &tp);
timer_interrupt_enable(TIMER2, TIMER_INT_UP);
timer_enable(TIMER2);
/* EXTI0 sur PA0, fronts montants et descendants */
gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOA, GPIO_PIN_SOURCE_0);
exti_init(EXTI_0, EXTI_INTERRUPT, EXTI_TRIG_BOTH);
exti_interrupt_flag_clear(EXTI_0);
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
nvic_irq_enable(TIMER2_IRQn, 1, 0);
nvic_irq_enable(EXTI0_1_IRQn, 1, 1);
while (1) {
__WFI; /* le coeur dort jusqu'a la prochaine interruption */
}
}
Expected Verification
Each distinct press toggles the LED, without double-triggering, and the program remains responsive (no delay loops). In the debugger, you will observe that the ticks_ms counter increments by 1 per real millisecond if the 108 MHz PLL is active. Power consumption is lower compared to an active loop thanks to WFI (wake-up in a few microseconds, on the order of 4.5 µs for sleep mode according to the datasheet).
Common Errors
- Interrupt triggering endlessly: EXTI or TIMER flag not cleared in the handler; every handler must clear its flag.
- Handler never called: RCU_AF clock forgotten (EXTI source selection does not work), or NVIC not enabled, or inconsistent priority group.
- Timer running at 2× or 13.5× the correct speed: the timer is clocked by the APB clock; if you assume 72 MHz (STM32 reflex) on a GD32 running at 108 MHz, the "20 ms" becomes 13 ms. Calculate based on the actual clock.
- Startup blocking on HSE: projects ported from ST without the HSE_STARTUP_TIMEOUT = 0xFFFF required by AN001.
- Flashing fails with Keil/IAR: replace the flash programming algorithm with the one for GD32 (AN001); the STM32 algorithm fails or locks up.
- Inverted EXTI/timer priorities: if EXTI overrides the timer, ticks_ms may freeze during bounces; keep handlers short.
Other tutorials for this board
Code examples for this board
clignotante.inoArduinobouton-led.inoArduinorespiration.inoArduinopotentiometre.inoArduinopont-uart.inoArduinooled-i2c.inoArduino