你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】USB CDC 类入门培训

[复制链接]
STMCU小助手 发布时间:2022-3-3 22:19
1 前言7 [2 i2 T6 d. ?" V3 P' F4 Q
本文节选自 2017 年度 USB CDC 类培训内容的整理,主要目的是以方便些没有到现场参加培训的碟粉们可以参阅学习。本文力求从理论到实践,尽量给读者一个整体了解 USB CDC 类的窗口。当然,阅读此文,还是需要基本的 USB 知识,这个请读者自行预备。
! h2 ]) o) D) y
; c4 H# P& L7 m5 C, _4 b% U0 q4 q# x/ ~# l, F% s5 m" G' Z( k9 D' `
2 USB CDC 类基础理论知识介绍
' I: X' d2 ~: [+ D  ^  C. r. D# x2.1 USB CDC 类、USB2.0 标准与 PSTN 之间的关系

- H$ K) w" Y2 x( p" K" LCDC(Communication Device Class)类是 USB2.0 标准下的一个子类,定义了通信相关设备的抽象集合。它与 USB2.0 标准以及其下的子类的相互关系如下图所示:
4 J3 J  k1 Y2 o5 A, }
: b8 C" K9 D9 X: z( g4 Y P(8JX8POO`@7AT%H[8RFPF7.png ) ~+ X$ S) Y; b4 _* S4 p

% U# K# L, J4 J% h. c" j  x" p' Q如上图,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

