本文基于MCUXpresso SDK的USB协议栈,讨论了多CDC ACM在NXP RT1060平台的实现。文中的工作在MCUXpresso SDK USB协议栈的基础上做了两处改进:
1.由于Notification功能在大多数实际的CDC ACM应用中基本不会用到,本文去掉了Notification使用的EP,在EHCI USB控制器支持8个双向硬件EP的情况下,实现了多达7个CDC ACM通道的理论最大值。
2.屏蔽了复杂的USB协议和代码的诸多细节,使得CDC ACM 的数量仅仅由一个宏来进行控制,这样把开发时切换CDC ACM数目的时间,从数小时到数天缩短为秒级,大幅提升了用户使用体验。改进之后的代码经过在RT1060 EVK上面测试,运行良好。
1.浅析CDC ACM
USB CDC的内容非常广泛,本文只讨论其最常见的一种应用模型——ACM。ACM是英文Abstract Control Model的缩写。这个模型是隶属于PSTN设备,PSTN是Public Switched Telephone Network的英文缩写。PSTN是一种非常古老的电话交换网络。ACM是为模拟PSTN中传统的Modem设备而设计的。
我们知道,Modem的控制接口正是UART。全功能支持Modem的UART控制信号线多达9根。但是在嵌入式的世界,更多使用的是精简版本的UART,即只使用TX、RX、GND这三个信号实现简单的双向通信。所以我们可以把CDC ACM看成是一个USB to UART的Bridge。
随着时代的发展,很多PC,尤其是笔记本电脑,目前已经取消了UART,USB成为了主流的标配。但是,多年来,由于UART简单易用,并且有深厚的群众基础,经年累月的使用习惯,放弃UART会是一件很痛苦的事情。而主流的OS比如Linux、Windows、MacOS对CDC ACM的支持都很好,CDC ACM逐渐的代替了传统的物理串口,延续了传统物理串口在新型电脑上的使用。在PC上,UART的肉体灭亡了,但是灵魂依旧永存,依附于USB,UART会一直存在。
从协议的角度讲CDC ACM,相信会很枯燥。因为USB浩如烟海的复杂协议会看得每个正常的人心烦意乱。所以小编这里并不准备把整个CDC ACM的协议内容在这里完整的讲一遍,那实在是太复杂了。但是如果完全不讲协议,小编也无法表达多CDC ACM实现技术的重点。下面小编会尝试用最简洁的方式来进行表达,会涉及协议,但是会突出重点,省略掉很多不重要的细节。
让我们先看看一个CDC ACM是如何构成的:
这里可以看到,一个CDC ACM实例由Communication接口和Data接口构成。Communication接口包含class专用接口和一个可选的中断型EP。而Data接口包含一对双向的BULK EP用于数据通信,他们就是虚拟UART的TX和RX的物理载体。
在我们SDK的默认实现中,Communication接口下的端点默认是使能的。这样,实现一个CDC ACM通道就需要2个端点,RT1060的USB控制器共有8个双向端点。除去端点0,只有7个端点可以用于CDC ACM,这样RT1060上可实现的CDC ACM实例最大数目为3。
而根据USB规范,Communication接口下的端点是可选的,小编尝试了对此端点进行移除。移除后,每个CDC ACM仅占用一个双向端点,这样,最终成功实现了7个CDC ACM实例。
小编在实践的过程中,心中一直有个疑问,那就是Communication接口的那个Notification用的INT IN EP到底是干嘛用的。借助协议分析仪提供的线索,小编终于在Spec中找到了定义:
说白了是全功能UART的设备状态模拟,用于原始Modem的状态上报和载波控制。而我们大多数的情况下使用CDC ACM,只是为了做简单的双向收发,而并不需要全功能的UART。在移除了Notification EP后,系统会变得更加简单,CDC ACM模拟的全功能UART退化为只有TX、RX的精简版本。
小编在实践过程中的另一个疑问是,对于多CDC的情况,设置波特率之类的命令,是如何区分该命令是给哪个CDC ACM的实例呢?
借助于USB逻辑分析仪,我们可以很快找到答案,每一组命令都是针对某一个Interface的,我们可以通过Interface的编号来识别当前的命令是传给哪一个CDC ACM实例的。
下图更加清晰地展示了实现的诸多细节。需要注意的是,这些命令是从EP0走的,移除Notification EP对此命令并无影响。
说到这里,小编想小结一下:CDC ACM是用于模拟PSTN环境下的Modem控制接口,即全功能UART,我们可以在此基础上进行精简,得到仅具备TX, RX功能的虚拟UART设备。
2.通过一个宏定义来控制CDC的实例数
由于USB协议和代码的复杂性,为了方便用户使用,小编实现了通过一个宏定义来控制CDC的通道数。用户只需要修改一个宏定义的值,就可以控制CDC的通道数量,代码如下:#define USB_DEVICE_CONFIG_CDC_ACM (6U)
具体的实现,有兴趣的读者可以去参考本文提供的代码,这里小编只说一下实现的思路。
为了实现通过一个宏控制CDC的数量,我们需要软件能够提供以下功能:自动分配每个CDC ACM的接口编号
自动分配每个CDC ACM的端点编号
自动生成配置描述符
自动初始化SDK USB协议栈的数据结构
自动匹配SDK USB协议栈的其他各种要求
为了完成所有这些自动化的操作,经粗略统计,在原始SDK的基础上,涉及的更改点大约有66处。
3.测试
1.CDC通道数测试:
小编依次修改配置宏定义,成功的测试了CDC ACM通道数从1到7的全部情况。
下面是7个通道枚举后,在设备管理器中显示的截图:
2.7个CDC通道同时通信测试:为了进行测试,小编同时开了7个串口终端,测试结果如下图所示,7个终端均能独立正常通信。
4.获取源代码
本文的原代码可以从github.com进行下载,无需密码,下载链接为:http://github.com/jiaguonxpcom/usb_ncdc
该代码编译后可以直接下载到RT1060 EVK运行。
5.特别感谢
在本人的实践过程中,最开始只是实现了nCDC用一个宏定义的自动生成部分,并未能成功实现CDC ACM的Communication接口下的EP移除,在RT1060最大仅能自动实现3个CDC ACM实例。
来自NXP LPC/Kinetis SE团队的ZhangYang同学提供的AN给出了详细的操作细节(ZhangYang同学成功的在Kinetis MCU上实现了15个CDC ACM实例,其USB控制器支持16个硬件端点)。另外,来自NXP中国区SE团队的资深USB专家梁平老师提供了宝贵的理论指导意见。在二位同事的热心帮助下,才使本文设计的实验能成功实现,并发布本文章。
在此,小编向二位表示深深的感谢!