从裸机到 FreeRTOS:用 SEGGER SystemView 定位任务抖动与中断延迟的完整方法

一、为什么裸机调试手段会失效

裸机时代,我们习惯用 GPIO 翻转配合示波器测执行时间,用 printf 看流程。但引入 FreeRTOS 后:

  • 任务被抢占、挂起、恢复,执行时间不再连续;
  • 中断延迟受 configMAX_SYSCALL_INTERRUPT_PRIORITY 和临界区影响;
  • printf 本身会阻塞、改变调度时序,测出来的不是真实行为。

SystemView 的思路是:不打断系统运行,通过 RTT(Real Time Transfer)把调度器事件实时送到主机,再在 PC 端还原成时间轴。它记录的是“事件 + 时间戳”,而不是采样波形,因此开销极小(通常 <1% CPU)。

二、SystemView 工作原理

  • 事件源:FreeRTOS 的 trace 钩子(vTaskSwitchContext、xTaskCreate、vTaskDelay 等)被 SEGGER 的 SEGGER_SYSVIEW_FreeRTOS.c 接管。
  • 传输通道:J-Link 的 RTT,使用目标 RAM 中的 ring buffer,无需额外 UART。
  • 时间基准:默认用 Cortex-M 的 DWT->CYCCNT 周期计数器,精度到 CPU 周期;也可换成定时器。
  • 主机端:SystemView 软件解析事件流,绘制任务状态、中断、API 调用。

关键点:时间戳必须单调且分辨率足够高。CYCCNT 在 168MHz 下分辨率约 6ns,完全够用。

三、移植配置步骤

1. 获取源码

从 SEGGER 官网下载 SystemView Target Sources,把以下文件加入工程:

  • SEGGER_RTT.c/.h
  • SEGGER_SYSVIEW.c/.h
  • SEGGER_SYSVIEW_ConfDefaults.h
  • SEGGER_SYSVIEW_FreeRTOS.c/.h
  • Config/SEGGER_SYSVIEW_Config_FreeRTOS.c

2. FreeRTOSConfig.h 关键宏

#define configUSE_TRACE_FACILITY            1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1
#define INCLUDE_xTaskGetIdleTaskHandle      1
#define INCLUDE_pxTaskGetStackStart         1

/* 让 FreeRTOS 调用 SEGGER 的钩子 */
extern void SEGGER_SYSVIEW_Conf(void);
#define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for(;;); }

3. 时间戳与初始化

在 SEGGER_SYSVIEW_Config_FreeRTOS.c 中实现:

void SEGGER_SYSVIEW_Conf(void)
{
    SEGGER_SYSVIEW_Init(0, SystemCoreClock,
                        &SYSVIEW_TIMESTAMP_IF,   /* 时间戳接口 */
                        &SYSVIEW_OS_IF);         /* OS 接口 */
    SEGGER_SYSVIEW_SetRAMBase(0x20000000);
}

/* 使能 DWT CYCCNT */
void SEGGER_SYSVIEW_Start(void)
{
    CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
    DWT->CYCCNT = 0;
    DWT->CTRL  |= DWT_CTRL_CYCCNTENA_Msk;
    SEGGER_SYSVIEW_Start();
}

4. 在 main 中启动

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    SEGGER_SYSVIEW_Conf();
    SEGGER_SYSVIEW_Start();

    xTaskCreate(TaskA, "A", 256, NULL, 3, NULL);
    xTaskCreate(TaskB, "B", 256, NULL, 2, NULL);
    vTaskStartScheduler();
    for(;;);
}

四、一个真实案例:定位 200us 任务抖动

现象:TaskA 周期 1ms,实测抖动达 200us。

分析流程:

  1. 在 SystemView 中打开 “Task” 视图,观察 TaskA 的 Ready→Running 间隔。
  2. 发现每次抖动都伴随一个 EXTI 中断事件,且中断服务函数执行时间约 180us。
  3. 查看中断优先级:该 EXTI 优先级为 5,高于 configMAX_SYSCALL_INTERRUPT_PRIORITY(设为 5 时等于临界区屏蔽阈值),导致它不受 FreeRTOS 临界区保护,但会抢占调度器。
  4. 结论:中断执行过长,直接推迟了任务切换。

修复: 把中断拆成“上半部只置标志,下半部用 xTaskNotifyFromISR 唤醒高优先级任务处理”,中断执行时间降到 3us,抖动消失。

五、注意事项

  • 中断优先级:任何调用 FromISR API 的中断,优先级数值必须 ≥ configMAX_SYSCALL_INTERRUPT_PRIORITY,否则会破坏内核。
  • RTT 缓冲区:默认 1KB,事件密集时可能丢包,可增大 BUFFER_SIZE_UP。
  • 时间戳溢出:CYCCNT 是 32 位,168MHz 下约 25.6s 溢出,SystemView 会自动处理回绕,但自定义时间戳要保证单调。
  • 不要用 printf 调试:会引入不可预测延迟,用 SystemView 的 SEGGER_SYSVIEW_PrintfHost 替代。
  • 生产环境:SystemView 仅用于调试,正式固件应关闭 trace 以节省 RAM 和 CPU。

六、小结

SystemView 把 FreeRTOS 的调度行为从“黑盒”变成“时间轴”,是定位任务抖动、中断延迟、优先级反转的利器。核心三步:正确移植 trace 钩子、保证时间戳精度、结合中断优先级分析。掌握后,实时性问题不再是玄学。