单片机 - STM32 非阻塞式编程详解:以 LED 和按键为例(附超详细寄存器级代码)

STM32 非阻塞式编程:以 LED 和按键为例

一、前言

在嵌入式开发中,我们经常需要处理多个任务,比如测距、闪灯、响应用户按键、处理串口接收等等。如果你在主循环中使用 delay() 之类的阻塞函数,那这些任务就只能一个一个做,效率低下,体验不佳。

非阻塞式编程,就是为了解决这个问题的一种思路。它避免在主循环中使用任何会"卡住 CPU"的阻塞操作,从而允许 CPU 每时每刻都能轮询检查所有任务是否需要处理,实现"多任务调度"的效果。

本文将从原理讲起,并用 STM32F407 开发板配合 LED 和按键 做一个完整的寄存器级非阻塞例程,帮你彻底理解这个核心知识点。

二、阻塞 vs 非阻塞:到底有什么区别

类型 示例 优点 缺点
阻塞式 delay(1000); 写起来简单直观 会让 CPU 等待,浪费资源
非阻塞式 if (millis - last >= 1000) 不卡主循环,适合多任务 写起来需要设计思路、变量管理

举个例子:

c 复制代码
// 阻塞式延时方式闪烁 LED
LED_ON();
delay_ms(1000);
LED_OFF();
delay_ms(1000);

这个时候,CPU 就被 delay_ms() 卡死了,什么事也干不了。

而非阻塞的写法如下:

c 复制代码
if (SysTickCounter - lastToggleTime >= 1000) {
    lastToggleTime = SysTickCounter;
    toggle_LED();
}

CPU 每次循环只判断一下时间是否到了,LED 闪烁的同时,主循环还能继续干别的事。

三、非阻塞的核心思路

非阻塞的核心是:"记录上一次事件发生的时间,并每次循环中判断是否满足条件。"

用人话解释:

  • 如果你要等一分钟,就不要站着等(阻塞);
  • 而是每隔几秒瞄一眼表,看时间到了没有(非阻塞)。

四、实现非阻塞:我们用 SysTick 来做时间基准

我们需要一个系统计时器来定时递增变量,用于判断时间间隔。在 STM32F4 中,最简单的方法就是用 SysTick 定时器

1. 初始化 SysTick 计时器(1ms中断一次)

c 复制代码
void SysTick_Init(void)
{
    // 设置重装载值,使得每 1ms 触发一次中断
    // 假设系统主频是 168MHz
    // 168000000 / 1000 = 168000
    SysTick->LOAD = 168000 - 1;

    // 设置时钟源为处理器时钟
    SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk;

    // 使能 SysTick 中断
    SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;

    // 使能 SysTick 定时器
    SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
}

2. SysTick 中断服务函数

c 复制代码
volatile uint32_t systick_ms = 0;  // 毫秒计数变量

void SysTick_Handler(void)
{
    systick_ms++;  // 每毫秒加一
}

五、LED 非阻塞闪烁代码

1. GPIO 初始化(LED 接 PA5)

c 复制代码
void LED_Init(void)
{
    RCC->AHB1ENR |= (1 << 0);  // 使能 GPIOA 时钟

    GPIOA->MODER &= ~(3 << (5 * 2));  // 清除模式位
    GPIOA->MODER |= (1 << (5 * 2));   // 设置为输出模式

    GPIOA->OTYPER &= ~(1 << 5);       // 推挽输出
    GPIOA->OSPEEDR |= (3 << (5 * 2)); // 高速
    GPIOA->PUPDR &= ~(3 << (5 * 2));  // 无上下拉
}

void LED_Toggle(void)
{
    GPIOA->ODR ^= (1 << 5);  // 取反输出
}

2. 非阻塞闪烁逻辑

