☰
STM32调试利器:DAPLink与RTT Viewer高效调试指南
2026/9/28 17:21:07 网站建设 项目流程

1. 为什么我最终选了DAPLink加RTT Viewer这套组合

搞STM32调试的朋友大概率都经历过这种场景:板子已经装进外壳里了,串口线拔来拔去不方便,偏偏这时候要打印几个变量看看运行状态。用传统的串口打印吧,得占用一个USART,还得接USB转TTL模块,线一多桌面就乱成一锅粥。我早期做项目的时候,为了省一个串口,甚至把日志输出重定向到LCD屏幕上,现在回头看真是又笨又费劲。

后来接触到DAPLink和RTT Viewer这套组合,才算真正把调试这件事理顺了。DAPLink本质上是一个开源的调试器固件,刷在STM32F103C8T6这类小板子上就能当CMSIS-DAP调试器用,支持SWD接口下载和调试。而RTT(Real Time Transfer)是Segger公司推出的一种双向通信机制,它不需要占用芯片的串口外设,直接通过调试接口在目标内存里开一块缓冲区,上位机通过调试器读写这块内存来实现数据交互。说白了就是:你调试器怎么读寄存器的,它就怎么读你的printf输出。

这套方案解决的核心问题有三个。第一是省外设,RTT不占用任何USART、USB或者定时器资源,对于引脚紧张的项目来说简直是救命稻草。第二是速度快,RTT的传输速率远高于普通串口,实测在10MHz SWD时钟下,每秒传几百KB的日志毫无压力,打时间戳和波形数据都不带卡的。第三是不打断实时性,RTT的写入操作是内存拷贝级别的,对中断响应的影响极小,这点在做电机控制或者PID调试的时候特别关键。

这篇文章适合谁看?如果你手上有DAPLink调试器(不管是买的成品还是自己刷的STM32F103C8T6小板),正在用STM32做项目,并且希望有一种不占串口、速度快、还能双向通信的调试手段,那这篇内容就是写给你的。我会从固件确认、驱动安装、RTT Viewer配置、代码移植到常见问题排查,一步步走完整个流程。即使你之前没用过RTT,跟着做也能在半小时内跑通。

2. 动手前的准备工作:固件、驱动和硬件确认

2.1 DAPLink固件版本确认与升级

市面上常见的DAPLink调试器主要有两种来源:一种是成品调试器,比如各种基于DAPLink固件的USB Dongle;另一种是自己用STM32F103C8T6核心板刷的DAPLink固件。不管哪种,第一步都是确认固件版本。标题里提到的V0.0.20是DAPLink上位机工具的版本号,但固件本身也有版本迭代,两者需要配合使用。

查看固件版本的方法很简单:把DAPLink插到电脑上,它会枚举出一个名为“DAPLINK”的U盘设备,打开里面的DETAILS.TXT文件,里面会写明固件版本和编译日期。如果版本太老,比如低于V0250,建议先升级固件。升级方法也不复杂:把新的固件文件(通常是.bin格式)拖进那个U盘里,设备会自动重启并完成升级。注意升级过程中不要拔线,否则可能变砖。

提示:自己刷DAPLink固件时,STM32F103C8T6的Flash只有64KB,要选对应容量的固件版本。刷错了会出现设备管理器里识别到但无法连接目标芯片的情况。

2.2 驱动安装与设备识别

DAPLink在Windows下通常免驱,系统会自动识别为HID设备。但如果你用的是自己刷的板子,可能会遇到驱动签名问题。这时候需要手动安装WinUSB驱动,可以用Zadig工具来操作。具体步骤是:打开Zadig,在Options里勾选List All Devices,找到DAPLink对应的接口,选择WinUSB驱动,点击Replace Driver即可。

安装完成后,在设备管理器里应该能看到“USB输入设备”下面有一个“CMSIS-DAP v2”或者类似的条目。如果看到黄色感叹号,说明驱动没装好,需要重新操作。另外,Keil MDK和IAR这类IDE也需要安装对应的设备支持包,否则在调试配置里找不到DAPLink选项。Keil的话,需要安装Keil.STM32F1xx_DFP这类芯片包,同时确保CMSIS-DAP调试驱动已经就位。

2.3 硬件连接检查清单