# X0 r+ v" V! ?* }. r8 H2.2 从一个具体的 CDC 类通信数据说起
9 Y0 C- m$ P) v  h
/ I* U% K4 t% k% C Y@W2P~2GX2%~KC6WR7CFM.png 0 e0 H0 T/ H" V; D- l
( 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

" f+ h2 G3 ^/ r- J3 W( c0 g2.3 CDC 类设备枚举过程
. p2 B9 X) _& b4 pCDC 类设备与其他标准 USB 设备枚举过程的并没有什么特殊的地方。在设备描述符内可以使用 DeviceClass=0x00,SubClass=0x00, Protocol=0x00 表示此类信息在接口描述符内给出;或者也可以使用 0x02,0x00,0x00;来表明该设备为CDC 类设备。或者使用 0xef, 0x02,0x01 表示当前为复合设备。1 L# S$ H3 N5 v( J( A
CDC 类设备在枚举过程中最主要的信息存储在配置描述符内:
; u! P; \' k2 y) [/ O) i
8 H) @- [) Z; Z. y8 `6 N $)3QWWHKWI[PRP88L8Y4YOQ.png
( P; S5 D) L4 V& c4 V' h  J1 o- i2 h) U0 P- e- V
如上图所示,CDC 类的配置描述符一般包含两个接口(Interface 0),一个控制接口,另外一个是数据接口(Interface 1), 除此之外,还有一个虚线指向的 IAD(Interface Association Description),这个表示这个是不是可选的,得根据实际情况来确定其是否真实存在。
+ U. R) v5 }  C! D4 c$ |9 F$ E9 ~. o! n
& h3 D5 C% M  }- r: ~
2.3 1 控制接口
% ]2 h5 s0 a1 @7 X控制接口下包含类描述符合一个端点(ie:0x82),这个端点(中断传输模式)为异步通知消息的端点,当设备端需要向 Host 端发送异步消息时,可以通错此端点来发送,但平时主机端都是通过端点 0 来向设备端发送控制消息的,比如那些虚拟串口的操作指令等等。
. S7 r9 `- s, L# ^3 S' A, m除这异步通知端点外,控制接口下还包含 CDC 类相关描述符,这其中就包含 Header 描述符,Call Management 描述符,ACM 描述符以及 Union 描述符。这些功能描述符整合在一起用来描述此 USB 设备的一些功能特性,比如 AT 指令支持情况,ACM 模型下的指令集支持情况,以及还有哪些接口与此接口一起对应 Host 端的一个功能(驱动)。
4 T) V: [. o$ {" ^$ ~5 u在具体配置描述符内的控制接口内,功能描述符紧跟在接口描述符后,最后才是端点描述符。
: ^, M7 [3 \3 E8 u" _  y9 h 控制接口
8 z* b- B( |: O7 w! w! n
/ w9 w9 t+ H# g( ~4 h# ~ JD@ZO`]W``(YK`I~YF%MBBY.png
6 Z9 ]9 W' o3 R* C: K  h# P' i" `5 r
  [) U0 Z( t$ t8 z$ Y. z! L# U控制接口主要用来做设备管理和电话管理(可选),设备管理涉及到请求(request)和通知(notification),端点 0 一般用做请求,一般用来控制和配置设备的运行状态,而非 0 端点(0x82)一般用作异步事件通知,设备端通过此端点向主机端发送设备内部的一些事件,比如串口状态变化事件,电话状态改变等等。
0 G0 W4 S3 i+ g: W1 }这里使用到 ACM 模型,后续将讲到这个模型,并且这里指明使用到 V250 版本的 AT 指令,这些指令是与电话相关的,但在我们这里讲的 CDC 通信实际上并不需要使用这些与电话相关的指令,它只是简单通信而已,这里指出 AT 指令也没有关系,只是实际不用它而已。
5 N* G% H5 n5 C) r' a7 p如上图,bNumEndpoints 表示此接口下包含的端点数,这里为 1 个,即那个异步通知端点。bInterfaceSubClass 为0x02,ACM 通信模型,bInterfaceProtocol 表示 AT 指令集的版本,虽然这里举例为 V2.50,但实际上并没有使用到任何 AT 指令,因此它放: k! j4 X) j8 J
 Header 功能描述符
: k- C* m! X6 [4 ?0 L
; b; y4 ]) L9 ]8 f4 N Q6QSNWIB3@10BAUU}SR[YO1.png " v- Q9 ~* r# R1 I

7 ~& p/ m8 j$ t; VHeader 功能描述符表示功能描述符的开始,其他紧跟的内容就是此设备的功能描述符的内容。bcdCDC 表示的是 CDC 的版本。/ F* Q  Q9 @8 W$ |- _7 c& P, [) v
 ACM 功能描述符
9 U. [: x4 Y  `8 n7 \6 P  N$ w
$ ]  ?/ z/ t# P* r: o1 G- g RSH0K8}49W5E{$_@VODUAXP.png
8 }: B" a: l' a8 v' L5 J
' H  g( d: R1 d) S: \ACM(Abstract Control Model),即抽象控制模型,PSTN 下,除了 ACM 模型还有还有 DLM(Direct Line Mode),TCM(Telephone Control Model)。1 O# \, i* T. u$ Y5 [
PSTN 定义了三种模型LM(Direct Line Mode),ACM(Abstract Control Model)和 TCM(Telephone Control Model)." t$ ~! N! r  U# T
• DLM 模型下,USB 设备直接将模拟信号转化为数字信号,并放到 USB 上传输,数据接口直接使用 Audio 类传输音频数据,控制接口传输的也都是些比较原始的指令,比如脉宽设置,发送脉宽等等;
$ w8 H" X$ U3 q' O7 U$ a# O) {/ m• ACM 模型则可以很好的支持 AT V250 指令集,数据接口可以使用 Audio 类或 CDC DATA,控制接口传输的也是比较抽象的高层指令,比如设置、获取波特率,设置获取与通信相关的参数等等,而 AT 指令可以通过控制接口或者数据接口,这个在控制接口下的功能描述符 Call Management Descriptor 中指明。4 \; e0 Z2 p2 Y& _% N2 ]# c( B9 c
• TCM 是指在物理上存在多个连接,可以将接口 0 和接口 1 分别对应到不同的物理连接上。
- E' H$ E- q, ?0 H! a此外,不同的通信模型对应的指令集合(控制指令)也是不同的,而上图中 bmCapliblities 为位图,内部 bit0~bit3 分别表示 4类控制指令集在此设备的支持情况。
- N6 q" m0 ~# T( w
3 e- v3 G# G: D4 X/ x7 E( C; L/ V- i" u
RLE`QUV0J(AVL]@1G~VIYZB.png
4 E2 @  y" ^/ U& V
: {- J$ B1 E+ _如上表,为 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 功能描述符
% M- v! a  p' O2 d. T9 P4 ]' R( y/ R
{LKZQ11KBEWBB]{WYES78F8.png
7 b, ^$ C  r% s" D; d, i6 U2 E0 d9 U0 e
4 N' G  \0 Y6 y4 @5 I5 UCall Management 描述的就是电话相关的东西,AT 指令集的支持情况。但在这里,我们并没有用到任何与电话相关的指令,因此 bmCapabilities 下的位图各个位都是为 0:Bit0:是否支持电话相关的指令(AT 指令集);Bit1:电话相关的指令(AT 指令集)是否经过 Comm. Class Interface; bDataInterface 表示如有电话时,电话数据内容对应的接口号。
: I. l) @5 Z/ c* c/ k$ x4 x3 t Union 功能描述符
5 W& t& T! b0 N: l# H0 E2 A  S" A3 L0 i' `
H8}TL~W[ULPS{)A%PCOJIZK.png 2 ]( |2 U3 A% ]5 ]7 Q9 i" _

2 I- ?" {' k1 ^# ~) oUnion 描述符就是用来告诉主机端,哪些接口是联合在一起的,对应着一个功能,这个功能需要主机装载对应的驱动来实现,因此,功能与驱动是一对一的关系。这里 bControlInterface 值为 0,则表示接口 0 为控制接口,bSubBoardinateInterface0 值为 1,表示接口 1 为控制接口 0 的下级接口,即数据接口。在 CDC 标准中,控制接口是必须的,而数据接口是可选的,因此,数据接口为控制接口的附属。3 e  w2 M' k# |* \6 E3 Y8 T
2.3.2 数据接口
2 T$ d/ U: V5 t! u9 o* {6 `3 r7 _0 V- W  ]( O5 s) n" [
I6J7EZN4G)JQ08SII~YZ3E4.png
/ U6 q8 A1 G2 _, Z3 U3 |& Z7 n& P; L6 v
数据接口比较简单,就是数据通信的,用到两个端点 IN/OUT 0x81/0x01,为块传输类型。
  ~6 m6 u2 E1 m3 g$ f! ~
8 F8 u" V0 J- k1 U# N( ~2.3.3 IAD(Interface Association Descriptor)7 g* n7 \8 r4 v
# ]6 i3 m8 ^, A$ h* y. Q% q
`}~4[$[WM(ER%LT4C7VZI{Q.png 3 p9 w! O, `4 x* G8 t7 j
$ r- ?9 U  z0 b. t
USB 刚出来的时候,一开始默认是一个接口对应一个功能,而一个功能对应着主机端的一个驱动,这在当时是 OK 的,但是后来,人们发现,需要多个接口对应一个功能的时候,比如这个 CDC,除了数据接口外还需要控制接口,这在当时是没有这方面的统一标准,于是就出了 Union 来表示多个接口对应一个功能的情况。再后来,USB 标准协会又增加了 IAD。
9 R* m: }- |6 l; D1 EIAD 与 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 是另外一个例子。
) r1 Q0 {$ R: N" G0 k" a如上图,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
LW54H3[[IE(W2{`23D7QCO6.png 5 e4 E9 {1 {2 w- S  e7 R

; L. P: i7 ^, ^+ H( _6 r5 E2 t如上图所示,一般 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

3 k. U  `4 _: ~  X2.3.4 ACM 模型  f% p% a' q( s/ r5 ^/ t
之前我们已经在控制接口中的功能描述符中已有对 ACM(Abstract Control Mode)模型的简介,也有提到过,在 PSTN 中,除了 ACM 模式,还有 TCM,DLM 模式。这三种模式,不同的模式下包含的控制指令集是不尽相同的,有部分控制指令可能同时存在两个或三个模式下,除了控制指令,还有异步通知消息,这个在三个不同模式下也是不相同的。
5 P2 q: R% K3 K: i, L5 k
. s& o8 h& K; i+ z) e  d. z: U: o' w 8I8Z_97XX}_7UBW]N)YA[Y6.png $ F: D" c" c4 Z
9(3S5)SQ}L0~2X)C}0[F9.png 9 l* C5 u9 M( N! A" b

- ?0 w; A& S3 O# V+ M由图 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 类代码就是这样,对接收到的任何控制指令到没有做任何处理,当然,如果需要的话,则按应用的需要来处理,这个完全取决于用户。
0 w! ~: w; C& [5 d
) f) P# |4 I3 d! v  F3 R `U8S3(G1V{2SK`7`QCOEZ[4.png
: W) n" L7 j: x6 h( w' W1 G. ~# z- W2 f( g+ X- Y
_01P[@]IN`5F}XA40%`PL~R.png ' s% R+ V! @8 P% g' N

, W8 k: U" C; w- Q2 q( Y' L' i: P* X3 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
0ROP0AWF08NW}Y5UQ_(X{FI.png
3 R) o8 P; |4 L8 |3 e. F! N6 \- w' F
. i5 u, j0 u* P8 K( }. T如上图所示,黄色 USB Device Core 部分为 USB 设备库文件,属于中间件,它为 USB 协议栈的核心源文件,一般不需要修改:
9 H, M+ k1 ~; @% x4 `2 A; ?- \% V& S USB Device Core 中,Log/debug 为打印/调试开关;4 k3 T$ b4 i2 N5 s
 core 为 USB 设备核心;
+ |5 Y2 l8 k3 ^. o. c 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$ ^
`WN8F`63IZMMAU8OLQ3%[%2.png : b3 {4 V7 J$ H, l

7 d5 S. f0 h9 i% G3.3 USBD 内核与 USBD_CDC 的关系
+ X- m: k- |! b6 W8 R/ c3.1 节中,我们已经提到过 ST 官方 Cube 库中提供的官方 USB 协议栈,主要是包含了 USBD 内核与 USB 各种类。USBD 内核一般是固定的,用户一般不需要修改,但 USBD 类,如果用户需要修改或者扩展,比如复合设备或者用户自定义设备,还有就是,ST 目前官方提供的 USB 设备类的 DEMO 程序并没有囊括所有 USB 类,因此,若用户需要实现这些官方提供DEMO 之外的 USB 类时,则用户需要根据自己的需要来定制化自己的 USB 类,那么又该如何开始呢?
% y8 \8 e1 _: c. S% u我们已经知道,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
  1. typedef struct _Device_cb
    6 _; i/ V) `# k0 ]
  2. {" P9 a4 ?7 m. l7 ?+ ?: @5 G( E2 Z0 v
  3. uint8_t (*Init) (struct _USBD_HandleTypeDef *pdev , uint8_t cfgidx);: y& _  b, O3 O. H' ]' H
  4. uint8_t (*DeInit) (struct _USBD_HandleTypeDef *pdev , uint8_t cfgidx);
    / e! ?1 s, {( v  ?8 l
  5. /* Control Endpoints*/
    - J# w6 Y. I! e5 ^$ V: }
  6. uint8_t (*Setup) (struct _USBD_HandleTypeDef *pdev , USBD_SetupReqTypedef
    # C$ g2 R; `! T. t' _. Y
  7. *req);$ G& F4 O: O& c1 `1 H
  8. uint8_t (*EP0_TxSent) (struct _USBD_HandleTypeDef *pdev );
    " T! W3 [8 R5 @; u& ?
  9. uint8_t (*EP0_RxReady) (struct _USBD_HandleTypeDef *pdev );
    $ b: B" q' |6 b& G7 m
  10. /* Class Specific Endpoints*/% N- S* m( U1 [4 A2 T  A
  11. uint8_t (*DataIn) (struct _USBD_HandleTypeDef *pdev , uint8_t epnum);5 ^. T( {- @3 R& y3 W% m9 {
  12. uint8_t (*DataOut) (struct _USBD_HandleTypeDef *pdev , uint8_t epnum);
    8 Y4 \' F: m; C9 N% T% f; v$ M1 t
  13. uint8_t (*SOF) (struct _USBD_HandleTypeDef *pdev);
    / e, c' _8 |9 ?
  14. uint8_t (*IsoINIncomplete) (struct _USBD_HandleTypeDef *pdev , uint8_t epnum);: P8 o7 P. n; h  K/ K
  15. uint8_t (*IsoOUTIncomplete) (struct _USBD_HandleTypeDef *pdev , uint8_t epnum);
    7 s. j4 g1 `! K3 H8 T
  16. uint8_t *(*GetHSConfigDescriptor)(uint16_t *length);
    ; Q* r( U7 z* K9 a* u/ P" d* F/ `
  17. uint8_t *(*GetFSConfigDescriptor)(uint16_t *length);
    ' V9 S  F' u' _6 R2 G
  18. uint8_t *(*GetOtherSpeedConfigDescriptor)(uint16_t *length);
    7 H$ r8 M+ C# J. y2 ^% V1 h
  19. uint8_t *(*GetDeviceQualifierDescriptor)(uint16_t *length);6 O9 n% V! |: S2 b; ~2 E% |
  20. #if (USBD_SUPPORT_USER_STRING == 1)
    8 |: z# I9 f4 e8 X
  21. uint8_t *(*GetUsrStrDescriptor)(struct _USBD_HandleTypeDef *pdev ,uint8_t index,
    + a/ a( G0 L6 W# ~# M! C
  22. uint16_t *length);. W3 `8 A4 l0 T9 x! P  b  Y& e
  23. #endif
    % [4 B# f  O2 J8 q2 J! g
  24. $ y% z% j( I* O- u+ Y4 `- K& m
  25. } USBD_ClassTypeDef;
复制代码

; f! M# l' W' k$ O3 P% S2 I这个结构体是一个抽象类,定义了一些虚拟函数,比如初始化,反初始化,类请求指令处理函数,端点 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
4(}YI%T@QS)TXM)(~`[WL.png
( z% o3 W$ ?3 [' _  X
, y( }. h1 Z5 G可以这么说,USBD 内核与 USBD 类之间的纽带就是 USBD_ClassType 这个结构体。
6 A2 m# a8 }& G" u+ ?下面我们来看看 ST 提供的 CDC DEMO 中具体 CDC 类:
& k" P8 A2 z; N+ m. l$ H) v* w! j6 W
  1. USBD_ClassTypeDef USBD_CDC =+ ?7 l2 t! W3 c: Q9 `8 o0 b& q/ ^5 U+ S
  2. {- y6 s  a5 u' b! N, |0 E% X
  3. USBD_CDC_Init, //初始化1 j' D( F/ C# {2 f8 I4 l2 |" a
  4. USBD_CDC_DeInit, //反初始化+ k9 z% c( C0 b* _. b
  5. USBD_CDC_Setup, //CDC 类请求指令处理,也就是指 CDC 类控制指令的处理  q- l0 x" Z& R8 d' s" F& t2 y) G
  6. NULL, /* EP0_TxSent, */ //端点 0 发送完成,不需要处理5 p8 D5 U" h7 z3 {& ^2 z) |1 c
  7. USBD_CDC_EP0_RxReady, //端点 0 接收处理,实际上当做 CDC 类控制指令来处理
    , N; Q4 B( Y* y6 [
  8. USBD_CDC_DataIn, //CDC 发送数据完成处理, a- L+ N9 `( A
  9. USBD_CDC_DataOut, //CDC 类接收数据处理' @$ \& V$ Z+ V$ c- }& N9 J& x" I
  10. NULL, //SOF 中断不做处理6 H1 D. r; R9 F) z+ L+ O% U
  11. NULL, //同步传输发送未完成中断不做处理( f+ x. J! c2 v9 W
  12. NULL, //同步传输接收未完成中断也不做处理0 z* ~% d! b) A* D* j( d' L
  13. USBD_CDC_GetHSCfgDesc, //获取高速 USB 配置描述符! T2 q& X! S5 p3 U+ ^, C" v7 H
  14. USBD_CDC_GetFSCfgDesc, //获取全速设备配置描述符
    ( F! ~, P" o# |& ]* d
  15. USBD_CDC_GetOtherSpeedCfgDesc,
      H7 S7 \6 O5 |' S
  16. USBD_CDC_GetDeviceQualifierDescriptor,
    6 h1 |( n" z2 F- ^5 ~7 B9 `1 }- ^
  17. };
复制代码

) U! ?2 G9 F* `* g6 \; i2 m- M& c这个就是具体一个 CDC 类实例化的对象,上层应用通过 USBD_RegisterClass 函数,将此对象注册到 usbd 内核 :
% W: |: ~7 f( O; M9 R, E2 [
  1. USBD_RegisterClass(&hUsbDeviceFS, &USBD_CDC);
复制代码
9 `* Q" t# v8 T: l* ~# ]2 c
它主要在 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 的关系
, `( _; X: g4 u, q1 }1 t讲完了 USBD 内核与 USBD_CDC 的关系,接下来我们来讲下 USB_CDC 与上层应用是如何对接的。为了将 USBD_CDC 与上层应用层完全分离出来,类似 USBD 内核与 USBD_CDC 类完全分离一般,USBD_CDC 类对上层同样提供一个抽象的数据操作接口 USBD_CDC_If 结构体:
, I3 F5 t  U' H: B# q  j
  1. typedef struct _USBD_CDC_Itf
      `$ o% a: B5 A- K
  2. {0 L, ?4 I1 L" E2 M( I0 Q( a; D
  3. int8_t (* Init) (void);+ U% o7 O/ m9 l0 B+ D" W
  4. int8_t (* DeInit) (void);# v, u+ E9 Q. ]% k
  5. int8_t (* Control) (uint8_t, uint8_t * , uint16_t);//处理收到的来自 Host 端的控制指令
    / u3 W& J$ u' K. S/ ]
  6. int8_t (* Receive) (uint8_t *, uint32_t *); //处理收到的数据
    ; s2 _2 n& ^( D8 j1 Z
  7. }USBD_CDC_ItfTypeDef;
复制代码

* _, Y" O  G! S9 e. ]如上所示,如何处理来自 Host 端发送过来的控制指令和数据,完全是由应用层来决定,具体实现是应用层将此抽象的操作接口具体实例化,并注册到 USBD_CDC 类对象中:
( V3 x7 l' |7 a) e8 g2 V3 C P$PVPNFBRQ32(9@{O{RH9O0.png ) A; Z$ d0 x8 ]- d0 e; U

, _  r3 `  Z& {7 A0 O3 S2 O如上图所示,通过引入 USBD_CDC_If 这么一个数据结构,就实现了 USBD_CDC 类与应用层的完全分离。USBD_CDC_If 的具体实例化对象如下:
- e* J6 q8 ~+ ]- u& V- F
  1. USBD_CDC_ItfTypeDef USBD_Interface_fops_FS =
    ( l! b- h3 u2 z# b
  2. {! V9 g4 r# O0 \% N5 P
  3. CDC_Init_FS,* Y- H' i  h1 A5 \5 \  Y& v
  4. CDC_DeInit_FS,$ u( m7 t/ r; N  i  c
  5. CDC_Control_FS,
    ! q5 M  H1 U; J4 a! J4 f
  6. CDC_Receive_FS
    * |. Y6 p0 R" H+ \8 ^9 M, {+ Z3 H
  7. };
复制代码
9 v# ?$ c' e' ^- T, Q" @
源文件 usbd_cdc_if.c 就是实现这些成员函数的过程,除此之外,还包含发送接口。最后应用层通过调用USBD_CDC_RegisterInterface 函数将此操作接口注册到 USBD_CDC 类中 :' c' {# v5 S/ V& E6 u
  1. USBD_CDC_RegisterInterface(&hUsbDeviceFS, &USBD_Interface_fops_FS);
复制代码
# X( A% G  w/ m2 C6 W- }5 d3 g+ v
3.5 应用接口
  n4 D( p& n" n& B6 E+ D% l 初始化 :% |' r+ x% O/ T, ^4 K
  1. void MX_USB_DEVICE_Init(void)
    ( }- s5 _. ]' B, |
  2. {0 ?" a% S& M9 J$ d- V% \
  3. /* Init Device Library,Add Supported Class and Start the library*/9 r- M9 b/ K* A
  4. USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS);& l0 h+ B6 }  C; B/ A, U6 [
  5. USBD_RegisterClass(&hUsbDeviceFS, &USBD_CDC); //注册 USBD_CDC 类到内核
    + C/ j% Z3 h/ `2 Y6 N4 F+ f# _  y
  6. USBD_CDC_RegisterInterface(&hUsbDeviceFS, &USBD_Interface_fops_FS);//注册 USBD_CDC_If 到- J4 N1 U+ x# `, j% j% {
  7. CDC 类! `! v+ a& k" b) D+ @/ @7 e6 V
  8. USBD_Start(&hUsbDeviceFS);9 E" m- v' t# p- q7 N
  9. }
复制代码
9 E7 R4 n5 C0 F% A
如上图所示 :
$ g% F- x" I% z5 U! W6 w+ U初始化分 4 步:
2 j( k) y0 j" x5 c1> 初始化 USBD 内核
! q/ v: P' h( ~* K5 j. J2> 给 USBD 内核注册 USBD_CDC 类
7 B4 w0 k9 B; Z9 s- ]- b( k3> 给 USBD_CDC 类注册 USBD_CDC_If 接口
, ?5 e6 h1 ^6 D2 k* r! G4> 正式启动 USBD! p! ^! l3 |/ p
 发送数据:2 Q7 ]+ x1 N0 u. T! _
uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len);
, G. Y0 m3 n8 S+ ]- t 接收回调处理:
! f' |0 C( f; _) wstatic int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len);) @. z' V% ]0 U- P" G% ?$ R- c0 G
 接收控制指令处理 :
, L* {* p5 b0 z8 R9 F+ L# Ustatic int8_t CDC_Control_FS (uint8_t cmd, uint8_t* pbuf, uint16_t length);
, @$ x% f! s& b. T. v' b$ f$ @8 c4 U7 _* Z3 D

* f7 V6 i( N- }# _: R+ e4 X4 实践动手部分
) w; I& S& }! m本实验的目的是让用户学会自己动手创建一个 STM32CubeMx 工程,一步一步实现通过使用 USB CDC 类与 PC 端进行串口通信。9 q+ _* I8 }& ]

! k! {0 t5 Y: g" t; q4.1 实验环境及 STM32F072-Discovery 板简介$ g! a0 q4 ^* C
硬件准备:
6 N) B# {& q7 {- n STM32F072 Discovery 板一块( k9 u( g3 }8 f2 E2 w
 Mini USB 线两根
1 D( V' @# _, K7 e) Y# { PC 一台
1 ]- a- r; U6 i' ?7 X' N9 g4 o) O/ H软件准备:
/ x! @1 l+ }6 U* L* s( O IAR V6.7.0 或者以上版本! b2 Z" O4 I3 V
 STM32CubeF0 V1.7.0
$ Y2 ]0 j' Z0 y3 v+ r STM32CubeMX V4.19
& V: f* ?  ]1 l$ s8 d SSCOM 串口工具
! i5 i: @' R- C0 C VCP 虚拟串口驱动
; V* f; \/ _- p  i  P
( k9 e; K+ E3 G, V ZU`8_}3%P`Y_KQTVBESX3NC.png
1 [) ~! u2 e% v9 L
4 Q2 T' Y8 y6 [3 \ OA2TAK)OR2H{XGI7][_]SR2.png
) E0 ^- G5 V4 }! ]) u7 `: l1 v
: |9 Z1 l8 m- ]$ P4.2 使用 STM32CubeMx 制作 CDC 工程8 Y! M7 Q2 P% P. x) n

+ s% d  C) M4 w) T+ a5 ` OK2RFXG2E@_NG1HI`FRXBRX.png
4 d7 n# h! i7 \/ w5 ?$ F; e- l; }( h! v- ^4 b1 j3 Z8 m" c, M
UFDC4QUET]{WCUO@Q@[@2Z2.png
& ?0 H3 m% x8 f2 ~* P
! w) c) D9 \; |2 @9 Q5 z使用内部 48M 的 HSI48 RC 作为时钟源。
1 S: w; p3 I, A( k1 E8 O
' U  e, }: }& `; y KST~4@3XA_5G(N{@XVUPL_7.png
9 U5 i* e" R$ n: [, f6 Y. _; R- ~2 _2 e8 l7 o3 {( V
最终生成的代码工程与 USB CDC 类软件框架的对应关系:
3 n' \8 C; i; _# j
- N/ p7 c! Z5 p' v$ Y( d( q1 X- ? `9GQ~`4{T6C@}IAT_I7%CMW.png
2 F" k: y# g' s6 }) Y* _9 S8 L
; W! ?: A2 b) P, f! |4 E8 _; W4.3 添加测试代码* Q5 r) k- [! }) y
为了更好的验证通信,我们需要添加点测试代码:) g; L/ x% R9 S6 n8 N
  1. static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len)) N9 O) e' x0 b$ K6 q
  2. {
    1 h, C' ^" x0 y! Y1 J3 Z+ A
  3. /* USER CODE BEGIN 6 */
    5 N. _' ^0 t/ D6 G* ?
  4. HandleReceiveData(Buf,*Len);
    + s) X$ V/ ]' J8 c" Z; K
  5. USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);6 ]9 z. U/ R; ]  e; q4 H9 U
  6. USBD_CDC_ReceivePacket(&hUsbDeviceFS);
    ! ^( K  U  a+ J( ~8 K# k8 t
  7. return (USBD_OK);6 c& \: u, O- O- |, Q
  8. /* USER CODE END 6 */" b0 \) M& D5 C( U
  9. }
复制代码
9 ]$ G* e* h: \* O6 H
在接收回调中,我们将接收到的数据转给 HandleReceiveData 函数处理:
& c, ^! v0 K6 j$ N. j
  1. void HandleReceiveData(uint8_t *pData, uint32_t len)* @0 `( D0 L7 \! c
  2. {
    3 x, x8 G  I+ [3 }7 Z* ~7 G
  3. CDC_Transmit_FS(pData,len);" B* z2 J$ _* d# J% z) L" o  ?
  4. }
复制代码

* p- E. Y7 S; ]4 n# V0 Q而在 HandleReceiveData 函数中我们将收到的数据原样返回给 Host 端,这样一来,Host 端的串口工具将发送什么就将收到什么。) G. x5 m5 |3 ~  E6 K$ |
  1. uint8_t SendData[256];
    ! Y6 O! F% e6 o0 S' X# L" k
  2. static uint8_t StartFlag =0;
    / x) s3 m! w; Z- h
  3. void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)4 {% W% u, c- D; }9 P* f
  4. {
    $ C$ |% L: `% P
  5. //CDC_Transmit_FS(SendData,64);$ u* ~" D' ^4 Y
  6. if(StartFlag ==0)
    5 x& U" f5 ~4 ?& e+ i7 c4 m' `
  7. {
    - ?# D' b$ X! d0 K2 r8 [
  8. StartFlag =1;$ w9 S8 L1 E  W0 I
  9. }+ e" n6 e- \/ E# N8 d9 M
  10. else4 C" ^- x( [$ ^  D6 ]& A' w4 l
  11. {5 W, ~0 `8 p: Z' n9 G) [: C
  12. StartFlag =0;# C; |. M$ Q% X7 o* F5 Q
  13. }
    : \8 _6 e8 l- m8 K0 k' P
  14. }
复制代码
1 r; d* X6 g: ^- r
另一方面,我们定义了一全局变量 StartFlag,它用来标志是否循环从 Device 端向 Host 端主动发送数据,其值由外部按键控制。然后在 Main 函数内的 while(1)循环内添加如下测试代码:8 ~" ]  f5 C2 y- g
  1. /* USER CODE BEGIN WHILE */. q+ r* [1 E7 `; V- G3 s
  2. while (1)$ B& a9 w* w8 k" \7 ^! M: V8 H
  3. {
    + u1 Q; `- M4 r
  4. /* USER CODE END WHILE */* G& r' P& X0 |% Q- x; b
  5. /* USER CODE BEGIN 3 */- b) S5 E% u* r9 ]/ R+ P( R
  6. if(StartFlag ==1)
    ( K9 m3 ]- u6 L0 Z+ b
  7. {& J% p0 l8 v7 Z; n. [" x  B
  8. if(hUsbDeviceFS.dev_state ==USBD_STATE_CONFIGURED)3 R2 U7 ~* D$ c: ^
  9. {
      I, {/ {1 r9 W; |. ?
  10. CDC_Transmit_FS(SendData,60);! F4 P# Z; [: n3 N  d* C
  11. }  i& T9 `1 w( V
  12. }
    & E$ L  D8 J2 W
  13. }3 O( t' D: X9 Z; n5 Z& g
  14. /* USER CODE END 3 */
复制代码
8 l% l5 z& Q4 d' ~- ?
只要 StartFlag 标志为 1,在枚举结束后则不断向 Host 端发送数据。
& O' a7 ]% C( ^, J! V* ^7 G2 E' T& w: o  b8 Z
4.4 验证结果! B% T! @$ h  o7 M; c6 J- U
在编译完并将代码烧录进 MCU 后,我们首先验证 PC 端通过串口工具发送数据的情况:
" x7 k! _6 n% ?. ^  ?) ?3 R
: H8 w4 L) x7 L2 {+ S, l1 \' o9 ~ 852$)9C[OPG}`0NNN4WF]$P.png - y0 T+ d( v, N7 U

8 h) b- h3 b- o; S: Y' s% V如上图所示,串口工具发送 63 个字节到 Device 端后,能够接收到从 Device 端返回到的一模一样的数据,这说明发送与接收都是正常的。. O" U) ~- s* O* H8 O; ]0 {8 d

1 L& s* }& d* u8 @, h/ Y D1A~2Y0B]]I4EK(]`Z$TZUE.png
. Y. O& m9 p: S2 v; H% a/ Z8 t
& Z% X; ]7 Y) D3 d3 H9 |1 \' G在按下用户按键后,串口工具能够无限收到来自 Device 端的数据。. t2 A" ]) y! s* K% Z! n
- v1 R$ N$ x* H/ |9 ^
[XBEEB]A6(5]T{%PWQE4`PU.png ' i9 S" M( `  r/ j2 E
  ?, 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

, g( k* Y) p/ Q4.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
收藏 2 评论0 发布时间:2022-3-3 22:19

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版