1. 为什么我放弃了USB转TTL,转投JLink VCOM
搞嵌入式开发的朋友大概率都经历过这样的场景:板子已经连了JLink做下载和调试,程序里想加几行printf打印看看变量状态,结果发现手头没有USB转TTL模块,或者串口线被别的设备占用了。翻箱倒柜找模块、接线、确认TX/RX有没有接反,折腾十几分钟,就为了看几行打印信息。这种体验说实话挺割裂的。
JLink的VCOM功能就是来解决这个问题的。它利用JLink自带的虚拟串口能力,把调试器本身变成一个串口通道,你不需要额外接任何USB转TTL模块,只需要在代码里把printf重定向到JLink的VCOM通道,就能在电脑上直接看到打印输出。一根USB线同时搞定下载、调试和串口打印,桌面清爽了,接线错误的概率也降到零。
这个方案特别适合几类人:一是手头只有JLink没有USB转TTL的开发者;二是调试空间狭小、不方便额外接线的场景;三是经常需要同时用SWD调试和串口输出的项目。STM32、NXP S32K、GD32、瑞萨RA系列等主流MCU都支持这个玩法,只要你的JLink固件版本不是太老,基本都能跑通。
我前后在STM32F103、STM32F407和S32K148上分别验证过这套方案,踩过一些坑,也总结了一些比较顺手的配置流程。下面把整个思路、原理、实操步骤和常见问题都摊开讲一遍。
2. JLink VCOM到底是怎么工作的
2.1 从硬件层面理解VCOM的通道来源
JLink调试器通过SWD接口和MCU通信,用到的是SWDIO和SWCLK两根信号线,外加GND和VTref。这四个引脚负责调试和下载,跟串口没有直接关系。那VCOM的串口数据是从哪里来的?
答案在JLink的固件里。JLink内部有一个叫做“虚拟串口”的模块,它通过调试接口的某种机制(具体实现是SEGGER私有的)在目标MCU和PC之间建立一条数据通道。这条通道不占用额外的物理引脚,而是复用SWD接口的通信能力。PC端看到的表现为一个USB CDC设备,也就是设备管理器里多出来的一个COM口。
这里有个关键点需要说清楚:VCOM的数据传输依赖JLink的调试通信链路,所以它的实时性和带宽跟真正的硬件串口有差异。实测下来,在STM32F103上跑115200波特率的打印,基本感觉不到延迟,但如果你要传大量数据或者对时序要求极高,VCOM可能不是最优选择。日常调试打印、日志输出完全够用。
2.2 SWDIO和SWCLK的角色再确认
很多新手会问“SWDIO和SWCLK能当普通IO口用吗”,这个问题跟VCOM有一定关联。SWDIO和SWCLK在MCU没有进入调试模式时,确实可以配置为普通GPIO使用,但一旦JLink连接并建立了调试会话,这两个引脚就被调试外设占用了。VCOM的数据通道正是建立在这个调试会话之上的,所以你不能在VCOM工作的同时把SWDIO/SWCLK当普通IO来用。
正确的接线方式是:JLink的SWDIO接MCU的SWDIO,SWCLK接SWCLK,GND接GND,VTref接MCU的供电参考电压(通常是3.3V)。这四根线接好之后,VCOM和调试下载共用这一套连接,不需要额外接线。
2.3 VCOM与USB虚拟串口的区别
这里要区分两个概念。STM32本身有USB外设,可以配置成USB CDC设备,也就是常说的“STM32 USB虚拟串口”。这种方案需要MCU的USB引脚(D+和D-)连接到PC,占用MCU的USB资源,还需要写USB协议栈。而JLink VCOM完全不占用MCU的USB外设,它是通过调试接口实现的,对MCU来说只是普通的串口外设(通常是USART1或LPUART1)在收发数据。
这个区别很重要。如果你的项目里USB外设已经被占用了(比如做USB HID或者USB MSC),你依然可以用JLink VCOM来做串口打印,两者不冲突。反过来说,如果你用的是STM32F103C8T6这种USB和CAN不能同时用的芯片,JLink VCOM也不会跟CAN抢资源。
3. 环境准备与驱动配置
3.1 JLink驱动安装的完整流程
VCOM功能需要JLink驱动版本在6.0以上,建议直接去JLink官网下载最新版。安装过程有几个细节需要注意。
下载页面会提供不同平台的安装包,Windows下选择“J-Link Software and Documentation Pack”的exe版本。安装时建议勾选“Install USB Driver”和“Update DLL in IDE directories”这两个选项,前者确保VCOM的USB CDC驱动被正确注册,后者让Keil、IAR等IDE能直接调用最新的JLink DLL。
安装完成后,把JLink插上电脑,打开设备管理器,你应该能看到两个设备:一个是“J-Link driver”下的JLink调试器,另一个是“端口”下的“JLink CDC UART Port”。如果只看到调试器没看到串口,说明VCOM驱动没有正确加载,需要手动更新驱动或者重新安装。
注意:Win11下有时候系统会自动安装一个旧版驱动,导致VCOM端口不出现。解决办法是在设备管理器里右键JLink设备,选择“更新驱动程序”,手动指向JLink安装目录下的USBDriver文件夹。
3.2 确认VCOM端口号并配置终端软件
设备管理器里看到的“JLink CDC UART Port (COMx)”就是VCOM的串口端口。记住这个COM号,后面在串口终端软件里要选它。
终端软件用什么都行,PuTTY、SecureCRT、MobaXterm,甚至Arduino IDE的串口监视器都可以。配置参数一般是:波特率115200(也可以设成其他值,VCOM对波特率的容忍度比较高),数据位8,停止位1,无校验,无流控。
这里有个实操心得:VCOM的波特率设置其实不影响实际传输速率,因为数据是通过调试链路走的,不是真正的UART时序。但为了跟代码里的UART初始化保持一致,建议还是设成一样的值,避免混淆。
3.3 目标MCU的UART外设初始化
VCOM在MCU侧表现为一个普通的UART外设。以STM32为例,你需要初始化一个USART,配置波特率、数据位、停止位等参数。但注意,这个UART的TX和RX引脚不需要接到任何外部设备,因为数据是通过JLink的调试通道传输的。
具体来说,你只需要初始化UART外设本身,不需要配置GPIO的复用功能。有些教程会让你把TX引脚配置成复用推挽输出,其实对于VCOM来说这是多余的。当然,如果你同时想用这个UART接外部设备,那就正常配置GPIO。
代码层面,以STM32 HAL库为例,初始化USART1的代码跟平时一样:
huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; HAL_UART_Init(&huart1);初始化完成后,直接调用HAL_UART_Transmit发送数据,PC端的串口终端就能收到。
4. printf重定向与打印实战
4.1 重定向printf到VCOM的两种方式
在嵌入式开发里,printf默认是输出到标准输出(通常是调试器的控制台),要让它走UART,需要重定向fputc或者_write函数。以Keil MDK为例,重定向fputc的代码如下:
#include <stdio.h> int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY); return ch; }这段代码的意思是,每次printf调用底层fputc时,把字符通过UART1发出去。因为VCOM在PC端表现为UART1对应的串口,所以数据就传到了PC的串口终端。
如果你用的是GCC编译器(比如STM32CubeIDE),需要重定向的是_write函数:
int _write(int file, char *ptr, int len) { HAL_UART_Transmit(&huart1, (uint8_t *)ptr, len, HAL_MAX_DELAY); return len; }两种方式的原理一样,只是编译器底层调用的函数名不同。Keil用fputc,GCC用_write,IAR用__write或者低层级的putchar。
4.2 避免printf阻塞的技巧
HAL_UART_Transmit是阻塞式发送,如果串口终端没打开或者VCOM通道异常,printf会一直卡在发送函数里,导致程序跑飞。这个问题在实际调试中很常见,尤其是你忘了打开串口终端的时候。
解决办法有两个。一是用带超时的发送函数,比如HAL_UART_Transmit(&huart1, &ch, 1, 10),超时10ms后自动返回,不会死等。二是用DMA或者中断方式发送,把数据丢进缓冲区就返回,不阻塞主循环。
我个人的习惯是用一个环形缓冲区加DMA发送,printf只负责往缓冲区里写数据,DMA在后台自动发送。这样即使串口终端没开,printf也不会阻塞,最多是缓冲区满了之后丢弃新数据。
4.3 实测打印效果与性能观察
在STM32F103C8T6上,主频72MHz,用115200波特率通过VCOM打印,实测每秒可以稳定输出约10KB的文本数据。打印一行50个字符的日志,耗时大约5ms。这个性能对于日常调试完全够用。
如果打印量特别大,比如在中断里频繁printf,建议把打印内容先存到缓冲区,在主循环里统一输出。中断里调用printf本身就不是好习惯,容易导致中断响应延迟。
实操心得:VCOM的打印延迟跟JLink的固件版本有关系。我测试过JLink V9.5和V9.7固件,V9.7的VCOM响应明显更快,尤其是在大量数据连续打印时,丢字符的情况少很多。如果你的JLink固件比较老,建议升级到最新版。
5. 常见问题排查与避坑指南
5.1 VCOM端口不出现的排查思路
这是最常见的问题。插上JLink,设备管理器里只有调试器没有串口端口。排查顺序如下:
第一步,确认JLink驱动版本。打开JLink Commander,输入“ver”命令,看固件版本是否在6.0以上。低于这个版本不支持VCOM。
第二步,检查设备管理器里有没有未知设备或者带感叹号的设备。如果有,说明驱动没装好,手动更新驱动。
第三步,换一个USB口试试。有些USB Hub或者前置面板的USB口供电不足,会导致VCOM设备枚举失败。
第四步,如果以上都没问题,可能是JLink硬件本身的问题。有些山寨JLink的VCOM功能被阉割了,这种情况只能换正版或者支持VCOM的型号。
5.2 打印乱码或丢字符的处理
乱码通常是因为波特率不匹配。虽然VCOM对波特率不敏感,但代码里UART初始化的波特率和串口终端设置的波特率最好保持一致。如果代码里设的是9600,终端设的是115200,打印出来的就是乱码。
丢字符的原因比较多。一是发送速度太快,VCOM通道来不及传输。解决办法是降低打印频率或者在发送函数里加适当延时。二是JLink的调试速度设置过高,导致VCOM数据被调试数据挤掉。可以在JLink设置里把SWD时钟降到1MHz试试。
三是缓冲区溢出。如果你用的是DMA发送,检查DMA缓冲区大小是否足够。打印长字符串时,缓冲区太小会导致数据被截断。
5.3 JLink连接不上目标板的情况
有时候JLink能识别到VCOM端口,但连不上目标MCU,导致下载和打印都不工作。这种情况通常是SWD接口的问题。
先检查接线:SWDIO、SWCLK、GND、VTref四根线是否接好。VTref一定要接,它是JLink检测目标板电压的参考,不接的话JLink可能无法识别目标板。
如果接线没问题,试试降低SWD时钟速度。在Keil的JLink设置里,把“Max Clock”从默认的4MHz降到1MHz甚至500kHz。有些板子的SWD走线比较长或者有干扰,高速时钟下容易通信失败。
还有一种情况是MCU进入了低功耗模式或者被读保护了。这时候需要先擦除芯片或者用JLink的“Connect under Reset”模式连接。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 设备管理器无VCOM端口 | 驱动版本过低 | 升级JLink驱动到6.0以上 |
| 打印乱码 | 波特率不匹配 | 统一代码和终端的波特率设置 |
| 打印丢字符 | 发送过快或SWD时钟过高 | 降低打印频率,SWD时钟降到1MHz |
| JLink连不上目标板 | 接线错误或SWD时钟过高 | 检查四线连接,降低SWD时钟 |
| printf卡死 | 阻塞式发送且终端未打开 | 改用带超时的发送或DMA发送 |
| VCOM端口时有时无 | USB供电不稳 | 换USB口,避免用Hub |
6. 进阶玩法与扩展思路
6.1 多路VCOM同时使用
如果你手头有多个JLink,可以同时插在电脑上,每个JLink都会生成一个独立的VCOM端口。这样你可以同时调试多个板子,每个板子的打印输出到不同的串口终端窗口,互不干扰。
在代码层面,每个板子用各自的UART外设,PC端用不同的COM号打开终端。这个方案在多节点调试或者主从板联合调试时特别有用。
6.2 VCOM与RTT的对比选择
SEGGER还提供了RTT(Real Time Transfer)功能,也是通过JLink进行数据交互。VCOM和RTT的区别在于:VCOM对MCU来说是一个真实的UART外设,需要占用UART资源和相应的引脚配置;RTT则是通过共享内存实现的,不需要任何外设资源,速度更快,但需要MCU端集成RTT库。
选择建议:如果你只是想快速加个打印,不想改太多代码,VCOM更简单,因为UART初始化是现成的。如果你追求极致性能或者UART资源紧张,RTT更合适。两者可以同时使用,互不冲突。
6.3 在RTOS环境下的VCOM使用
在FreeRTOS或者RT-Thread环境下使用VCOM,需要注意线程安全。多个任务同时printf会导致输出交错,看起来一团糟。解决办法是加一个互斥锁,每次printf前获取锁,打印完成后释放。
另外,RTOS下建议用DMA加空闲中断的方式接收VCOM数据,避免在中断里做耗时操作。发送方向可以用一个独立的打印任务,其他任务通过消息队列把打印内容发给打印任务,由打印任务统一输出。
我在FreeRTOS项目里就是这么做的:创建一个优先级较低的打印任务,其他任务通过队列发送字符串指针,打印任务负责格式化并输出到VCOM。这样既保证了线程安全,又不会阻塞高优先级任务。
6.4 量产测试中的VCOM应用
VCOM不仅适用于开发阶段,在产线测试中也有用武之地。产线工人只需要连接JLink,就能同时完成固件烧录和测试日志输出,不需要额外接串口线。测试脚本可以通过VCOM读取板子的自检信息,判断是否合格。
这个方案的好处是减少了产线工装夹具的复杂度,一根JLink线搞定烧录和测试。当然,产线环境需要考虑JLink的耐用性和连接可靠性,建议用带锁扣的JLink插座,避免反复插拔导致接触不良。
7. 一些实操中的个人体会
JLink VCOM这个功能我用了大概两年多,从最初的STM32F103到后来的S32K148,基本上成了我调试时的标配。最大的感受是方便,尤其是桌面空间有限或者临时调试的时候,不用再翻找USB转TTL模块。
踩过的坑主要集中在驱动和固件版本上。早期用的是一个老款JLink V8,固件版本5.x,死活找不到VCOM端口,后来升级到V9才搞定。所以如果你手头的JLink比较老,先确认一下固件版本,别在驱动上浪费时间。
另一个体会是,VCOM的稳定性跟SWD时钟频率有一定关系。我一般会把SWD时钟设在1MHz到2MHz之间,既能保证下载速度,又不会影响VCOM的数据传输。设太高的话,长时间打印偶尔会出现丢字符的情况。
最后分享一个小技巧:如果你在Keil里调试,可以在Debug设置里勾选“Reset and Run”,这样每次下载完程序自动运行,VCOM的打印从第一行就能看到,不用手动按复位键。这个细节虽小,但能省不少事。