
1. 为什么PY32调试值得单独写一篇配置指南PY32系列单片机这两年在家电控制、电机驱动、消费类电子里铺得很快核心原因就一个性价比。它用的是ARM Cortex-M0内核主频常见到48MHzFlash从16KB到64KB不等封装也小做小家电主控、充电器协议、传感器采集这些场景非常合适。但很多朋友从STM32或者51单片机转过来第一道坎不是写代码而是调试工具链怎么搭。我最早接触PY32的时候手上只有一块开发板和一根J-Link官方例程能编译但下载进去之后想看变量、想看串口打印发现常规的串口助手根本不够用——因为PY32的SWD调试口和串口是复用的你一旦把SWD引脚配置成串口J-Link就断连了。这时候RTT Viewer就是救命的东西。RTT全称Real Time Transfer是J-Link配套的一种调试信息传输机制它不需要占用额外的串口外设直接通过SWD调试通道把日志从芯片内存里读出来速度比串口快得多而且不打断程序运行。这篇内容就是把我自己在PY32上跑通J-Link加RTT Viewer的完整过程拆开讲清楚。从驱动安装、工程配置、RTT代码移植到实际调试中遇到的“No J-Link found”“RTT不输出”“中文乱码”这些坑全部给出来。适合刚拿到PY32开发板、手上有J-Link或者J-Link OB的嵌入式新手也适合从STM32转过来想快速上手PY32的工程师。你不需要有很深的ARM调试经验跟着步骤走就能把RTT跑起来。2. 整体方案设计与工具选型思路2.1 为什么选J-Link而不是PY32官方下载器PY32官方一般配套的是PY-Link或者某些第三方下载器价格便宜但功能比较单一主要就是烧录。J-Link的优势在于调试生态完整支持SWD和JTAG支持RTT支持J-Scope实时波形支持无限断点正版而且J-Link的驱动和上位机软件更新非常勤快。你如果手上已经有一根J-Link不管是正版还是常见的OB版本都可以直接拿来用不需要额外买PY-Link。这里要说明一点PY32的SWD接口和STM32是兼容的都是SWCLK和SWDIO两根线加上GND和VCC。J-Link的20针接口里PIN7是SWDIOPIN9是SWCLKPIN4和PIN6是GNDPIN1和PIN2是VCC参考电压。你如果用杜邦线连接只需要接这四到五根线就能识别到芯片。我实测过J-Link V9、J-Link OB、以及某宝上几十块的J-Link克隆版在PY32上都能正常识别但克隆版在RTT高速模式下偶尔会丢包后面会讲怎么调。2.2 RTT相比串口打印的优势在哪里串口打印是大家最熟悉的调试方式但在PY32上有几个现实问题。第一PY32的串口引脚经常和SWD复用你一旦把PA13、PA14配置成串口J-Link就连不上了下次想烧录还得先擦除或者改BOOT配置。第二串口需要占用一个外设和对应的引脚对于引脚资源紧张的小封装芯片来说很浪费。第三串口打印需要初始化波特率、中断或者DMA代码量不小而且打印本身会占用CPU时间。RTT的原理不一样。它在芯片的内存里开辟一块缓冲区J-Link通过SWD接口直接读取这块内存不需要芯片执行任何额外的通信外设操作。你只需要在代码里调用SEGGER_RTT_printf数据写进缓冲区J-Link在后台自动搬运到电脑上。整个过程对CPU的占用极低而且速度可以到几百KB每秒。对于PY32这种M0内核、主频不高的芯片来说RTT几乎是零负担的调试方案。2.3 配置文件在其中的角色J-Link和RTT Viewer的配置主要涉及两个文件一个是J-Link的器件配置文件告诉J-Link PY32的Flash地址、RAM地址、以及烧录算法另一个是RTT Viewer的配置文件保存连接参数、字体、编码格式等。很多朋友第一次用RTT Viewer打开之后一片空白就是因为没有正确选择目标器件或者没有加载RTT控制块。PY32的器件配置文件在J-Link的安装目录下路径通常是C:\Program Files\SEGGER\JLink\Devices。如果你安装的J-Link驱动版本比较老里面可能没有PY32的选项这时候就需要手动添加或者升级驱动。我后面会给出一个可用的配置文件模板你直接放到对应目录就能在J-Link Commander和RTT Viewer里选到PY32。3. 环境准备与驱动安装的完整步骤3.1 J-Link驱动下载与安装要点去SEGGER官网下载J-Link Software and Documentation Pack选Windows版本。安装的时候有一个选项叫“Update DLL in existing installations”如果你电脑上装过Keil、IAR或者STM32CubeIDE建议勾上这样这些IDE里的J-Link驱动也会一起更新到最新版。安装路径默认是C:\Program Files\SEGGER\JLink记住这个路径后面放配置文件要用。安装完成后把J-Link插到电脑USB口打开设备管理器应该能看到“J-Link driver”或者“USB Serial Device”之类的设备。如果显示黄色感叹号说明驱动没装好右键更新驱动手动指向J-Link安装目录下的USBDriver文件夹。我遇到过Win11下J-Link OB识别成串口的情况这时候需要先装一遍J-Link驱动再重新插拔。注意如果你用的是J-Link OB板载调试器有些开发板上的OB固件版本很老可能不支持PY32。这时候需要用J-Link Commander执行exec firmwareupdate来升级OB固件但升级有风险操作前确认开发板资料里有没有说明。3.2 PY32器件配置文件的放置与验证J-Link驱动默认支持的器件列表里PY32不一定在列。你可以先打开J-Link Commander输入connect然后输入?查看支持的器件列表搜索“PY32”。如果没有就需要手动添加。配置文件是一个XML格式的文件放在C:\Program Files\SEGGER\JLink\Devices目录下文件名类似PY32F0xx.jlink。我整理了一份PY32F002、PY32F003、PY32F030通用的配置文件核心内容包括Flash基地址0x08000000Flash大小根据具体型号填写比如PY32F003是32KBRAM基地址0x20000000RAM大小4KB烧录算法使用Cortex-M0的通用算法放好文件后重新打开J-Link Commander输入connect再输入?应该就能看到PY32的选项了。选中之后J-Link会尝试读取芯片ID如果连接正常会显示“Cortex-M0 identified”之类的信息。3.3 RTT Viewer的安装与基本配置RTT Viewer包含在J-Link软件包里安装完J-Link驱动之后开始菜单里就能找到“J-Link RTT Viewer”。打开之后第一件事是配置连接参数Connection to J-Link选USBSpecify Target Device手动输入PY32F003或者你用的型号Target InterfaceSWDSpeed默认4000kHz如果线比较长或者克隆版J-Link可以降到1000kHzRTT Control Block选Auto Detection配置好之后点OK如果芯片里已经运行了带RTT的固件就能看到日志输出。如果显示“Could not find RTT Control Block”说明芯片里没有RTT代码或者RTT控制块地址不对。4. 工程中移植RTT代码的关键细节4.1 RTT源码文件的获取与裁剪RTT的源码是免费的在J-Link安装目录下的Samples\RTT文件夹里核心文件是SEGGER_RTT.c、SEGGER_RTT.h、SEGGER_RTT_Conf.h和SEGGER_RTT_printf.c。你把这四个文件复制到自己的PY32工程里添加到编译列表。SEGGER_RTT_Conf.h里有几个关键配置BUFFER_SIZE_UP上行缓冲区大小默认1024字节PY32 RAM小的话可以改成256BUFFER_SIZE_DOWN下行缓冲区大小如果只用打印可以改成16SEGGER_RTT_MAX_NUM_UP_BUFFERS上行缓冲区数量默认1个就够SEGGER_RTT_MODE_DEFAULT默认模式选SEGGER_RTT_MODE_NO_BLOCK_SKIP这样缓冲区满的时候直接跳过不会阻塞程序我一般会把上行缓冲区改成512下行改成16这样在PY32F003这种4KB RAM的芯片上也不会占用太多内存。4.2 初始化代码的放置位置RTT不需要显式初始化第一次调用SEGGER_RTT_printf的时候会自动初始化控制块。但有一个坑如果你在main函数之前就调用RTT打印比如在SystemInit里可能会因为RTT控制块还没被J-Link识别而丢数据。我的做法是在main函数开头加一句SEGGER_RTT_Init()虽然这个函数在源码里是空的但加上之后逻辑更清晰。还有一个更隐蔽的问题PY32的启动文件里默认堆栈大小可能不够。RTT的SEGGER_RTT_printf会用到一些局部变量如果栈太小打印长字符串的时候会HardFault。我建议把启动文件里的栈大小从默认的0x400改成0x800堆至少保留0x200。4.3 重定向printf到RTT的两种方式很多朋友习惯用printf不想改成SEGGER_RTT_printf。这时候可以用重定向的方式。第一种是重写fputc函数#include SEGGER_RTT.h int fputc(int ch, FILE *f) { SEGGER_RTT_PutChar(0, ch); return ch; }然后在Keil的Target选项里勾选“Use MicroLIB”。这种方式最简单但MicroLIB对浮点打印支持不好打印浮点数会出问题。第二种是用宏定义替换#define printf(...) SEGGER_RTT_printf(0, __VA_ARGS__)这种方式不依赖MicroLIB但要注意SEGGER_RTT_printf的格式字符串和标准printf略有不同比如不支持%f需要用整数方式打印浮点。实操心得我建议直接用SEGGER_RTT_printf不要重定向。因为RTT的printf支持颜色控制比如SEGGER_RTT_printf(0, RTT_CTRL_TEXT_BRIGHT_RED Error: %d\r\n, code)打印出来的错误信息是红色的在RTT Viewer里非常醒目。5. 实操过程与核心环节实现5.1 硬件连接与J-Link识别测试先接线。以PY32F003的TSSOP20封装为例SWDIO是PA13SWCLK是PA14VCC和GND分别接3.3V和地。J-Link的PIN7接SWDIOPIN9接SWCLKPIN4接GNDPIN1接3.3V参考。注意PY32的供电范围是1.7V到5.5V但J-Link的参考电压一般接3.3V所以开发板要单独供3.3V不要指望J-Link的PIN1能带得动整个板子。接好线之后打开J-Link Commander依次输入connect PY32F003 S 4000如果一切正常会显示Cortex-M0 identified.如果显示“No J-Link found”先检查USB线是不是数据线有些线只能充电。如果显示“Could not connect to target”检查SWDIO和SWCLK有没有接反或者芯片是不是处于低功耗模式。PY32在Stop模式下SWD会断开需要先复位或者唤醒。5.2 编译带RTT的工程并烧录在Keil里新建或者打开PY32的工程把RTT的四个文件加进去在main.c里包含SEGGER_RTT.h然后在主循环里加打印#include SEGGER_RTT.h int main(void) { SystemInit(); SEGGER_RTT_Init(); SEGGER_RTT_printf(0, PY32 RTT Test Start\r\n); while(1) { SEGGER_RTT_printf(0, Tick: %d\r\n, tick); Delay_ms(500); } }编译之前在Keil的Debug选项里选J-LinkSettings里选SWD器件选PY32F003。烧录的时候J-Link会自动擦除、下载、复位。如果烧录失败提示“Flash Download failed”检查配置文件里的Flash算法地址是不是0x08000000RAM地址是不是0x20000000。5.3 RTT Viewer连接与日志查看烧录完成后不要关闭Keil的调试会话直接打开RTT Viewer。配置里Target Device选PY32F003Interface选SWDSpeed选4000kHzRTT Control Block选Auto Detection。点OK之后如果一切正常就能看到“PY32 RTT Test Start”和每秒两行的Tick计数。如果显示“Could not find RTT Control Block”有几个排查方向芯片里运行的固件没有包含RTT代码RTT控制块地址被编译器优化掉了需要在SEGGER_RTT_Conf.h里把SEGGER_RTT_SECTION改成.bss或者.dataJ-Link的连接速度太快降到1000kHz试试我遇到过最奇怪的一次是RTT Viewer能连上但日志不刷新。后来发现是SEGGER_RTT_printf的缓冲区满了因为我在主循环里打印太快而RTT Viewer的刷新率跟不上。解决办法是把BUFFER_SIZE_UP改大或者在打印之间加延时。5.4 用RTT Viewer的终端功能做交互RTT Viewer不只是看日志还可以做输入。在RTT Viewer的“Input”标签页里你可以发送字符串到芯片的下行缓冲区。芯片里用SEGGER_RTT_Read或者SEGGER_RTT_HasKey来读取。这个功能在调试命令行参数、模拟按键输入的时候非常方便。比如你可以写一个简单的命令解析char buf[32]; if (SEGGER_RTT_HasKey()) { int len SEGGER_RTT_Read(0, buf, sizeof(buf)); if (len 0) { SEGGER_RTT_printf(0, Received: %s\r\n, buf); } }这样你在RTT Viewer里输入“hello”芯片就会回显“Received: hello”。注意下行缓冲区默认只有16字节如果要传长命令需要把BUFFER_SIZE_DOWN改大。6. 常见问题与排查技巧实录6.1 J-Link无法识别芯片的几种情况现象可能原因解决方法No J-Link foundUSB线是充电线换数据线Could not connect to targetSWDIO/SWCLK接反交换两根线Could not connect to target芯片处于低功耗模式复位或唤醒Unknown device配置文件缺失添加PY32配置文件Flash Download failedFlash算法地址错误检查配置文件我踩过最深的坑是J-Link OB的固件太老识别不到PY32的ID。后来用J-Link Commander执行exec firmwareupdate升级了OB固件才正常识别。但升级OB固件有风险如果中途断电OB可能变砖所以操作前一定要确认开发板资料里有没有提供恢复方法。6.2 RTT不输出的排查思路RTT不输出是最常见的问题排查顺序如下确认芯片里运行的固件确实调用了SEGGER_RTT_printf确认RTT Viewer的Target Device选对了确认RTT Control Block的地址。可以在map文件里搜索_SEGGER_RTT看看它的地址是多少然后在RTT Viewer里手动输入这个地址确认J-Link的连接速度。克隆版J-Link在4000kHz下可能不稳定降到1000kHz确认芯片没有进入Sleep或者Stop模式。PY32在低功耗模式下SWD会断开RTT自然也不工作独家技巧如果RTT Viewer一直找不到控制块可以在代码里把_SEGGER_RTT结构体放到一个固定的地址比如0x20000000然后在RTT Viewer里手动指定这个地址。具体做法是在SEGGER_RTT_Conf.h里定义SEGGER_RTT_SECTION为.ARM.__at_0x20000000但要注意不要和其他变量冲突。6.3 中文乱码与编码设置RTT Viewer默认的编码是UTF-8如果你在代码里打印的是GB2312编码的中文字符串就会显示乱码。解决办法有两个一是把RTT Viewer的编码改成GB2312在“Terminal”设置里可以改二是把代码文件的编码改成UTF-8Keil里在Edit-Configuration-Encoding里选UTF-8。我建议统一用UTF-8因为现在大部分编辑器默认都是UTF-8而且RTT Viewer对UTF-8的支持最好。如果你用的是IAR注意IAR默认可能是GB2312需要手动改。6.4 高速打印丢包与缓冲区溢出RTT虽然快但也不是无限的。如果你在主循环里不加延时地打印缓冲区很快就会满。默认模式下缓冲区满了之后SEGGER_RTT_printf会直接返回数据就丢了。你可以改成阻塞模式但那样会影响程序实时性。我的做法是调试阶段用SEGGER_RTT_MODE_NO_BLOCK_SKIP丢包就丢包不影响程序运行需要抓完整日志的时候把缓冲区改大比如2048字节然后在打印之间加1ms延时。另外RTT Viewer的“Auto Scroll”和“Show Timestamp”选项也会影响刷新率如果日志量很大可以关掉时间戳显示。7. 配置文件模板与参数说明7.1 PY32器件配置文件内容下面是我在用的PY32F003配置文件保存为PY32F003.jlink放到C:\Program Files\SEGGER\JLink\Devices目录下Database Device ChipInfo VendorPuya NamePY32F003 CoreJLINK_CORE_CORTEX_M0 WorkRAMAddr0x20000000 WorkRAMSize0x1000/ FlashBankInfo NameFlash BaseAddr0x08000000 MaxSize0x8000 LoaderDevices/PY32F003_Flash.jlink LoaderTypeFLASH_ALGO_TYPE_OPEN/ /Device /Database其中MaxSize是Flash大小PY32F003是32KB所以是0x8000。PY32F002是16KB改成0x4000。PY32F030是64KB改成0x10000。WorkRAMSize是RAM大小PY32F003是4KB所以是0x1000。7.2 RTT Viewer配置文件保存与复用RTT Viewer的配置保存在%APPDATA%\SEGGER\RTTViewer目录下文件名是RTTViewer.ini。你配置好连接参数、字体、编码之后这个文件会自动更新。如果换电脑可以把这文件复制过去省得重新配置。我一般会把常用的配置导出成不同的ini文件比如PY32F003.ini、PY32F030.ini用的时候直接替换。注意ini文件里的TargetDevice字段要和J-Link Commander里选的一致否则连不上。7.3 关键参数速查表参数推荐值说明BUFFER_SIZE_UP512上行缓冲区PY32F003够用BUFFER_SIZE_DOWN16下行缓冲区只做打印可以更小SEGGER_RTT_MODE_DEFAULTNO_BLOCK_SKIP不阻塞程序J-Link Speed1000-4000kHz克隆版建议1000kHzRTT Control BlockAuto Detection找不到时手动指定地址栈大小0x800防止HardFault堆大小0x200RTT printf需要8. 进阶用法与个人经验补充RTT还有一个很实用的功能叫RTT Channel可以开多个上行通道把不同模块的日志分开显示。比如通道0打系统日志通道1打传感器数据通道2打错误信息。在RTT Viewer里可以同时打开多个终端窗口每个窗口对应一个通道。配置方法是在SEGGER_RTT_Conf.h里把SEGGER_RTT_MAX_NUM_UP_BUFFERS改成3然后在代码里用SEGGER_RTT_printf(1, ...)指定通道。另外J-Link的J-Scope功能也可以和RTT配合把变量实时画成波形。你只需要在代码里把变量地址告诉J-Scope它就能通过SWD实时采样。这个在调试PID参数、看传感器变化趋势的时候特别有用。不过J-Scope对PY32的支持需要J-Link驱动版本比较新老版本可能找不到PY32的器件定义。我个人在实际操作中的体会是PY32加J-Link加RTT这套组合一旦跑通调试效率比串口打印高一个数量级。但前期配置确实有几个坎尤其是器件配置文件和RTT控制块地址这两个地方最容易卡住。我的建议是先把J-Link Commander连通确认能识别芯片、能烧录再搞RTT。不要一上来就同时调J-Link和RTT那样出了问题很难定位。最后分享一个小技巧如果你手头没有J-Link只有ST-Link也可以用ST-Link加RTT Viewer但需要把ST-Link的固件刷成J-Link OB这个操作有风险而且不是所有ST-Link都支持。稳妥起见还是用J-Link或者J-Link OB。PY32的调试口兼容性很好只要SWDIO和SWCLK接对供电正常基本都能连上。