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

资讯详情

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

Keil MDK虚拟串口调试串口:方案选型、配置步骤与避坑实战

Keil MDK虚拟串口调试串口:方案选型、配置步骤与避坑实战 搞单片机开发这行最磨人的不是业务逻辑写不出来而是一套“稳定可复现”的调试环境搭不起来。尤其到了 Keil MDK 里调串口程序的时候手里只有一个物理串口既要接传感器抓数据又要看调试日志偶尔还得用上位机发指令做联调这时候才发现“串口资源”根本不够用。我这些年攒下来的经验是把虚拟串口用起来很多问题都能提前在电脑上解决开发板都不用反复动。这篇文章就从 Keil MDK 的实际使用场景出发把虚拟串口调试串口的几种思路、完整的配置步骤和一些只能在实践里碰到的坑一次性梳理清楚。1. 先想清楚虚拟串口到底帮你解决了什么问题1.1 串口调试中的三个典型痛点先说最常见的场景。你写了一个 STM32 程序UART1 接了 WiFi 模组UART2 接了传感器剩下一个 UART3 想用来打印调试日志结果板子上只引出了一路串口。这个时候你面临一个选择日志和数据到底让哪个功能用串口就算你通过跳线把串口切来切去调试效率也低得让人抓狂。第二个痛点是“独占”问题。一个物理串口在 Windows 里对应一个 COM 口而这个 COM 口同一时间只能被一个程序打开。你开着串口助手看数据另一边想再开一个抓包工具监听同一路串口流量系统直接提示“端口被占用”很多低级联调需求就这么被卡住了。第三个痛点是“环境不可复现”。现场设备发回来的串口数据你没法稳定地回放到开发板上复现问题只能靠肉眼盯着屏幕或者反复让现场同事重试。这类问题上线之后非常折腾。这三类问题的共同根源是物理串口资源太死板。虚拟串口的思路就是把它变成“软件资源”在操作系统层面虚拟出串口设备、把物理串口拆成多个分支、甚至把串口数据转发到网络中。理解了这个本质你用起来就不会被具体工具绑住。1.2 虚拟串口的几种形态别混为一谈“虚拟串口”这个词涵盖的东西挺多刚开始容易搞混我建议先分清楚四种形态。第一种是纯软件的虚拟串口对。比如 VSPD 或者 Com0Com 这类工具可以在电脑里创建一对虚拟 COM 口COM7 和 COM8这一对口之间是连通的往 COM7 里写数据COM8 能原样收到。这主要用于在没有任何硬件的情况下模拟单片机串口和上位机之间的双向通信。第二种是物理串口的拆分和桥接。用这类工具的 Split 或 Bridge 功能把一个物理 COM 口比如 CH340 转出来的 COM3的数据复制分发到多个虚拟 COM 口这样日志助手、抓包工具、Modbus 调试助手可以同时打开虚拟口观察同一路数据互不影响。第三种是调试器自带的虚拟串口。ST-Link、J-Link 这类调试器上集成的 VCP 功能设备枚举出来后也是一个 COM 口比如“STMicroelectronics Virtual COM Port (COM5)”。它本质上是通过调试器的 USB 通道把目标板 UART 引脚的数据转发到电脑不需要单独的 USB 转串口线。第四种是 Keil MDK 的 Debug (printf) Viewer 加 SWO/ITM 输出。这个严格来说不是标准串口但它能实现“不占用 UART 的日志输出”程序调用 printf 后数据通过 SWO 引脚进入调试器最终显示在 Keil 的调试窗口里。调试串口程序时这种形态非常解渴。1.3 怎么选一张表看懂四种方案的适用场景方案硬件需求成本最擅长的事情主要限制PC端虚拟串口对VSPD/Com0Com无低无硬件模拟通信、协议联调需要自行组装链路物理串口拆分/桥接USB转串口模块低多工具同时监听同一路数据依赖虚拟串口软件的稳定性调试器VCPST-Link/J-Link带VCP的调试器中一条USB线同时完成下载串口收发受调试器硬件和固件限制Keil Debug(printf) Viewer支持SWO的调试器中不占UART的日志输出只能输出不能接收数据选型逻辑很简单如果你只是自己看日志调试器 VCP 和 MDK 的 Debug (printf) Viewer 最省事如果你要做协议联调、抓包、模拟数据回放那纯软件虚拟串口对和拆分功能就是主力。大多数时候我不会只用一种而是组合使用。2. 环境准备与工具选型2.1 Keil MDK 工程这边要做的事MDK 版本我用得比较久的是 5.3x 系列最近也接触了 6.x整体差别不大。在调试串口程序前有几项工程配置值得先检查一下。编译器版本AC5 和 AC6 在 printf 重定向上的兼容性略有差异我用 AC6 时遇到过fputc声明冲突的情况后来统一在 C 文件里包含stdio.h并把重定向函数的原形写对问题就消失了。MicroLIB如果你的工程没有特殊要求建议在 Options for Target 的 Target 页勾选 Use MicroLIB。这个选项能省 ROM 空间更重要的是用了 MicroLIB 之后 printf 重定向会简单很多不容易跑到半主机模式里卡死。调试器设置如果打算用 SWO 或者调试器 VCPDebug 页的调试器选型、Port 选 SW、最大时钟频率这些都要提前配好。后面我在实操部分会展开讲。这些小配置看着不起眼但真到了现场调试才发现很多“诡异问题”都出在这几分钟的设置上。2.2 虚拟串口工具和串口助手怎么选虚拟串口工具我用过几款这里直接说结论。VSPDVirtual Serial Port Driver功能最全稳定性和 CPU 占用都控制得不错支持创建串口对、拆分物理串口、桥接串口适合长期做开发调试不过它是商业软件需要用官方试用版或者购买授权。Com0Com 是开源免费的创建串口对足够用但界面和驱动的用户体验比较粗糙桥接物理串口这种高级功能不如 VSPD 顺手适合临时应急。串口助手方面XCOM、SSCOM 这类经典工具我用得最多接收区支持 ASCII 和 HEX 切换定时发送、文件发送都有。如果想把串口数据画成波形曲线可以用 VOFA 或者 SerialPlot对分析传感器数据曲线非常有帮助。这里提醒一句不管用什么工具尽量从官网下载网上的第三方整合包经常捆绑全家桶装完系统被塞一堆东西得不偿失。2.3 驱动这一步最容易被卡住虚拟串口能不能用首先取决于物理串口能不能被识别。CH340 的驱动在 Win10/Win11 上大概率是系统自动安装的但我也遇到过系统提示“无法验证设备驱动”设备管理器里出现黄色感叹号的情况。处理方法一般是先删掉异常设备再重新扫描硬件改动或者手动指定驱动目录实在不行用“禁用驱动强制签名”的方式进一次系统把驱动装上后再恢复正常启动。ST-Link 的虚拟串口需要装 ST 官方驱动去 ST 官网搜 STSW-STM32080 或者对应新版驱动包安装后设备管理器里应该会出现 Virtual COM Port。J-Link 的话新版 J-Link 软件安装包会自动带上 VCOM 驱动。这类驱动装好之后有个细节容易被忽略驱动装好了但设备管理器里没出现 COM 口很多时候是 USB 线的问题数据线没接对或者线材供电不足。尤其 ST-Link 这种对供电敏感的调试器换一根粗一点的 USB 线往往就解决了。3. 实操三条路线带你搭起虚拟串口调试链路3.1 路线APC端虚拟串口对桥接物理串口最常用先说最实用的一套组合拳适用于“MCU 连着 USB 转串口但我想在电脑上同时开多个工具观察数据”的场景。我用 VSPD 举例Com0Com 的操作逻辑也类似。第一步安装 VSPD 后打开主界面选择 Add pair创建一串虚拟串口对。COM 号尽量避开现有的物理串口比如物理串口是 COM3虚拟对就选 COM7 和 COM8。创建完成后设备管理器里会多出两个 COM 口这两个口在系统层面就是“通”的。第二步验证虚拟串口对。打开两个串口助手一个选 COM7一个选 COM8波特率都设成 115200在 COM7 这边发一条测试数据COM8 那边能收到同样的内容说明这对虚拟串口工作正常。这一步千万别跳过我见过有人在没验证的情况下直接接 MCU折腾了半天才发现是虚拟串口软件被安全软件拦截了驱动根本没装上。第三步把物理串口接入链路。这里用到 VSPD 的 Bridge 功能把 COM3物理 CH340和 COM7虚拟口桥接起来。设置完成之后MCU 通过物理串口发出来的数据就会经过 COM3 转发到 COM7再由 VSPD 内部转发到 COM8。链路示意如下MCU UART ↓ CH340 (COM3物理串口) ↓ VSPD桥接 COM7 --- COM8 (虚拟串口对) ↓ ↓ 串口助手A 抓包工具/另一路串口助手这样你在 COM8 上接一个 Modbus 调试助手或者串口监听工具在 COM7 上打开常用串口助手两边能同时看到 MCU 发的所有数据而且是同一份。就实际体验而言VSPD 的转发延迟在毫秒级对普通日志查看和报文分析完全够用。3.2 路线B调试器虚拟串口与Keil的Debug输出窗口如果你的开发板板载了 ST-Link或者你用的是支持 VCOM 的 J-Link那调试串口程序的体验可以做得非常舒服。STM32 Nucleo 板、很多国产开发板都集成了 ST-Link VCP插上 USB 线板子的 UART2 会直接映射成电脑里的一个 COM 口。你不需要外接任何 USB 转串口模块Keil 下载程序、打开串口助手收发数据一共就用一根 USB 线。这里要注意板载 ST-Link VCP 虽然叫“虚拟串口”但它是靠目标板 UART 引脚和 ST-Link 的 VCP 引脚连接的所以你仍然要占用一个物理 UART。很多开发板出厂默认把 UART2 接到了 ST-Link VCP 上如果你想改到其他串口要看原理图甚至要改板子跳线。如果不想占用任何 UART就轮到 Keil 的 Debug (printf) Viewer 出场了。具体操作分四步打开 Options for Target - Debug选择 ST-Link Debugger或者 J-Link并在右侧 Settings 里确认 Port 是 SW最大时钟设成芯片支持的合理值。在 Settings 弹出的窗口里切到 Trace 页勾选 Trace EnableCore Clock 填成你工程的系统主频STM32F103 一般填 72单位 MHz。然后找到 ITM Stimulus Ports勾选 Port 0。代码里把 printf 重定向到 ITM 通道。最粗暴的方式是直接在 fputc 里调用 ITM_SendChar(ch)但更好的方式是重定向 printf让所有打印通过这个函数走。编译下载进入调试模式CtrlF5全速运行F5然后打开 View - Serial Windows - Debug (printf) Viewerprintf 的输出就会实时显示在这里。这个方案的原理是程序调用 printf 后数据通过内核的 ITM 模块打包经 SWO 引脚输出到调试器调试器再通过 USB 转发到 Keil 窗口。整个过程完全绕开了 UART 外设和波特率MCU 端的 UART 资源可以全留给业务功能。唯一要确认的是 SWO 引脚有没有接到调试器上在 STM32F103 上一般是 PB3/TRACESWO多数开发板已经处理好了自己打板的话要记得引线。3.3 路线C没有开发板时怎么用虚拟串口并行开发很多时候硬件还在画板返工但上位机程序和单片机协议栈已经可以开始联调了。这时候虚拟串口对的价值就体现出来了。我常用的做法是用 Com0Com 创建一对虚拟串口比如 COM3 和 COM4。上位机程序直接打开 COM3另一边写一个简单的 Python 脚本用 pyserial 打开 COM4模拟单片机按协议发包。Python 脚本里可以生成模拟的传感器数据帧、心跳包、错误码上位机开发人员完全不需要真实硬件就能把协议解析逻辑跑通。与此同时单片机这边在 Keil MDK 里用模拟器Simulator跑逻辑。MDK 的模拟器能模拟 STM32F103 这类芯片的内核运行你可以在没有板子的情况下单步调试代码观察寄存器变化甚至通过 View - Serial Windows 里的 UART 窗口查看模拟串口收发情况。虽然这个窗口的数据不会直接发到虚拟 COM 口但已经足以验证串口收发逻辑的流程是否正确。等硬件一到把 PC 端的模拟数据源替换成真实 MCU上位机改动几乎为零。这种“虚拟串口对模拟设备端 MDK模拟器验证 MCU 端”的并行模式帮我避开了至少两周的等待时间。3.4 可直接抄的测试代码printf重定向与串口接收下面给一份可以直接拿到工程里用的测试代码以 STM32F103 和 HAL 库为例。先初始化调试串口这里用 USART2波特率 115200PA2/PA3 作为 TX/RXUART_HandleTypeDef huart2; void DebugUART_Init(void) { __HAL_RCC_USART2_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitTypeDef gpio {0}; gpio.Pin GPIO_PIN_2 | GPIO_PIN_3; gpio.Mode GPIO_MODE_AF_PP; gpio.Pull GPIO_PULLUP; gpio.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, gpio); huart2.Instance USART2; huart2.Init.BaudRate 115200; huart2.Init.WordLength UART_WORDLENGTH_8B; huart2.Init.StopBits UART_STOPBITS_1; huart2.Init.Parity UART_PARITY_NONE; huart2.Init.Mode UART_MODE_TX_RX; huart2.Init.HwFlowCtl UART_HWCONTROL_NONE; HAL_UART_Init(huart2); }然后在任意 C 文件里重定向 printf#include stdio.h extern UART_HandleTypeDef huart2; int fputc(int ch, FILE *f) { if (HAL_UART_Transmit(huart2, (uint8_t *)ch, 1, HAL_MAX_DELAY) ! HAL_OK) { return EOF; } return ch; }接收可以走中断方式在 main 函数里先启动一次接收比如uint8_t rx_byte; HAL_UART_Receive_IT(huart2, rx_byte, 1); void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART2) { // 处理 rx_byte HAL_UART_Receive_IT(huart2, rx_byte, 1); } }然后主循环里简单打印uint16_t counter 0; while (1) { printf(Hello from MCU, counter %d\r\n, counter); HAL_Delay(1000); }这段代码跑起来之后配合前面配置的虚拟串口链路你在电脑上就能看到 MCU 周期性的日志输出。如果你跑的是 FreeRTOS 这类多任务系统要注意多个任务同时调用 printf 时字符会互相穿插。我的习惯是把日志打印封装成一个带互斥量的接口或者直接用一个队列加独立打印任务这样就不会出现日志乱成一团的问题。4. 实战问题排查与避坑实录4.1 COM口相关找不到、打不开、被占用先说“设备管理器里看不到虚拟串口”。这个大概率是虚拟串口工具的驱动没装成功或者被安全软件拦截了。尤其是 Com0Com 这种需要签名的驱动安装完之后最好重启一次电脑再检查设备管理器里有没有多出 COM 口。VSPD 安装时偶尔会触发 Windows 驱动签名提示选择“仍要安装”即可但前提是你下载的是官方原版驱动。“串口助手打不开 COM 口”是我遇到最多的问题。检查顺序是先用设备管理器确认 COM 口存在然后确认没有其他程序占用它。占用串口的程序有时候很隐蔽比如某个后台运行的上位机程序、Keil 的调试窗口、甚至 Windows 的“设备发现服务”。最快的排查办法是把无关的串口相关软件全部退出再试一次。如果还不行就用串口占用检测工具查一下通常能立刻定位到是哪个进程。还有一个老生常谈的问题COM 口编号超过 COM9 之后很多老版本的串口助手打不开。解决办法是在设备管理器里右击端口属性高级设置里把 COM 端口号改成 COM1-COM8 之间的小号改完再打开工具就正常了。4.2 数据链路问题乱码、丢字节、工具无输出乱码这种事90% 是波特率不匹配剩下 10% 是时钟配置和地线问题。波特率不匹配的表现是能收到数据但全是乱码。排查的时候可以先发一组 0x55、0xAA 这种特征字节用示波器或者逻辑分析仪去看波形脉宽算一下实际波特率然后和设置值对比。很多时候是 STM32 的时钟配置错了外部晶振 8MHz 但代码里配置成 12MHzHAL 库的波特率分频算出来的实际波特率就差了一大截。丢字节的问题多半出现在“物理串口拆分”场景里因为虚拟串口软件要把一份数据复制分发到多个虚拟口内部转发有缓冲如果某个接收端程序处理不过来缓冲满了就会丢。我的处理方法有三个一是尽量用 VSPD 这类成熟的商业工具它的内部缓冲抗压能力强二是接收端程序用高优先级线程或者独立线程及时读走数据三是在 MCU 端打开串口 FIFO 和中断接收避免硬件 FIFO 溢出。总之数据量大的应用不要指望虚拟链路一点不掉包调试前先估算数据速率必要时降低打印频率。工具无输出的情况请先确认串口助手真的“打开”了。XCOM 这类工具打开成功后状态栏会有显示没打开就发数据所有数据都会丢在系统缓冲里。还有一种情况是 MCU 的日志程序执行到 HAL_UART_Transmit 后阻塞等待但你没有打开串口接收端导致发送未完成阻塞在那一行。我就在量产版本里踩过这种坑拔了 USB 转串口程序在打印日志的地方直接卡住。所以正式发布版本里要么把调试打印关掉要么用“非阻塞 超时”的发送接口。4.3 调试器/Keil侧问题SWO无输出、串口烧写失败SWO 没有输出的原因我按出现频率排一下Trace Enable 没勾、ITM Stimulus Port 0 没勾、SWO 引脚没接、内核时钟频率填错。ST-Link 配 STM32F103 的时候Trace 页里 Core Clock 填 72MHz启动调试后它才能正确计算 SWO 的波特率。如果你看到 Debug (printf) Viewer 窗口里什么都没有优先去 Peripherals - Core Peripherals - ITM 窗口看 ITM 使能状态再做针对性修改。“串口烧写失败”这个现象更多是 ISP 下载场景比如用串口工具给 STM32 下程序。常见原因有BOOT0 没拉高MCU 没进入 BootloaderTXD/RXD 接反地没和电脑共地USB 转串口模块供电不足。我自己的排查习惯是先看设备管理器确认 COM 口正常再用串口助手发一个 0x7F看能不能收到 0x79STM32 的 Bootloader 应答能收到就说明物理链路没问题问题出在工具配置或者下载时序上。4.4 我的三个调试习惯第一条每个项目建一个“调试接口速查表”写清楚物理串口用的哪个 UART、哪个 GPIO、波特率多少、虚拟串口工具创建的 COM 号对应关系以及每个 COM 口是给谁用的。这个表放在 README 里换人接手调试时能省大量沟通成本。第二条日志一定要分级加前缀。调试阶段可以怎么方便怎么来但进入联调阶段我严格要求日志按[ERR]、[WARN]、[INFO]、[DBG]四个级别输出并且带模块名。虚拟串口数据量一大你会发现没有前缀根本没法快速定位问题。第三条硬件环境和软件环境解耦。凡是能用 PC 端虚拟串口模拟的绝不动开发板。先把协议层调通再上硬件联调这样出问题时你才知道是硬件问题还是协议代码问题而不是两头一起抓瞎。5. 进阶玩法把虚拟串口用出花来5.1 一拖二Modbus下发报文抓包同时进行工业设备调试经常用 Modbus 协议你一边用 Modbus 调试助手给设备下发命令一边又想记录设备返回的完整报文但一个物理串口不够用。我的做法是把物理串口COM3通过 VSPD 拆分成两个虚拟口COM6 和 COM7Com6 给 Modbus 调试助手做命令交互COM7 接一个串口监听工具开被动接收。这样同一路串口数据能被完整记录下来问题是“发出去的指令对不对”“设备回的报文是不是符合协议”一目了然。这个玩法对大多数总线型协议都适用比如自己私有协议、带 CRC 校验的帧格式只要你能抓到完整的原始字节流定位帧格式错误、CRC 计算错误的问题就非常快。5.2 数据回放复现偶发问题现场设备偶发异常但开发板上复现不出来是嵌入式调试最烦人的事之一。现在我的做法是先把现场串口的原始数据保存成文件再用 Python 脚本按原时序往虚拟串口对的一端发MCU 或者上位机从另一端接收。这样事件发生的字节序、时间间隔、前后文的报文都跟现场一致复现概率大幅提升。下面是一个非常简单的回放脚本配合 pyserial 用import serial import time with serial.Serial(COM3, 115200, timeout1) as s: with open(uart_capture.bin, rb) as f: data f.read() for b in data: s.write(bytes([b])) time.sleep(0.001)原数据如果带有时间戳你可以把 sleep 的时间改成按原始时间戳差值计算回放精度会更高。回放时虚拟串口对起着“数据通道”的作用COM3 连脚本COM4 连被测设备或上位机整个过程不依赖真实串口硬件随时能跑。5.3 COM口转网络远程调现场虚拟串口工具普遍支持把 COM 口映射到 TCP 端口这一点对“人不在现场但要远程看设备”的场景非常有用。在设备端电脑上把物理 COM 口映射成 TCP Server你在办公室通过网络连接这个端口就能像本地串口一样收发数据。VSPD 的 Serial to Network 功能或者 com2tcp 这类命令行工具都能实现。不过要提醒一下网络串口转发有明显的延迟和抖动对时序敏感的数据交互不适合远程调试但用来远程看日志、排查异常状态是完全够用的。我在出差时用这个办法远程盯过几次现场设备的启动日志省了不少差旅费。5.4 最后说点心得体会做串口调试工具永远在变但我慢慢发现真正值钱的不是某款软件而是对“数据流链路”的理解。无论是物理串口还是虚拟串口你要时刻清楚数据从哪个设备产生经过什么路径最终被谁消费掉。链路越清晰出问题时你越不会慌。虚拟串口这套玩法本质上就是把原本一条不能变通的物理链路变成了可以拆分、复制、转发、回放的软件链路而 Keil MDK 提供了很好的落地点让这一切在工程内部就能闭环。希望这篇文章能帮你把串口调试的链路搭顺把更多时间留给真正难啃的业务逻辑。
返回列表