USB CDC从理论到实践

时间:2024-03-31 07:56:45

https://blog.csdn.net/king_jie0210/article/details/76713938

本文摘自ST官网的“USB CDC类入门培训”。整理的内容是我能够看得懂的,认为比较实用的,记录下来,以便以后查阅,同时也把原文档中的笔误给更正了一下。若要看更详细的可以去ST技术文档中查看,链接为: 
http://bbs.21ic.com/forum.php?mod=viewthread&tid=726814&page=1&extra=#pid4225064

1 USB CDC类基础理论知识介绍

1.1 USB CDC类、USB2.0标准与PSTN之间的关系

CDC(Communication Device Class)类是USB2.0标准下的一个子类,定义了通信相关设备的抽象集合。它与USB2.0标准以及其下的子类的相互关系如下图所示: 
USB CDC从理论到实践
如上图,USB2.0标准下定义了很多子类,有音频类,CDC类,HID,打印,大容量存储类,HUB,智能卡等等,这些在usb.org官网上有具体的定义,这里主要介绍通信类CDC。

1.2 从一个具体的CDC类通信数据说起

USB CDC从理论到实践
如上图,USB CDC类的通信部分主要包含三部分:枚举过程、虚拟串口操作和数据通信。其中虚拟串口操作部分并不一定强制需要,因为若跳过这些虚拟串口的操作,实际上USB依然是可以通信的,这也就是为什么上图中,在操作虚拟串口之前会有两条数据通信的数据。之所以会有虚拟串口操作,主要是我们通常使用PC作为Host端,在PC端使用一个串口工具来与其进行通信,PC端的对应驱动将其虚拟成一个普通串口,这样一来,可以方便PC端软件通过操作串口的方式来与其进行通信,但实际上,Host端与Device端物理上是通过USB总线来进行通信的,与串口没有关系,这一虚拟化过程,起决定性作用的是对应驱动,包含如何将每一条具体的虚拟串口操作对应到实际上的USB操作。需要注意的是,Host端与Device端的USB通信速率并不受所谓的串口波特率影响,它就是标准的USB2.0全速(12Mbps)速度,实际速率取决于总线的实际使用率、驱动访问USB外设有效速率(两边)以及外部环境对通信本身造成的干扰率等因素组成。

1.3 CDC类设备枚举过程

CDC类设备与其他标准USB设备枚举过程的并没有什么特殊的地方。在设备描述符内可以使用DeviceClass=0x00, SubClass=0x00, Protocol=0x00 表示此类信息在接口描述符内给出;或者也可以使用0x02,0x00,0x00;来表明该设备为CDC类设备。或者使用0xef, 0x02,0x01表示当前为复合设备。 
CDC类设备在枚举过程中最主要的信息存储在配置描述符内: 
USB CDC从理论到实践 
如上图所示,CDC类的配置描述符一般包含两个接口:一个控制接口(Interface 0),另外一个是数据接口(Interface 1), 除此之外,还有一个虚线指向的IAD(Interface Association Description),表示这个是可选的,得根据实际情况来确定其是否真实存在。 
在ST给出的CDC例程中,主要是使用SetLineCoding指令来设置和修改虚拟串口的波特率,使用GetLineCoding来获取当前波特率,使用SetControlLineState来打开或关闭串口,这种操作是在Host端CDC驱动来具体映射实现的, Device端收到控制指令可以处理也可以不处理,用CubeMx自动生成的CDC类代码对接收到的任何控制指令到没有做任何处理,如果需要的话,用户可按应用的需要来处理。 
USB CDC从理论到实践

2 CDC类软件框架介绍

2.1 CDC软件框架简介

USB CDC从理论到实践
如上图所示,黄色USB Device Core部分为USB设备库文件,属于中间件,它为USB协议栈的核心源文件,一般不需要修改:

USB Device Core中,Log/debug为打印/调试开关; 
core为USB设备核心; 
USB request中定义了枚举过程中各种标准请求的处理; 
I/O request为底层针对USB通信接口的封装。

