1. 单片机任务调度方法比较
1.1 Delay循环方式
-
• 实现原理
while(1) { task1(); // 任务1 delay_ms(10); // 阻塞延迟 task2(); // 任务2 delay_ms(10); // 阻塞延迟 } -
• 优点
-
• 实现简单,易于理解
-
• 无需复杂的数据结构
-
• 资源消耗极低
-
-
• 缺点
-
• 任务间相互阻塞,效率低下
-
• 无法实现真正的多任务
-
• 响应实时性差
-
• 难以管理复杂的任务调度
-
• 实现原理
while(1) {
task1(); // 任务1
delay_ms(10); // 阻塞延迟
task2(); // 任务2
delay_ms(10); // 阻塞延迟
}
• 优点
• 实现简单,易于理解
• 无需复杂的数据结构
• 资源消耗极低
• 缺点
• 任务间相互阻塞,效率低下
• 无法实现真正的多任务
• 响应实时性差
• 难以管理复杂的任务调度
• 实现原理
void main() {
init(); // 初始化
while(1) {
task1(); // 前台任务(主循环)
task2();
check_events(); // 检查事件标志
}
}
void interrupt_isr() { // 后台(中断服务)
set_event_flag(); // 设置事件标志
}
• 优点:
• 相对简单的多任务处理
• 中断响应及时
• 资源消耗适中
• 适合中等复杂度的应用
• 缺点:
• 任务优先级管理困难
• 任务间同步复杂
• 每个任务都有单独的时间检查函数
• 调试难度较大
• 实现原理
void main() {
timer_init(); // 初始化定时器
create_timer(100, task1); // 创建100ms定时任务
create_timer(200, task2); // 创建200ms定时任务
while(1) {
timer_check(); // 检查并执行到期任务
idle_task(); // 空闲任务
}
}
• 优点:
• 任务调度灵活
• 时间管理精确
• 非阻塞执行
• 适合周期性任务
• 资源消耗相对较低
• 缺点
• 任务执行时间必须很短
• 无法处理高优先级抢占
• 复杂任务链管理困难
• 实现原理
void task1(void *param) { // 任务1
while(1) {
do_work();
osDelay(100); // 主动让出CPU
}
}
void task2(void *param) { // 任务2
while(1) {
do_work();
osDelay(200);
}
}
• 优点:
• 真正的多任务并行
• 任务优先级管理
• 丰富的同步机制(信号量、消息队列等)
• 任务间通信方便
• 系统稳定性高
• 缺点:
• 资源消耗大(RAM、ROM)
• 学习曲线陡峭
• 调试复杂
• 需要特定的硬件支持
| 调度方法 | 优点 | 缺点 | 适用场景 | 资源消耗 |
|---|---|---|---|---|
| Delay循环 | 1. 实现极其简单2. 无需额外数据结构3. 零内存开销4. 代码直观易懂 | 1. 任务间相互阻塞 2. 无法多任务并行 3. 响应实时性极差 4. CPU利用率低 5. 难以扩展 | • 单一任务的小型项目 • 学习和演示代码 • 对实时性无要求 | ★☆☆☆☆极低 |
| 前后台系统 | 1.结构相对简单2. 中断响应及时3. 适合中等复杂度应用4. 资源消耗可控 | 1. 任务优先级管理困难2. 任务同步机制复杂3. 容易出现优先级反转 4. 调试难度较大 5. 代码结构易混乱 | • 简单多任务应用• 实时性要求中等• 资源受限的MCU | ★★☆☆☆较低 |
| 软件定时器 | 1.时间管理精确2. 非阻塞任务调度3. 适合周期性任务4. 资源消耗较低5. 扩展性较好 | 1. 任务执行必须简短2. 无法实现任务抢占3. 复杂任务链管理困难 4. 缺少高级同步机制 | • 周期性任务调度• 时间敏感型应用• 资源受限的中型项目 | ★★☆☆☆适中 |
| RTOS | 1. 真正的多任务并行2. 完善的任务管理3. 丰富的同步机制4. 系统稳定性高5. 支持优先级抢占 | 1. 资源消耗大 2. 学习曲线陡峭 3. 调试复杂度高 4. 需要特定硬件支持5. 软件成本可能较高 | • 复杂多任务系统• 高实时性要求• 需要任务间通信• 工业级应用 | ★★★★★较高 |
typedef struct {
uint32_t interval; // 定时周期(毫秒)
uint32_t elapsed; // 已流逝时间
uint32_t repeat_count; // 重复次数(0=无限)
uint32_t executed_count; // 已执行次数
void (*callback)(void*); // 回调函数指针
void* param; // 回调参数
uint8_t active; // 激活状态
uint32_t user_data; // 用户数据(用于任务通信)
} SoftTimer_t;
初始化定时器池
↓
创建定时器(分配ID、设置参数)
↓
SysTick中断 → SoftTimer_UpdateTick() ← 更新时间戳
↓
主循环调用SoftTimer_Execute()
↓
检查每个定时器 elapsed >= interval?
↓
是 → 执行回调函数 → 更新执行计数
↓
检查重复次数是否达到
↓
是 → 标记定时器为非激活状态
void SoftTimer_UpdateTick(uint32_t tick_ms) {
system_time += tick_ms; // 更新系统时间
for (i = 0; i < MAX_SOFT_TIMERS; i++) {
if (timer_pool[i].active) {
timer_pool[i].elapsed += tick_ms; // 更新每个激活定时器的计时
}
}
}
void SoftTimer_Execute(void) {
// 第一阶段:收集到期定时器(中断保护)
for (i = 0; i < MAX_SOFT_TIMERS; i++) {
if (timer_pool[i].active &&
timer_pool[i].elapsed >= timer_pool[i].interval) {
expired[n++] = i; // 记录到期ID
timer_pool[i].elapsed = 0; // 重置计时
}
}
// 第二阶段:执行回调(不在中断保护内)
for (i = 0; i < n; i++) {
uint8_t idx = expired[i];
timer_pool[idx].callback(timer_pool[idx].param); // 执行回调
// 更新执行计数并检查重复次数
timer_pool[idx].executed_count++;
if (timer_pool[idx].repeat_count > 0 &&
timer_pool[idx].executed_count >= timer_pool[idx].repeat_count) {
timer_pool[idx].active = 0; // 达到重复次数,停用
}
}
}
// 设置用户数据(生产者)
SoftTimer_SetUserData(timer_id, data);
// 获取用户数据(消费者)
uint32_t data;
SoftTimer_GetUserData(timer_id, &data);
• 应用场景
• 任务间传递状态信息
• 传递执行结果
• 简单的消息通信
1. 在 soft_time.h 中设置中断控制函数 和 最大支持的软件定时器数量
/* 中断控制宏 */
#define OS_DISABLE_IRQ() __disable_irq()
#define OS_ENABLE_IRQ() __enable_irq()
/* 定义最大支持的软件定时器数量 */
#define MAX_SOFT_TIMERS 10
2. SysTick中断处理
// SysTick中断服务函数
void SysTick_Handler(void) {
// 更新时间基准(假设SysTick配置为1ms中断)
SoftTimer_UpdateTick(1);
// 其他需要1ms处理的任务
// ...
}
3. 在主循环中处理调用 SoftTimer_Execute
int main(void) {
// 硬件初始化
HAL_Init();
SystemClock_Config();
// 软件定时器初始化
SoftTimer_Init();
// 创建需要的定时器
// ...
while (1) {
// 检查并执行到期的定时器
SoftTimer_Execute();
// 其他任务(非阻塞)
process_uart();
update_display();
// 低功耗处理(如果有)
// __WFI();
}
}
4. 初始化并且创建定时器和回调函数
// 在main函数开始处初始化
SoftTimer_Init();
// 配置SysTick中断(例如1ms中断一次)
SysTick_Config(SystemCoreClock / 1000);
// 定时器回调函数
void led_blink(void* param) {
GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
(void)param; // 未使用参数
}
void sensor_read(void* param) {
uint8_t sensor_id = *(uint8_t*)param; // 使用参数
float value = read_sensor(sensor_id);
send_to_uart(value);
}
void complex_task(void* param) {
// 注意:回调函数执行时间要尽量短!
// 长时间任务应该分解或使用状态机
static uint8_t state = 0;
switch(state) {
case 0: /* 步骤1 */ break;
case 1: /* 步骤2 */ break;
// ...
}
}
// 创建LED闪烁定时器(500ms一次,无限重复)
uint8_t led_timer = SoftTimer_Create(500, 0, led_blink, NULL);
// 创建传感器读取定时器(1000ms一次,读取10次)
uint8_t sensor_id = 1;
uint8_t sensor_timer = SoftTimer_Create(1000, 10, sensor_read, &sensor_id);
// 创建单次定时器(2000ms后执行一次)
uint8_t once_timer = SoftTimer_Create(2000, 1, some_task, NULL);
1. 动态调整定时器
// 动态改变LED闪烁频率
static uint32_t led_interval = 500;
void adjust_led_timer(uint8_t timer_id) {
// 删除原定时器
SoftTimer_Delete(led_timer);
// 根据条件调整间隔
if (is_battery_low()) {
led_interval = 1000; // 电量低时慢闪
} else {
led_interval = 200; // 正常时快闪
}
// 重新创建定时器
led_timer = SoftTimer_Create(led_interval, 0, led_blink, NULL);
}
2. 任务间通信
// 生产者任务
void data_producer(void* param) {
static uint32_t counter = 0;
// 产生数据
uint32_t data = read_adc();
// 通过用户数据传递
SoftTimer_SetUserData(producer_timer_id, data);
// 设置标志通知消费者
counter++;
if (counter % 10 == 0) {
SoftTimer_SetUserData(producer_timer_id, data | 0x80000000); // 设置高位标志
}
}
// 消费者任务
void data_consumer(void* param) {
uint32_t data;
// 获取用户数据(会自动清零)
if (SoftTimer_GetUserData(producer_timer_id, &data)) {
if (data & 0x80000000) {
// 处理带标志的数据
process_important_data(data & 0x7FFFFFFF);
} else {
// 处理普通数据
process_normal_data(data);
}
}
}
3. 定时器链(任务序列)
void task_sequence_step1(void* param);
void task_sequence_step2(void* param);
void task_sequence_step3(void* param);
void start_sequence(void) {
// 启动第一步
SoftTimer_Create(0, 1, task_sequence_step1, NULL);
}
void task_sequence_step1(void* param) {
// 执行第一步任务
do_step1();
// 启动第二步(100ms后)
SoftTimer_Create(100, 1, task_sequence_step2, NULL);
}
void task_sequence_step2(void* param) {
// 执行第二步任务
do_step2();
// 启动第三步(50ms后)
SoftTimer_Create(50, 1, task_sequence_step3, NULL);
}
void task_sequence_step3(void* param) {
// 执行第三步任务
do_step3();
// 序列完成
sequence_complete_flag = 1;
}
4. 使用注意事项
1. 回调函数执行时间要短
• 尽量在1ms内完成
• 长时间任务应该分解或使用状态机
2. 避免在中断中创建/删除定时器
3. 定时器ID有效性检查
• 使用前检查返回值是否为SOFT_TIMER_INVALID_ID
• 定期检查定时器是否仍处于激活状态
4. 资源管理
• 定时器是有限资源(MAX_SOFT_TIMERS)
• 不用的定时器及时删除