尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32调试利器:DAPLink与RTT Viewer高效调试指南

STM32调试利器:DAPLink与RTT Viewer高效调试指南 1. 为什么我最终选了DAPLink加RTT Viewer这套组合搞STM32调试的朋友大概率都经历过这种场景板子已经装进外壳里了串口线拔来拔去不方便偏偏这时候要打印几个变量看看运行状态。用传统的串口打印吧得占用一个USART还得接USB转TTL模块线一多桌面就乱成一锅粥。我早期做项目的时候为了省一个串口甚至把日志输出重定向到LCD屏幕上现在回头看真是又笨又费劲。后来接触到DAPLink和RTT Viewer这套组合才算真正把调试这件事理顺了。DAPLink本质上是一个开源的调试器固件刷在STM32F103C8T6这类小板子上就能当CMSIS-DAP调试器用支持SWD接口下载和调试。而RTTReal 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宏打开。打开后编译出来的代码会大一些但用起来方便。除了printfRTT还提供了更底层的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几乎是必备工具。
返回列表