🛠 Interruption EXTI + anti-rebond par timer
Objetivos
Reemplazar el antirrebote bloqueante por un diseño verdaderamente orientado a eventos: la pulsación en PA0 genera una interrupción externa (EXTI0), un temporizador general de 16 bits que funciona a 1 kHz confirma la pulsación 20 ms después, y el núcleo pasa su tiempo en WFI (wait for interrupt). Manipulará lo siguiente en la SPL oficial (GD32F10x Firmware Library V2.2.x): RCU, GPIO, AFIO/exti_source, EXTI, TIMER, NVIC. Esta es la base de todo firmware reactivo en Cortex-M3.
Material
- Tarjeta GD32F103C8T6 / CBT6 compatible con Blue Pill, cristal HXTAL de 8 MHz soldado (el SystemInit del pack SPL configura el PLL para 108 MHz desde 8 MHz)
- Botón en PA0 (pulsación = HIGH, resistencia pull-down), LED en PC13 (activa en LOW) — pinout verificado según la ficha técnica
- Programador SWD (ST-Link) + OpenOCD compatible con GD32 (tool-openocd-gd32) o GD-Link
- SPL V2.2.x (espejo CommunityGD32Cores/gigadevice-firmware-and-docs) + herramientas ARM GCC o Keil (¡con el algoritmo de flash GD32, no STM32!)
Código completo
/* 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 */
}
}
Verificación esperada
Cada pulsación clara cambia el estado del LED, sin doble disparo, y el programa permanece reactivo (ningún bucle delay). En el depurador, se observa que el contador ticks_ms avanza 1 por cada milisegundo real si el PLL de 108 MHz está correctamente activo. El consumo disminuye respecto al bucle activo gracias al WFI (despertar en unas pocas microsegundos, del orden de 4,5 µs para el modo sleep según la hoja de datos).
Errores frecuentes
- Interrupción que se re-dispara indefinidamente: bandera EXTI o TIMER no borrada en el handler; todo handler debe limpiar su flag.
- El handler nunca se llama: reloj RCU_AF olvidado (la selección de fuente EXTI no funciona), o NVIC no activado, o grupo de prioridad incoherente.
- Temporizador a 2× o 13,5× de la velocidad correcta: el temporizador está sincronizado con el reloj APB; si asume 72 MHz (reflejo de STM32) en un GD32 a 108 MHz, los «20 ms» se convierten en 13 ms. Calcule desde el reloj real.
- Bloqueo al arrancar en HSE: proyectos portados de ST sin el HSE_STARTUP_TIMEOUT = 0xFFFF exigido por AN001.
- Fallo de flash con Keil/IAR: reemplace el algoritmo de programación flash por el de GD32 (AN001); el algoritmo STM32 falla o bloquea.
- Prioridades EXTI/timer invertidas: si EXTI sobreescribe al timer, ticks_ms puede congelarse durante los rebotes; mantenga los handlers cortos.
Otros tutoriales para esta placa
Ejemplos de código para esta placa
clignotante.inoArduinobouton-led.inoArduinorespiration.inoArduinopotentiometre.inoArduinopont-uart.inoArduinooled-i2c.inoArduino