硬件连接这块看着简单,但实际踩坑最多的就是这里。我整理了一个检查清单,每次连接新板子的时候都过一遍:

  • SWD接口四根线:VCC、GND、SWDIO、SWCLK,缺一不可。有些板子还需要接RESET,但大多数情况下不接也能正常调试。
  • 目标板供电:DAPLink的VCC输出能力有限,一般只有100mA左右。如果目标板功耗较大,建议单独供电,只共地不共VCC。
  • 线材长度:SWD线不要超过20厘米,太长会导致信号质量下降,出现连接不稳定或者识别不到芯片的情况。
  • 目标芯片供电电压:DAPLink的IO电平需要和目标芯片匹配。如果目标板是1.8V系统,而DAPLink输出3.3V,可能会损坏芯片或者通信失败。

注意:连接之前一定要确认目标板的SWD接口没有被复用为GPIO。有些项目为了省引脚,把PA13和PA14配置成了普通IO,这时候调试器是连不上的,需要先通过其他方式擦除芯片或者拉高BOOT引脚进入系统存储器启动模式。

3. RTT Viewer上位机配置全流程

3.1 软件获取与安装

RTT Viewer是Segger公司J-Link软件包里的一个工具,但它本身是免费使用的,而且不限制必须用J-Link硬件。你可以在Segger官网下载J-Link Software and Documentation Pack,安装完成后在开始菜单里就能找到RTT Viewer。安装过程中会提示是否安装USB驱动,如果你只用DAPLink,这一步可以跳过。

安装完成后,打开RTT Viewer,界面看起来比较朴素,但功能很全。主界面分为几个区域:连接配置区、终端显示区、输入发送区。第一次使用的时候,需要先配置连接参数,否则会提示找不到目标。

3.2 连接参数配置详解

RTT Viewer支持多种连接方式,对于DAPLink来说,需要选择“USB”作为连接类型,然后在“Target Device”里选择对应的芯片型号。这里有个细节:如果你用的是自己刷的DAPLink,可能需要在“Interface”里选择“CMSIS-DAP”而不是默认的“JTAG”。选好之后,点击“OK”就会尝试连接目标芯片。

连接成功后,RTT Viewer会自动搜索目标内存中的RTT控制块。这个控制块是RTT机制的核心,它包含了缓冲区描述符、读写指针等信息。如果搜索不到,通常是因为目标程序里还没有初始化RTT,或者RTT控制块所在的内存区域被优化掉了。这时候需要检查代码里是否正确调用了SEGGER_RTT_Init()函数。

连接参数里还有一个重要的选项是“RTT Address”,默认是自动搜索。如果你的程序里把RTT控制块放在了特定的地址,可以手动指定。大多数情况下自动搜索就够了,不需要改。

3.3 终端窗口与输入发送

连接成功后,RTT Viewer会显示一个终端窗口,默认是Terminal 0。如果你在代码里用了多个通道,比如Terminal 0打日志、Terminal 1收命令,可以在窗口上方切换。终端窗口支持ANSI转义序列,所以你可以用颜色区分不同级别的日志,比如错误用红色、警告用黄色、信息用绿色。

输入发送区在窗口底部,可以直接输入字符发送给目标芯片。这个功能在做交互式调试的时候特别有用,比如你可以发送一个字符来切换PID参数,或者触发一次数据采集。发送的数据会写入RTT的下行缓冲区,目标芯片通过SEGGER_RTT_Read()函数读取。

提示:RTT Viewer的终端窗口默认只显示最新的内容,如果日志刷得太快,可以勾选“Auto Scroll”来跟随最新输出,或者取消勾选来暂停滚动方便查看历史记录。

4. STM32端RTT代码移植与实操

4.1 RTT源码文件添加与工程配置

RTT的源码是免费开放的,你可以在Segger的安装目录下找到RTT文件夹,里面包含SEGGER_RTT.c、SEGGER_RTT.h、SEGGER_RTT_Conf.h等文件。把这些文件复制到你的STM32工程里,然后在IDE中添加对应的源文件和头文件路径。

在Keil MDK里,右键点击工程分组,选择“Add Existing Files to Group”,把SEGGER_RTT.c加进去。然后在“Options for Target”的“C/C++”选项卡里,把RTT头文件所在的目录添加到“Include Paths”中。编译一下,如果没有报错,说明移植基本成功。

这里有个容易忽略的点:SEGGER_RTT_Conf.h里定义了缓冲区的大小和数量。默认配置是上行1个通道、下行1个通道,每个通道缓冲区大小是1KB。如果你的日志量很大,可以适当增大缓冲区,比如改成4KB。但要注意,这些缓冲区是分配在RAM里的,STM32F103C8T6只有20KB RAM,别设太大导致内存不够用。

