3. RTT组件与文件结构
SEGGER RTT 并不是一个需要链接库、依赖操作系统或额外驱动的组件,它的全部实现就是几个 C 源文件和头文件。理解这几个文件各自的职责,是把它干净地塞进裸机工程的前提。很多初学者集成失败,并不是因为 RTT 复杂,而是因为把配置宏放错了文件、把缓冲区定义重复了,或者忘了给 RTT 提供一块可用的 RAM。本节先把文件结构讲清楚,再给出一个可复现的最小集成步骤。
三个核心文件的分工
RTT 的源码包通常包含 SEGGER_RTT.c、SEGGER_RTT.h、SEGGER_RTT_Conf.h,以及可选的 SEGGER_RTT_printf.c、SEGGER_RTT_Syscalls_*.c 等。对裸机工程而言,前三个是必须的,第四个按需添加。
SEGGER_RTT.c 是唯一的实现文件。它内部维护了上行缓冲区和下行缓冲区,实现了 SEGGER_RTT_Write()、SEGGER_RTT_Read()、SEGGER_RTT_WriteString() 等函数,并负责控制块的初始化。控制块是 RTT 的核心数据结构,它位于 RAM 中,被目标端和调试主机同时访问:目标端往里写数据,主机端通过调试接口读取。控制块里保存了每个通道的缓冲区指针、大小、读写偏移量等元信息。默认情况下,RTT 提供通道 0 用于终端输出,通道 1 可用于其他数据流。
SEGGER_RTT.h 是面向用户的接口头文件。它声明了所有公开函数、通道编号宏、以及 SEGGER_RTT_Write 系列函数的原型。应用程序只需要包含这个头文件,就能调用 RTT 的 API。它还会根据配置决定是否启用 SEGGER_RTT_printf 等扩展功能。注意,SEGGER_RTT.h 本身不定义缓冲区,也不分配内存,它只是接口声明。
SEGGER_RTT_Conf.h 是配置头文件,决定了 RTT 的资源占用和行为。最关键的几个宏包括:BUFFER_SIZE_UP 定义上行缓冲区大小,默认 1024 字节;BUFFER_SIZE_DOWN 定义下行缓冲区大小,默认 16 字节;SEGGER_RTT_MAX_NUM_UP_BUFFERS 和 SEGGER_RTT_MAX_NUM_DOWN_BUFFERS 定义通道数量,默认各 3 个;SEGGER_RTT_MODE_DEFAULT 决定默认模式,通常为 SEGGER_RTT_MODE_NO_BLOCK_SKIP,即缓冲区满时跳过新数据而不是阻塞。这些宏直接影响 RAM 占用和实时性,必须根据 MCU 的 RAM 容量和日志频率来调整。
源码集成到裸机工程的最小步骤
下面以一个典型的 Cortex-M 裸机工程为例,说明把 RTT 加入工程的最小操作。假设工程使用 Keil MDK 或 GCC Makefile,MCU 有至少 4KB RAM 可用。
第一步:拷贝文件。把 SEGGER_RTT.c、SEGGER_RTT.h、SEGGER_RTT_Conf.h 三个文件复制到工程的 Middlewares/SEGGER_RTT/ 目录下。如果希望使用 SEGGER_RTT_printf(),再额外加入 SEGGER_RTT_printf.c。不要修改 SEGGER_RTT.c 和 SEGGER_RTT.h 的原始内容,所有可调参数都放在 SEGGER_RTT_Conf.h 中。
第二步:加入编译。在 IDE 中把 SEGGER_RTT.c(以及可选的 SEGGER_RTT_printf.c)添加到工程的源文件列表,并把 Middlewares/SEGGER_RTT/ 加入头文件搜索路径。对于 Makefile 工程,在 SRCS 中加入对应 .c 文件,在 INC 中加入目录即可。这一步不需要链接任何额外库,也不需要启动文件或链接脚本的特殊修改。
第三步:调整配置。打开 SEGGER_RTT_Conf.h,根据实际需求修改缓冲区大小。例如,如果日志量较大且 RAM 充裕,可以把 BUFFER_SIZE_UP 改为 2048 或 4096;如果 RAM 紧张,可以降到 256。下行缓冲区默认 16 字节,通常够用,因为主机发给目标的数据一般很少。通道数量如果只用通道 0,可以保持默认 3 个,也可以减到 1 个以节省少量 RAM。模式建议保持 SEGGER_RTT_MODE_NO_BLOCK_SKIP,这样在缓冲区满时不会阻塞 CPU,适合实时性要求高的裸机环境。
第四步:初始化与调用。RTT 不需要显式的初始化函数。第一次调用 SEGGER_RTT_Write() 或 SEGGER_RTT_printf() 时,SEGGER_RTT.c 会自动完成控制块和缓冲区的初始化。因此,在 main() 中直接包含头文件并调用即可:
#include "SEGGER_RTT.h"
int main(void)
{
SEGGER_RTT_WriteString(0, "RTT boot OK\r\n");
SEGGER_RTT_printf(0, "SystemCoreClock = %u Hz\r\n", SystemCoreClock);
while (1)
{
SEGGER_RTT_printf(0, "tick = %u\r\n", tick_count);
delay_ms(500);
}
}
注意通道号 0 表示默认上行通道。如果使用 SEGGER_RTT_printf,需要确保 SEGGER_RTT_printf.c 已加入编译,并且 SEGGER_RTT_Conf.h 中没有禁用 printf 相关宏。
集成后的验证与常见问题
编译通过后,把程序下载到目标板,打开 J-Link RTT Viewer 或 Ozone,选择正确的目标器件和接口速度,连接后应该能看到 RTT boot OK 以及后续的 tick 输出。如果看不到任何内容,按以下顺序排查:
- 确认
SEGGER_RTT.c确实被编译进了工程,而不是只放在目录里没有加入源文件列表。 - 确认
SEGGER_RTT_Conf.h被SEGGER_RTT.c正确包含,且没有多个版本冲突。如果工程里存在两份同名配置头文件,编译器可能选错。 - 确认 RAM 足够。如果
BUFFER_SIZE_UP设得过大,链接时可能报 RAM 溢出,或者运行时控制块被其他变量覆盖。 - 确认调试器连接正常,RTT 控制块地址能被主机正确找到。RTT 通过搜索 RAM 中的控制块签名来定位,如果控制块被优化掉或放在不可访问区域,主机就找不到。
- 如果使用
SEGGER_RTT_printf,确认没有开启半主机模式或重定向冲突。裸机工程中如果同时使用了printf重定向到串口,需要避免符号冲突。
.bss 段,由 SEGGER_RTT.c 中的静态数组提供。如果链接脚本把 .bss 放在外部 RAM 或未初始化区域,可能导致控制块不可访问。此时可以把控制块显式放到内部 RAM 段,或检查链接脚本中 .bss 的地址范围。
最小集成的本质是:三个文件加入编译,配置宏按需调整,应用层包含头文件后直接调用。RTT 不依赖中断、不依赖 DMA、不依赖操作系统,因此它几乎可以嵌入任何裸机工程。理解 SEGGER_RTT.c 负责实现、SEGGER_RTT.h 负责接口、SEGGER_RTT_Conf.h 负责资源裁剪这一分工,就能在遇到编译或运行问题时快速定位到正确的文件。