引言

在嵌入式系统中,定时器是灵魂般的存在。无论是延时、超时检测,还是周期性任务调度,都离不开定时器。而在RTOS环境下,软定时器(Software Timer)与硬件看门狗(Watchdog)的协同设计,更是保障系统长期稳定运行的核心手段。本文将结合FreeRTOS,深入探讨如何将二者有机结合,打造一个既灵活又可靠的定时器任务框架。

软定时器原理与RTOS集成

1. 软定时器的工作机制

软定时器由RTOS内核管理,基于系统节拍(Tick)计数。与硬件定时器不同,软定时器不占用硬件资源,而是通过内核的定时器服务任务(Timer Service Task)统一处理。每个软定时器都有一个回调函数,当定时时间到达时,回调函数会被放入定时器队列,由服务任务依次执行。

关键点:

  • 回调函数运行在定时器服务任务上下文中,因此不能阻塞或执行耗时操作。
  • 软定时器分为一次性(One-shot)和自动重载(Auto-reload)两种模式。
  • 定时精度取决于系统Tick周期,通常为1ms或10ms。

2. 与看门狗协同的必要性

硬件看门狗(如STM32的IWDG)用于检测系统死锁或程序跑飞。但看门狗独立于RTOS运行,若某个任务卡死但系统节拍仍正常,看门狗可能不会触发。此时,软定时器可作为“心跳”信号,定期“喂狗”,同时监控各任务状态,实现更智能的故障恢复。

配置步骤:以FreeRTOS为例

1. 硬件看门狗初始化

以STM32F4系列为例,配置独立看门狗(IWDG),超时时间设为1秒。

// iwdg.c
#include "stm32f4xx_hal.h"

void IWDG_Init(void)
{
    __HAL_RCC_LSI_ENABLE();  // 使能LSI时钟
    
    IWDG_HandleTypeDef hiwdg;
    hiwdg.Instance = IWDG;
    hiwdg.Init.Prescaler = IWDG_PRESCALER_64;   // 分频系数
    hiwdg.Init.Reload = 1250;                    // 重装载值,约1秒超时
    HAL_IWDG_Init(&hiwdg);
}

void IWDG_Feed(void)
{
    HAL_IWDG_Refresh(&hiwdg);
}

2. 软定时器创建与配置

在FreeRTOS中,创建两个软定时器:一个用于周期喂狗(每500ms),另一个用于监控任务状态(每200ms)。

// app_timer.c
#include "FreeRTOS.h"
#include "timers.h"

TimerHandle_t xWatchdogTimer;
TimerHandle_t xMonitorTimer;

void vWatchdogCallback(TimerHandle_t xTimer)
{
    // 喂狗操作
    IWDG_Feed();
}

void vMonitorCallback(TimerHandle_t xTimer)
{
    // 检查各任务运行状态,若异常则记录或复位
    if (xTaskGetTickCount() - g_lastTaskTick > 1000) {
        // 任务超时,执行恢复操作
        vTaskReset();
    }
}

void App_TimerInit(void)
{
    xWatchdogTimer = xTimerCreate("Watchdog", pdMS_TO_TICKS(500), pdTRUE, NULL, vWatchdogCallback);
    xMonitorTimer = xTimerCreate("Monitor", pdMS_TO_TICKS(200), pdTRUE, NULL, vMonitorCallback);
    
    if (xWatchdogTimer && xMonitorTimer) {
        xTimerStart(xWatchdogTimer, 0);
        xTimerStart(xMonitorTimer, 0);
    }
}

3. 任务中更新心跳变量

每个关键任务在正常运行周期内更新一个全局变量,供监控定时器检查。

// task1.c
volatile uint32_t g_task1Tick = 0;

void vTask1(void *pvParameters)
{
    for (;;) {
        // 任务主体
        g_task1Tick = xTaskGetTickCount();
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

完整代码示例

以下是一个综合示例,展示软定时器与看门狗协同工作的完整流程。

// main.c
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"

// 硬件看门狗初始化(略)
void IWDG_Init(void);
void IWDG_Feed(void);

// 软定时器回调
void vWatchdogCallback(TimerHandle_t xTimer)
{
    IWDG_Feed();
}

void vMonitorCallback(TimerHandle_t xTimer)
{
    if (xTaskGetTickCount() - g_task1Tick > 500) {
        // 任务1超时,记录错误并复位系统
        vTaskDelay(pdMS_TO_TICKS(10)); // 确保喂狗完成
        NVIC_SystemReset();
    }
}

// 任务1
volatile uint32_t g_task1Tick = 0;
void vTask1(void *pvParameters)
{
    for (;;) {
        g_task1Tick = xTaskGetTickCount();
        // 模拟任务工作
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

// 任务2
void vTask2(void *pvParameters)
{
    for (;;) {
        // 其他工作
        vTaskDelay(pdMS_TO_TICKS(200));
    }
}

int main(void)
{
    IWDG_Init();
    
    // 创建软定时器
    TimerHandle_t xWatchdogTimer = xTimerCreate("WD", pdMS_TO_TICKS(500), pdTRUE, NULL, vWatchdogCallback);
    TimerHandle_t xMonitorTimer = xTimerCreate("MON", pdMS_TO_TICKS(200), pdTRUE, NULL, vMonitorCallback);
    
    // 创建任务
    xTaskCreate(vTask1, "Task1", 128, NULL, 1, NULL);
    xTaskCreate(vTask2, "Task2", 128, NULL, 2, NULL);
    
    // 启动定时器
    xTimerStart(xWatchdogTimer, 0);
    xTimerStart(xMonitorTimer, 0);
    
    // 启动调度器
    vTaskStartScheduler();
    
    for (;;);
}

注意事项与常见陷阱

  • 回调函数中禁止阻塞:软定时器回调运行在服务任务中,若调用vTaskDelay或等待信号量,会阻塞整个定时器服务,导致其他定时器失效。
  • 喂狗时间要留余量:看门狗超时时间应大于最长的任务周期,且喂狗间隔要小于超时时间的一半,避免因调度抖动导致误复位。
  • 监控任务状态需谨慎:不要仅依赖单一心跳变量,可结合任务优先级和运行时间综合判断。
  • 软定时器精度有限:若需要高精度定时(微秒级),应使用硬件定时器,软定时器适合毫秒级周期任务。
  • 内存开销:每个软定时器会占用约100字节RAM,在资源受限的MCU上需合理规划。

总结

软定时器与看门狗协同设计,既发挥了RTOS的灵活调度优势,又弥补了看门狗对任务级故障的盲区。通过周期喂狗和任务监控,系统能够在异常发生时快速恢复,大幅提升可靠性。实际项目中,还需根据具体硬件和业务需求调整参数,并充分测试边界条件。希望本文能为你构建稳健的嵌入式系统提供参考。