最近有小伙伴反应USB中的 usb_examples/usb_device_cdc_vcom 例程(USB虚拟串口VCOM)中的一些使用问题,今天集中来说说使用example的必知要点~
实验平台和软件版本说明
本篇文章的实验平台为:SDK_2_5_0_LPC54605J512oardslpcxpresso54608usb_examplesusb_device_cdc_vcom 但实际上本篇文章适用于NXP大部分的硬件平台,因为usb_device_cdc_vcom(以下简称vcom)这部分例程代码和硬件关系并不大,属于USB Stack之上的应用部分,另外这部分代码在SDK的各个版本上变化也不是很大,所以如果您使用的新版本的SDK,本篇文章也同样适用。
目标读者
关于vcom的一些基础环境搭建/编译下载等基础问题这篇文章不再赘述,具体可以参考example文件夹下的readme.pdf. 这里假设读者:
有一定的USB基础知识
已经成功跑过这个vcom例程,大概浏览过源代码,并且准备使用vcom的代码作为参考开发自己的项目产品
重要概念解释
首先一个最基本的概念:USB所有传输都是主机发起的,从机只是被动的响应主机发来的请求
USB OUT 传输: 即 USB Host(如PC)向USB Device(如MCU)下发数据, 对应vcom 例程中事件kUSB_DeviceCdcEventRecvResponse. 这个很好理解:对于vcom例子,就是虚拟串口上有数据发到了MCU(比如PC端有一个上位机软件,打开了虚拟串口,并且向虚拟串口写入数据)。每当MCU收到数据,都会进入kUSB_DeviceCdcEventRecvResponse.在kUSB_DeviceCdcEventRecvResponse事件中,需要MCU这边尽快的调用USB_DeviceCdcAcmRecv API将USB中的数据读取出来,然后USB Stack会和USB硬件一起准备好下次USB OUT事件接收工作。(类似串口的DMA接收机制)
从下图的CallStack可以看出,kUSB_DeviceCdcEventRecvResponse本质就是BulkOut中断回调上来的:
USB IN传输:即USB Host(PC) 向USB Device(如MCU)索要数据, 比OUT传输稍微难理解一些:在vcom这个例程中,由于vcom属于buck传输。每当从机响应上一个IN token之后,就会进入kUSB_DeviceCdcEventSendResponse 事件,从下图的CallStack也可以看到, kUSB_DeviceCdcEventSendResponse事件本质就是 USB Buck In 中断回调上来的:
所以每当进入到kUSB_DeviceCdcEventSendResponse的时候,都说明USB IN传输已经完成(或取消)。那么从机如何向主机发送数据呢?调用USB_DeviceCdcAcmSend 这个API。但是调用这个API你需要注意,每次调用这个API,你都需要等待发送完成事件(kUSB_DeviceCdcEventSendResponse) 或超时(第一次除外)。在任何时候,你都不能在代码里连续调用USB_DeviceCdcAcmSend 多次。这个机制类似于串口DMA发送,即:每次调用串口DMA发送的时候,你都要确保上一次串口DMA发送已经完成。 总结如下:
实际上,vcom例程实现的东西很简单,就是自发自收(echo),把虚拟串口接到的数据再原封不动的发回而已。所涉及的数据传输过程中的事件也只有:kUSB_DeviceCdcEventSendResponse 和kUSB_DeviceCdcEventRecvResponse. 其他的USB Class回调事件实际上多半是有关一些配置,控制 (波特率,打开,关闭虚拟串口)等,这部分内容暂不展开,需自学。
usb_device_cdc_vcom的问题 这个例程不太方便的地方就是代码里把发送和接收是耦合在一起的,对于新手且对USB不熟悉的用户,都不知道怎么解耦。实际应用中,串口的发送和接收应该是独立的,没有太大关系的,但是很可惜,这个example设计的时候硬生生的把发送和接收”粘”在一起,让新手不太容易剥离开。
这里给出一个简单的改造方案,把VCOM的发送和接收拆开:
1. 首先对于USB_IN: 注释掉之前的 USB_DeviceCDCAcmRecv部分,USB IN 传输和USB OUT之间没有必然关系。另外在kUSB_DeviceCdcEventSendResponse中,定义一个标志is_cdc_in_compelte (类比于串口的发送完成中断,或者DMA发送完成中断):
2. 对于USB OUT: 将之前的代码替换为下图,在收到Host下发的数据后,第一时间调用USB_DeviceCDCAcmRecv,把数据接下来,然后通过一个消息队列(你可以用你自己实现的一个消息队列) 发送给应用层。不要在DeviceCdcEventSendResponse中做过多的应用层处理:
3. 其他的有关原demo中的一些变量,比如s_recvSize, s_sendSize 之类的,删掉处理。在while(1)主循环中,处理USB中断回调发出来的消息队列:
通过解耦VCOM的Tx(发送)与Rx(接收),代码不仅变得清晰简洁,还提升了模块间的独立性和可维护性。这种设计促进了代码的复用性和可扩展性,为未来的功能升级或定制开发奠定了坚实基础。无论是对于初次接触的开发者还是资深工程师,都能从中受益,享受更流畅的编程体验。希望本期分享对大家有所帮助!
恩智浦致力于打造安全的连接和基础设施解决方案,为智慧生活保驾护航。
全部0条评论
快来发表一下你的评论吧 !