4.2 初始化与基本打印函数

RTT的初始化非常简单,只需要在main()函数开头调用一次SEGGER_RTT_Init()即可。这个函数会初始化RTT控制块,设置缓冲区指针。如果你用的是RTOS,建议在创建任务之前调用,避免多任务竞争。

基本打印函数是SEGGER_RTT_printf(),用法和标准printf几乎一样,只是第一个参数是通道号。比如:

SEGGER_RTT_printf(0, "System started, tick = %d\r\n", HAL_GetTick());

这里的0表示使用Terminal 0通道。如果你需要格式化浮点数,RTT的printf默认不支持,需要在SEGGER_RTT_Conf.h里把SEGGER_RTT_PRINTF_FLOAT宏打开。打开后编译出来的代码会大一些,但用起来方便。

除了printf,RTT还提供了更底层的SEGGER_RTT_Write()函数,可以直接写二进制数据。这个在做波形显示的时候很有用,你可以把ADC采样值直接写进去,上位机解析后画波形。

4.3 多通道与颜色输出实战

RTT支持多个上行和下行通道,默认配置是1个上行、1个下行。如果你需要多个通道,可以在SEGGER_RTT_Conf.h里修改SEGGER_RTT_MAX_NUM_UP_BUFFERS和SEGGER_RTT_MAX_NUM_DOWN_BUFFERS的值。比如改成3个上行通道,就可以把日志、波形数据、调试信息分开输出,互不干扰。

颜色输出是通过ANSI转义序列实现的。比如要输出红色文字,可以这样写:

SEGGER_RTT_printf(0, "\x1b[31mError: sensor timeout\x1b[0m\r\n");

