ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

【STM32踩坑日记】铁头山羊标准库平衡车电池电压检测 裸机环境下的“时间切片”:用类似 FreeRTOS 的思想写出非阻塞代码

【STM32踩坑日记】铁头山羊标准库平衡车电池电压检测 裸机环境下的“时间切片”:用类似 FreeRTOS 的思想写出非阻塞代码 1. 引言告别“线性思维”在裸机环境下写 STM32 代码很容易陷入一种“线性思维”——干完这件事再干下一件事。如果遇到需要等待的场景比如让一个低电量报警灯以 10Hz 的频率闪烁初学者往往会顺手写一个delay_ms(100)。但平时咱们写稍微复杂一点的控制板特别是遇到那种又要处理多路传感器、又要搞底层闭环控制甚至还要跟上位机比如 ROS 2 框架进行高频通信的机器人项目比如搞类似 FarmCNC 这种农业底盘时代码里是绝对容不下半个delay_ms()的。死等延时会把主循环完全卡死导致整个系统的控制周期崩溃。今天借着写一个极简的“电池电压监控模块”来聊聊怎么在裸机环境下用类似 FreeRTOS 的“任务切片”与“状态机”思想写出极其顺滑的非阻塞代码。2. 核心思想交出 CPU 的控制权在 FreeRTOS 中任务在等待某个条件时会主动挂起Yield把 CPU 让给其他就绪任务。在裸机的while(1)循环中我们其实可以利用系统的滴答定时器SysTick来实现类似的效果让每个功能块变成一个非阻塞的“任务函数”每次主循环调用它时它只看一眼时间戳如果时间没到立马return交出控制权。假设我们现在要用三颗 LED 来指示极低电量时的“闪烁报警”。为了避免代码堆叠在一块难以阅读我们将这个逻辑拆解为三个要素来看。3. 要素一定义任务锚点与状态首先我们要为这个任务准备两个全局或静态变量它们是整个状态机的“记忆”。// 记录上一次状态翻转的时间戳staticuint32_tlastTime0;// 记录当前 LED 的状态0代表熄灭1代表点亮staticuint8_tstage0;lastTime用来锚定时间而stage则是状态机的核心让函数在下一次被调用时知道自己上次干到了哪一步。4. 要素二构建“时间门”打卡机制接下来我们在任务切片函数App_Bat_Proc()中利用GetTick()构建一个时间屏障。voidApp_Bat_Proc(void){// ... 前面是正常电量时的长亮逻辑 ...// 极低电量下的报警逻辑else{uint32_tnowGetTick();// 获取当前系统时间// 核心非阻塞逻辑只在时间差大于 100ms 时才放行if(now-lastTime100){// ----- 这里放入状态流转代码 (见要素三) -----lastTimenow;// 任务完成后刷新时间戳为下一次计时做准备}}}这段代码完美体现了时间调度思维每次主循环切到这里它做的第一件事就是做个减法。如果没到 100ms整个if块直接跳过函数瞬间执行完毕CPU 无缝去跑下个任务。绝不霸占算力。5. 要素三填入状态机流转当 100ms 的时间片终于到了系统进入if内部这时候就轮到switch(stage)登场了。它会根据当前的记忆直接执行对应的动作并把状态拨向下一步。switch(stage){case0:// 如果当前是熄灭状态{// 执行点亮GPIO_WriteBit(GPIOA,GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6,Bit_SET);stage1;// 把状态切换为“已点亮”break;}case1:// 如果当前是点亮状态{// 执行熄灭GPIO_WriteBit(GPIOA,GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6,Bit_RESET);stage0;// 把状态切换回“已熄灭”break;}}6. 总结从“流水账”到“系统级架构”这种“基于时间戳的时间切片 简单的状态机”结构可以说是裸机并发编程的万金油。代码拆分开来看逻辑异常清爽。只要把各个外设的控制逻辑都封装成这样的非阻塞函数你就可以在main的while(1)里放肆地挂载十几个甚至几十个任务。系统不仅不会卡顿还能保持极高的响应速度这也是从“流水账式写代码”迈向“系统级架构设计”的关键一步。
返回列表