小小调度器:轻量任务调度的应用
参考http://www.51hei.com/bbs/dpj-132959-1.htmlhttps://www.armbbs.cn/forum.php?modviewthreadtid110648https://bbs.eeworld.com.cn/thread-501913-1-1.html仓库https://github.com/smset028/xxddqhttps://github.com/fxyc87/xxddq改版前面我们从理论层面详细介绍了“小小调度器”的设计思路和实现原理现在到了把它真正用起来的阶段在嵌入式开发的世界里资源永远都是最宝贵的尤其是在一些低端或者老旧的单片机上内存和处理能力都非常有限此时一个轻量、高效且易用的任务调度方案尤为重要“小小调度器”恰恰在这方面表现得非常出色它的设计极为精简每个任务只需要保存两个状态变量一个 unsigned short 用作定时器计数器和一个 unsigned char 用来记录任务当前执行的代码行位置换句话说一个任务的 RAM 占用最小仅为 3 个字节这在嵌入式系统中实在是太“香”了不仅节省了宝贵的内存空间也让系统能够同时管理更多任务这种极简设计使得“小小调度器”在资源受限的环境下依然能够稳定、可靠地执行多任务调度满足绝大多数实际需求无论是传感器采样、串口通信还是屏幕刷新都可以轻松实现任务之间的有序切换既避免了复杂的状态机设计也避免了繁重的 RTOS 资源开销准备工作将 xxddq 文件添加到 Keil 工程的包含路径中因为调度器使用状态机宏的方式大部分任务实际上是死循环但函数声明仍有返回值如果不想看到烦人的告警可以这样处理在魔术棒 - C/C 编译器 - Misc Controls 添加:--diag_suppress128,111任务结构定义每个任务都需要定义一个包含 C_task 成员的结构体例如Class(LED0Task){C_task task;// 必须包含uint8_ti;// 用户自定义变量}led0{.i0};说明C_task task; 是必须包含的调度器会根据这个成员来管理任务用户自定义变量可以任意添加但注意不要与调度器内部使用的变量重名任务函数定义任务函数使用 TaskFun 和 EndFun 包围如下TaskFun(LED0Task){while(1){if(me.i0){GPIO_WriteBit(GPIOA,GPIO_Pin_10,Bit_RESET);me.i1;}elseif(me.i1){GPIO_WriteBit(GPIOA,GPIO_Pin_10,Bit_SET);me.i0;}WaitX(500);// 必须手动主动释放CPU否则该任务永远占用CPU}}EndFun如果任务不需要延时也需要使用 WaitX(0); 来释放 CPU 控制权任务调度与更新机制外设中任务时间更新考虑到每个任务变量是文件内定义的我们采用“曲线救国”的办法在每个外设对应的 .c 文件中定义更新函数如voidGPIOTask_Update(void){UpdateTimer(led0);UpdateTimer(key0);}外设中任务运行函数同样为了更好的模块化每个外设文件中也定义运行函数voidGPIOTask_Run(void){RunTask(LED0Task,led0);RunTask(KEY0Task,key0);}更新函数在 SysTick 中调用voidSysTick_Handler(void){Ticks;// 可选全局计时器GPIOTask_Update();// 定时更新所有任务计时器}在主循环中执行任务intmain(void){// 初始化代码...while(1){GPIOTask_Run();// 执行各任务逻辑}}总结“小小调度器”使用极简的宏和状态机思想实现了对多个任务的低成本管理非常适合内存紧张的 MCU 项目通过合理组织代码结构使用更新和执行函数对任务状态进行控制你可以在不引入 RTOS 的情况下实现类似多任务调度的效果这套方法虽然看似“原始”但在资源受限系统中却非常实用尤其适用于裸机开发场景中让 MCU 稳定地跑多个任务而不会陷入状态机地狱或时间片混乱注意:由于“小小调度器”本身内部的任务调度机制依赖于 C 语言的 switch-case 结构进行任务状态切换因此在任务函数内部不要再嵌套使用 switch-case这可能会导致状态跳转逻辑冲突甚至出现难以排查的行为错误如果确实需要实现复杂的分支逻辑建议改用 if-else 或将该任务拆分为多个子任务分别管理以保持调度系统的稳定性与可维护性阅读原文小小调度器轻量任务调度的应用