# mini_scheduler **Repository Path**: yao_mi/mini_scheduler ## Basic Information - **Project Name**: mini_scheduler - **Description**: 50行C语言,裸机实现us级多任务调度切换系统,无上下文切换。 - **Primary Language**: C - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-02-28 - **Last Updated**: 2026-02-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Mini Scheduler - 轻量级优先级任务调度器 Mini Scheduler 是一个极简的基于优先级的非抢占式任务调度器,适用于嵌入式系统或资源受限的环境,支持任务延时等待、优先级排序、非阻塞延时等核心功能,代码轻量、易移植、易扩展。无需上下文切换,裸机开销极小,可通过配置更高精度的计时器提高系统运行精度,实现ns或us级任务调度切换。 ## 项目特性 - 🎯 **优先级调度**:支持任务优先级配置(数值越小优先级越高) - ⏳ **非阻塞延时**:提供毫秒级非阻塞延时检查,避免任务阻塞 - ⚡ **轻量级设计**:核心代码不足50行,无外部依赖(除标准C库) - 🔧 **易扩展**:简单的API接口,快速添加自定义任务 - 📝 **调试友好**:任务名称、优先级、周期可视化打印 - 🕒 **高精度计时**:支持微秒级计时接口,满足高精度任务需求 ## 目录结构 ``` mini_scheduler/ ├── delay.c/.h # 延时/计时核心实现 ├── scheduler_tasks.c/.h # 调度器核心逻辑(任务控制、队列、调度循环) ├── mytasks.c/.h # 自定义任务实现(LED/KEY/DISP/SERIAL示例) ├── scheduler_sim.c # 调度器仿真主程序 └── .vscode/ # VSCode编译配置(可选) ``` ## 快速开始 ### 1. 环境要求 - 编译工具:GCC(Linux)/ MinGW(Windows) - 依赖:标准C库(`stdio.h`、`stdint.h`、`time.h`等) - 仿真运行:支持POSIX接口的系统(Linux/macOS) ### 2. 编译运行 #### 方式1:使用VSCode(推荐) 1. 打开项目目录,VSCode自动识别`.vscode/tasks.json`配置 2. 按下`Ctrl+Shift+B`(或点击「终端」→「运行生成任务」) 3. 选择「C/C++: gcc 生成活动文件」,自动编译生成可执行文件 4. 运行编译后的程序: ```bash ./scheduler_sim ``` #### 方式2:手动编译 ```bash # 进入项目目录 cd mini_scheduler # 编译所有源文件 gcc scheduler_tasks.c mytasks.c delay.c scheduler_sim.c -o scheduler_sim -lm # 运行程序 ./scheduler_sim ``` ### 3. 预期输出 ``` === Simple Priority Scheduler Demo === Scheduler initialized with 4 tasks, priority order: [0] DISP (prio=0, cycle=10ms) [1] KEY (prio=1, cycle=2ms) [2] LED (prio=2, cycle=50ms) [3] SERIAL (prio=3, cycle=10ms) Simulation running for 1000 ms... [ 2] KEY: 10 scans detected [ 4] KEY: 10 scans detected [ 6] KEY: 10 scans detected [ 8] KEY: 10 scans detected [ 10] DISP updated [ 10] KEY: 10 scans detected [ 10] SERIAL: system alive [ 12] KEY: 10 scans detected ... [ 60] DISP updated [ 60] KEY: 10 scans detected [ 60] LED ON [ 60] SERIAL: system alive ... [ 70] KEY: 10 scans detected [ 70] LED OFF [ 70] SERIAL: system alive ... [ 80] DISP updated [ 80] KEY: 10 scans detected [ 80] LED Tog 0 [ 80] SERIAL: system alive ... [ 90] DISP updated [ 90] KEY: 10 scans detected [ 90] LED Tog 1 [ 90] SERIAL: system alive ... === Simulation Finished === ``` ## 使用教程 ### 1. 添加自定义任务 #### 步骤1:定义任务ID 在`mytasks.h`的`TaskID_t`枚举中添加新任务: ```c typedef enum { TASK_LED = 0, TASK_KEY, TASK_DISP, TASK_SERIAL, TASK_MY_CUSTOM, // 新增自定义任务 TASK_NUM // 任务总数(必须放在最后) } TaskID_t; ``` #### 步骤2:实现任务函数 在`mytasks.c`中实现自定义任务逻辑: ```c // 自定义任务示例:每秒打印一次自定义信息 void MY_CUSTOM_Task(uint8_t taskid) { //注册状态放在最开头 (step = 1,2,3,.....),step从1开始,而不是0 Wait_register(TASK_MY_CUSTOM,1); //存放你自己的临时变量 // 根据需要,可以使用static类型变量 // 非阻塞延时1000ms(1秒),对应注册使用的参数 Wait_ms(TASK_MY_CUSTOM, 1, 1000); printf_show("[%5u] Custom Task: Hello Mini Scheduler!\n", YM_GetTick()); } ``` #### 步骤3:注册任务到调度器 在`scheduler_tasks.c`的`sch_tasks`数组中添加任务配置: ```c TaskCB_t sch_tasks[TASK_NUM] = { [TASK_LED] = {.func = LED_Task, .cycles = 50, .priority = 2, .state = 0, .ticks = 0, .name = "LED"}, [TASK_KEY] = {.func = KEY_Task, .cycles = 2, .priority = 1, .state = 0, .ticks = 0, .name = "KEY"}, [TASK_DISP] = {.func = DISP_Task, .cycles = 10, .priority = 0, .state = 0, .ticks = 0, .name = "DISP"}, [TASK_SERIAL] = {.func = SERIAL_Task, .cycles = 10, .priority = 3, .state = 0, .ticks = 0, .name = "SERIAL"}, // 新增自定义任务配置 [TASK_MY_CUSTOM] = {.func = MY_CUSTOM_Task, .cycles = 1000, .priority = 4, .state = 0, .ticks = 0, .name = "CUSTOM"} }; ``` #### 步骤4:重新编译运行 即可看到自定义任务按配置的周期和优先级执行。 ### 2. 核心API说明 | 函数/宏 | 功能说明 | |------------------------|--------------------------------------------------------------------------| | `YM_GetTick()` | 获取系统毫秒级时间戳(需根据硬件移植) | | `YM_DelayMs_Check()` | 非阻塞延时检查:返回1表示延时完成,自动更新时间戳;返回0表示未完成 | | `GetTimeUs()` | 获取微秒级高精度时间戳(用于任务内高精度延时) | | `Wait_ms(TaskID, step, ms)` | 任务内非阻塞延时宏:指定任务ID、状态步、延时毫秒数,自动处理状态跳转 | | `Wait_register()` | 任务状态注册宏:配合`Wait_ms`使用,实现状态恢复 | | `Scheduler_Init()` | 调度器初始化:排序任务队列、初始化时间戳 | | `Scheduler_Run()` | 调度器主循环:按优先级检查任务周期,执行到期任务 | ### 3. 任务优先级与周期配置 - **优先级**:`priority`数值越小,优先级越高(0为最高) - **执行周期**:`cycles`为任务执行周期(毫秒),调度器会按周期检查是否执行任务 - **任务队列**:初始化时自动按优先级排序,高优先级任务优先执行 ## 移植指南 ### 1. 嵌入式系统移植(以STM32为例) #### 步骤1:替换时间戳接口 将`delay.c`中的`YM_GetTick()`替换为HAL库的`HAL_GetTick()`: ```c // 原实现(仿真) uint32_t YM_GetTick(void) { return system_tick; } // 移植后(STM32) uint32_t YM_GetTick(void) { return HAL_GetTick(); } ``` #### 步骤2:移除POSIX依赖 若不需要微秒级计时,可删除`GetTimeUs()`中`clock_gettime`相关代码,或替换为硬件定时器实现: ```c // 简化版(无微秒计时需求) uint64_t GetTimeUs(void) { // 根据硬件定时器实现,或返回0 return 0; } ``` #### 步骤3:适配打印接口 将`printf_show`替换为串口打印(如`printf`重定向到USART): ```c // 原宏定义 #define printf_show(format, ...) printf(format,__VA_ARGS__) // 移植后 #define printf_show(format, ...) {sprintf(buf, format, ##__VA_ARGS__); HAL_UART_Transmit(&huart1, (uint8_t*)buf, strlen(buf), 100);} ``` 当然,如不想打印,为空即可: ```c // 移植后 #define printf_show(format, ...) ``` #### 步骤4:调整主循环 将`scheduler_sim.c`的主循环替换为嵌入式系统的主循环: ```c // STM32主循环示例 int main(void) { HAL_Init(); SystemClock_Config(); MX_USART1_UART_Init(); Scheduler_Init(); while (1) { Scheduler_Run(); // 调度器主循环 } } ``` ### 2. 关键注意事项 - 任务函数需为非阻塞设计,避免长时间占用CPU(通过`Wait_ms`实现分段执行) - 任务内的变量若需跨执行周期保存,需声明为`static` - 优先级高的任务会优先执行,需合理配置优先级避免低优先级任务饥饿 - 系统时间戳需保证单调递增,否则会导致延时检查异常 ## 注意事项 1. **任务设计原则**: - 避免在任务函数中使用阻塞式延时(如`delay_ms(1000)`),应使用`Wait_ms`非阻塞延时 - 任务执行时间应远小于其周期,避免影响其他任务执行 - 共享资源访问需加锁(如全局变量),避免多任务竞争 2. **性能考量**: - 任务数量不宜过多(建议≤16个),否则排序和遍历会增加开销 - 高精度计时(`GetTimeUs`)在嵌入式系统中需依赖硬件定时器,避免软件模拟 3. **调试技巧**: - 初始化时会打印任务优先级和周期,确认配置是否正确 - 通过`printf_show`打印任务执行日志,排查执行周期和状态异常 - 检查`system_tick`(或硬件时间戳)是否正常递增 4. **兼容性**: - 代码使用标准C编写,兼容C99及以上版本 - 嵌入式系统需适配编译器(如ARMCC、GCC for ARM) ## 贡献指南 欢迎提交Issue或PR改进项目: 1. Fork 本仓库 2. 创建特性分支(`git checkout -b feature/xxx`) 3. 提交修改(`git commit -m 'add: xxx功能'`) 4. 推送分支(`git push origin feature/xxx`) 5. 提交Pull Request ## 常见问题 ### Q1:任务执行不及时? A1:检查任务优先级配置(高优先级任务会抢占执行机会),或任务执行时间过长导致阻塞,建议拆分长任务为多个`Wait_ms`分段执行。 ### Q2:`Wait_ms`宏使用后任务无响应? A2:需确保先调用`Wait_register`注册状态,且`step`数值唯一,避免状态冲突。 ### Q3:在for循环或者其他循环中使用Wait_ms出现异常? A3:检查变量是否不是static类型的,非static类型在Wait_ms退出函数时变量值会被清空,导致无法继续往下。建议需要连续使用的变量改为static类型。 ### Q4:移植到STM32后时间戳不更新? A4:检查`HAL_GetTick()`是否正常初始化,确保SysTick定时器已开启。