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

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

[复制链接]
STMCU小助手 发布时间:2022-3-3 22:19
1 前言% t7 I2 k. r  o( `9 b
本文节选自 2017 年度 USB CDC 类培训内容的整理,主要目的是以方便些没有到现场参加培训的碟粉们可以参阅学习。本文力求从理论到实践,尽量给读者一个整体了解 USB CDC 类的窗口。当然,阅读此文,还是需要基本的 USB 知识,这个请读者自行预备。
; k: Q5 s5 F1 S9 u+ H; P3 E% c/ i
9 V4 E. Q( |, Q, x( N" a% P5 y+ [& e' `" m( K- O) g' B
2 USB CDC 类基础理论知识介绍
" W2 g# A7 ^6 {( Z2.1 USB CDC 类、USB2.0 标准与 PSTN 之间的关系
" b, j3 @! k+ u+ `) z7 H" G
CDC(Communication Device Class)类是 USB2.0 标准下的一个子类,定义了通信相关设备的抽象集合。它与 USB2.0 标准以及其下的子类的相互关系如下图所示:
  j7 ^5 u8 n% S  @# S6 l! V7 W/ Q1 ?& t
P(8JX8POO`@7AT%H[8RFPF7.png : v( Q  }! w4 b. G, X+ P: C

- P" w( R# W. y如上图,USB2.0 标准下定义了很多子类,有音频类,CDC 类,HID,打印,大容量存储类,HUB,智能卡等等,这些在urb.org 官网上有具体的定义,这里我们主要讲的是通信类 CDC,CDC 类下面,根据具体的应用场合,又有一些子类,这里我们主要讲的是 PSTN(Public Switched Telephone Network)。从 PSTN 官方标准文档来看,PSTN 子类是一个与电信相关的子类,而这里,我们只是将它作为一个普通的通信设备使用,并没有使用到它的一些电话特性。
; \% H3 r, I2 E3 ?0 ]- I' O1 d: f4 D
2.2 从一个具体的 CDC 类通信数据说起
! e3 `6 t, [" h5 @( Y& C# ~
' D  e3 @7 W* a$ ?8 k Y@W2P~2GX2%~KC6WR7CFM.png
+ R& G/ F/ L, D9 S3 |/ Z/ p( Z2 X+ C1 L$ {& }
如上图,USB CDC 类的通信部分主要包含三部分:枚举过程、虚拟串口操作和数据通信。其中虚拟串口操作部分并不一定强制需要,因为若跳过这些虚拟串口的操作,实际上 USB 依然是可以通信的,这也就是为什么上图中,在操作虚拟串口之前会有两条数据通信的数据。之所以会有虚拟串口操作,主要是我们通常使用 PC 作为 Host 端,在 PC 端使用一个串口工具来与其进行通信,PC 端的对应驱动将其虚拟成一个普通串口,这样一来,可以方便 PC 端软件通过操作串口的方式来与其进行通信,但实际上,Host 端与 Device 端物理上是通过 USB 总线来进行通信的,与串口没有关系,这一虚拟化过程,起决定性作用的是对应驱动,包含如何将每一条具体的虚拟串口操作对应到实际上的 USB 操作。这里需要注意地是,Host 端与 Device端的 USB 通信速率并不受所谓的串口波特率影响,它就是标准的 USB2.0 全速(12Mbps)速度,实际速率取决于总线的实际使用率、驱动访问 USB 外设有效速率(两边)以及外部环境对通信本身造成的干扰率等等因素组成。
% N7 }, h( n4 b* Z. C
, c* q$ A  `3 R1 c# `) k+ `5 J0 V
4 l+ ~( A2 _/ M" w  g+ V2.3 CDC 类设备枚举过程
) K4 ]3 D) F9 Z) hCDC 类设备与其他标准 USB 设备枚举过程的并没有什么特殊的地方。在设备描述符内可以使用 DeviceClass=0x00,SubClass=0x00, Protocol=0x00 表示此类信息在接口描述符内给出;或者也可以使用 0x02,0x00,0x00;来表明该设备为CDC 类设备。或者使用 0xef, 0x02,0x01 表示当前为复合设备。; X0 g! X" _( C: e& \
CDC 类设备在枚举过程中最主要的信息存储在配置描述符内:! d9 Q0 h; D. B2 U

% [4 s$ o# }3 j* y $)3QWWHKWI[PRP88L8Y4YOQ.png
& h4 _: v5 o2 L! p; f3 k9 y8 z7 x, O6 F" U. w- i$ y2 ^
如上图所示,CDC 类的配置描述符一般包含两个接口(Interface 0),一个控制接口,另外一个是数据接口(Interface 1), 除此之外,还有一个虚线指向的 IAD(Interface Association Description),这个表示这个是不是可选的,得根据实际情况来确定其是否真实存在。' t* a$ ~- _& h2 f& w/ n
5 C# u! G* p' k; N* a, `
8 @% e- w" B  h. C7 J2 N
2.3 1 控制接口
5 W# v5 P" z. h& }- {* m控制接口下包含类描述符合一个端点(ie:0x82),这个端点(中断传输模式)为异步通知消息的端点,当设备端需要向 Host 端发送异步消息时,可以通错此端点来发送,但平时主机端都是通过端点 0 来向设备端发送控制消息的,比如那些虚拟串口的操作指令等等。) \# F& f" O; H9 h
除这异步通知端点外,控制接口下还包含 CDC 类相关描述符,这其中就包含 Header 描述符,Call Management 描述符,ACM 描述符以及 Union 描述符。这些功能描述符整合在一起用来描述此 USB 设备的一些功能特性,比如 AT 指令支持情况,ACM 模型下的指令集支持情况,以及还有哪些接口与此接口一起对应 Host 端的一个功能(驱动)。7 ?( l2 y( h- _- t( u
在具体配置描述符内的控制接口内,功能描述符紧跟在接口描述符后,最后才是端点描述符。& u( u% y  Q" Q' Q2 ?! q- q+ o. W
 控制接口( p# O" j3 A- W8 Z
% K- ]) G3 L4 l% ~1 {
JD@ZO`]W``(YK`I~YF%MBBY.png $ r& P2 q6 v: ~" ]# T
3 T5 y) P9 |' V$ |
控制接口主要用来做设备管理和电话管理(可选),设备管理涉及到请求(request)和通知(notification),端点 0 一般用做请求,一般用来控制和配置设备的运行状态,而非 0 端点(0x82)一般用作异步事件通知,设备端通过此端点向主机端发送设备内部的一些事件,比如串口状态变化事件,电话状态改变等等。' X3 R# p1 d" U0 w
这里使用到 ACM 模型,后续将讲到这个模型,并且这里指明使用到 V250 版本的 AT 指令,这些指令是与电话相关的,但在我们这里讲的 CDC 通信实际上并不需要使用这些与电话相关的指令,它只是简单通信而已,这里指出 AT 指令也没有关系,只是实际不用它而已。
/ e; B6 x# v  i6 f0 G如上图,bNumEndpoints 表示此接口下包含的端点数,这里为 1 个,即那个异步通知端点。bInterfaceSubClass 为0x02,ACM 通信模型,bInterfaceProtocol 表示 AT 指令集的版本,虽然这里举例为 V2.50,但实际上并没有使用到任何 AT 指令,因此它放2 v: D7 D! M  q1 G
 Header 功能描述符
. H6 K  M% R( i0 _& r- C8 c$ _9 D) N
Q6QSNWIB3@10BAUU}SR[YO1.png
7 O/ k. S( r9 U! ~( r& Q' z4 ?) _9 N+ E6 g% V
Header 功能描述符表示功能描述符的开始,其他紧跟的内容就是此设备的功能描述符的内容。bcdCDC 表示的是 CDC 的版本。; a1 Q7 o6 d1 z; v  K
 ACM 功能描述符
. i- o3 d3 {, x: \) ]
6 a: O  l- j' x2 U9 d! Z RSH0K8}49W5E{$_@VODUAXP.png
1 J1 _8 f( h2 @2 O7 P/ K. l0 z9 r4 R6 r+ d, t* w
ACM(Abstract Control Model),即抽象控制模型,PSTN 下,除了 ACM 模型还有还有 DLM(Direct Line Mode),TCM(Telephone Control Model)。
6 B6 S6 M$ D: G8 O' wPSTN 定义了三种模型LM(Direct Line Mode),ACM(Abstract Control Model)和 TCM(Telephone Control Model).9 [# K: v, E9 L0 L' m5 Q3 K1 d
• DLM 模型下,USB 设备直接将模拟信号转化为数字信号,并放到 USB 上传输,数据接口直接使用 Audio 类传输音频数据,控制接口传输的也都是些比较原始的指令,比如脉宽设置,发送脉宽等等;
( D0 M/ B8 k& J" r8 d( S( f, U! }• ACM 模型则可以很好的支持 AT V250 指令集,数据接口可以使用 Audio 类或 CDC DATA,控制接口传输的也是比较抽象的高层指令,比如设置、获取波特率,设置获取与通信相关的参数等等,而 AT 指令可以通过控制接口或者数据接口,这个在控制接口下的功能描述符 Call Management Descriptor 中指明。
/ F3 Q9 n; r4 q) b5 n• TCM 是指在物理上存在多个连接,可以将接口 0 和接口 1 分别对应到不同的物理连接上。6 k6 Z6 Q1 ^9 x7 d$ x0 ^/ l. Q
此外,不同的通信模型对应的指令集合(控制指令)也是不同的,而上图中 bmCapliblities 为位图,内部 bit0~bit3 分别表示 4类控制指令集在此设备的支持情况。
  A) f8 ^( }* f- R
# I$ H1 R) c2 v, b9 ?6 n1 c4 g4 Q5 i6 l5 [
RLE`QUV0J(AVL]@1G~VIYZB.png
7 i# q: t* I3 w0 _% \
) l4 O" e1 O7 r" v7 G' Y如上表,为 ACM 模型下的指令集,但不是说,这些个指令就一定会在 ACM 模型下存在,此 USB 设备是不是支持此某个控制指令,还得看 bmCapliblities 这个参数具体对应位是否使能。5 }& Y* m. [% P9 b- X; e3 p
在实际的 STM32 USB 协议栈中,针对于 CDC 类,使用 LineStateCoding,GetLineCoding,SetControlState 类指令,用来读取,设置串口波特率以及串口的打开与关闭,这个具体的映射实现是通过主机端的驱动来实现;从设备端来看,当设备端收到这些来自主机端操作串口的控制指令时,这些指令具体怎么执行完全取决于设备端,也就说,所有的这些操作,比如设置波特率为 115200,对于设备端来说这个只是个通过 SetLineCoding 指令传过来的一个参数而已,具体怎么处理这个参数,取决于设备端应用程序具体怎么处理这个参数,这个有用户来处理,这个 115200 波特率与 USB 本身的波特率 12Mbps(全速)是没有关系的。
7 _6 _6 H) Z. G$ q9 G+ [/ @ Call Management 功能描述符) O1 x0 t. @* v2 m, A, I5 w1 {$ ?1 G

$ \% Y! o, G% a5 h- \' x' P7 M" J6 F {LKZQ11KBEWBB]{WYES78F8.png
' Q2 z$ Q, \* T. u, `8 R
1 F4 ]. r! u( W4 @6 k" o" y+ C! o4 ~Call Management 描述的就是电话相关的东西,AT 指令集的支持情况。但在这里,我们并没有用到任何与电话相关的指令,因此 bmCapabilities 下的位图各个位都是为 0:Bit0:是否支持电话相关的指令(AT 指令集);Bit1:电话相关的指令(AT 指令集)是否经过 Comm. Class Interface; bDataInterface 表示如有电话时,电话数据内容对应的接口号。! o0 z8 \& v; v' p  ?0 `# B
 Union 功能描述符
* q0 V2 o; V% O0 K; Q/ u* Q1 g) ], I0 N" d. G# m( p& f: N
H8}TL~W[ULPS{)A%PCOJIZK.png
6 I! W  A. R: n2 R" p. X9 M
! B3 {! W) Q* {2 i9 r0 X' o# @Union 描述符就是用来告诉主机端,哪些接口是联合在一起的,对应着一个功能,这个功能需要主机装载对应的驱动来实现,因此,功能与驱动是一对一的关系。这里 bControlInterface 值为 0,则表示接口 0 为控制接口,bSubBoardinateInterface0 值为 1,表示接口 1 为控制接口 0 的下级接口,即数据接口。在 CDC 标准中,控制接口是必须的,而数据接口是可选的,因此,数据接口为控制接口的附属。% A, [1 B, M9 g- N8 i, H( g
2.3.2 数据接口
/ X4 \) K5 q. h2 h
# {/ ^6 k) }2 N- `! t6 @ I6J7EZN4G)JQ08SII~YZ3E4.png / N) Q1 l0 f- ^. P% s6 K

' }$ d' q  R5 T& }7 H# L数据接口比较简单,就是数据通信的,用到两个端点 IN/OUT 0x81/0x01,为块传输类型。
# m2 r' r1 N. B0 |5 f% d
1 G! o0 J+ k7 \  d! h$ o2.3.3 IAD(Interface Association Descriptor)8 V& |$ n/ {9 i( k2 A
. Q2 @9 r4 c) j  U3 \& Z
`}~4[$[WM(ER%LT4C7VZI{Q.png ! K' ?. a8 D0 F! {: O, H& u
* |" T+ H0 s5 ~1 C! N: g
USB 刚出来的时候,一开始默认是一个接口对应一个功能,而一个功能对应着主机端的一个驱动,这在当时是 OK 的,但是后来,人们发现,需要多个接口对应一个功能的时候,比如这个 CDC,除了数据接口外还需要控制接口,这在当时是没有这方面的统一标准,于是就出了 Union 来表示多个接口对应一个功能的情况。再后来,USB 标准协会又增加了 IAD。- L6 ^" G# z  d, E
IAD 与 Union 类似,Union 是旧版本下实现多个接口对应一个功能的功能描述符,而 IAD 是 USB 协会后来针对多个接口对应一个功能的情况而扩展的,旧的主机可能只支持 Union 方式,但 IAD 并不会影响旧版本主机对设备的识别,因为旧版本主机会通过 Union 来识别哪些接口是联合在一起的,对于 IAD 则跳过忽略;而新版主机则可以通过 IAD 来识别,跳过忽略老的Union,因此两者可以完美兼容,互不影响。因而主机端可以精确地装载对应的驱动。
: s; `# o% J  v/ UIAD 只用在设备描述符中只用了 device class code,并且指明了使用 IAD 来识别设备,比如 bDeviceClass: Miscellaneous(0xef), bDeviceSubClass: Common (0x02), bDeviceProtocol: Interface Association Descriptor (0x01)就是一个例子;0x02,0x00,0x00 是另外一个例子。
$ E0 U8 C4 H9 S# n( K9 z; P; w如上图,bFirstInterface 值为 0,表示第一个接口个接口 0,默认为控制接口;bInterfaceCount 值为 2,标志此功能总共存在2 个接口,那么第二个接口就是接口 1,因为 USB2.0 IAD ECN 补充标准规定,这里提到的接口号必须是连续的,也就是说,接口 0 为第一个控制接口,那么接口 1 则为数据接口。  _6 Q) V% B" g( r+ L0 x  v; t8 m
下面我们来个具体的 IAD 例子:
6 L" g; l. ?: K  C
- S1 S2 s2 h% l% h& J& p- H$ K# @' ? LW54H3[[IE(W2{`23D7QCO6.png   n8 r2 ]* c% d
# m$ A. o9 m! |; L2 V
如上图所示,一般 IAD 存在的情况下,在设备描述符中 DeviceClass 等三个参数不再都为 0x00,图 12 中为 0xef,0x02,0x01,这个表示是复合设备,此时,可以使用 IAD 来定义多个接口联合起来对应一个 USB 驱动。从 IAD 中可以看出,bFunctionClass 参数就定义了此 IAD 表示的设备为 CDC 类设备,ACM 模型。就这样,通过 IAD 描述符,实现了与 Union 功能描述符相同的功能。. u5 U2 C, V4 k) b# ^2 V7 v
3 H6 s3 c) D9 N8 O3 V

  |* i* {/ U, C9 r0 r2.3.4 ACM 模型
6 x2 n$ z. u+ I$ D4 l4 X8 u之前我们已经在控制接口中的功能描述符中已有对 ACM(Abstract Control Mode)模型的简介,也有提到过,在 PSTN 中,除了 ACM 模式,还有 TCM,DLM 模式。这三种模式,不同的模式下包含的控制指令集是不尽相同的,有部分控制指令可能同时存在两个或三个模式下,除了控制指令,还有异步通知消息,这个在三个不同模式下也是不相同的。
$ T8 H( n) ]/ d7 s3 b$ Q  J" O5 K' c9 F, [( s
8I8Z_97XX}_7UBW]N)YA[Y6.png
: h! b4 B* r/ v' l* g, w% o) O1 O! B 9(3S5)SQ}L0~2X)C}0[F9.png 6 G# a7 D) x$ Y" @2 f
* _- O7 z" ~" x$ {, m
由图 14~19 可知,当设备选择了某个模型后,其控制指令集和异步通知消息也就得符合此模式下的对应集合,否则则不符合标准。这里我们主要是使用到 ACM 模式,因此,此 ACM 模式下的有 Host 端发现 Device 端的控制指令和有 Device 端向Host 端发送的异步通知消息都是固定的那么几条指令或消息,但并不是说,只要是 ACM 模式,那么就表示此模式下的所有控制指令和异步通知消息都必须支持。控制指令在设备端的控制接口描述符中的 ACM 功能描述符中的 bCapabilities 字段有按位定义 ACM 模式下的控制指令的支持情况,而异步通知消息,则完全看 device 端的应用情况是否需要,并没有在任何描述符中指出那些消息是否支持。6 @! F. s! t) d: p7 T
在 ST 给出的 CDC 例程中,主要是使用到了 SetLineCoding 指令来设置和修改虚拟串口的波特率,使用 GetLineCoding 来获取当前波特率,使用 SetControlLineState 来打开或关闭串口,这种操作是在 Host 端 CDC 驱动来具体映射实现的,至于Device 端收到这些个控制指令该怎么处理,就是另外一回事了,Device 端也可以完全不做任何处理,有 CubeMx 自动生成的CDC 类代码就是这样,对接收到的任何控制指令到没有做任何处理,当然,如果需要的话,则按应用的需要来处理,这个完全取决于用户。* I9 k, v. {0 z$ ]; o
1 b( |" [3 o2 g% T9 b
`U8S3(G1V{2SK`7`QCOEZ[4.png ' Z8 K  G/ q' u0 R
# q& h" B0 ^9 v- f* r
_01P[@]IN`5F}XA40%`PL~R.png & g; S  f& b' H# z

! b" `# B+ v; E0 [3 CDC 类软件框架介绍
0 Y2 w) F6 k& R( ?0 B3.1 CDC 软件框架简介
2 ~  ~$ c1 \3 E7 Y) i

