你是否曾听说过 J-Link 的 RTT (Real-Time Transfer) 技术?它被官方描述为一种无需占用额外硬件引脚即可进行系统监控和交互式输入/输出的高效方案。
简单来说,拥有 J-Link 后,你可以享受以下便利:
- 无需占用 USART 或额外的 USB 转串口工具,就能将
printf 输出重定向到由 J-Link 提供的虚拟串口上。
- 支持 J-Link 声称兼容的几乎所有芯片。
- 高速通信,且基本不影响芯片的实时响应性能。
当然,它的局限性也很明显:
- 你必须拥有一个 J-Link。如果你使用的是 CMSIS-DAP 或 ST-Link 等第三方调试工具,则无法直接使用这一功能。
- 你必须在工程中手动集成特定的代码库。
之前在介绍使用开源 DAPLink 替代 J-Link 的文章中,我们已经实现了很多超越原版的功能,但仍有不少用户在评论区表示,无法替代 J-Link 的 RTTView 和 J-Scope 波形显示功能。

那么,如果我告诉你,开源社区早已开发出支持 DAPLink 的 RTTView 和波形显示功能,你会不会心动呢?
RTTView 开源地址: https://github.com/XIVN1987/RTTView,作者为 XIVN1987。

实现原理
RTTView
RTT 的原理并不复杂。SEGGER_RTT 库定义了一个名为 SEGGER_RTT_CB 的核心结构体,所有通过 RTT 通道打印和接收的数据都存储在名为 _SEGGER_RTT 的全局变量中。上位机软件(如 RTTViewer)通过调试器(这里是 DAPLink)读取和写入单片机内存中这个结构体的内容,从而实现双向通信。
SEGGER_RTT_CB 结构体大致如下:
typedef struct {
char acID[16];
int MaxNumUpBuffers;
int MaxNumDownBuffers;
SEGGER_RTT_BUFFER_UP aUp[SEGGER_RTT_MAX_NUM_UP_BUFFERS];
SEGGER_RTT_BUFFER_DOWN aDown[SEGGER_RTT_MAX_NUM_DOWN_BUFFERS];
} SEGGER_RTT_CB;
SEGGER_RTT_PUT_CB_SECTION(SEGGER_RTT_CB_ALIGN(SEGGER_RTT_CB _SEGGER_RTT));
SEGGER_RTT_PUT_BUFFER_SECTION(SEGGER_RTT_BUFFER_ALIGN(static char _acUpBuffer [BUFFER_SIZE_UP]));
SEGGER_RTT_PUT_BUFFER_SECTION(SEGGER_RTT_BUFFER_ALIGN(static char _acDownBuffer[BUFFER_SIZE_DOWN]));
RTTView 上位机正是通过调试器读写单片机内存中的 _SEGGER_RTT 变量。要找到这个变量,需要知道它在内存中的确切地址。这可以通过查看 IDE(如 Keil MDK)编译后生成的 .map 文件来实现。

如上图所示,_SEGGER_RTT 位于地址 0x24000040。RTTView 上位机可以自动扫描从设定地址开始的 128KB 内存区域,来寻找这个关键变量。
波形显示
波形显示的原理与 RTTView 类似。上位机通过加载 .map 文件,获取全局变量在 RAM 中的具体地址。然后,调试器周期性地读取用户指定变量的内存值,并将其绘制成实时波形图。这种方式为嵌入式开发中的调试和实时数据监控提供了极大便利,你可以在云栈社区的技术文档板块找到更多关于调试技巧和工具使用的深入讨论。
RTTView 功能实战
MCU 端移植 RTT
RTT 的源码是 J-Link 软件包的一部分,通常位于安装目录的 Samples/RTT 文件夹下,例如:C:\Program Files (x86)\SEGGER\JLink_V632f\Samples\RTT。
-
复制源码:将下图中的几个核心文件复制到你的工程目录下,并添加到编译列表中,同时包含头文件路径。