黄色USB Device Class部分为USB类文件,也属于中间件,USB设备库,目前ST DEMO中支持的类有HID, Customer HID, CDC, MSC, DFU, Audio, ST提供了这些类的源码框架,其他的Class或者是复合设备需要自己根据实际需求情况进行扩展或定制。如果用户需求只是需要一个标准类,比如CDC通信,那么最好就使用现成的代码,不需要做任何修改就可以实现这个CDC类通信的功能。 
蓝色USB Device HAL Driver为HAL库部分,是对USB外设接口的封装,属于底层驱动,不需要修改,它分为PCD和LL Driver,PCD处于LL Driver之上。 
洋红色USB Device Configuration为USB配置封装,位于USB底层HAL层驱动与中间件USB协议栈之间,一方面向上层(USB设备库)提供各种操作调用接口,另一方面,向底层USB驱动提供各种回调接口。正是由于它的存在,使得USB协议栈(USB设备库)与底层硬件完全分离,从而使USB设备库具有更加兼容所有STM32的通用性。USB Device Configuration为开放给用户的源文件,用户可以根据自己的某些特殊需要进行修改,也可以使用默认的源文件,假如没有任何特殊要求的话,我们使用默认即可。 
Application为应用层,USB Device Class有可能将自己对应该的操作接口封装在一个操作数据结构中,由应用来具体实现这些操作,在系统初始化时,由应用将已经定义好的操作接口注册到对应的USB类中,比如usbd_cdc_if, 就这样,使得应用层的应用代码与属于中间件层的USB协议栈分离。同时,USB协议栈会将一些字符串描述符放到APP中,当USB初始化时将这些已经定义好的字符串通过指针初始化到USB协议栈中,以便后续需要时获取。

2.2 工程源码文件与软件框架的对应关系

USB CDC从理论到实践

2.3 USBD内核与USBD_CDC的关系

2.1节中,提到过ST官方Cube库中提供的官方USB协议栈,主要是包含了USBD内核与USB各种类。USBD内核一般是固定的,用户一般不需要修改,但USBD类,如果用户需要修改或者扩展,比如复合设备或者用户自定义设备,还有就是,ST目前官方提供的USB设备类的DEMO程序并没有囊括所有USB类,因此,若用户需要实现这些官方提供DEMO之外的USB类时,则用户需要根据自己的需要来定制化自己的USB类。 
ST提供的USB协议栈中已经有USBD内核,且这个内核源文件一般是不需要修改的,我们需要自定义这么一个USB类,我们首先得知道要自定义的USB类是如何与USBD内核打交道的。 
USB协议栈将所有USB类都抽象成一个数据结构:USBD_ClassTypeDef,其定义如下所示: 
USB CDC从理论到实践
这个结构体是一个抽象类,定义了一些虚拟函数,比如初始化,反初始化,类请求指令处理函数,端点0发送完成,端点0接收处理,数据发送完成,数据接收处理,SOF中断处理,同步传输发送未完成,同步传输接收未完成处理等等;用户在实现自己具体的USB类的时候需要将它实例化,USBD_ClassTypeDef结构体是USBD内核提供给外部定义一个USB设备类的窗口,而USB类文件(如usbd_cdc.c)实际就是实现这个结构体具体实例化的过程。最后将这个具体实例化的对象注册到USBD内核的同时, USBD内核与USBD类也进行了关联。 
USB CDC从理论到实践 
可以这么说,USBD内核与USBD类之间的纽带就是USBD_ClassType这个结构体。 
下面我们来看看ST提供的CDC DEMO中具体CDC类: 
USB CDC从理论到实践
这个就是具体一个CDC类实例化的对象,上层应用通过USBD_RegisterClass函数,将此对象注册到usbd内核 : 
USB CDC从理论到实践
它主要在usbd_cdc.c源文件中实现它的各个成员函数,当然,usbd_cdc.c源文件中,除了这些CDC类成员函数的具体实现之外,还包含其他一些对上层提供的接口,比如发送USBD_CDC_TransmitPacket, USBD_CDC_RegisterInterface,上层应用通过调用USBD_CDC_TransmitPacket来发送数据,通过USBD_CDC_RegisterInterface来注册操作接口,这也是我们接下来将要讲述的内容。

