【个人经验分享】MCU任务调度方法与软件定时器

1. 单片机任务调度方法比较

1.1 Delay循环方式

  • • 实现原理

    while(1) {
        task1();               // 任务1
        delay_ms(10);          // 阻塞延迟
        task2();               // 任务2
        delay_ms(10);          // 阻塞延迟
    }
    
  • • 优点

    • • 实现简单,易于理解

    • • 无需复杂的数据结构

    • • 资源消耗极低

  • • 缺点

    • • 任务间相互阻塞,效率低下

    • • 无法实现真正的多任务

    • • 响应实时性差

    • • 难以管理复杂的任务调度

1.2 前后台(裸机)系统

  • • 实现原理

    void main() {
        init();                // 初始化
        while(1) {
            task1();           // 前台任务(主循环)
            task2();
            check_events();    // 检查事件标志
        }
    }
    
    void interrupt_isr() {     // 后台(中断服务)
        set_event_flag();      // 设置事件标志
    }
    
  • 优点:

    • • 相对简单的多任务处理

    • • 中断响应及时

    • • 资源消耗适中

    • • 适合中等复杂度的应用

  • 缺点:

    • • 任务优先级管理困难

    • • 任务间同步复杂

    • • 每个任务都有单独的时间检查函数

    • • 调试难度较大

1.3 软件定时器方法

  • • 实现原理

    void main() {
        timer_init();          // 初始化定时器
        create_timer(100, task1);  // 创建100ms定时任务
        create_timer(200, task2);  // 创建200ms定时任务
        
        while(1) {
            timer_check();     // 检查并执行到期任务
            idle_task();       // 空闲任务
        }
    }
    
  • 优点:

    • • 任务调度灵活

    • • 时间管理精确

    • • 非阻塞执行

    • • 适合周期性任务

    • • 资源消耗相对较低

  • 缺点

    • • 任务执行时间必须很短

    • • 无法处理高优先级抢占

    • • 复杂任务链管理困难

1.4 RTOS(实时操作系统)

  • • 实现原理

    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)

    • • 学习曲线陡峭

    • • 调试复杂

    • • 需要特定的硬件支持

1.5 总结

调度方法 优点 缺点 适用场景 资源消耗
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. 软件成本可能较高 • 复杂多任务系统• 高实时性要求• 需要任务间通信• 工业级应用 ★★★★★较高

2. 软件定时器实现逻辑详解

2.1核心数据结构体

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;

2.2工作原理流程

    初始化定时器池
          ↓
    创建定时器(分配ID、设置参数)
          ↓
    SysTick中断 → SoftTimer_UpdateTick() ← 更新时间戳
          ↓
    主循环调用SoftTimer_Execute()
          ↓
    检查每个定时器 elapsed >= interval?
          ↓
    是 → 执行回调函数 → 更新执行计数
          ↓
    检查重复次数是否达到
          ↓
    是 → 标记定时器为非激活状态

2.3关键逻辑代码

2.3.1 时间更新机制

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;  // 更新每个激活定时器的计时
        }
    }
}
  • • 在SysTick/硬件定时器中断中调用(非常建议1ms一次)。

2.3.2 定时器检查与执行

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;      // 达到重复次数,停用
        }
    }
}

2.3.3 用户数据通信机制

// 设置用户数据(生产者)
SoftTimer_SetUserData(timer_id, data);

// 获取用户数据(消费者)
uint32_t data;
SoftTimer_GetUserData(timer_id, &data);
  • • 应用场景

    • • 任务间传递状态信息

    • • 传递执行结果

    • • 简单的消息通信

2.4. 软件定时器使用方法

  1. 1. 在 soft_time.h 中设置中断控制函数 和 最大支持的软件定时器数量

    /* 中断控制宏 */
    #define OS_DISABLE_IRQ() __disable_irq()
    #define OS_ENABLE_IRQ()  __enable_irq()
    
    /* 定义最大支持的软件定时器数量 */
    #define MAX_SOFT_TIMERS 10
    
  2. 2. SysTick中断处理

    // SysTick中断服务函数
    void SysTick_Handler(void) {
        // 更新时间基准(假设SysTick配置为1ms中断)
        SoftTimer_UpdateTick(1);
        
        // 其他需要1ms处理的任务
        // ...
    }
    
  3. 3. 在主循环中处理调用 SoftTimer_Execute

    int main(void) {
        // 硬件初始化
        HAL_Init();
        SystemClock_Config();
        
        // 软件定时器初始化
        SoftTimer_Init();
        
        // 创建需要的定时器
        // ...
        
        while (1) {
            // 检查并执行到期的定时器
            SoftTimer_Execute();
            
            // 其他任务(非阻塞)
            process_uart();
            update_display();
            
            // 低功耗处理(如果有)
            // __WFI();
        }
    }
    
  4. 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);
    

2.5 其他使用技巧

  1. 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. 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. 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. 4. 使用注意事项

    1. 1. 回调函数执行时间要短

      • • 尽量在1ms内完成

      • • 长时间任务应该分解或使用状态机

    2. 2. 避免在中断中创建/删除定时器

      • • 这些操作有临界区保护,但在中断中使用可能导致问题
    3. 3. 定时器ID有效性检查

      • • 使用前检查返回值是否为SOFT_TIMER_INVALID_ID

      • • 定期检查定时器是否仍处于激活状态

    4. 4. 资源管理

      • • 定时器是有限资源(MAX_SOFT_TIMERS)

      • • 不用的定时器及时删除