\x1b[31m是红色,\x1b[0m是重置颜色。常用的颜色代码有:30黑色、31红色、32绿色、33黄色、34蓝色、35洋红、36青色、37白色。用颜色区分日志级别,一眼就能看出问题所在。

注意:RTT Viewer的终端窗口需要支持ANSI转义序列才能显示颜色。如果显示的是乱码,检查一下终端设置里是否开启了ANSI支持。

5. 常见问题排查与避坑经验

5.1 连接失败与识别不到芯片

这是最常见的问题,表现是RTT Viewer提示“Could not connect to target”或者“No RTT control block found”。排查思路可以按以下顺序来:

  • 检查SWD线序:SWDIO和SWCLK有没有接反?VCC和GND有没有接对?这是最基础的,但也是最容易出错的。
  • 检查目标芯片供电:用万用表量一下目标板的VCC和GND之间是不是3.3V。如果电压偏低,可能是供电不足。
  • 检查复位状态:有些芯片在复位期间SWD接口不可用,需要确保芯片已经正常启动。
  • 检查RTT初始化:确认代码里调用了SEGGER_RTT_Init(),并且没有被编译器优化掉。可以在初始化后加一句SEGGER_RTT_printf(0, "RTT init ok\r\n");来验证。

如果以上都正常但还是连不上,可以试试降低SWD时钟频率。在RTT Viewer的连接设置里,把“Interface Speed”从默认的10MHz降到1MHz试试。有时候信号质量不好,降速能解决。

5.2 RTT控制块搜索失败

RTT Viewer连接成功后,如果提示“No RTT control block found”,说明它在目标内存里没找到RTT控制块。可能的原因有:

  • RTT控制块被优化:编译器把_SEGGER_RTT结构体优化掉了,因为它没有被显式引用。解决方法是在代码里加一句volatile引用,或者在链接脚本里保留这个符号。
  • RTT控制块地址不对:如果程序里把RTT控制块放在了特定的段里,需要在RTT Viewer里手动指定地址。
  • 目标芯片处于低功耗模式:有些低功耗模式下,调试接口会被关闭,导致RTT Viewer无法访问内存。这时候需要先唤醒芯片。

我遇到过一次比较隐蔽的情况:程序里用了__attribute__((section(".rtt")))把RTT控制块放到了自定义段,但链接脚本里没有正确分配这个段,导致控制块地址异常。后来改成默认段就好了。

5.3 日志丢失与缓冲区溢出

RTT的缓冲区是固定大小的,如果日志产生速度超过上位机读取速度,缓冲区就会溢出,导致日志丢失。表现是终端窗口里看到“RTT buffer overflow”的提示,或者日志中间断了一截。

解决这个问题有几个方向:一是增大缓冲区,在SEGGER_RTT_Conf.h里把BUFFER_SIZE_UP改大,比如从1024改成4096。二是降低日志频率,比如把循环里的打印改成每100次打印一次。三是提高SWD时钟频率,让上位机读得更快。

还有一个容易被忽略的点:RTT的写入是阻塞式的,如果缓冲区满了,SEGGER_RTT_printf()会等待直到有空间。这在中断里调用会导致中断响应变慢。所以不要在中断里直接调用RTT打印函数,正确的做法是在中断里设置标志位,在主循环里打印。

5.4 常见问题速查表

问题现象可能原因解决方法
RTT Viewer提示连接失败SWD线序错误或供电不足检查线序,测量电压,降低SWD时钟
找不到RTT控制块未调用初始化或控制块被优化确认调用SEGGER_RTT_Init(),加volatile引用
日志输出乱码终端编码或波特率不匹配检查RTT Viewer终端设置,确认ANSI支持
日志丢失缓冲区溢出增大缓冲区,降低日志频率,提高SWD时钟
中断响应变慢在中断里调用RTT打印中断里设标志,主循环打印
浮点数打印异常未开启浮点支持在配置头文件里打开SEGGER_RTT_PRINTF_FLOAT

提示:如果以上方法都试过了还是不行,可以试试用J-Link Commander来手动连接目标芯片,看看能不能读到内存。这能帮你判断是DAPLink的问题还是RTT配置的问题。

6. 进阶技巧:用RTT做波形显示与PID调试

6.1 二进制数据传输与波形绘制

RTT不仅能打文本日志,还能传二进制数据。在做PID调试的时候,我习惯把设定值、实际值、输出值三个变量打包成结构体,通过RTT的二进制通道发送到上位机,然后用Python脚本解析并画波形。这样比看数字直观多了,调参数的时候一眼就能看出超调量和响应时间。

具体做法是:定义一个结构体,包含时间戳和三个float变量,然后调用SEGGER_RTT_Write()把结构体数据写进去。上位机这边用Python的pylink或者直接读RTT Viewer的日志文件来解析。如果不想写代码,也可以用Segger的RTT Client配合J-Scope来直接画波形,J-Scope支持实时显示RTT数据,配置好变量地址和类型就能用。

6.2 交互式调试与参数在线修改

RTT的下行通道可以用来接收上位机发送的命令。比如你可以定义一个简单的协议:收到'P'就打印当前PID参数,收到'S'就进入参数设置模式。这样调试的时候不用重新烧录程序,直接在上位机里改参数就行。

实现方法是:在主循环里调用SEGGER_RTT_Read()检查有没有新数据,如果有就解析并执行对应操作。注意这个函数是非阻塞的,没有数据的时候会立即返回,不会影响主循环的实时性。

char cmd; if (SEGGER_RTT_Read(0, &cmd, 1) == 1) { switch (cmd) { case 'P': SEGGER_RTT_printf(0, "Kp=%.2f Ki=%.2f Kd=%.2f\r\n", Kp, Ki, Kd); break; case 'S': setting_mode = 1; break; default: break; } }

这套机制在调试电机控制的时候特别顺手,你可以一边看波形一边改参数,效率比反复烧录高太多了。

6.3 多通道分离与日志分级

项目大了之后,日志会变得很多,全混在一起看很累。我的做法是用不同的RTT通道输出不同级别的日志:Terminal 0输出错误和警告,Terminal 1输出调试信息,Terminal 2输出波形数据。这样在RTT Viewer里可以只打开关心的通道,避免信息过载。

配置多通道需要在SEGGER_RTT_Conf.h里把SEGGER_RTT_MAX_NUM_UP_BUFFERS改成3,然后重新编译。每个通道的缓冲区大小可以单独设置,比如错误通道设小一点,波形通道设大一点。

日志分级可以用宏来实现:

#define LOG_ERROR(fmt, ...) SEGGER_RTT_printf(0, "\x1b[31m[ERROR] " fmt "\x1b[0m\r\n", ##__VA_ARGS__) #define LOG_WARN(fmt, ...) SEGGER_RTT_printf(0, "\x1b[33m[WARN] " fmt "\x1b[0m\r\n", ##__VA_ARGS__) #define LOG_INFO(fmt, ...) SEGGER_RTT_printf(1, "[INFO] " fmt "\r\n", ##__VA_ARGS__) #define LOG_DEBUG(fmt, ...) SEGGER_RTT_printf(1, "[DEBUG] " fmt "\r\n", ##__VA_ARGS__)

这样用起来就和标准日志库差不多了,而且颜色区分让问题一目了然。

7. 我踩过的坑与实操心得

7.1 自己刷DAPLink固件的注意事项

用STM32F103C8T6刷DAPLink固件确实便宜,但有几个坑得提前知道。第一是晶振问题:DAPLink固件默认使用8MHz外部晶振,如果你的板子用的是12MHz或者没有晶振,需要修改固件里的时钟配置,否则USB枚举会失败。第二是Flash容量:F103C8T6只有64KB Flash,要选对应容量的固件,刷128KB的版本会报错。第三是USB接口:有些核心板的USB接口没有接上拉电阻,导致电脑识别不到设备,需要自己补一个1.5K电阻到3.3V。

刷固件的方法有两种:一种是通过串口用STM32的系统存储器启动模式刷,另一种是用另一个调试器通过SWD刷。第一种方法需要把BOOT0拉高,用FlyMcu之类的工具烧录。第二种方法更简单,但前提是你已经有一个能用的调试器。

7.2 RTT与串口打印的性能对比

我做过一个简单的测试:在STM32F103C8T6上,用115200波特率的串口打印一条20字节的日志,耗时大约1.7毫秒。而用RTT打印同样的内容,耗时不到10微秒。差距是170倍。这意味着在中断里用串口打印会严重拖慢响应速度,而RTT几乎不影响。

另一个差异是CPU占用。串口打印是阻塞式的,CPU要等待发送寄存器空才能写下一个字节。RTT是内存拷贝,写完就返回,不占用CPU时间。所以在做高频控制循环的时候,RTT的优势非常明显。

不过RTT也有缺点:它依赖调试器连接。如果调试器拔了,RTT的写入操作会直接返回失败,不会阻塞程序。这点比串口好,串口如果没接终端,数据就丢了,但程序不会卡住。

7.3 在RTOS环境下使用RTT的建议

在FreeRTOS或者RT-Thread这类RTOS里用RTT,需要注意线程安全。RTT的写入函数本身不是线程安全的,多个任务同时调用SEGGER_RTT_printf()可能会导致输出交错。解决方法有两种:一是加互斥锁,二是用不同的通道给不同的任务。

我一般用第二种方法,因为加锁会增加开销。比如任务A用Terminal 0,任务B用Terminal 1,这样互不干扰。如果通道不够用,可以在SEGGER_RTT_Conf.h里增加通道数量。

还有一个细节:RTT的初始化要在RTOS启动之前完成,否则多个任务同时初始化可能会出问题。我习惯在main()函数开头就调用SEGGER_RTT_Init(),然后再创建任务。

7.4 长期运行的稳定性观察

RTT在长时间运行下的稳定性还是不错的,我有个项目连续跑了72小时,RTT日志一直正常输出,没有出现断连或者缓冲区死锁的情况。但有一点要注意:如果目标芯片频繁进入低功耗模式,RTT可能会断开。因为低功耗模式下调试接口可能被关闭,上位机读不到数据。解决方法是在低功耗模式下关闭RTT输出,或者用调试器保持芯片不进入深度休眠。

另外,如果SWD线太长或者干扰太大,RTT可能会出现数据错误。表现是终端窗口里偶尔出现乱码。这时候可以降低SWD时钟频率,或者在SWDIO和SWCLK上并联一个小电容滤波。我一般会在SWD线上串一个100欧姆的电阻,能有效抑制振铃。

8. 从调试工具到开发习惯的转变

用顺了DAPLink加RTT这套组合之后,我的开发习惯发生了不小的变化。以前调试靠串口打印,代码里到处是printf,还得小心翼翼地管理串口资源。现在RTT成了默认的调试手段,代码里直接写SEGGER_RTT_printf(),不用考虑外设冲突,也不用担心影响实时性。

更重要的是,RTT的双向通信能力让我可以把调试接口做成一个简单的命令行界面。项目后期,我甚至可以通过RTT在线修改参数、触发校准、读取状态,不用重新烧录固件。这种交互式的调试方式,比传统的“改代码-编译-烧录-观察”循环效率高太多了。

如果你还在用串口打印调试,我建议你花半个小时试试RTT。一开始可能会觉得配置有点麻烦,但一旦跑通,你会发现回不去了。尤其是做电机控制、电源管理这类对实时性敏感的项目,RTT几乎是必备工具。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询