G0001配置定时器之非阻塞编程技巧
分享作者:F888
作者昵称:我爱小易
评测品牌:灵动微电子
评测型号:MM32G0001A1TC
发布时间:2025-10-15 11:16:59
1
前言
前面的铁子已经教会大家如何配置串口和SPI了这一次我们来教大家利用定时器进行非阻塞的编程技巧
开源口碑分享内容

平常的开发中,我们经常常常用 循环等待某个操作或 阻塞试的delay() 函数实现延时,比如:

while (1) {
    LED_ON;
    delay(500ms);
    LED_OFF;
    delay(500ms);
}
while(按键弹起)

但这种方式会完全阻塞 CPU,导致系统无法响应其他事件(如串口指令、传感器数据、按键等)。
今天这个教程就是教大家来让cpu“同时做多件事” —— 这就是 非阻塞编程(Non-blocking Programming) 的意义。

今天,我们就以 MM32G0001(Mini-G0001) 为例,通过 定时器中断 + 状态机,实现一个 “按键切换LED闪烁频率” 的功能,彻底告别 delay()

一、项目需求

  • 按下按键(KEY1,接 PA2),循环切换 LED 闪烁模式:
  • 主循环 不能使用任何延时函数
  • 按键需支持 防抖(本例通过定时器周期自然消抖)

二、核心思想:时间驱动 + 状态机

我们借助 TIM1 定时器,每 1ms 产生一次中断,在中断中:

  1. 累加全局时间计数器times
  2. 调用按键与LED状态处理函数KEY_Main()

主循环可以做自己的事,真正做到 “非阻塞”

三、硬件与初始化

1. 按键初始化(PA2,上拉输入)

void KEY_INIT(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);
    
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;  // 内部上拉
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2;      // KEY1
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
}
按键按下时,PA2 为低电平(Bit_RESET)

2. 定时器 TIM1 初始化(1ms 中断)

系统由于时钟为 48MHz):

void TIMER1_INIT(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1ENR_TIM1, ENABLE);

    TIM_TimeBaseInitTypeDef TIM_InitStruct;
    TIM_InitStruct.TIM_Prescaler = 48 - 1;      // 48MHz / 48 = 1MHz → 1us/计数
    TIM_InitStruct.TIM_Period = 1000 - 1;       // 1000 * 1us = 1ms
    TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_InitStruct.TIM_ClockDivision = TIM_CKD_Div1;
    TIM_InitStruct.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM1, &TIM_InitStruct);

    // 配置 NVIC 中断
    NVIC_InitTypeDef NVIC_InitStruct;
    NVIC_InitStruct.NVIC_IRQChannel = TIM1_BRK_UP_TRG_COM_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPriority = 2;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);

    TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE);
    TIM_Cmd(TIM1, ENABLE);
}


四、非阻塞逻辑实现

1. 全局变量(记录状态)

static uint8_t key_now_num;      // 当前按键状态
static uint8_t key_formerly_num; // 上次按键状态
static int times = 0;            // 全局时间计数器(单位:1ms)

2. 按键扫描函数

#define KEY1_NUM 1
#define GET_KEY1 GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2)

uint8_t Get_KEY_Num(void)
{
    if (GET_KEY1 == Bit_RESET) return KEY1_NUM;
    return 0;
}

3. 核心状态机:KEY_Main()

uint8_t KEY_Main(void)
{
    static uint8_t i = 0;          // LED 状态(0/1)
    static uint8_t key_num_mode = 0; // 闪烁模式(0/1/2)

    // 检测按键上升沿(按下瞬间)
    key_formerly_num = key_now_num;
    key_now_num = Get_KEY_Num();
    if (key_formerly_num != key_now_num && key_now_num == KEY1_NUM)
    {
        key_num_mode = (key_num_mode + 1) % 3; // 切换模式
    }

    // 根据模式控制 LED 闪烁
    switch (key_num_mode)
    {
        case 0: // 250ms
            if (times % 250 == 0) {
                GPIO_WriteBit(GPIOA, GPIO_Pin_11, (BitAction)(i & 0x01));
                i = ~i;
            }
            break;
        case 1: // 500ms
            if (times % 500 == 0) {
                GPIO_WriteBit(GPIOA, GPIO_Pin_11, (BitAction)(i & 0x01));
                i = ~i;
            }
            break;
        case 2: // 1000ms
            if (times % 1000 == 0) {
                GPIO_WriteBit(GPIOA, GPIO_Pin_11, (BitAction)(i & 0x01));
                i = ~i;
            }
            break;
    }

    return key_num_mode;
}

4. 定时器中断服务函数

void TIM1_BRK_UP_TRG_COM_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET)
    {
        KEY_Main();   // 处理按键与LED
        times++;      // 时间+1ms
        TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
    }
}

五、结果

通过本项目,我们掌握了嵌入式开发中至关重要的 非阻塞编程思想
用“时间标记”代替“时间等待”,用“状态机”代替“顺序执行”。

这是迈向 RTOS(实时操作系统)事件驱动架构 的第一步。



全部评论
暂无评论
0/144