如何使用 APM32F402 USB CDC 做日志输出
本帖最后由 kai迪皮 于 2025-6-24 18:00 编辑## 1 背景
为啥突然想用 USB CDC 做日志输出?很多开发者在做 MCU 项目时,往往会用 UART 串口来打印调试信息。可是一旦项目复杂了,UART 线缆多、资源占用大、带宽还有限,就会开始琢磨:“能不能利用 USB 的特性,让它既传输数据又打印日志,一根线搞定?” 正好,APM32F402 这颗芯片具备 USB OTG 功能,我们可以让设备模拟成一个“复合设备(Composite Device)”,同时跑 WinUSB(高速数据传输)和 CDC(虚拟串口)这两个接口。这样只要插上一条 USB 数据线,就能既供应电源、又上传下载数据、还输出调试日志,堪称“一线三用”。
举个具体例子:
1. 比如一款 Data Logger(数据记录器)要定期或实时采集某种传感器数据,比如压力、流量、震动幅度等;
2. 现场维护人员可以用笔记本通过 USB 与设备建立 WinUSB 通道,一次性下载大量的历史数据;
3. 另一方面,调试工程师能在终端软件里打开 CDC 虚拟串口,看到 MCU 的实时运行状态或告警信息,刷刷往外吐;
4. 整个过程只需要一根 USB 线,简化了线缆管理,也不耽误高带宽的数据传输。
!(data/attachment/forum/202506/24/174654cqe6knnbsxnyk6qy.png "image2.png")
## 2 为什么选择 USB CDC?它比 UART 好在哪儿?
说到日志输出,传统串口 UART 一般满足日常需求,不过也有不可忽视的缺点:
* 带宽不足:常见波特率 115200bps,一旦数据量大了,容易拖慢速度;若是使用高波特率又容易受干扰,对线材的性能又要求。
* 需要额外转接:如果开发板只提供 TTL 接口,还得准备 USB 转 TTL 线;
* 接线繁琐:调试线、电源线、串口线挤在一起,非常容易打结。
而 USB CDC(虚拟串口)能直接呈现给操作系统一个 COM 口,速率理论上可比常规 UART 更高,加上只要一根 USB 数据线就能兼顾供电、调试和传输。尤其是在需要同时跑其他 USB 功能(如 WinUSB、U 盘模拟、HID 等)时,CDC 可以与它们共存,大大提升灵活度。想象一下,我们在 PC 端通过 USB 与设备高速交换数据的同时,也能在一个虚拟 COM 终端看到调试日志,何乐而不为?
## 3 什么是 USB 复合设备?
“USB 复合设备”指的是在同一个物理 USB 接口上,开启多个逻辑接口(Interface)。典型组合像下面这样:
* CDC 接口(虚拟串口)——输出调试日志;
* WinUSB 接口(自定义协议)——跟上位机程序进行高速数据交互;
* 其他 HID、MSC(U 盘)、Audio 之类也可以加进去,只要资源够用。
对于 APM32F402 而言,Geehy 官方提供了一个名为 OTGD\_Composite\_CDC\_WINUSB 的例程。它已经帮我们写好了复合设备的描述符和端点分配,我们只需要在对应的工程里稍微改动或者增加一点代码,就能把 printf 输出定向到 CDC 虚拟串口上。不影响 WinUSB 通道的数据传输,也不会阻塞其他 USB 功能,完美实现“一条 USB 线,双通道齐头并进”。
!(data/attachment/forum/202506/24/174709ejunjt6ou6h8guie.png "image.png")
## 4 搞定 printf 重定向
### 4.1 USB CDC 初始化
先确认当前工程里已经包含了 USB 库的相关文件,例如 usb\_core、usb\_init、usb\_cdc 等驱动。如果是基于官方 OTGD\_Composite\_CDC\_WINUSB 工程,那么在 `main()` 或者您自定义的初始化函数里,需要调用 `USB_DeviceInit()`,让系统启动时就注册并枚举 CDC 和 WinUSB 接口。
### 4.2 找到“重定向”大本营
在 MDK 或者其他编译器环境下,通常实现了针对 printf 的底层函数,比如 `fputc(int ch, FILE *f)` 或者 `_write()`。咱们只要在这些函数中,把原本“写到 UART 的”那部分改成“调用 USB CDC 的发送函数”。考虑到如果逐字发送,会频繁调用发送函数,会占用资源,我们可以在这里用一个静态缓冲区,凑够一定长度再将数据一次性打包发给 USB。
### 4.3 核心代码示例
下面给出一个简单的 `fputc()` 示例,带有缓冲区机制,供大家参考。请注意,此处示例函数名、宏定义等可能需要和您具体的工程环境保持一致。
```c
#define CDC_TX_BUF_SIZE(128)
/*!
* Redirect C Library function printf to serial port.
* After Redirection, you can use printf function.
*
* @param ch:The characters that need to be send.
*
* @param *f:pointer to a FILE that can recording all information
* needed to control a stream
*
* @retval The characters that need to be send.
*
* @note
*/
int fputc(int ch, FILE* f)
{
/* send a byte of data to the serial port */
// USART_TxData(DEBUG_USART, (uint8_t)ch);
// /* wait for the data to be send*/
// while (USART_ReadStatusFlag(DEBUG_USART, USART_FLAG_TXBE) == RESET);
/* static 关键字确保在函数内部维持状态 */
static uint8_t s_cdcTxBuf;
static uint16_t s_cdcTxCount = 0;
/* 如果在输出时需要将 '\n' 转化为 Windows 习惯的 "\r\n",可按需处理 */
if (ch == '\n')
{
/* 注意下一步操作前先检查避免缓冲区越界 */
if (s_cdcTxCount >= CDC_TX_BUF_SIZE)
{
USBD_FS_CDC_ItfSend(s_cdcTxBuf, s_cdcTxCount);
s_cdcTxCount = 0;
}
}
/* 将当前字符装入缓冲区 */
s_cdcTxBuf = (uint8_t)ch;
/* 如果缓冲区已满,或者本次输出是换行字符,就立刻发送 */
if (s_cdcTxCount >= CDC_TX_BUF_SIZE || ch == '\n')
{
USBD_FS_CDC_ItfSend(s_cdcTxBuf, s_cdcTxCount);
s_cdcTxCount = 0;
}
return (ch);
}
```
#### 相关说明
1. s\_cdcTxBuf\[ \]:用来累积数据,避免频繁调用发送函数;
2. s\_cdcTxCount:记录当前已经存进缓冲区的字节数;
3. 若遇到 `'\n'` 或者缓冲区满了,我们就调用 `USBD_FS_CDC_ItfSend(...)` 一次性输出;
### 4.4 编译 & 测试
写完重定向函数后,编译并下载到 APM32F402 板子上,插上 USB 线,看看电脑里面设备管理器是否出现了“虚拟 COM 端口”和一个 WinUSB 设备。如果都枚举成功,用任意串口调试软件打开该虚拟 COM 口,就能实时看见 printf 的日志输出了。
* 波特率填多少通常没太大影响,因为 USB CDC 其实是“假装”有一个波特率;
* 如果你要传输的数据量很大,就可以在 WinUSB 接口那边跑高速协议,而这边 CDC 不受影响,依旧可以当调试输出使用。
* !(data/attachment/forum/202506/24/174815rj5v2zteavcv9hjj.gif "PixPin_2025-06-24_14-56-50.gif")
*
## 5 总结
通过在 Geehy 官方 OTGD\_Composite\_CDC\_WINUSB 例程中做少量改动,我们就能成功把 APM32F402 的 printf 重定向到 USB CDC 虚拟串口上,实现以下优点:
1. 只用一根 USB 数据线,开发、供电、调试都搞定;
2. 节省硬件 UART 资源,留给需要硬件流控或者其他串口外设;
3. 还能让 WinUSB 通道继续高效传输大数据,CDC 专心搞日志,两条通路互不打架。
如果你打算让这个 Data Logger 或其他设备在工业、医疗或消费领域使用,那么通过 USB CDC + WinUSB 的融合方式,不仅能减少线缆成本,还能统一管理数据和日志。至于更高阶玩法,比如 DMA、RTOS 下的多线程发送队列,就可以在此基础上继续扩展。
希望这篇文章能帮到还在为“哪里再插一条串口线”而头痛的朋友,也欢迎留言一起探讨更多进阶技巧。
本帖最后由 kai迪皮 于 2025-6-24 17:59 编辑
#申请原创# @21小跑堂 极海提供的winUSB驱动还需要付费吗?
我一直想使用USB做为调试输出。
但对USB协议的理解一直较差,看了楼主的帖子,倒是觉得USB通讯的难度也不大啊 讲的很不错,原创帖不容易,向楼主学习 ZenithSeeker 发表于 2025-6-29 16:08
讲的很不错,原创帖不容易,向楼主学习
感谢支持,只是学习分享{:titter:} VelvetNight 发表于 2025-6-29 08:42
我一直想使用USB做为调试输出。
但对USB协议的理解一直较差,看了楼主的帖子,倒是觉得USB通讯的难度也不大 ...
其实很多东西我们在极海的官方提供的SDK例程上改改就搞定了{:victory:} 我也有APM32F103的小板。回头我也试试USB去 支持原创
页:
[1]