4. 在目标工程中集成SystemView
SystemView 的集成并不是“把库文件拖进工程就能用”这么简单。它依赖一条完整的软件链路:目标固件中的 SEGGER 系统文件负责采集事件,RTT 控制块负责把事件从目标内存搬运到主机,主机端的 SystemView 软件再通过 J-Link 读取并解析这些数据。本节围绕这条链路,说明在目标工程中真正需要落地的四件事:添加哪些文件、如何初始化配置、RTT 控制块放在哪里、以及初始化与启动的调用顺序。
一、需要加入工程的 SEGGER 系统文件
SystemView 的目标端代码通常位于 SEGGER 提供的 SystemView Target Sources 包中,核心文件包括以下几类:
- SEGGER_SYSVIEW.c / .h:SystemView 的核心实现,负责事件记录、时间戳处理、事件缓冲区管理,以及通过 RTT 向上位机发送数据。
- SEGGER_SYSVIEW_ConfDefaults.h:提供所有配置项的默认值,一般不需要修改,作为配置的兜底。
- SEGGER_SYSVIEW_Conf.h:用户配置文件,用于覆盖默认值,例如系统频率、事件缓冲区大小、是否启用时间戳等。
- SEGGER_RTT.c / .h:RTT 的底层实现,SystemView 通过它把数据写入目标内存中的 RTT 控制块。
- SEGGER_RTT_Conf.h:RTT 的配置,包括缓冲区大小、通道数量、控制块位置等。
- SEGGER_SYSVIEW_Config_*.c(如 FreeRTOS 版本):针对具体 RTOS 的适配文件,提供系统描述、任务列表回调等。
把这些文件加入工程时,要注意两点:一是头文件搜索路径必须包含 SystemView 与 RTT 所在目录;二是如果工程同时使用了 SEGGER 的其他中间件(例如 emWin、embOS),要确认 RTT 版本一致,避免出现重复定义或版本冲突。对于裸机工程,可以只加入 SEGGER_SYSVIEW.c、SEGGER_RTT.c 以及对应的头文件;对于 FreeRTOS 工程,还需要加入 SEGGER_SYSVIEW_FreeRTOS.c 这类适配文件,并在 FreeRTOSConfig.h 中启用 trace 相关的宏。
二、初始化 SEGGER_SYSVIEW_Conf
SEGGER_SYSVIEW_Conf 是 SystemView 的配置入口,通常由用户在 SEGGER_SYSVIEW_Conf.c 中实现,或者直接修改 SEGGER_SYSVIEW_Conf.h 中的宏。它主要完成三件事:设置系统频率、设置事件缓冲区、注册系统描述回调。
系统频率决定了时间戳的换算关系。例如目标 MCU 主频为 168 MHz,则配置为:
#define SEGGER_SYSVIEW_GET_TIMESTAMP() (*(volatile U32 *)(0xE0001004))
#define SEGGER_SYSVIEW_SYSDESC_FREQ (168000000u)
其中 0xE0001004 是 Cortex-M 的 DWT CYCCNT 寄存器地址,用于获取 CPU 周期计数。如果目标平台没有 DWT,也可以使用定时器计数,但必须保证时间戳单调递增且频率与 SYSDESC_FREQ 一致。
事件缓冲区大小决定了单次能缓存多少事件。默认值通常为 1024 字节,对于事件密集的场景(例如高频任务切换、中断嵌套)可以增大到 2048 或 4096 字节。缓冲区越大,占用 RAM 越多,但能减少因缓冲区满而丢事件的概率。
系统描述回调用于告诉上位机当前系统有哪些任务、中断和模块。对于 FreeRTOS,适配文件会通过 SEGGER_SYSVIEW_RegisterModule 和 SEGGER_SYSVIEW_SendTaskList 等接口自动完成;对于裸机工程,可以只注册一个简单的系统描述,或者不注册,SystemView 仍能显示基本的时间线。
三、配置 RTT 控制块
RTT 控制块是目标内存中的一块结构体,SystemView 和 J-Link 都通过它来交换数据。控制块的位置有两种常见方式:
- 自动分配:由 SEGGER_RTT_Init 在 RAM 中静态分配,地址由链接器决定。这种方式简单,但每次重新编译后地址可能变化,J-Link 需要重新扫描。
- 固定地址:通过 SEGGER_RTT_Conf.h 中的 SEGGER_RTT_SECTION 或链接脚本,把控制块放到一个固定地址段,例如 0x20000000。这样 J-Link 可以直接按地址访问,连接更稳定。
在 SEGGER_RTT_Conf.h 中,关键配置包括:
#define BUFFER_SIZE_UP (1024)
#define BUFFER_SIZE_DOWN (16)
#define SEGGER_RTT_MAX_NUM_UP_BUFFERS (2)
#define SEGGER_RTT_MAX_NUM_DOWN_BUFFERS (2)
#define SEGGER_RTT_MODE_DEFAULT SEGGER_RTT_MODE_NO_BLOCK_SKIP
其中 BUFFER_SIZE_UP 是上行缓冲区大小,SystemView 的事件数据就写在这里。如果事件量大,可以适当增大到 2048 或 4096。SEGGER_RTT_MODE_NO_BLOCK_SKIP 表示缓冲区满时跳过新数据而不是阻塞,这对实时性影响最小,但会丢失部分事件;如果希望不丢事件,可以改为 SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL,但会阻塞调用线程。
四、调用 SEGGER_SYSVIEW_Init 与 SEGGER_SYSVIEW_Start
初始化和启动的调用顺序非常关键。通常的流程是:先初始化 RTT,再初始化 SystemView,最后启动 SystemView。示例代码如下:
#include "SEGGER_RTT.h"
#include "SEGGER_SYSVIEW.h"
int main(void)
{
/* 硬件初始化,例如时钟、GPIO、串口等 */
SystemInit();
/* 初始化 RTT,配置控制块 */
SEGGER_RTT_Init();
/* 初始化 SystemView,传入系统描述频率和 CPU 频率 */
SEGGER_SYSVIEW_Init(168000000u, 168000000u, 0, 0);
/* 启动 SystemView,开始记录事件 */
SEGGER_SYSVIEW_Start();
/* 创建任务或进入主循环 */
while (1) {
/* 应用代码 */
}
}
SEGGER_SYSVIEW_Init 的参数依次为:系统频率、CPU 频率、系统描述回调、时间戳回调。对于大多数 Cortex-M 工程,前两个参数相同,后两个可以传 0,表示使用默认实现。SEGGER_SYSVIEW_Start 会打开事件记录开关,之后所有 SEGGER_SYSVIEW_Record* 调用才会真正写入缓冲区。
如果使用 FreeRTOS,还需要在创建任务之前调用 SEGGER_SYSVIEW_Start,并在 FreeRTOSConfig.h 中配置 traceTASK_SWITCHED_IN、traceISR_ENTER 等宏,让 FreeRTOS 在任务切换和中断时自动记录事件。否则 SystemView 只能看到手动记录的事件,无法看到任务调度细节。
五、集成后的验证与常见问题
完成上述步骤后,用 J-Link 连接目标板,打开 SystemView 软件,选择目标器件和 RTT 控制块地址,点击开始记录。如果一切正常,应该能看到时间线上出现任务切换、中断和用户事件。如果看不到数据,可以按以下顺序排查:
- 确认 SEGGER_SYSVIEW_Start 已被调用,且调用位置在任务创建之前。
- 确认 RTT 控制块地址与 SystemView 中配置的地址一致,或者让 SystemView 自动扫描。
- 检查 SEGGER_SYSVIEW_Conf.h 中的 SEGGER_SYSVIEW_SYSDESC_FREQ 是否与实际 CPU 频率一致,否则时间轴会缩放错误。
- 如果事件丢失严重,增大 BUFFER_SIZE_UP 或降低事件记录频率。
- 确认 J-Link 固件版本与 SystemView 版本兼容,必要时升级 J-Link 驱动。
下面这张图概括了从目标固件到主机端 SystemView 的完整数据链路,以及四个关键集成点在链路中的位置。
这张图把目标端和主机端分开表示,中间用橙色虚线表示 RTT 数据流。可以看到,四个集成点全部位于目标固件一侧:文件添加决定了代码是否存在,Conf 决定了时间戳和缓冲区行为,RTT 控制块决定了数据放在哪里,Init 与 Start 决定了记录何时开始。只有这四步都正确,主机端才能看到有意义的时间线。
最后给出一个可直接参考的集成检查清单,便于在工程中逐项确认:
| 检查项 | 预期状态 | 常见问题 |
|---|---|---|
| SEGGER_SYSVIEW.c / .h 已加入工程 | 编译无缺失符号 | 头文件路径未添加 |
| SEGGER_RTT.c / .h 已加入工程 | RTT 控制块可被 J-Link 识别 | 与已有 RTT 版本冲突 |
| SEGGER_SYSVIEW_Conf.h 已配置 | SYSDESC_FREQ 与实际主频一致 | 频率填错导致时间轴缩放异常 |
| RTT 控制块地址固定或可扫描 | SystemView 能连接并读取 | 地址变化导致连接失败 |
| SEGGER_SYSVIEW_Init 在 Start 之前调用 | 启动后立即有事件 | 顺序颠倒导致无数据 |
| FreeRTOS trace 宏已启用 | 任务切换事件可见 | 未配置 traceTASK_SWITCHED_IN |