* W5 B/ ~) h7 o! W) `; R9 i

  y2 K, O  s9 B% ?; J1 [ 0ROP0AWF08NW}Y5UQ_(X{FI.png
: {9 @/ N" j+ i7 V" w5 P+ }7 [8 G( V4 M$ w% Q. F/ ]6 Y
如上图所示,黄色 USB Device Core 部分为 USB 设备库文件,属于中间件,它为 USB 协议栈的核心源文件,一般不需要修改:9 d0 d" H. }  G. w3 _
 USB Device Core 中,Log/debug 为打印/调试开关;0 ?/ A+ [7 Y& ~+ h0 m
 core 为 USB 设备核心;+ w: {. X3 l# M8 z* ]
 USB request 中定义了枚举过程中各种标准请求的处理;! Y, T$ ~* Q9 w& l7 l
 I/O request 为底层针对 USB 通信接口的封装。
; l! Y8 }3 _' W5 ^黄色 USB Device Class 部分为 USB 类文件,也属于中间件,USB 设备库,目前 ST DEMO 中支持的类有 HID, Customer HID,CDC, MSC, DFU, Audio, ST 提供了这些类的源码框架,其他的 Class 或者是复合设备需要自己根据实际需求情况进行扩展或定制。如果用户需求只是需要一个标准类,比如 CDC 通信,那么最好就使用现成的代码,不需要做任何修改就可以实现这个CDC 类通信的功能。
/ D* h1 t+ `5 j. Q7 p. a" T8 R蓝色 USB Device HAL Driver 为 HAL 库部分,是对 USB 外设接口的封装,属于底层驱动,不需要修改,它分为 PCD 和 LLDriver,PCD 处于 LL Driver 之上。
9 b1 N+ Y4 \' S8 _) v' @; P洋红色 USB Device Configuration 为 USB 配置封装,位于 USB 底层 HAL 层驱动与中间件 USB 协议栈之间,一方面向上层(USB 设备库)提供各种操作调用接口,另一方面,向底层 USB 驱动提供各种回调接口。正是由于它的存在,使得 USB 协议栈(USB 设备库)与底层硬件完全分离,从而使 USB 设备库具有更加兼容所有 STM32 的通用性。USB Device Configuration为开放给用户的源文件,用户可以根据自己的某些特殊需要进行修改,也可以使用默认的源文件,假如没有任何特殊要求的话,我们使用默认即可。, U( s2 v7 }9 W; x. P$ I
Application 为应用层,USB Device Class 有可能将自己对应该的操作接口封装在一个操作数据结构中,由应用来具体实现这些操作,在系统初始化时,由应用将已经定义好的操作接口注册到对应的 USB 类中,比如 usbd_cdc_if, 就这样,使得应用层的应用代码与属于中间件层的 USB 协议栈分离。同时,USB 协议栈会将一些字符串描述符放到 APP 中,当 USB 初始化时将这些已经定义好的字符串通过指针初始化到 USB 协议栈中,以便后续需要时获取。
4 }# H. l; L$ R1 G4 M6 O
0 m% \/ a9 P4 Y3.2 工程源码文件与软件框架的对应关系
  o0 y/ q( S9 g. m% \* b3 ?' N6 M$ w) G3 K# ?
`WN8F`63IZMMAU8OLQ3%[%2.png
; t5 x# K. J+ ~) ]9 w. B. z4 h5 U( p: v
3.3 USBD 内核与 USBD_CDC 的关系
! B6 a7 @; o8 l2 k$ A3.1 节中,我们已经提到过 ST 官方 Cube 库中提供的官方 USB 协议栈,主要是包含了 USBD 内核与 USB 各种类。USBD 内核一般是固定的,用户一般不需要修改,但 USBD 类,如果用户需要修改或者扩展,比如复合设备或者用户自定义设备,还有就是,ST 目前官方提供的 USB 设备类的 DEMO 程序并没有囊括所有 USB 类,因此,若用户需要实现这些官方提供DEMO 之外的 USB 类时,则用户需要根据自己的需要来定制化自己的 USB 类,那么又该如何开始呢?
% Q0 y# w7 j( `3 O( ]- D+ M# D我们已经知道,ST 提供的 USB 协议栈中已经有 USBD 内核,且这个内核源文件一般是不需要修改的,那么这里我们需要自定义这么一个 USB 类,那么我们首先得知道,这个我们需要自定义的 USB 类是如何与 USBD 内核打交道的?
7 d1 P: t# r! v; P; A7 D' t8 _USB 协议栈将所有 USB 类都抽象成一个数据结构:USBD_ClassTypeDef,其定义如下所示:
; I' K% l9 F- [% C/ ^
  1. typedef struct _Device_cb& j7 y; v/ ?9 [1 k  f" C
  2. {) V! z  ?! S9 a  u
  3. uint8_t (*Init) (struct _USBD_HandleTypeDef *pdev , uint8_t cfgidx);$ u1 O* l( j3 e( ^6 f
  4. uint8_t (*DeInit) (struct _USBD_HandleTypeDef *pdev , uint8_t cfgidx);$ @+ U8 O, ~( h9 \" W2 Y
  5. /* Control Endpoints*/
    ( f/ {$ k' b' H/ K; U
  6. uint8_t (*Setup) (struct _USBD_HandleTypeDef *pdev , USBD_SetupReqTypedef
    $ R1 {& E( _; Z6 b% z
  7. *req);
    3 b% E: X" H# v' ?
  8. uint8_t (*EP0_TxSent) (struct _USBD_HandleTypeDef *pdev );
    5 H) _! H2 O6 m/ y% h$ X& ]4 k
  9. uint8_t (*EP0_RxReady) (struct _USBD_HandleTypeDef *pdev );" q# ?+ J: \7 z
  10. /* Class Specific Endpoints*/
    . l3 N1 Q/ _' K; N- m) j
  11. uint8_t (*DataIn) (struct _USBD_HandleTypeDef *pdev , uint8_t epnum);
    , v% x0 T$ |4 o
  12. uint8_t (*DataOut) (struct _USBD_HandleTypeDef *pdev , uint8_t epnum);7 ]1 ^9 `. A) \, l9 P9 }4 a2 Y
  13. uint8_t (*SOF) (struct _USBD_HandleTypeDef *pdev);4 b1 b; f! d8 U; [; `7 E$ m
  14. uint8_t (*IsoINIncomplete) (struct _USBD_HandleTypeDef *pdev , uint8_t epnum);
    9 v$ W. M9 \  {( u
  15. uint8_t (*IsoOUTIncomplete) (struct _USBD_HandleTypeDef *pdev , uint8_t epnum);* q  |* s! [! \# {* G0 ]- s. m
  16. uint8_t *(*GetHSConfigDescriptor)(uint16_t *length);3 s* w8 l) ?4 G' A. l9 i
  17. uint8_t *(*GetFSConfigDescriptor)(uint16_t *length);5 R; S/ y# p0 q. O5 u
  18. uint8_t *(*GetOtherSpeedConfigDescriptor)(uint16_t *length);
    ( Y  `5 t" Y0 _
  19. uint8_t *(*GetDeviceQualifierDescriptor)(uint16_t *length);( P. K3 ?. g, W4 ^5 A7 R
  20. #if (USBD_SUPPORT_USER_STRING == 1)" m" u( }8 r. r2 P4 S# z8 i, ]
  21. uint8_t *(*GetUsrStrDescriptor)(struct _USBD_HandleTypeDef *pdev ,uint8_t index,$ G- S3 I# A1 f4 P+ Q# I0 o" Q
  22. uint16_t *length);
    ' q% T; p) u" Y
  23. #endif% B! [0 X2 p6 O2 S+ ^# `
  24. 6 U7 @% ?, E2 ~5 o* o8 @' p
  25. } USBD_ClassTypeDef;
复制代码

5 F! Q( Y" d  N' O- V8 `这个结构体是一个抽象类,定义了一些虚拟函数,比如初始化,反初始化,类请求指令处理函数,端点 0 发送完成,端点 0接收处理,数据发送完成,数据接收处理,SOF 中断处理,同步传输发送未完成,同步传输接收未完成处理等等;用户在实现自己具体的 USB 类的时候需要将它实例化,USBD_ClassTypeDef 结构体是 USBD 内核提供给外部定义一个 USB 设备类的窗口,而 USB 类文件(如 usbd_cdc.c)实际就是实现这个结构体具体实例化的过程。最后将这个具体实例化的对象注册到USBD 内核的同时, USBD 内核与 USBD 类也进行了关联。4 M) a/ ^+ T& P: w0 }& t
- A/ {; K- I/ [* o/ [
4(}YI%T@QS)TXM)(~`[WL.png 7 S" G& G/ {1 T* L+ @+ ?3 L5 y4 O) r
- b, j8 ~: ]3 ]# X6 }# g; U8 F- e
可以这么说,USBD 内核与 USBD 类之间的纽带就是 USBD_ClassType 这个结构体。0 w0 E$ X' S4 {6 d
下面我们来看看 ST 提供的 CDC DEMO 中具体 CDC 类:' b$ b8 m, A) g% w) h
  1. USBD_ClassTypeDef USBD_CDC =% f2 \4 w: j& c
  2. {2 Z  O+ l8 o0 S! [+ |
  3. USBD_CDC_Init, //初始化
    , i5 u- C+ d. A8 ^4 z
  4. USBD_CDC_DeInit, //反初始化- [; o, _8 k& A4 H
  5. USBD_CDC_Setup, //CDC 类请求指令处理,也就是指 CDC 类控制指令的处理4 |# p) m9 k! m; M
  6. NULL, /* EP0_TxSent, */ //端点 0 发送完成,不需要处理
    , q2 d: `# k. n9 o% C
  7. USBD_CDC_EP0_RxReady, //端点 0 接收处理,实际上当做 CDC 类控制指令来处理
    % T" E& j/ v; u% R) @6 i
  8. USBD_CDC_DataIn, //CDC 发送数据完成处理4 l- W" Y( h1 K
  9. USBD_CDC_DataOut, //CDC 类接收数据处理
    2 ?- ]& t' @" x" _- ~' @9 n
  10. NULL, //SOF 中断不做处理
    + F& F( S5 P4 c* x( |/ L8 {$ h
  11. NULL, //同步传输发送未完成中断不做处理9 e$ z' P, C! y$ l4 g
  12. NULL, //同步传输接收未完成中断也不做处理
    7 m% k' }* b4 B. g% Z9 F
  13. USBD_CDC_GetHSCfgDesc, //获取高速 USB 配置描述符
    ; F/ m. h5 x% ?" d
  14. USBD_CDC_GetFSCfgDesc, //获取全速设备配置描述符7 H8 f' {8 D8 ?9 s/ N. g3 V- I
  15. USBD_CDC_GetOtherSpeedCfgDesc,3 K" `0 A2 Y9 H
  16. USBD_CDC_GetDeviceQualifierDescriptor,& m: c& [+ I$ P3 C5 W  P3 S
  17. };
复制代码

8 ^5 Z7 C. n$ [1 d+ `- x这个就是具体一个 CDC 类实例化的对象,上层应用通过 USBD_RegisterClass 函数,将此对象注册到 usbd 内核 :; i, G; a5 E/ ~8 X& l1 |! T# u
  1. USBD_RegisterClass(&hUsbDeviceFS, &USBD_CDC);
复制代码
5 O0 Y4 @/ b/ O. r- K
它主要在 usbd_cdc.c 源文件中实现它的各个成员函数,当然,usbd_cdc.c 源文件中,除了这些 CDC 类成员函数的具体实现之外,还包含其他一些对上层提供的接口,比如发送 USBD_CDC_TransmitPacket, USBD_CDC_RegisterInterface,上层应用通过调用 USBD_CDC_TransmitPacket 来发送数据,通过 USBD_CDC_RegisterInterface 来注册操作接口,这也是我们接下来将要讲述的内容。9 O, G& k/ l2 ~# n' H$ o  e3 S9 y

% L) R% l( ?8 A5 i3.4 USBD_CDC 与 USBD_CDC_If 的关系" I, n# o5 _% J: R
讲完了 USBD 内核与 USBD_CDC 的关系,接下来我们来讲下 USB_CDC 与上层应用是如何对接的。为了将 USBD_CDC 与上层应用层完全分离出来,类似 USBD 内核与 USBD_CDC 类完全分离一般,USBD_CDC 类对上层同样提供一个抽象的数据操作接口 USBD_CDC_If 结构体:7 e1 P7 G2 o& K+ P' J% s& c% {
  1. typedef struct _USBD_CDC_Itf/ y8 C" C) D& D0 ]/ q
  2. {! z- Z) U- T5 K& O2 I
  3. int8_t (* Init) (void);$ Y" F2 s0 K7 f: S) q
  4. int8_t (* DeInit) (void);
    4 g: h+ B1 h# |' M- w* f7 C) h  n
  5. int8_t (* Control) (uint8_t, uint8_t * , uint16_t);//处理收到的来自 Host 端的控制指令" y% B0 v( z: U* s. f& k# L
  6. int8_t (* Receive) (uint8_t *, uint32_t *); //处理收到的数据6 Z3 C+ D$ }" B; b, q9 C
  7. }USBD_CDC_ItfTypeDef;
复制代码
! o0 U) w. m: m. c; c4 K% w
如上所示,如何处理来自 Host 端发送过来的控制指令和数据,完全是由应用层来决定,具体实现是应用层将此抽象的操作接口具体实例化,并注册到 USBD_CDC 类对象中:
* ]- T. q" e$ {, j P$PVPNFBRQ32(9@{O{RH9O0.png
, I; P- L7 ?% @4 |: K! x( m2 q' j1 B6 {
如上图所示,通过引入 USBD_CDC_If 这么一个数据结构,就实现了 USBD_CDC 类与应用层的完全分离。USBD_CDC_If 的具体实例化对象如下:+ v& y. J+ V' }& f1 A$ A
  1. USBD_CDC_ItfTypeDef USBD_Interface_fops_FS =0 ?- p8 S4 g( H- e1 N' h$ ]" u
  2. {
    9 [0 g" F" z4 {- ^0 I
  3. CDC_Init_FS,8 G3 b1 D* a( {. g! E) y' U
  4. CDC_DeInit_FS,% F/ ]% g/ L5 O/ a
  5. CDC_Control_FS,/ [/ @5 V% q  B. d& g. O0 F, ?6 Q
  6. CDC_Receive_FS8 ^7 s) w6 h  A3 W5 v  b; _, Y
  7. };
复制代码

- B7 p8 R4 k/ p+ \源文件 usbd_cdc_if.c 就是实现这些成员函数的过程,除此之外,还包含发送接口。最后应用层通过调用USBD_CDC_RegisterInterface 函数将此操作接口注册到 USBD_CDC 类中 :
  P: Z2 e) c3 B
  1. USBD_CDC_RegisterInterface(&hUsbDeviceFS, &USBD_Interface_fops_FS);
复制代码
$ b2 O0 r, i) X6 V& j, d# O7 i
3.5 应用接口
% g9 p% Z# [9 R; J9 K3 u  b 初始化 :
9 M$ \4 o& Y2 K8 H# H! L
  1. void MX_USB_DEVICE_Init(void)
    ; ~$ J- \7 L+ X/ X- l' k
  2. {4 L6 |. j5 r! v& V% H" q
  3. /* Init Device Library,Add Supported Class and Start the library*/
    5 a/ H/ T( }0 C
  4. USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS);5 _8 v: W. K2 m' k% U- d
  5. USBD_RegisterClass(&hUsbDeviceFS, &USBD_CDC); //注册 USBD_CDC 类到内核& @" |! x* w# q) T' A
  6. USBD_CDC_RegisterInterface(&hUsbDeviceFS, &USBD_Interface_fops_FS);//注册 USBD_CDC_If 到
    5 Z1 v0 k" |" ?# @+ v
  7. CDC 类( c, n) ~6 f5 k2 u: ^& {7 R
  8. USBD_Start(&hUsbDeviceFS);  f* ~, g  u) `$ H: `! J& Q
  9. }
复制代码
' P) J1 ?6 B1 o
如上图所示 :
' `1 Y( L; n2 i2 m( V5 g* Z: {初始化分 4 步:
) d6 |# h$ M8 z1 C* l; \( [1> 初始化 USBD 内核! v! A, G, O  G
2> 给 USBD 内核注册 USBD_CDC 类
# x# x% r. ^* G3> 给 USBD_CDC 类注册 USBD_CDC_If 接口
+ `  w: v. n  W* H: C6 C+ @( d& ~4> 正式启动 USBD) M& k" p3 F2 v9 r) y! x
 发送数据:
# D* f( l; f5 v& A+ B2 h, [uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len);
: Y, a6 X& w' k 接收回调处理:
" m: U- x5 T# U' tstatic int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len);
! G/ T$ C& L& @4 k% O 接收控制指令处理 :
! o3 Z3 e0 q' l) M  Pstatic int8_t CDC_Control_FS (uint8_t cmd, uint8_t* pbuf, uint16_t length);. S' s1 B' Z( ]+ }
1 P* s4 {" W: i* f) {  l

5 H6 r9 K' `/ y- o2 o  l4 实践动手部分3 P3 T  _  r% K8 t" m+ j
本实验的目的是让用户学会自己动手创建一个 STM32CubeMx 工程,一步一步实现通过使用 USB CDC 类与 PC 端进行串口通信。2 v, D- Z/ c6 k2 Z: {
$ S# i. n& c2 \+ |
4.1 实验环境及 STM32F072-Discovery 板简介
# ^% `: D8 f: Q  k' E6 v硬件准备:
" S0 M$ g" r6 J3 ?; { STM32F072 Discovery 板一块& s# o5 }8 y* Q9 G2 [  |4 T& x
 Mini USB 线两根% M8 |1 f, F. O
 PC 一台
0 P$ {* |& Q4 {- ?& l软件准备:
- P1 W: E8 K- t3 I5 R3 ? IAR V6.7.0 或者以上版本, f' G% q( a2 Q0 F3 R
 STM32CubeF0 V1.7.0
8 }2 Q. G8 B) A0 ? STM32CubeMX V4.19
- v, g0 G6 l8 ~2 d% H SSCOM 串口工具
4 A9 ^+ c- ]' p+ f0 o VCP 虚拟串口驱动
6 A, o9 K' S, ^
( x9 _% c6 Q/ R; F ZU`8_}3%P`Y_KQTVBESX3NC.png . F5 R' j9 f; Q
; w& v; ?3 `9 b$ f& S) G3 v
OA2TAK)OR2H{XGI7][_]SR2.png 1 {0 |6 [  E5 X% @( k& u

1 _- R0 N; o; y4.2 使用 STM32CubeMx 制作 CDC 工程
9 M. F- D( R2 ?/ u) k) W
1 W; K: N5 z" l+ y* Q6 f3 } OK2RFXG2E@_NG1HI`FRXBRX.png 1 @+ T2 l3 |2 A6 g' T' \

/ N9 e9 G/ @4 N- }$ @8 f" s% T UFDC4QUET]{WCUO@Q@[@2Z2.png 4 t/ F, I$ Y* V. U7 V8 ]5 S

- }8 L& j6 T" {( T/ e: R0 ]! H使用内部 48M 的 HSI48 RC 作为时钟源。( H1 g& H8 p  X3 W* {* i* z

; D6 Z3 l8 |% N6 j, l KST~4@3XA_5G(N{@XVUPL_7.png
8 D9 l  L9 h7 K$ A! h
& e2 ~6 V* {  a最终生成的代码工程与 USB CDC 类软件框架的对应关系:! c! F  Y3 L/ ?4 d5 M# X

* q; U' i1 C8 J2 q" e/ X( y2 R0 a `9GQ~`4{T6C@}IAT_I7%CMW.png ( q) g( v. }5 _( s5 Y
! ~& [# i: @$ ]1 N  P
4.3 添加测试代码% Q/ u7 K% b0 J$ ?
为了更好的验证通信,我们需要添加点测试代码:1 h. {. ]0 Y# b
  1. static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len)
    $ W9 `+ u3 S3 r! T4 u+ {
  2. {
    $ l( C/ ~" W. x0 ]
  3. /* USER CODE BEGIN 6 */1 ^3 Y$ }7 {. g! G" P  i8 Y
  4. HandleReceiveData(Buf,*Len);
    % `$ u% ]! ]2 C& O0 _+ S
  5. USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);+ P$ U( k: z: g! u0 K8 _8 j* {
  6. USBD_CDC_ReceivePacket(&hUsbDeviceFS);
    / U1 c# b1 M: E3 c+ o$ r
  7. return (USBD_OK);
    0 ?6 O+ X+ R' ]+ b+ \3 t
  8. /* USER CODE END 6 */8 H: V/ h% J0 K4 F6 \
  9. }
复制代码
( g5 N% H  I3 m2 z5 g: k
在接收回调中,我们将接收到的数据转给 HandleReceiveData 函数处理:
2 }! L( T7 D9 T0 K8 Q3 M  X5 P
  1. void HandleReceiveData(uint8_t *pData, uint32_t len)7 N% J+ g9 N3 X6 c- \* O1 R. D
  2. {
    % v+ ]" }" q4 S! Q
  3. CDC_Transmit_FS(pData,len);* l& I) s$ J" [6 E2 P# ~  N
  4. }
复制代码
9 N& ~4 S+ q/ S- g
而在 HandleReceiveData 函数中我们将收到的数据原样返回给 Host 端,这样一来,Host 端的串口工具将发送什么就将收到什么。$ R* s5 F5 R% W# t5 s/ Y
  1. uint8_t SendData[256];" t2 I; L6 C/ t, T, r
  2. static uint8_t StartFlag =0;
    2 t/ S1 ]) Q& E: U( p- \  W& ~
  3. void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)' ^! ], L4 _8 |3 y0 v0 @3 b: Y% g
  4. {
    / i( w0 q9 B# T; l
  5. //CDC_Transmit_FS(SendData,64);3 x( s0 J$ n9 G0 p+ S
  6. if(StartFlag ==0)# D' T# e0 m2 p: O9 _+ n0 l
  7. {
    ) R& F! u+ R  ]* s( w2 |3 E" v
  8. StartFlag =1;
    ' I! _* b+ g& ~
  9. }
    9 X1 G) p3 d7 W
  10. else
    - K5 i3 g5 I* m
  11. {
    9 F1 s7 l; K$ f- T
  12. StartFlag =0;
    5 j7 o* x2 }$ h
  13. }
    2 n' R% n; ?5 G4 A* ~4 K
  14. }
复制代码

# V( v/ ~. S4 m另一方面,我们定义了一全局变量 StartFlag,它用来标志是否循环从 Device 端向 Host 端主动发送数据,其值由外部按键控制。然后在 Main 函数内的 while(1)循环内添加如下测试代码:
# ~: T+ \* ^1 ~5 U8 `
  1. /* USER CODE BEGIN WHILE */
    4 W2 T- O' v$ O7 u
  2. while (1)
    9 C/ n* R+ _4 h. T
  3. {
    / A/ J2 h1 A( ?- z5 O3 A3 d
  4. /* USER CODE END WHILE */2 N+ {5 e9 D$ ?9 [6 f" ?
  5. /* USER CODE BEGIN 3 */& R* l( r4 j+ g2 n: O
  6. if(StartFlag ==1)+ a- w0 F2 ^' Y4 z& p# x  p
  7. {% M/ `6 W5 q: b/ n9 k
  8. if(hUsbDeviceFS.dev_state ==USBD_STATE_CONFIGURED)
    ' h1 S+ p% u( Y/ `
  9. {
    , M5 _; ^9 e6 j& D* \$ z  e" Y
  10. CDC_Transmit_FS(SendData,60);
    7 R! d2 j* ~- `9 E9 H
  11. }2 `# N, K* R, ^: D8 }  g( b
  12. }
    . _* V/ `/ @6 ?1 U9 G& S- Z
  13. }
    % X2 w9 [  W7 c4 T) m6 c0 o
  14. /* USER CODE END 3 */
复制代码
8 K$ N, V2 T8 r
只要 StartFlag 标志为 1,在枚举结束后则不断向 Host 端发送数据。6 \6 y/ c, m" Q* ]5 g
% w3 z( W, V1 y$ f4 T
4.4 验证结果
2 a! A8 w. w! P. S9 P2 w4 s, E在编译完并将代码烧录进 MCU 后,我们首先验证 PC 端通过串口工具发送数据的情况:
. a6 m- q  ~% l" Q( J( {' E) L$ V# J; X# R( f
852$)9C[OPG}`0NNN4WF]$P.png
1 _( e" M7 {- f" @1 B# |4 R1 g' a2 D* i& S1 \9 F
如上图所示,串口工具发送 63 个字节到 Device 端后,能够接收到从 Device 端返回到的一模一样的数据,这说明发送与接收都是正常的。
, W. t* E1 C' _4 \9 ~3 I& ^* }
5 V# A$ Y" W. \' @, v4 X" D0 Z D1A~2Y0B]]I4EK(]`Z$TZUE.png
3 Z+ c- p$ e* v1 Q; b, M0 {3 `' t" E( i8 \7 w& |+ W* G
在按下用户按键后,串口工具能够无限收到来自 Device 端的数据。$ D% R' J8 o1 f1 H

- j  J  ^& \; v: ~( L [XBEEB]A6(5]T{%PWQE4`PU.png
4 e9 ]1 J# o# [% _( j9 N" j- O( a0 x0 w/ n
收发同时进行也是正常的。至此,USB CDC 设备端的收发验证均未正常。, u* ]: J1 u: y9 J2 N* B' |
' X" l- D, N: f6 O
2 P0 h& Y3 W& t8 U+ K& E
4.5 注意事项: ~$ X2 G5 X. t
 STM32CubeMx 默认生成的工程在发送 64 整数倍数据的时候 PC 端收不到,这个问题已经在之前的文章中有所描述,这里不再重复。
* M' C0 r) k0 R4 k- W3 L" v* P CDC device 端若无限向 PC 端发送数据,若 PC 端没有及时读走数据,导致 PC 端接收缓存爆满,此时 PC 端回复NACK,此时会导致发送返回 BUSY.7 I2 P% @0 H1 M9 i- t

: ^% b/ j" `6 {+ g
, j2 }, G* G: Z3 A
收藏 2 评论0 发布时间:2022-3-3 22:19

举报

0个回答

所属标签

相似分享

官网相关资源

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