2.4 USBD_CDC与USBD_CDC_If的关系

讲完了USBD内核与USBD_CDC的关系,接下来讲USBD_CDC与上层应用是如何对接的。为了将USBD_CDC与上层应用层完全分离出来,类似USBD内核与USBD_CDC类完全分离一般,USBD_CDC类对上层同样提供一个抽象的数据操作接口USBD_CDC_If结构体: 
USB CDC从理论到实践
如上所示,如何处理来自Host端发送过来的控制指令和数据,完全是由应用层来决定,具体实现是应用层将此抽象的操作接口具体实例化,并注册到USBD_CDC类对象中: 
USB CDC从理论到实践
如上图所示,通过引入USBD_CDC_If这么一个数据结构,就实现了USBD_CDC类与应用层的完全分离。USBD_CDC_If的具体实例化对象如下: 
USB CDC从理论到实践
源文件usbd_cdc_if.c就是实现这些成员函数的过程,除此之外,还包含发送接口。最后应用层通过调用USBD_CDC_RegisterInterface函数将此操作接口注册到USBD_CDC类中 : 
USB CDC从理论到实践

2.5 应用接口

初始化 : 
USB CDC从理论到实践
如上图所示 : 
初始化分4步: 
1> 初始化USBD内核 
2> 给USBD内核注册USBD_CDC类 
3> 给USBD_CDC类注册USBD_CDC_If接口 
4> 正式启动USBD

  • 发送数据: 
    uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len);
  • 接收回调处理: 
    static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len);
  • 接收控制指令处理 : 
    static int8_t CDC_Control_FS (uint8_t cmd, uint8_t* pbuf, uint16_t length);

3 实践动手部分

3.1 实验环境及STM32F072-Discovery板简介

硬件准备:

  • STM32F072 Discovery板一块
  • Mini USB线两根
  • PC一台

软件准备:

  • IAR V6.7.0 或者以上版本
  • STM32CubeF0 V1.7.0
  • STM32CubeMX V4.19
  • SSCOM串口工具
  • VCP虚拟串口驱动 
    USB CDC从理论到实践
    USB CDC从理论到实践

3.2 使用STM32CubeMx制作CDC工程

USB CDC从理论到实践 
USB CDC从理论到实践

使用内部48M的HSI48 RC作为时钟源 
USB CDC从理论到实践 
USB CDC从理论到实践
USB CDC从理论到实践
最终生成的代码工程与USB CDC类软件框架的对应关系: 
USB CDC从理论到实践

3.3 添加测试代码

为了更好的验证通信,我们需要添加点测试代码: 
USB CDC从理论到实践
在接收回调中,我们将接收到的数据转给HandleReceiveData函数处理: 
USB CDC从理论到实践
而在HandleReceiveData函数中我们将收到的数据原样返回给Host端,这样一来,Host端的串口工具将发送什么就将收到什么。 
USB CDC从理论到实践
另一方面,我们定义了一全局变量StartFlag,它用来标志是否循环从Device端向Host端主动发送数据,其值由外部按键控制。然后在Main函数内的while(1)循环内添加如下测试代码: 
USB CDC从理论到实践
只要StartFlag标志为1,在枚举结束后则不断向Host端发送数据。

3.4 验证结果

在编译完并将代码烧录进MCU后,我们首先验证PC端通过串口工具发送数据的情况: 
USB CDC从理论到实践 
如上图所示,串口工具发送63个字节到Device端后,能够接收到从Device端返回到的一模一样的数据,这说明发送与接收都是正常的。 
USB CDC从理论到实践

在按下用户按键后,串口工具能够无限收到来自Device端的数据。 
USB CDC从理论到实践
收发同时进行也是正常的。至此,USB CDC设备端的收发验证均正常。

3.5 注意事项