SEGGER RTT 日志:原理、配置与实战对比
调试嵌入式系统,日志输出是基本功。但很多工程师还在用串口打印,速度慢、占引脚、还影响实时性。今天我要聊的 SEGGER RTT,是 J-Link 调试器自带的一种「黑科技」日志方案。我个人用了快十年,从早期的 Cortex-M3 到现在的 RISC-V,几乎每个项目都会把它集成进去。
RTT 原理与优势
RTT 的全称是 Real-Time Transfer,实时传输。它不像串口那样需要硬件外设,而是直接在 RAM 里划出一块缓冲区,配合调试器的 SWD/JTAG 接口,实现双向数据交换。
说白了,就是你的目标芯片在 RAM 里开了一个「信箱」。调试器通过调试接口,随时来取信、放信。整个过程不占用串口、不占用定时器、不产生中断。
核心优势:
- 极低延迟:写入缓冲区只需几条内存操作指令,微秒级完成
- 零中断开销:不触发任何硬件中断,不影响实时任务
- 双向通信:上行(目标→主机)和下行(主机→目标)同时工作
- 带宽高:典型场景下可达 1-2 MB/s,远超串口的 115200 bps
- 不占引脚:复用 SWD 接口,不需要额外 UART 引脚
我记得第一次在项目中用 RTT 替换串口日志,系统响应时间从原来的 200 微秒降到了 5 微秒。当时团队里还有人怀疑是不是测量错了,反复测了三遍才信。
RTT 配置与初始化
配置 RTT 其实很简单。SEGGER 官方提供了两个核心文件:SEGGER_RTT.c 和 SEGGER_RTT.h。把它们加到工程里,再调用初始化函数就行。
下面是一个典型的初始化流程:
/* 1. 包含头文件 */
#include "SEGGER_RTT.h"
/* 2. 配置缓冲区大小(在 SEGGER_RTT_Conf.h 中) */
#define BUFFER_SIZE_UP (1024) /* 上行缓冲区,目标→主机 */
#define BUFFER_SIZE_DOWN (64) /* 下行缓冲区,主机→目标 */
/* 3. 初始化 RTT(在 main 函数开头调用) */
int main(void)
{
/* 硬件初始化 */
SystemInit();
/* RTT 初始化 —— 其实就是清空缓冲区 */
SEGGER_RTT_Init();
/* 测试输出 */
SEGGER_RTT_WriteString(0, "RTT 日志已启动\n");
while(1)
{
/* 主循环 */
SEGGER_RTT_printf(0, "系统运行中,时间戳: %u\n", GetTick());
Delay_ms(1000);
}
}
我的习惯:我会在 SEGGER_RTT_Conf.h 里把上行缓冲区设到 2KB 以上。因为日志量大的时候,缓冲区太小会导致数据丢失。下行缓冲区 64 字节通常够用,除非你要传大量命令。
上行通道与下行通道
RTT 支持多个通道,每个通道都是独立的缓冲区。通道 0 默认用于标准日志,通道 1 可以用于调试命令,通道 2 可以用于数据采集。
| 通道 | 方向 | 典型用途 | 缓冲区大小建议 |
|---|---|---|---|
| 0 | 上行 | 标准日志输出 | 1024-4096 字节 |
| 1 | 下行 | 调试命令输入 | 64-256 字节 |
| 2 | 上行 | 数据采集(如传感器数据) | 4096-16384 字节 |
上行通道是目标芯片往主机发数据。你调用 SEGGER_RTT_WriteString() 或 SEGGER_RTT_printf(),数据就写进缓冲区。调试器通过 SWD 接口定期轮询,把数据读走。
下行通道是主机往目标芯片发数据。比如你在 J-Link RTT Viewer 里输入一条命令,数据会先写到下行缓冲区,目标芯片再通过 SEGGER_RTT_Read() 读取。
我曾经踩过的坑:下行通道的读取必须在主循环或任务中主动调用 SEGGER_RTT_Read()。如果你只写上行日志,忘了处理下行,主机发过来的命令就会一直积压在缓冲区里。有一次我调试一个电机控制程序,命令延迟了 2 秒才响应,差点以为是 PID 参数出了问题。
与串口日志的对比
很多工程师习惯用串口打印日志,觉得简单可靠。但 RTT 在多个维度上完胜串口。我整理了一张对比表:
| 对比项 | 串口日志 | RTT 日志 |
|---|---|---|
| 硬件依赖 | 需要 UART 外设 + 引脚 | 仅需 SWD/JTAG 接口 |
| 传输速度 | 通常 115200 bps(约 11.5 KB/s) | 1-2 MB/s(快 100 倍以上) |
| 中断开销 | 每个字符触发中断 | 零中断,纯内存操作 |
| 实时性影响 | 高,尤其在高波特率下 | 极低,微秒级写入 |
| 双向通信 | 需要额外引脚和协议 | 原生支持,通道分离 |
| 调试器依赖 | 不需要调试器 | 需要 J-Link 或兼容调试器 |
| 数据完整性 | 可能丢数据(缓冲区溢出) | 缓冲区机制,可配置大小 |
你想想看,串口每发一个字节都要进中断,CPU 要保存现场、处理数据、恢复现场。如果日志量大,中断频繁,系统的实时性就崩了。RTT 呢?就是几条内存写指令,连缓存都不用刷。
什么时候用串口?
- 没有调试器,只有串口线
- 需要与外部设备通信(如蓝牙模块、传感器)
- 日志量极小,实时性要求不高
什么时候用 RTT?
- 有 J-Link 调试器
- 需要高频日志(如 1ms 一次的任务调度记录)
- 系统对实时性敏感
- 引脚资源紧张
RTT 工作流程示意图
下面这张图展示了 RTT 的完整数据流。从目标芯片的 RAM 缓冲区,到调试器的 SWD 接口,再到 PC 端的 RTT Viewer 软件。
实际项目中的选择建议
我个人的经验是:只要手头有 J-Link,就优先用 RTT。串口留给真正需要物理连接的外设。比如蓝牙模块、GPS 模块、RS485 总线这些,串口是必须的。但调试日志?RTT 完胜。
有一次我在一个 RTOS 项目里调试任务切换。用串口打印任务 ID,结果系统跑起来后,日志输出占用了 30% 的 CPU 时间。换成 RTT 后,CPU 占用降到了 0.5% 以下。你想想看,这差距有多大。
小技巧:如果你用 RTT 打印浮点数,记得用 SEGGER_RTT_printf() 的 %f 格式。但要注意,有些编译器默认不开启浮点支持,需要在链接选项中加上 -u _printf_float。