
1 前言7 [2 i2 T6 d. ?" V3 P' F4 Q 本文节选自 2017 年度 USB CDC 类培训内容的整理,主要目的是以方便些没有到现场参加培训的碟粉们可以参阅学习。本文力求从理论到实践,尽量给读者一个整体了解 USB CDC 类的窗口。当然,阅读此文,还是需要基本的 USB 知识,这个请读者自行预备。 / ~# l, F% s5 m" G' Z( k9 D' ` 2 USB CDC 类基础理论知识介绍 2.1 USB CDC 类、USB2.0 标准与 PSTN 之间的关系 CDC(Communication Device Class)类是 USB2.0 标准下的一个子类,定义了通信相关设备的抽象集合。它与 USB2.0 标准以及其下的子类的相互关系如下图所示: ![]() 如上图,USB2.0 标准下定义了很多子类,有音频类,CDC 类,HID,打印,大容量存储类,HUB,智能卡等等,这些在urb.org 官网上有具体的定义,这里我们主要讲的是通信类 CDC,CDC 类下面,根据具体的应用场合,又有一些子类,这里我们主要讲的是 PSTN(Public Switched Telephone Network)。从 PSTN 官方标准文档来看,PSTN 子类是一个与电信相关的子类,而这里,我们只是将它作为一个普通的通信设备使用,并没有使用到它的一些电话特性。3 S% U) h- G) L0 a! n8 x j. A 2.2 从一个具体的 CDC 类通信数据说起 ![]() ( Z+ g, \; t" b3 S0 ^' }! b 如上图,USB CDC 类的通信部分主要包含三部分:枚举过程、虚拟串口操作和数据通信。其中虚拟串口操作部分并不一定强制需要,因为若跳过这些虚拟串口的操作,实际上 USB 依然是可以通信的,这也就是为什么上图中,在操作虚拟串口之前会有两条数据通信的数据。之所以会有虚拟串口操作,主要是我们通常使用 PC 作为 Host 端,在 PC 端使用一个串口工具来与其进行通信,PC 端的对应驱动将其虚拟成一个普通串口,这样一来,可以方便 PC 端软件通过操作串口的方式来与其进行通信,但实际上,Host 端与 Device 端物理上是通过 USB 总线来进行通信的,与串口没有关系,这一虚拟化过程,起决定性作用的是对应驱动,包含如何将每一条具体的虚拟串口操作对应到实际上的 USB 操作。这里需要注意地是,Host 端与 Device端的 USB 通信速率并不受所谓的串口波特率影响,它就是标准的 USB2.0 全速(12Mbps)速度,实际速率取决于总线的实际使用率、驱动访问 USB 外设有效速率(两边)以及外部环境对通信本身造成的干扰率等等因素组成。8 C$ k( B$ }% t' @( g5 D. P $ f8 b" O9 ^7 w' U 2.3 CDC 类设备枚举过程 CDC 类设备与其他标准 USB 设备枚举过程的并没有什么特殊的地方。在设备描述符内可以使用 DeviceClass=0x00,SubClass=0x00, Protocol=0x00 表示此类信息在接口描述符内给出;或者也可以使用 0x02,0x00,0x00;来表明该设备为CDC 类设备。或者使用 0xef, 0x02,0x01 表示当前为复合设备。1 L# S$ H3 N5 v( J( A CDC 类设备在枚举过程中最主要的信息存储在配置描述符内: ![]() 1 o- i2 h) U0 P- e- V 如上图所示,CDC 类的配置描述符一般包含两个接口(Interface 0),一个控制接口,另外一个是数据接口(Interface 1), 除此之外,还有一个虚线指向的 IAD(Interface Association Description),这个表示这个是不是可选的,得根据实际情况来确定其是否真实存在。 $ |9 F$ E9 ~. o! n & h3 D5 C% M }- r: ~ 2.3 1 控制接口 控制接口下包含类描述符合一个端点(ie:0x82),这个端点(中断传输模式)为异步通知消息的端点,当设备端需要向 Host 端发送异步消息时,可以通错此端点来发送,但平时主机端都是通过端点 0 来向设备端发送控制消息的,比如那些虚拟串口的操作指令等等。 除这异步通知端点外,控制接口下还包含 CDC 类相关描述符,这其中就包含 Header 描述符,Call Management 描述符,ACM 描述符以及 Union 描述符。这些功能描述符整合在一起用来描述此 USB 设备的一些功能特性,比如 AT 指令支持情况,ACM 模型下的指令集支持情况,以及还有哪些接口与此接口一起对应 Host 端的一个功能(驱动)。 在具体配置描述符内的控制接口内,功能描述符紧跟在接口描述符后,最后才是端点描述符。 控制接口 ![]() 控制接口主要用来做设备管理和电话管理(可选),设备管理涉及到请求(request)和通知(notification),端点 0 一般用做请求,一般用来控制和配置设备的运行状态,而非 0 端点(0x82)一般用作异步事件通知,设备端通过此端点向主机端发送设备内部的一些事件,比如串口状态变化事件,电话状态改变等等。 这里使用到 ACM 模型,后续将讲到这个模型,并且这里指明使用到 V250 版本的 AT 指令,这些指令是与电话相关的,但在我们这里讲的 CDC 通信实际上并不需要使用这些与电话相关的指令,它只是简单通信而已,这里指出 AT 指令也没有关系,只是实际不用它而已。 如上图,bNumEndpoints 表示此接口下包含的端点数,这里为 1 个,即那个异步通知端点。bInterfaceSubClass 为0x02,ACM 通信模型,bInterfaceProtocol 表示 AT 指令集的版本,虽然这里举例为 V2.50,但实际上并没有使用到任何 AT 指令,因此它放: k! j4 X) j8 J Header 功能描述符 ![]() Header 功能描述符表示功能描述符的开始,其他紧跟的内容就是此设备的功能描述符的内容。bcdCDC 表示的是 CDC 的版本。/ F* Q Q9 @8 W$ |- _7 c& P, [) v ACM 功能描述符 ![]() ACM(Abstract Control Model),即抽象控制模型,PSTN 下,除了 ACM 模型还有还有 DLM(Direct Line Mode),TCM(Telephone Control Model)。1 O# \, i* T. u$ Y5 [ PSTN 定义了三种模型 ![]() • DLM 模型下,USB 设备直接将模拟信号转化为数字信号,并放到 USB 上传输,数据接口直接使用 Audio 类传输音频数据,控制接口传输的也都是些比较原始的指令,比如脉宽设置,发送脉宽等等; • ACM 模型则可以很好的支持 AT V250 指令集,数据接口可以使用 Audio 类或 CDC DATA,控制接口传输的也是比较抽象的高层指令,比如设置、获取波特率,设置获取与通信相关的参数等等,而 AT 指令可以通过控制接口或者数据接口,这个在控制接口下的功能描述符 Call Management Descriptor 中指明。4 \; e0 Z2 p2 Y& _% N2 ]# c( B9 c • TCM 是指在物理上存在多个连接,可以将接口 0 和接口 1 分别对应到不同的物理连接上。 此外,不同的通信模型对应的指令集合(控制指令)也是不同的,而上图中 bmCapliblities 为位图,内部 bit0~bit3 分别表示 4类控制指令集在此设备的支持情况。 7 E( C; L/ V- i" u ![]() 如上表,为 ACM 模型下的指令集,但不是说,这些个指令就一定会在 ACM 模型下存在,此 USB 设备是不是支持此某个控制指令,还得看 bmCapliblities 这个参数具体对应位是否使能。% a; O8 i: F4 \ 在实际的 STM32 USB 协议栈中,针对于 CDC 类,使用 LineStateCoding,GetLineCoding,SetControlState 类指令,用来读取,设置串口波特率以及串口的打开与关闭,这个具体的映射实现是通过主机端的驱动来实现;从设备端来看,当设备端收到这些来自主机端操作串口的控制指令时,这些指令具体怎么执行完全取决于设备端,也就说,所有的这些操作,比如设置波特率为 115200,对于设备端来说这个只是个通过 SetLineCoding 指令传过来的一个参数而已,具体怎么处理这个参数,取决于设备端应用程序具体怎么处理这个参数,这个有用户来处理,这个 115200 波特率与 USB 本身的波特率 12Mbps(全速)是没有关系的。% J2 Q$ b) a% N- Y- ] Call Management 功能描述符 . T9 P4 ]' R( y/ R ![]() Call Management 描述的就是电话相关的东西,AT 指令集的支持情况。但在这里,我们并没有用到任何与电话相关的指令,因此 bmCapabilities 下的位图各个位都是为 0:Bit0:是否支持电话相关的指令(AT 指令集);Bit1:电话相关的指令(AT 指令集)是否经过 Comm. Class Interface; bDataInterface 表示如有电话时,电话数据内容对应的接口号。 Union 功能描述符 2 A S" A3 L0 i' ` ![]() Union 描述符就是用来告诉主机端,哪些接口是联合在一起的,对应着一个功能,这个功能需要主机装载对应的驱动来实现,因此,功能与驱动是一对一的关系。这里 bControlInterface 值为 0,则表示接口 0 为控制接口,bSubBoardinateInterface0 值为 1,表示接口 1 为控制接口 0 的下级接口,即数据接口。在 CDC 标准中,控制接口是必须的,而数据接口是可选的,因此,数据接口为控制接口的附属。3 e w2 M' k# |* \6 E3 Y8 T 2.3.2 数据接口 0 V- W ]( O5 s) n" [ ![]() , Z3 U3 |& Z7 n& P; L6 v 数据接口比较简单,就是数据通信的,用到两个端点 IN/OUT 0x81/0x01,为块传输类型。 2.3.3 IAD(Interface Association Descriptor)7 g* n7 \8 r4 v # ]6 i3 m8 ^, A$ h* y. Q% q ![]() $ r- ?9 U z0 b. t USB 刚出来的时候,一开始默认是一个接口对应一个功能,而一个功能对应着主机端的一个驱动,这在当时是 OK 的,但是后来,人们发现,需要多个接口对应一个功能的时候,比如这个 CDC,除了数据接口外还需要控制接口,这在当时是没有这方面的统一标准,于是就出了 Union 来表示多个接口对应一个功能的情况。再后来,USB 标准协会又增加了 IAD。 IAD 与 Union 类似,Union 是旧版本下实现多个接口对应一个功能的功能描述符,而 IAD 是 USB 协会后来针对多个接口对应一个功能的情况而扩展的,旧的主机可能只支持 Union 方式,但 IAD 并不会影响旧版本主机对设备的识别,因为旧版本主机会通过 Union 来识别哪些接口是联合在一起的,对于 IAD 则跳过忽略;而新版主机则可以通过 IAD 来识别,跳过忽略老的Union,因此两者可以完美兼容,互不影响。因而主机端可以精确地装载对应的驱动。% `6 [) W: C' R0 J$ h IAD 只用在设备描述符中只用了 device class code,并且指明了使用 IAD 来识别设备,比如 bDeviceClass: Miscellaneous(0xef), bDeviceSubClass: Common (0x02), bDeviceProtocol: Interface Association Descriptor (0x01)就是一个例子;0x02,0x00,0x00 是另外一个例子。 如上图,bFirstInterface 值为 0,表示第一个接口个接口 0,默认为控制接口;bInterfaceCount 值为 2,标志此功能总共存在2 个接口,那么第二个接口就是接口 1,因为 USB2.0 IAD ECN 补充标准规定,这里提到的接口号必须是连续的,也就是说,接口 0 为第一个控制接口,那么接口 1 则为数据接口。: E+ R3 @! X" c7 l8 S# @0 }7 Q 下面我们来个具体的 IAD 例子:5 W1 ?* s5 j7 M% }8 M1 w7 T 6 U0 T( `1 Y7 y$ ?/ Q ![]() 如上图所示,一般 IAD 存在的情况下,在设备描述符中 DeviceClass 等三个参数不再都为 0x00,图 12 中为 0xef,0x02,0x01,这个表示是复合设备,此时,可以使用 IAD 来定义多个接口联合起来对应一个 USB 驱动。从 IAD 中可以看出,bFunctionClass 参数就定义了此 IAD 表示的设备为 CDC 类设备,ACM 模型。就这样,通过 IAD 描述符,实现了与 Union 功能描述符相同的功能。8 `; N* g) b3 j6 @9 o! @ 9 v: g8 r a- z T% l: p 2.3.4 ACM 模型 f% p% a' q( s/ r5 ^/ t 之前我们已经在控制接口中的功能描述符中已有对 ACM(Abstract Control Mode)模型的简介,也有提到过,在 PSTN 中,除了 ACM 模式,还有 TCM,DLM 模式。这三种模式,不同的模式下包含的控制指令集是不尽相同的,有部分控制指令可能同时存在两个或三个模式下,除了控制指令,还有异步通知消息,这个在三个不同模式下也是不相同的。 ![]() ![]() 由图 14~19 可知,当设备选择了某个模型后,其控制指令集和异步通知消息也就得符合此模式下的对应集合,否则则不符合标准。这里我们主要是使用到 ACM 模式,因此,此 ACM 模式下的有 Host 端发现 Device 端的控制指令和有 Device 端向Host 端发送的异步通知消息都是固定的那么几条指令或消息,但并不是说,只要是 ACM 模式,那么就表示此模式下的所有控制指令和异步通知消息都必须支持。控制指令在设备端的控制接口描述符中的 ACM 功能描述符中的 bCapabilities 字段有按位定义 ACM 模式下的控制指令的支持情况,而异步通知消息,则完全看 device 端的应用情况是否需要,并没有在任何描述符中指出那些消息是否支持。" \% z+ c0 O1 g: M7 T2 { 在 ST 给出的 CDC 例程中,主要是使用到了 SetLineCoding 指令来设置和修改虚拟串口的波特率,使用 GetLineCoding 来获取当前波特率,使用 SetControlLineState 来打开或关闭串口,这种操作是在 Host 端 CDC 驱动来具体映射实现的,至于Device 端收到这些个控制指令该怎么处理,就是另外一回事了,Device 端也可以完全不做任何处理,有 CubeMx 自动生成的CDC 类代码就是这样,对接收到的任何控制指令到没有做任何处理,当然,如果需要的话,则按应用的需要来处理,这个完全取决于用户。 ![]() ' W1 G. ~# z- W2 f( g+ X- Y ![]() 3 CDC 类软件框架介绍2 U1 t: S7 l* S& H( ^2 r 3.1 CDC 软件框架简介& R& X9 n! e$ P- ^ - q o) i0 i! V/ K( L* p 7 Z# m) U3 ~0 f5 j6 _3 l ![]() 如上图所示,黄色 USB Device Core 部分为 USB 设备库文件,属于中间件,它为 USB 协议栈的核心源文件,一般不需要修改: USB Device Core 中,Log/debug 为打印/调试开关;4 k3 T$ b4 i2 N5 s core 为 USB 设备核心; USB request 中定义了枚举过程中各种标准请求的处理;! }/ s: X/ h1 K" p. |, C9 l I/O request 为底层针对 USB 通信接口的封装。' M& c7 l" L- M; b( _ 黄色 USB Device Class 部分为 USB 类文件,也属于中间件,USB 设备库,目前 ST DEMO 中支持的类有 HID, Customer HID,CDC, MSC, DFU, Audio, ST 提供了这些类的源码框架,其他的 Class 或者是复合设备需要自己根据实际需求情况进行扩展或定制。如果用户需求只是需要一个标准类,比如 CDC 通信,那么最好就使用现成的代码,不需要做任何修改就可以实现这个CDC 类通信的功能。* [" v5 u: [1 `8 d m$ r% ^ 蓝色 USB Device HAL Driver 为 HAL 库部分,是对 USB 外设接口的封装,属于底层驱动,不需要修改,它分为 PCD 和 LLDriver,PCD 处于 LL Driver 之上。' k! q k% G% l; t 洋红色 USB Device Configuration 为 USB 配置封装,位于 USB 底层 HAL 层驱动与中间件 USB 协议栈之间,一方面向上层(USB 设备库)提供各种操作调用接口,另一方面,向底层 USB 驱动提供各种回调接口。正是由于它的存在,使得 USB 协议栈(USB 设备库)与底层硬件完全分离,从而使 USB 设备库具有更加兼容所有 STM32 的通用性。USB Device Configuration为开放给用户的源文件,用户可以根据自己的某些特殊需要进行修改,也可以使用默认的源文件,假如没有任何特殊要求的话,我们使用默认即可。, W# a4 t. r) t# f( [ Application 为应用层,USB Device Class 有可能将自己对应该的操作接口封装在一个操作数据结构中,由应用来具体实现这些操作,在系统初始化时,由应用将已经定义好的操作接口注册到对应的 USB 类中,比如 usbd_cdc_if, 就这样,使得应用层的应用代码与属于中间件层的 USB 协议栈分离。同时,USB 协议栈会将一些字符串描述符放到 APP 中,当 USB 初始化时将这些已经定义好的字符串通过指针初始化到 USB 协议栈中,以便后续需要时获取。 E" z9 `. j' ^0 P2 s/ S6 W; g * @. Y6 s) o+ @: q& t 3.2 工程源码文件与软件框架的对应关系3 V0 \- t& R0 F$ ]+ d: | , V x7 |+ b' G$ N: h$ ^ ![]() 3.3 USBD 内核与 USBD_CDC 的关系 3.1 节中,我们已经提到过 ST 官方 Cube 库中提供的官方 USB 协议栈,主要是包含了 USBD 内核与 USB 各种类。USBD 内核一般是固定的,用户一般不需要修改,但 USBD 类,如果用户需要修改或者扩展,比如复合设备或者用户自定义设备,还有就是,ST 目前官方提供的 USB 设备类的 DEMO 程序并没有囊括所有 USB 类,因此,若用户需要实现这些官方提供DEMO 之外的 USB 类时,则用户需要根据自己的需要来定制化自己的 USB 类,那么又该如何开始呢? 我们已经知道,ST 提供的 USB 协议栈中已经有 USBD 内核,且这个内核源文件一般是不需要修改的,那么这里我们需要自定义这么一个 USB 类,那么我们首先得知道,这个我们需要自定义的 USB 类是如何与 USBD 内核打交道的?8 k& v+ W9 `; j B+ \: Z USB 协议栈将所有 USB 类都抽象成一个数据结构:USBD_ClassTypeDef,其定义如下所示:/ l" [6 g) F/ Q& U6 J% D
这个结构体是一个抽象类,定义了一些虚拟函数,比如初始化,反初始化,类请求指令处理函数,端点 0 发送完成,端点 0接收处理,数据发送完成,数据接收处理,SOF 中断处理,同步传输发送未完成,同步传输接收未完成处理等等;用户在实现自己具体的 USB 类的时候需要将它实例化,USBD_ClassTypeDef 结构体是 USBD 内核提供给外部定义一个 USB 设备类的窗口,而 USB 类文件(如 usbd_cdc.c)实际就是实现这个结构体具体实例化的过程。最后将这个具体实例化的对象注册到USBD 内核的同时, USBD 内核与 USBD 类也进行了关联。& J0 y X: B- H% H6 d % I* v, K1 s5 N ![]() 可以这么说,USBD 内核与 USBD 类之间的纽带就是 USBD_ClassType 这个结构体。 下面我们来看看 ST 提供的 CDC DEMO 中具体 CDC 类:
这个就是具体一个 CDC 类实例化的对象,上层应用通过 USBD_RegisterClass 函数,将此对象注册到 usbd 内核 :
它主要在 usbd_cdc.c 源文件中实现它的各个成员函数,当然,usbd_cdc.c 源文件中,除了这些 CDC 类成员函数的具体实现之外,还包含其他一些对上层提供的接口,比如发送 USBD_CDC_TransmitPacket, USBD_CDC_RegisterInterface,上层应用通过调用 USBD_CDC_TransmitPacket 来发送数据,通过 USBD_CDC_RegisterInterface 来注册操作接口,这也是我们接下来将要讲述的内容。2 s" `" _: k. D2 a; @! c / n5 m1 t. g6 x) [8 F8 m 3.4 USBD_CDC 与 USBD_CDC_If 的关系 讲完了 USBD 内核与 USBD_CDC 的关系,接下来我们来讲下 USB_CDC 与上层应用是如何对接的。为了将 USBD_CDC 与上层应用层完全分离出来,类似 USBD 内核与 USBD_CDC 类完全分离一般,USBD_CDC 类对上层同样提供一个抽象的数据操作接口 USBD_CDC_If 结构体:
如上所示,如何处理来自 Host 端发送过来的控制指令和数据,完全是由应用层来决定,具体实现是应用层将此抽象的操作接口具体实例化,并注册到 USBD_CDC 类对象中: ![]() 如上图所示,通过引入 USBD_CDC_If 这么一个数据结构,就实现了 USBD_CDC 类与应用层的完全分离。USBD_CDC_If 的具体实例化对象如下:
源文件 usbd_cdc_if.c 就是实现这些成员函数的过程,除此之外,还包含发送接口。最后应用层通过调用USBD_CDC_RegisterInterface 函数将此操作接口注册到 USBD_CDC 类中 :' c' {# v5 S/ V& E6 u
3.5 应用接口 初始化 :% |' r+ x% O/ T, ^4 K
如上图所示 : 初始化分 4 步: 1> 初始化 USBD 内核 2> 给 USBD 内核注册 USBD_CDC 类 3> 给 USBD_CDC 类注册 USBD_CDC_If 接口 4> 正式启动 USBD! p! ^! l3 |/ p 发送数据:2 Q7 ]+ x1 N0 u. T! _ uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len); 接收回调处理: static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len);) @. z' V% ]0 U- P" G% ?$ R- c0 G 接收控制指令处理 : static int8_t CDC_Control_FS (uint8_t cmd, uint8_t* pbuf, uint16_t length); $ @8 c4 U7 _* Z3 D 4 实践动手部分 本实验的目的是让用户学会自己动手创建一个 STM32CubeMx 工程,一步一步实现通过使用 USB CDC 类与 PC 端进行串口通信。9 q+ _* I8 }& ] 4.1 实验环境及 STM32F072-Discovery 板简介$ g! a0 q4 ^* C 硬件准备: STM32F072 Discovery 板一块( k9 u( g3 }8 f2 E2 w Mini USB 线两根 PC 一台 软件准备: IAR V6.7.0 或者以上版本! b2 Z" O4 I3 V STM32CubeF0 V1.7.0 STM32CubeMX V4.19 SSCOM 串口工具 VCP 虚拟串口驱动 ![]() ![]() 4.2 使用 STM32CubeMx 制作 CDC 工程8 Y! M7 Q2 P% P. x) n ![]() ( h! v- ^4 b1 j3 Z8 m" c, M ![]() 使用内部 48M 的 HSI48 RC 作为时钟源。 ![]() ; R- ~2 _2 e8 l7 o3 {( V 最终生成的代码工程与 USB CDC 类软件框架的对应关系: ![]() 4.3 添加测试代码* Q5 r) k- [! }) y 为了更好的验证通信,我们需要添加点测试代码:) g; L/ x% R9 S6 n8 N
在接收回调中,我们将接收到的数据转给 HandleReceiveData 函数处理:
而在 HandleReceiveData 函数中我们将收到的数据原样返回给 Host 端,这样一来,Host 端的串口工具将发送什么就将收到什么。) G. x5 m5 |3 ~ E6 K$ |
另一方面,我们定义了一全局变量 StartFlag,它用来标志是否循环从 Device 端向 Host 端主动发送数据,其值由外部按键控制。然后在 Main 函数内的 while(1)循环内添加如下测试代码:8 ~" ] f5 C2 y- g
只要 StartFlag 标志为 1,在枚举结束后则不断向 Host 端发送数据。 7 G2 E' T& w: o b8 Z 4.4 验证结果! B% T! @$ h o7 M; c6 J- U 在编译完并将代码烧录进 MCU 后,我们首先验证 PC 端通过串口工具发送数据的情况: ![]() 如上图所示,串口工具发送 63 个字节到 Device 端后,能够接收到从 Device 端返回到的一模一样的数据,这说明发送与接收都是正常的。. O" U) ~- s* O* H8 O; ]0 {8 d ![]() 在按下用户按键后,串口工具能够无限收到来自 Device 端的数据。. t2 A" ]) y! s* K% Z! n - v1 R$ N$ x* H/ |9 ^ ![]() ?, W) ], U/ _3 S3 P U8 O# {7 v 收发同时进行也是正常的。至此,USB CDC 设备端的收发验证均未正常。3 v5 h1 S0 a* z$ n 5 u( S6 P$ I) i& D5 A. k 4.5 注意事项& U- A# v8 ~5 e3 ~ STM32CubeMx 默认生成的工程在发送 64 整数倍数据的时候 PC 端收不到,这个问题已经在之前的文章中有所描述,这里不再重复。2 \3 V% I' l1 l, t; a CDC device 端若无限向 PC 端发送数据,若 PC 端没有及时读走数据,导致 PC 端接收缓存爆满,此时 PC 端回复NACK,此时会导致发送返回 BUSY.+ c( W- b2 l' Y7 l : b- {$ k! @# b" p. ^5 o 6 I# [- u" h) V" z/ h n$ {' c |
最全USB HID开发资料,悉心整理一个月,亲自测试
实战经验 | 选择USBX模块生成USB CDC ACM无PD的项目
STM32 USB HID键盘例程
刘氓兔的杂谈【001】-片上USB 高速PHY
【经验分享】在进行 USB CDC 类开发时,无法发送 64整数倍的数据
【源码】STLINK-V3MINI 高速USB仿真器,成功改刷【高速CMSIS-DAP】
在线直播|无需编写任何代码即可在STM32上实现USB-C Power Delivery
STM32 USB CDC 虚拟多串口
圈圈发布USB图书第二版有感,以及分享一些我学习USB过程...
USB Audio设计与实现