从裸机到 RTOS:用 SEGGER SystemView 定位任务抖动与优先级反转的真实案例

背景:从裸机到 RTOS 的阵痛

在裸机系统中,我们常用 while(1) 轮询或前后台架构,任务执行时间确定,抖动小。但项目复杂度上升后,引入 FreeRTOS 管理多任务。某电机控制项目迁移后,出现两个典型问题:

  • 任务抖动:电机 PWM 更新任务偶尔延迟几十微秒,导致电流环异常。
  • 优先级反转:低优先级任务持有互斥锁,高优先级任务被中优先级任务抢占,系统响应变慢。

传统调试手段(串口打印、GPIO 翻转)难以捕捉瞬时事件。我们引入 SEGGER SystemView,它通过 J-Link 实时记录 RTOS 事件,图形化展示任务调度、中断、API 调用,让问题无处遁形。

原理:SystemView 如何工作

SystemView 由两部分组成:

  • 目标端:在 MCU 中嵌入 SEGGER_SYSVIEW 代码,通过 RTT(Real Time Transfer)将事件数据发送到主机。
  • 主机端:SystemView 软件解析数据,绘制时间线。

关键事件包括:

  • 任务切换(Task Start/Stop)
  • 中断进入/退出
  • RTOS API 调用(如 xQueueSendxSemaphoreTake
  • 用户自定义事件(SEGGER_SYSVIEW_RecordEnterISR 等)

时间精度可达微秒级,且对目标系统影响极小(RTT 内存拷贝)。

配置步骤:在 FreeRTOS 中集成 SystemView

1. 添加源文件

从 SEGGER 官网下载 SystemView 包,将以下文件加入工程:

  • SEGGER_SYSVIEW.cSEGGER_SYSVIEW_Config_FreeRTOS.c
  • SEGGER_RTT.cSEGGER_RTT_ASM_ARMv7M.S(汇编加速)

2. 修改 FreeRTOSConfig.h

#define configUSE_TRACE_FACILITY 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1
#define INCLUDE_xTaskGetCurrentTaskHandle 1
#define INCLUDE_xTaskGetSchedulerState 1

3. 初始化 SystemView

main() 中,创建任务前调用:

#include "SEGGER_SYSVIEW.h"

int main(void) {
    HAL_Init();
    SystemClock_Config();
    SEGGER_SYSVIEW_Conf();  // 初始化 SystemView
    SEGGER_SYSVIEW_Start(); // 开始记录
    // 创建任务...
    vTaskStartScheduler();
}

4. 添加用户事件(可选)

在关键代码段插入标记,便于分析:

SEGGER_SYSVIEW_RecordEnterISR();
// 中断服务程序
SEGGER_SYSVIEW_RecordExitISR();

完整代码示例:复现优先级反转

以下代码模拟优先级反转场景:高优先级任务 vHighTask 等待互斥锁,低优先级任务 vLowTask 持有锁但被中优先级任务 vMidTask 抢占。

#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "SEGGER_SYSVIEW.h"

SemaphoreHandle_t xMutex;

void vHighTask(void *pvParameters) {
    while (1) {
        SEGGER_SYSVIEW_PrintfTarget("High: try take");
        xSemaphoreTake(xMutex, portMAX_DELAY);
        SEGGER_SYSVIEW_PrintfTarget("High: got mutex");
        // 模拟临界区
        for (volatile int i = 0; i < 100000; i++);
        xSemaphoreGive(xMutex);
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

void vMidTask(void *pvParameters) {
    while (1) {
        SEGGER_SYSVIEW_PrintfTarget("Mid: running");
        for (volatile int i = 0; i < 200000; i++);
        vTaskDelay(pdMS_TO_TICKS(50));
    }
}

void vLowTask(void *pvParameters) {
    while (1) {
        SEGGER_SYSVIEW_PrintfTarget("Low: try take");
        xSemaphoreTake(xMutex, portMAX_DELAY);
        SEGGER_SYSVIEW_PrintfTarget("Low: got mutex");
        for (volatile int i = 0; i < 300000; i++);
        xSemaphoreGive(xMutex);
        vTaskDelay(pdMS_TO_TICKS(200));
    }
}

int main(void) {
    SEGGER_SYSVIEW_Conf();
    SEGGER_SYSVIEW_Start();
    xMutex = xSemaphoreCreateMutex();
    xTaskCreate(vHighTask, "High", 256, NULL, 3, NULL);
    xTaskCreate(vMidTask, "Mid", 256, NULL, 2, NULL);
    xTaskCreate(vLowTask, "Low", 256, NULL, 1, NULL);
    vTaskStartScheduler();
    while (1);
}

分析 SystemView 时间线

运行后,SystemView 显示:

  • 任务抖动High 任务本应每 100ms 执行,但实际间隔波动大。查看中断事件,发现 SysTick 中断被长时间屏蔽(如 taskENTER_CRITICAL 内耗时操作)。
  • 优先级反转Low 任务持有互斥锁时,Mid 任务就绪并抢占,导致 High 任务等待锁的时间远超预期。SystemView 中可见 High 任务处于 Blocked 状态,而 Mid 任务持续运行。

解决方法

  • 使用互斥锁的优先级继承(FreeRTOS 互斥锁默认支持),但需确保 configUSE_MUTEXES 为 1。
  • 缩短临界区,避免在锁内调用 vTaskDelay 或长循环。
  • 调整任务优先级,确保高优先级任务不被中优先级任务阻塞。

注意事项

  • RTT 缓冲区大小:默认 1KB,高频率事件可能溢出,可增大 BUFFER_SIZE_UP
  • 中断优先级:SystemView 使用 RTT,需确保其不被高优先级中断打断,建议将 RTT 中断优先级设为最低。
  • 时间戳:使用 Cortex-M 的 DWT 周期计数器,需在 SEGGER_SYSVIEW_Conf() 中配置 SYSTICKDWT
  • 性能影响:SystemView 本身开销小,但频繁调用 SEGGER_SYSVIEW_PrintfTarget 会增加负载,建议仅用于调试。
  • FreeRTOS 版本:确保与 SystemView 的 FreeRTOS 补丁兼容,通常需替换 port.c 中的部分函数。

通过 SystemView,我们不仅解决了当前问题,还建立了 RTOS 性能分析流程,为后续优化提供数据支撑。