不知道大家在单片机开发时,是不是跟我一样觉得特别烦:每次开始调试,不仅要插下载器(J-Link/ST-Link),还得再找个 USB 转 TTL 模块接串口看打印信息。

这就导致板子上挂着一堆线,电脑的 USB 口经常不够用,桌面上乱糟糟的。

其实,既然我们已经插了 J-Link 下载器,为什么不能直接用它来看打印信息呢?完全可以!今天就来介绍一下如何利用 J-Link 的 RTT 功能,扔掉串口线,真正实现“一根线”搞定下载和调试

下载器五花八门,但我现在最常用 J-Link,原因很简单:省事

相比于又要接 SWD 下载口、又要额外接 GND/TX/RX 串口线的传统方式,J-Link 只需要原本的那四根线(VCC, GND, SWDIO, SWCLK),就能同时完成程序下载日志打印。体积小,接线少,简直是强迫症福音,YYDS!

J-Link OB ARM 仿真调试器

下载链接https://www.segger.com/downloads/jlink/

买回来 J-Link 后,一般卖家都会提供驱动程序,驱动安装完成后就可以下载调试程序了。

J-Link 驱动下载页面

当然我们现在要使用 J-Link 的 RTT 功能 (Real Time Transfer,实时传输,可不是 RT-Thread 操作系统啊),就需要在官网下载完整的 J-Link 包。最新版本的是 V8.92,当然旧版本通常也可以

下载完成后直接安装即可

3. 移植 RTT

以前的教程说 RTT 源码包就在安装目录里,但我发现现在的安装包里没有这个文件了。查看 官方文档 发现源码包放在了 GitHub 上:

将这个源码包克隆或者下载下来

RTT GitHub 源码页面

然后将 RTT 文件夹复制到我们编写程序的工程文件夹中:

RTT 文件夹结构

RTT 工程文件夹结构

注意config文件夹里面的文件也要放到RTT文件夹

RTT Config 文件夹结构

接着在 IDE 项目中(如 Keil)新建一个 RTT 分组,并将 RTT 文件夹中的两个 .c 文件添加进来:

  • SEGGER_RTT.c
  • SEGGER_RTT_printf.c

Keil 添加 RTT 源文件

添加 RTT 的头文件路径。

Keil 添加 RTT 头文件路径

到这里基本就移植成功了,是不是很简单?就是把 RTT 的源码添加到工程中即可,完全不需要修改别的操作。

4. RTT 打印输出

接下来就可以在代码中进行打印输出了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "stm32f4xx.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "SEGGER_RTT.h" // 引入头文件

int main(void)
{
delay_init(); // 初始化延时函数
LED_Init(); // 初始化LED

while(1)
{
// 通道0是默认通道
SEGGER_RTT_printf(0, "hello world\r\n");
delay_ms(500);
}
}

编译无误后,连接好下载器。

  1. 连接硬件:确保 SWD 接口(CLK, DIO, GND, VCC)连接正确。
  2. 打开查看器:打开 J-Link 安装目录下的 JLinkRTTViewer.exe

软件位置示例

按照如下配置(选择对应的芯片型号和接口方式):

配置界面

将代码下载到单片机中,就可以在软件界面看到完美的打印输出了。

J-Link RTT Viewer 输出界面

5. RTT 的使用技巧

5.1 修改 RTT 缓冲大小

有时候我们发现信息不能完全打印出来,可能是因为缓冲不够。默认缓冲区大小是 1K (1024) 字节,如果不够可以在 SEGGER_RTT_Conf.h 中改大一点。

RTT 缓冲区配置

5.2 多虚拟端口使用

RTT 支持向不同的虚拟端口(Terminal)中打印信息,使用方法如下。

首先在 RTT Viewer 软件中分别打开三个虚拟端口:

多虚拟终端

编写代码:

1
2
3
4
5
6
7
8
9
10
11
12
while (1) {
SEGGER_RTT_SetTerminal(0);
SEGGER_RTT_printf(0, "hello world, SEGGER RTT Terminal 0!\r\n");

SEGGER_RTT_SetTerminal(1);
SEGGER_RTT_printf(0, "hello world, SEGGER RTT Terminal 1!\r\n");

SEGGER_RTT_SetTerminal(2);
SEGGER_RTT_printf(0, "hello world, SEGGER RTT Terminal 2!\r\n");

delay_ms(1000);
}

编译、链接、下载,观察现象:

  • 窗口 0:显示 Terminal 0 的信息
  • 窗口 1:显示 Terminal 1 的信息
  • 窗口 2:显示 Terminal 2 的信息

RTT 多终端显示效果

5.3 修改打印字符颜色

RTT 支持不同颜色的字符显示,这对于区分报警信息非常有用。

RTT 彩色字符宏定义

使用时,在字符串前面加上对应颜色的宏定义即可。

1
2
3
4
5
6
7
8
9
10
11
12
while (1) {
SEGGER_RTT_SetTerminal(0);
SEGGER_RTT_printf(0, RTT_CTRL_TEXT_RED "Error: System Fault!\r\n");

SEGGER_RTT_SetTerminal(1);
SEGGER_RTT_printf(0, RTT_CTRL_TEXT_GREEN "Info: System Normal.\r\n");

SEGGER_RTT_SetTerminal(2);
SEGGER_RTT_printf(0, RTT_CTRL_TEXT_BLUE "Debug: Value = 100.\r\n");

delay_ms(1000);
}

5.4 使用 printf 重定向

项目中使用 printf 的地方非常多,如果可以直接修改 printf 重定向到 RTT 组件,则会非常方便(无需修改原有代码中的打印语句)。

方法是直接使用 RTT 提供的 API 实现 fputc

重定义 fputc 函数:

1
2
3
4
5
6
7
#include <stdio.h>

// 重定义 fputc 函数
int fputc(int ch, FILE *f) {
SEGGER_RTT_PutChar(0, ch);
return ch;
}

替换之前的代码,直接使用 printf:

1
2
3
4
while (1) {
printf("hello world, printf via SEGGER RTT!\r\n");
delay_ms(1000);
}

编译、链接、下载,效果与使用 SEGGER_RTT_printf 一致


结语
虽然串口(UART)调试是经典做法,但对于我们这种追求效率(图省事)的开发者来说,RTT 简直是神技。

自从用了 RTT,我的 USB 转 TTL 模块就可以去吃灰了。

  • 不需要单独接串口线;
  • 不需要担心占用单片机的 UART 硬件资源;
  • 不需要担心波特率设置不对导致乱码。

如果你也想少插一根线,还原一个清爽的开发桌面,强烈建议试一试这个方法!