-
编写集成代码:以下示例展示了如何将 RTT 集成到一个轻量级 shell 命令行中。
#include "SEGGER_RTT.h"
int reboot(void)
{
SCB->AIRCR = 0X05FA0000 | (uint32_t)0x04;
return 0;
}
MSH_CMD_EXPORT(reboot, reboot)
uint16_t shell_read_data(wl_shell_t *ptObj, char *pchBuffer, uint16_t hwSize)
{
return SEGGER_RTT_Read(0,(uint8_t *)pchBuffer, hwSize);
}
uint16_t shell_write_data(wl_shell_t *ptObj, const char *pchBuffer, uint16_t hwSize)
{
return SEGGER_RTT_Write(0, pchBuffer, hwSize);
}
int main(void)
{
...
SEGGER_RTT_Init();
...
shell_ops_t s_tOps = {
.fnReadData = shell_read_data,
.fnWriteData = shell_write_data,
};
shell_init(&s_tShellObj,&s_tOps);
}
移植成功后,打开 RTTView 软件并连接 DAPLink,就能看到 shell 的交互界面了。

波形显示功能实战
为了演示波形显示,我们可以在 MCU 上运行一段生成正弦波数据的代码。
__USED uint32_t approx_t = 0;
__USED uint32_t sine_var;
#define MAX_VALUE 1000000
#define MIN_VALUE 0
#define PERIOD 1000
#define M_PI 3.14159265358979323846
#include <math.h>
uint32_t approx_sine(uint32_t t) {
double angle = 2.0 * M_PI * (t % PERIOD) / PERIOD;
double sine_value = (sin(angle) + 1.0) / 2.0;
return (uint32_t)(MIN_VALUE + sine_value * (MAX_VALUE - MIN_VALUE));
}
int main(void)
{
...
while (1) {
...
sine_var = approx_sine(approx_t);
approx_t++;
HAL_Delay(1);
}
}
编译后,在生成的 .map 文件中,可以找到 sine_var 变量的地址,例如 0x24001A68。

配置 RTTView 显示波形
- 打开 RTTView,加载编译生成的
.map 文件。
- 在变量列表中选择你想要观察的变量(例如
sine_var)。
- 勾选“波形显示”功能。

最终,你就能看到一个实时的正弦波形图。

在 MDK 中将 printf 重定向到 RTT
如果你希望直接使用 printf 函数,并通过 RTT 输出,可以按照以下步骤在 Keil MDK 中配置。
步骤一:RTE 环境配置
- 打开 RTE 配置 窗口(菜单:
Project -> Manage -> Run-Time Environment)。
- 勾选以下组件:
- 在 CMSIS-Compiler 下勾选 CORE。
- 在 STDOUT(API) 下勾选 Custom。

注意:如果你的 MDK 版本较低,在 RTE 中找不到 CMSIS-Compiler 和 CMSIS-View,可以尝试从 Arm 官网直接下载对应的软件包 (CMSIS-Pack),或者使用下图所示的旧版组件选择方式,在 Compiler 分类下寻找重定向选项。

步骤二:实现 stdout_putchar() 函数
实现这个函数,将标准输出的字符发送到 RTT 通道。
int stdout_putchar(int ch)
{
SEGGER_RTT_PutChar(0, ch);
return ch;
}
完成上述配置后,工程中的 printf 语句的输出就会被自动重定向到 RTT,可以在 RTTViewer 中查看。
结语
通过开源工具 RTTView,基于 DAPLink 的调试方案已经补上了“RTT调试与波形显示”这块重要的拼图。对于那些产品不便外接调试串口,但又需要实时监控和调试需求的场景,在固件中移植一个轻量级的 Shell 并结合 RTT 功能,是一个极具性价比的解决方案。它不仅实现了异步、高速的日志输出,还能为更复杂的开源实战项目(例如自定义数据监控仪表)打下基础,更多嵌入式开发的奇思妙想和实践,欢迎到云栈社区与大家交流探讨。
如果你的应用对空间和实时性有严格要求,那么一个精心设计的轻量级命令行工具将是你的得力助手。它不仅能在后台安静地记录日志到 Flash,还能在关键时刻提供交互式诊断能力,极大地方便了后期的问题定位与分析。