🛠 Interruption EXTI + anti-rebond par timer

GD32F103 (GigaDevice) — chip + placas compatibles Blue Pill · avance · Schéma : IMG-gd32f103-avance-1-exti-timer-debounce.svg — chronogramme EXTI sur PA0, timer 1 kHz, machine à états anti-rebond sans temporisation bloquante, LED PC13 commandée en SPL

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

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

Otros tutoriales para esta placa

Ejemplos de código para esta placa

← Volver a la ficha 🛠 Todos los tutoriales