GD32F103 USBD CDC中断驱动改造实战

张开发
2026/4/20 9:40:24 15 分钟阅读

分享文章

GD32F103 USBD CDC中断驱动改造实战
1. 为什么需要中断驱动改造用GD32F103做USB虚拟串口开发时官方提供的CDC例程采用的是轮询方式。这种模式在main函数里不断检查USB状态会严重占用CPU资源。我做过一个实际项目主循环里需要处理传感器数据采集、无线通信和界面刷新结果发现USB通信经常丢包——因为轮询方式把大部分时间都耗在等待USB数据上了。中断驱动的优势很明显当USB有数据到达时硬件自动触发中断CPU可以立即响应处理处理完又能马上回到主循环。实测下来改造后系统响应速度提升3倍以上主循环里的其他任务再也不会被USB通信阻塞。就像快递员送货轮询是你每隔5分钟下楼查看一次而中断是快递到了直接按门铃通知你。2. 工程准备与关键点定位我用的是GD32F10x_Firmware_Library_V2.2.4库模板工程选择cdc_acm。打开工程后重点关注三个文件cdc_acm_core.c- 包含核心数据处理逻辑usbd_int.c- 中断服务函数所在文件usbd_core.c- USB协议栈底层实现官方例程的main函数结构有个明显问题它会死等USB枚举完成。在实际项目中我们可能需要同时初始化其他外设。我的做法是直接删掉这个等待循环改为在中断回调里判断枚举状态。关键函数cdc_acm_data_out需要重点改造它原本只是在收到数据时设置标志位。我们要让它直接触发中断处理流程类似STM32的HAL库处理方式。这里有个坑要注意GD32的中断优先级配置和STM32不同NVIC_IRQChannelPreemptionPriority要设为比系统定时器更高的优先级。3. 中断服务函数改造实战先看数据接收部分的改造。原版的cdc_acm_data_out函数只是简单标记数据到达static void cdc_acm_data_out (usb_dev *udev, uint8_t ep_num) { usb_cdc_handler *cdc (usb_cdc_handler *)udev-class_data[CDC_COM_INTERFACE]; cdc-packet_receive 1U; cdc-receive_length udev-transc_out[ep_num].xfer_count; }我把它改造成直接调用中断回调static void cdc_acm_data_out (usb_dev *udev, uint8_t ep_num) { usb_cdc_handler *cdc (usb_cdc_handler *)udev-class_data[CDC_COM_INTERFACE]; cdc-packet_receive 1U; cdc-receive_length udev-transc_out[ep_num].xfer_count; usbd_cdc_data_out_irq_callback(udev, ep_num); // 新增中断回调 }回调函数里要特别注意缓冲区管理。我设计了一个环形缓冲区结构typedef struct { uint8_t *buf; uint16_t size; volatile uint16_t pos; } usbd_cdc_recv_t; usbd_cdc_recv_t usbd_cdc_recv { .buf malloc(256), .size 256, .pos 0 };4. 数据发送的异步处理发送部分同样需要改造。原版是在main循环里轮询发送我们改为由应用层主动触发void usbd_cdc_send_data(usb_dev *udev, uint8_t* data, uint16_t len) { usb_cdc_handler *cdc (usb_cdc_handler *)udev-class_data[CDC_COM_INTERFACE]; if(len USB_CDC_TX_LEN) len USB_CDC_TX_LEN; memcpy(cdc-data, data, len); usbd_ep_send(udev, CDC_IN_EP, cdc-data, len); }这里有个实际项目中的经验当连续发送大量数据时建议添加发送完成回调。我在工程里增加了usbd_cdc_tx_complete_callback在cdc_acm_data_in函数中触发它这样上层应用可以知道何时可以发送下一批数据。5. 稳定性优化与实测改造完成后我做了三项关键测试压力测试连续发送10MB数据校验错误率延迟测试测量从数据到达中断到应用层收到的时间并发测试同时进行USB通信和其他外设操作测试发现两个需要优化的点当USB总线繁忙时偶尔会出现数据包错位。解决方法是在每次接收前重置端点缓冲区。高负载时中断处理时间过长。通过将数据处理移到主循环中断只负责标记和唤醒任务解决了这个问题。最终的中断处理函数优化为void usbd_cdc_data_out_irq_callback(usb_dev *udev, uint8_t ep_num) { usb_cdc_handler *cdc (usb_cdc_handler *)udev-class_data[CDC_COM_INTERFACE]; usbd_ep_recev(udev, CDC_OUT_EP, (uint8_t*)(cdc-data), USB_CDC_RX_LEN); if (ep_num CDC_OUT_EP) { // 仅做标记实际处理在主循环 usbd_cdc_recv.new_data_flag 1; usbd_cdc_recv.new_data_len cdc-receive_length; } }6. 实际项目集成建议在我的一个工业控制器项目中这套框架稳定运行了两年多。分享几个实用技巧如果使用RTOS建议创建一个专门的USB处理任务用信号量同步中断和任务调试时可以用GPIO引脚输出高低电平用逻辑分析仪测量中断响应时间遇到枚举失败时检查VBUS供电是否稳定这是我踩过最深的坑对于需要更高性能的场景可以考虑使用DMA配合双缓冲机制。不过GD32F103的USB外设不支持DMA这个方案只适用于更高端的型号。

更多文章