发新帖本帖赏金 80.00元(功能说明)我要提问
返回列表
打印
[APM32F4]

如何使用 APM32F402 USB CDC 做日志输出

[复制链接]
485|9
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主

[i=s] 本帖最后由 kai迪皮 于 2025-6-24 18:00 编辑 [/i]<br /> <br />

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 线,简化了线缆管理,也不耽误高带宽的数据传输。

    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 线,双通道齐头并进”。

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() 示例,带有缓冲区机制,供大家参考。请注意,此处示例函数名、宏定义等可能需要和您具体的工程环境保持一致。

 #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[CDC_TX_BUF_SIZE];
    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[s_cdcTxCount++] = (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 不受影响,依旧可以当调试输出使用。
  • 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 下的多线程发送队列,就可以在此基础上继续扩展。

希望这篇文章能帮到还在为“哪里再插一条串口线”而头痛的朋友,也欢迎留言一起探讨更多进阶技巧。

使用特权

评论回复

打赏榜单

21小跑堂 打赏了 80.00 元 2025-06-26
理由:恭喜通过原创审核!期待您更多的原创作品~~

评论
21小跑堂 2025-6-26 14:03 回复TA
借助Geehy 官方 OTGD_Composite_CDC_WINUSB 例程,实现PM32F402 的 printf 重定向到 USB CDC 虚拟串口。节省UART资源,传输更为高效 
沙发
kai迪皮|  楼主 | 2025-6-24 17:52 | 只看该作者
本帖最后由 kai迪皮 于 2025-6-24 17:59 编辑

#申请原创# @21小跑堂

使用特权

评论回复
板凳
永恒的一瞥| | 2025-6-25 16:56 | 只看该作者
极海提供的winUSB驱动还需要付费吗?

使用特权

评论回复
评论
kai迪皮 2025-6-25 19:11 回复TA
不需要啊,winUSB在Windows10以上版本是免驱的,系统自带了。 
地板
VelvetNight| | 2025-6-29 08:42 | 只看该作者
我一直想使用USB做为调试输出。
但对USB协议的理解一直较差,看了楼主的帖子,倒是觉得USB通讯的难度也不大啊

使用特权

评论回复
5
ZenithSeeker| | 2025-6-29 16:08 | 只看该作者
讲的很不错,原创帖不容易,向楼主学习

使用特权

评论回复
6
kai迪皮|  楼主 | 2025-6-29 17:13 | 只看该作者
ZenithSeeker 发表于 2025-6-29 16:08
讲的很不错,原创帖不容易,向楼主学习

感谢支持,只是学习分享

使用特权

评论回复
7
kai迪皮|  楼主 | 2025-6-29 17:14 | 只看该作者
VelvetNight 发表于 2025-6-29 08:42
我一直想使用USB做为调试输出。
但对USB协议的理解一直较差,看了楼主的帖子,倒是觉得USB通讯的难度也不大 ...

其实很多东西我们在极海的官方提供的SDK例程上改改就搞定了

使用特权

评论回复
发新帖 本帖赏金 80.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

35

主题

232

帖子

11

粉丝