c 复制代码
void LED_Blink_NonBlocking(void)
{
    static uint32_t last_time = 0;

    // 判断当前时间与上次时间的间隔是否 >= 500ms
    if (systick_ms - last_time >= 500)
    {
        last_time = systick_ms;  // 记录新的时间戳
        LED_Toggle();            // 切换 LED 状态
    }
}

六、按键非阻塞检测(支持消抖)

1. GPIO 初始化(按键接 PC13)

c 复制代码
void Key_Init(void)
{
    RCC->AHB1ENR |= (1 << 2);     // 使能 GPIOC 时钟

    GPIOC->MODER &= ~(3 << (13 * 2));  // 设置为输入模式
    GPIOC->PUPDR |= (1 << (13 * 2));   // 上拉
}

2. 非阻塞方式读取按键(支持简单消抖)

c 复制代码
uint8_t Key_GetPress(void)
{
    static uint8_t key_state = 1;     // 初始为释放状态(PC13 默认为高)
    static uint32_t last_debounce_time = 0;

    if ((GPIOC->IDR & (1 << 13)) == 0)  // 按键被按下(低电平)
    {
        if (systick_ms - last_debounce_time >= 20) // 20ms去抖
        {
            last_debounce_time = systick_ms;

            if (key_state == 1)
            {
                key_state = 0;  // 记录状态为"已按下"
                return 1;       // 返回按键事件
            }
        }
    }
    else
    {
        key_state = 1;  // 松开后恢复初始状态
    }

    return 0;  // 没有新事件
}

七、主函数整合

c 复制代码
int main(void)
{
    SysTick_Init();
    LED_Init();
    Key_Init();

    while (1)
    {
        LED_Blink_NonBlocking();  // 每500ms闪烁一次

        if (Key_GetPress())       // 按键按下就立刻响应
        {
            // 可以加入更多操作,比如切换模式、串口输出等
            GPIOA->ODR ^= (1 << 5);  // 再次切换 LED
        }

        // 可拓展:串口接收处理、传感器轮询等其他非阻塞任务
    }
}

八、总结:非阻塞的精髓

  • 避免用 delay(),而是用时间戳差值判断
  • 每个任务都维护自己的状态变量和计时变量
  • 适合多个任务并发运行的情况(状态机 + 事件驱动)

九、扩展阅读建议

  • 状态机编程模型
  • 事件驱动模型
  • 基于定时器的软定时器管理模块设计
  • 使用 RTOS 代替非阻塞轮询的多任务调度

(完)

相关推荐
Natsume171037 分钟前
嵌入式开发:GPIO、UART、SPI、I2C 驱动开发详解与实战案例
c语言·驱动开发·stm32·嵌入式硬件·mcu·架构·github
MeshddY2 小时前
(超详细)数据库项目初体验:使用C语言连接数据库完成短地址服务(本地运行版)
c语言·数据库·单片机
m0_555762902 小时前
STM32常见外设
stm32·单片机·嵌入式硬件
森焱森2 小时前
无人机三轴稳定化控制(1)____飞机的稳定控制逻辑
c语言·单片机·算法·无人机
循环过三天2 小时前
3-1 PID算法改进(积分部分)
笔记·stm32·单片机·学习·算法·pid
天天爱吃肉82183 小时前
ZigBee通信技术全解析:从协议栈到底层实现,全方位解读物联网核心无线技术
python·嵌入式硬件·物联网·servlet
东风点点吹3 小时前
STM32F103的boot跳转APP不成功问题排除
stm32·单片机·嵌入式硬件
猫猫的小茶馆6 小时前
【STM32】预分频因子(Prescaler)和重装载值(Reload Value)
c语言·stm32·单片机·嵌入式硬件·mcu·51单片机
riveting6 小时前
明远智睿H618:开启多场景智慧生活新时代
人工智能·嵌入式硬件·智能硬件·lga封装·3506
三万棵雪松7 小时前
【STM32HAL-第1讲 基础篇-单片机简介】
stm32·单片机·嵌入式硬件