1. 系统概述
本代码实现了一个基于 SysTick 定时器的合作式时间片轮询调度器,适用于资源受限的 Cortex-M 系列 MCU(如 STM32F4、GD32F4、N32G4 等)裸机开发环境。
核心思想:
- 使用 SysTick 定时器产生每 1ms 一次的中断,作为全局时间基准。
- 在中断中执行最高实时性任务。
- 在主循环中通过任务函数数组轮询方式,实现多个不同周期任务的准时执行。
- 无需操作系统,代码体积小、开销低、确定性强、易于调试和维护。
适用于:
- 电机控制、传感器采集、通信协议处理、状态机、LED 指示等典型嵌入式应用。
2. 整体架构图
SysTick (1ms 中断)
│
├── 更新 systick_cnt (全局毫秒计数器)
└── 执行 Task1ms()(最高优先级任务)
主循环 (while(1))
│
└── 遍历 tasks[] 数组
│
└── 对于每个任务:
如果 (当前时间 - 上次执行时间 >= 周期)
更新上次执行时间
执行任务函数
3. 代码详细说明
3.1 头文件与任务声明
#include "stm32f4xx.h"
// 任务函数声明
void Task1ms(void);
void Task10ms(void);
void Task100ms(void);
void Task1000ms(void);
- 所有任务函数必须无参数、无返回值(void)。
- 建议按执行频率命名,便于管理。
3.2 任务结构体与数组
typedef struct {
void (*func)(void); // 任务函数指针
uint32_t period_ms; // 执行周期(单位:毫秒)
uint32_t last_run; // 上次执行的时间戳(基于 systick_cnt)
} Task_t;
// 任务调度表
Task_t tasks[] = {
{ Task10ms, 10, 0 },
{ Task100ms, 100, 0 },
{ Task1000ms,1000, 0 },
};
#define TASKS_COUNT (sizeof(tasks) / sizeof(tasks[0]))
优点:
- 添加新任务只需在数组中增加一行,无需修改调度逻辑。
- 所有周期一目了然,便于调整。
- last_run 初始化为 0,上电后各任务会在对应时间点后首次执行。
3.3 全局时间基准
volatile uint32_t systick_cnt = 0; // 1ms 递增的全局计数器
- volatile 防止编译器优化,确保中断与主循环可见一致值。
- 32 位计数器约 49.7 天溢出一次,使用减法比较方式可安全处理溢出。
3.4 SysTick 中断服务函数
void SysTick_Handler(void)
{
systick_cnt++;
Task1ms(); // 最高实时性任务在此执行
}
- 每 1ms 执行一次。
- 建议只放执行时间极短(<50~100μs)的任务,如:
- PWM 占空比更新
- 控制环计算
- ADC 触发
- 编码器采样
3.5 主函数
int main(void)
{
SystemClock_Config(); // 系统时钟初始化
SysTick_Config(SystemCoreClock / 1000); // 配置 1ms 中断
GPIO_Init();
UART_Init();
// 初始化任务上次执行时间(防止启动瞬间全部执行)
for (uint32_t i = 0; i < TASKS_COUNT; i++) {
tasks[i].last_run = systick_cnt;
}
while (1)
{
uint32_t current = systick_cnt;
// 遍历所有任务
for (uint32_t i = 0; i < TASKS_COUNT; i++) {
if (current - tasks[i].last_run >= tasks[i].period_ms) {
tasks[i].last_run = current;
tasks[i].func();
}
}
// __WFI(); // 可选:空闲时进入睡眠,降低功耗
}
}
关键点:
- current - last_run >= period_ms:防溢出安全比较方式。
- 主循环仅负责调度,可执行复杂逻辑(如浮点运算、字符串处理)。
- 可开启 __WFI() 实现低功耗待机,仅由 SysTick 中断唤醒。
3.6 示例任务实现
void Task1ms(void) { /* 高实时任务 */ }
void Task10ms(void) { /* 传感器读取、滤波、协议处理 */ }
void Task100ms(void) { /* LED 闪烁、按键扫描 */ }
void Task1000ms(void) { /* 串口打印日志、心跳包、参数保存 */ }
4. 使用指南
4.1 添加新任务
-
声明函数原型:void NewTask(void);
-
实现函数体。
-
在 tasks[] 数组中添加一行:
{ NewTask, 50, 0 }, // 每 50ms 执行一次
4.2 调整任务优先级
- 要求最高实时性的任务 → 放入 SysTick_Handler 中(如 Task1ms)。
- 次高优先级 → 放在周期较小的任务(例如 5ms、10ms)。
- 低优先级、非实时任务 → 周期较大(如 500ms、1000ms)。
4.3 性能与实时性注意事项
- 任何单个任务执行时间不应超过其周期的 50%(建议 <30%),避免阻塞其他任务。
- 中断中任务总执行时间建议 <100μs。
- 若任务执行时间较长,可拆分为状态机,分多次完成。
5. 优点总结
| 特性 | 说明 |
|---|---|
| 确定性强 | 所有任务周期固定,无抢占延迟 |
| 资源占用极低 | 无栈切换、无信号量、无队列 |
| 易调试 | 单线程逻辑,断点跟踪简单 |
| 可维护性高 | 任务数组集中管理,新增/删除方便 |
| 低功耗支持 | 支持 __WFI() 睡眠 |
6. 适用场景与升级路径
适用:中小型嵌入式系统(<20 个任务,实时要求中等)
不适用:复杂多任务、需要优先级抢占、动态创建任务的场景
升级路径:
- 需要抢占式调度 → 移植 FreeRTOS
- 需要消息队列、信号量 → 使用 RT-Thread 或 uCOS
本调度框架已在众多工业控制、仪表、消费电子裸机项目中稳定运行,是嵌入式开发者首选的时间管理方案。
源码:
// main.c
#include "stm32f4xx.h"
// ==================== 任务函数声明 ====================
void Task1ms(void); // 每 1ms 执行(放在中断中)
void Task10ms(void); // 每 10ms 执行
void Task100ms(void); // 每 100ms 执行
void Task1000ms(void); // 每 1000ms 执行
// ==================== 任务结构体定义 ====================
typedef struct {
void (*func)(void); // 任务函数指针
uint32_t period_ms; // 执行周期(毫秒)
uint32_t last_run; // 上次执行时间(基于 systick_cnt)
} Task_t;
// ==================== 任务数组 ====================
// 注意:Task1ms 单独处理(放在中断中),其余放在主循环数组中
Task_t tasks[] = {
{ Task10ms, 10, 0 },
{ Task100ms, 100, 0 },
{ Task1000ms,1000, 0 },
// 如需添加新任务,直接在此添加一行即可
// { NewTask, 50, 0 },
};
#define TASKS_COUNT (sizeof(tasks) / sizeof(tasks[0]))
// ==================== 全局变量 ====================
volatile uint32_t systick_cnt = 0; // 1ms 时间基准计数器
// ==================== SysTick 中断服务函数 ====================
void SysTick_Handler(void)
{
systick_cnt++;
// 高实时性任务直接放在这里执行(执行时间必须极短!)
Task1ms();
}
// ==================== 主函数 ====================
int main(void)
{
// 系统时钟配置(例如 168MHz)
SystemClock_Config();
// 配置 SysTick 为 1ms 中断
SysTick_Config(SystemCoreClock / 1000);
// 初始化外设(GPIO、UART 等)
GPIO_Init();
UART_Init();
// 初始化任务数组的 last_run(可选,防止启动瞬间全部执行)
for (uint32_t i = 0; i < TASKS_COUNT; i++) {
tasks[i].last_run = systick_cnt;
}
while (1)
{
uint32_t current = systick_cnt;
// 遍历任务数组,检查是否到达执行时间
for (uint32_t i = 0; i < TASKS_COUNT; i++) {
if (current - tasks[i].last_run >= tasks[i].period_ms) {
tasks[i].last_run = current; // 更新上次执行时间
tasks[i].func(); // 执行任务
}
}
// 主循环空闲时可进入低功耗模式
// __WFI(); // 等待中断唤醒,节省功耗
}
}
// ==================== 任务函数实现示例 ====================
void Task1ms(void)
{
// 高优先级、硬实时任务
// 示例:电机 PWM 更新、ADC 采样触发、控制环计算等
// 注意:执行时间建议 < 50~100us
}
void Task10ms(void)
{
// 中等频率任务
// 示例:传感器数据读取、滤波、PID 计算、CAN 报文处理等
}
void Task100ms(void)
{
// 低频任务
// 示例:LED 闪烁、按键消抖扫描、状态机更新
static uint8_t led_state = 0;
if (led_state) {
GPIOC->BSRR = GPIO_PIN_13; // 点灯(假设低电平点亮)
} else {
GPIOC->BSRR = (uint32_t)GPIO_PIN_13 << 16; // 灭灯
}
led_state = !led_state;
}
void Task1000ms(void)
{
// 1秒周期任务
// 示例:心跳包发送、运行时间统计、参数保存、串口打印日志等
}