1、基础知识/ ]5 l9 d+ M2 `3 q
等级划分:
- i& V% o) o. a6 D
6 b& l4 Y% v' K6 `" Q+ ?
2 `! M: ^; z" i% `! `0 l2 ~; n6 i4 \ S) l5 S( i+ N/ i
接口类型:
- h/ `4 s" D5 y# E6 N1 x! f7 X: z$ i" b- {% W' B
0 @* R7 D* W2 n; |' w
5 ]) K9 D1 p8 g& T Q' b7 J$ iSTM32基础型(F1系列)所带的USB是全速。
0 k: m& e7 Z3 Y6 A3 h0 K' h0 Y! ?0 g8 O3 o1 C+ e4 M c
2、电气属性
3 `* m- A' N3 PUSB的通信都是由主机发起的,这一点与IIC协议是类似的。
. k2 g4 [& S8 I& X9 k; ?2 |4 h+ p5 e# k6 M" |
2.1 数据线
- `2 ?) l% ]6 KUSB使用差分传输模式,有两条数据线,分别是:( _, D" ^, E, R4 Y
2 w5 \ p( P# KUSB数据正信号线,USB Data Positive,即USB-DP线,简写为D+
: Q! ?( [5 T. |: H$ \: FUSB数据负信号线,USB Data Minus, 即USB-DM线,简写为D-
: j L- ]8 y; g6 {5 w4 Q" o8 N2 A' k! S [/ D& U5 g. M
剩下的就是电源线(5V-Vbus)和地线(GND)。. N4 I3 X5 V5 ^- B ^; ?7 j. N
" v5 u+ \. Z: Q# a% _/ b8 U
2.2 USB主机是如何识别设备是高速设备/全速设备/低速设备?
# O( b0 j6 g; u0 C主机的D+和D-都接有15K下拉电阻。+ o7 Y# g( a9 u4 E) N+ C- ~
9 t, x+ o9 X& s; z0 q! @" R7 v% A全速USB设备的数据线D+接有1.5K的上拉电阻,一旦接入主机,主机的D+被拉高
1 s: ?! V* G) a5 A! _低速USB设备的数据线D-接有1.5K的上拉电阻,一旦接入主机,主机的D-会被拉高
) a% H& H6 C# g+ V1 ^4 F7 ^
! ]7 _5 n7 N4 B3 B7 s% ]6 ]/ {4 c% W* e
因此,主机就可以根据检测到自己的D+为高还是D-为高,从而判断接入的设备是一个全速还是低速设备。. e9 ~% Q# d8 H4 J: F$ z
f8 B! g" z1 {; r
所以你可以看到STM32板子上的USB口D+有一个上拉电阻,而且是必须有的:6 y! T. v9 B) r) j) O0 _
, r$ r$ B! p) V
! [: q. g4 {- f/ z- @
" O4 l9 {5 E. n; @' U' c
3、USB设备分类
: f9 Q# a8 z9 j
$ S8 O- Z1 V: A, s: @+ S
! f4 S9 b' \+ K* S+ H/ o/ P
" D5 f \1 r" \4 G3 ?+ a s+ k) |$ z看一个CDC虚拟串口的设备分类:
1 U$ \ G! _* [8 W
2 Z4 m$ P9 t6 t' Z; T$ H9 F
6 M% j& F5 j+ f/ x( w: h
% B. n# C. s8 O, ]" ^# g在看一个MSC的(模拟U盘):
7 g y3 p, F1 _' G" M+ [% R6 Z9 R
6 L; e7 k' B* [( _1 `
+ J2 j3 c8 Q5 g6 \( B" `
3 |4 c' u6 U! Y( ]" B2 U& z" T6 I不同的类有不同的用途;不同的应用场合对应不同的产品形态;不同的产品形态可能会有自己特殊的描述符,比如: HID类有报告描述符、CDC类有ACM、Union描述符等。
0 u! l) E/ u) @' W
( c1 P3 p4 ?) F; K( {4、描述符详解
3 _) o' Z3 @: ]以STM32里的MSC设备为例,MSC类所需要的描述符有:设备描述符+配置描述符+接口描述符(数量由配置描述符里的bNumInterfaces字段决定)+端点描述符(数量由配置描述符里的bNumEndpoints决定),所以MSC类结构就是这样的:
1 t9 m4 [& u; d* X
* p+ B ] q0 j4 `: U- 配置描述符) M- z9 H1 u- R; k
- {. t* [2 u% {; i; C% H
- 接口描述符1' W/ D! w/ C9 K* E3 _! c) m
- {
! T, B. H$ H2 d3 _ C - 端点描述符1( u& b' v. A* G$ p% Q2 X% u, S
- {
' s l" ~/ S0 n9 S8 a+ K - $ H. p) @6 P! e2 Q
- }
1 v5 k0 S* K+ Q5 b0 S3 e& ~2 P - 端点描述符2
4 g5 W' h5 q. } - {% y& ?# E* s# v5 N+ |$ d
- # B K X+ ?1 n, r$ k8 |
- }
) P) }" h' u. k8 j6 T6 R - }
! y- p% p/ { x - }
复制代码 8 g$ `8 ~: x% t- h( s( ?
而CDC类是这样的:
* x: Q O/ J- @: i# K& ~. r) F7 f& R2 m [4 e# {( e1 t
- 配置描述符5 X4 O7 e1 s6 t2 a: s, e
- {
1 }* _: A( I/ m0 w8 t9 m2 A - 接口描述符1(通信接口)
/ E- ?" c3 U7 \. o - {, W. J W3 R$ g8 o1 X; K6 p' r
- 其他描述符(特殊描述符): O. ]+ n9 \9 ~- N! T0 `. B7 v
- { a4 o6 o. C; { c4 l4 m; N
- /*Header Functional Descriptor*/; q& R q' m5 o% m
- /*Call Management Functional Descriptor*/
* U' U7 i/ u% a+ e - /*ACM Functional Descriptor*/2 b6 p* l: H6 ?0 [# ]
- /*Union Functional Descriptor*/
- X9 j; j- A7 o0 E: [5 d' B( ^ - }
: M; x' r( s4 g* r; o - 端点描述符(命令端点)
- G& n3 R+ {$ z! _! B - {1 A7 r7 n5 W# B( }1 o& X4 w& k
-
/ A- t9 a: r" W/ ?' F1 I - }/ x' P P8 ^' U' \3 T
- }
/ _ }4 V1 l7 E; Q$ v! m - 接口描述符2(数据接口)
# F0 q& L1 S% O* w9 g; e. N - {
# w' `! M+ d! N# c- F4 L - 端点描述符1(输出端口)
! y% q# m6 l% ]3 C0 n - {6 b& ]# m! W9 C+ q( D# f
- + g% p* ~( z E8 _8 l5 J
- }$ _3 a& q. x5 }3 M
- 端点描述符2(输入端口)
: Y5 y2 S. r) {6 | - {
# T, ~# f" Z9 [2 l$ S$ i - 4 H/ F* b* G( Y6 g% _; Z' _0 ^
- }
" Q1 V- c+ V; @# F1 D/ | - }
* S8 B" J6 P8 A* N; p8 y - }
6 F; J7 }7 ^7 o2 j
复制代码 5 o/ r: H) h# l/ Q6 @$ K
4.1 设备描述符
( A2 ]$ p& I1 y' A1 U每个USB设备都必须且只有一个设备描述符,摘取一下STM32的MSC设备里的实例代码:, [( o1 L* v4 T- |5 j8 s
& {8 l# |( R* V3 g( n. Y
1 ?/ i9 X4 P- t; Z; `
, D6 X. ^) ~$ I
每个字段的含义:4 m/ R) X% }( y9 b/ H" Y4 J
bLength:描述符大小.固定为0x12;! G- q; B& M" P; u
bDescriptorType:设备描述符类型.固定为0x01;8 N" P. H( _2 I# z* V7 b% Z
bcdUSB:USB 规范发布号.表示了本设备能适用于那种协议,如2.0=0200,1.1=0110;
" J* {! ]& b. Z( X. ~+ O) k7 YbDeviceClass:类型代码(由USB指定)。当它的值是0时,表示所有接口在配置描述符里,并且所有接口是独立的。当它的值是1到FEH时,表示不同的接口关联的。当它的值是FFH时,它是厂商自己定义的;% M# G2 [8 E; y1 P
bDeviceSubClass:子类型代码(由USB分配),如果 bDeviceClass值是0,一定要设置为0。其它情况就跟据USB-IF组织定义的编码;
' x6 a W; B3 V/ mbDeviceProtocol:协议代码(由USB分配),如果使用USB-IF组织定义的协议,就需要设置这里的值,否则直接设置为0。如果厂商自己定义的可以设置为FFH;
- P" W5 x! \% \% P/ R' I$ V/ c: ebMaxPacketSize0:端点0最大分组大小(只有8,16,32,64有效);
, `- `9 L: D1 l6 qidVendor:供应商ID(由USB分配);. x; }/ p+ ]3 w; P0 Y
idProduct : 产品ID(由厂商分配),由供应商ID和产品ID,就可以让操作系统加载不同的驱动程序2 f0 q! d$ U9 Q$ O/ b# F
bcdDevice :设备出产编码.由厂家自行设置;* O @9 r! G: {* l$ U
iManufacturer :厂商描述符字符串索引.索引到对应的字符串描述符. 为0则表示没有;" O f4 V5 H9 D' n" R
iProduct :产品描述符字符串索引.同上;
$ Y% v3 i; ^. }4 u+ H+ F, qiSerialNumber:设备序列号字符串索引.同上;
1 o- D( r5 E& f6 fbNumConfigurations :可能的配置数.指配置字符串的个数。) x, L2 K* q2 C- V/ f' L' L. X9 c
7 o8 W m$ ]$ m W- l6 T" u: i4.2 配置描述符. c* A9 t: j& m- Q- ?
配置描述符定义了设备的配置信息,一个设备可以有多个配置描述符。摘一个STM32的MSC设备的配置描述符:
( [) ?; @* `4 F
1 D" ?8 E* O( |# }/ C' W
/ H: Z5 z7 u8 f$ e; Y: X" k% l1 n; x5 F1 I
用C语言组合就是这样的一个结构:
: T6 w& M, f" D' q9 b- X9 {
% [4 @7 p( E" q2 }' Y$ V* R- typedef struct _USB_CONFIGURATION_DESCRIPTOR_& h( m6 k3 p9 w7 [6 G `
- {
) e4 m+ {( D8 S) P3 @8 Y - BYTE bLength,/ c6 M5 |1 d6 d2 T5 J! x& k
- BYTE bDescriptorType,
) y' I7 M% f7 d; r# |* P0 r - uint16_t wTotalLength,4 N' p+ E3 w/ W, _" Q$ Y5 m
- BYTE bNumInterfaces,
: T3 Q; E8 s' P- g$ H7 o. Y - BYTE bConfigurationValue,
* s* E$ s' g* v( H - BYTE iConfiguration,# f3 t; G1 C5 b4 f5 z0 [' L
- BYTE bmAttributes, g$ ^0 j1 a$ T
- BYTE MaxPower% i, M t* C3 y4 ]& B
- }USB_CONFIGURATION_DESCRIPTOR;$ I- U' D( i" d8 `
复制代码
/ C8 u4 B5 [1 z( V* G1 M, Z每个字段含义如下:
~" n0 {' f$ p. q5 q5 z) F4 jbLength:描述符大小,固定为0x09.
. F0 s4 Y+ A9 Z* _8 f! vbDescriptorType:配置描述符类型,固定为0x02.
3 o5 ]' {5 {3 b7 l& l z# ?wTotalLength:返回整个数据的长度,指此配置返回的配置描述符,接口描述符以及端点描述符的全部大小4 k3 i/ ~! }7 H
bNumInterfaces:配置所支持的接口数。指该配置配备的接口数量也表示该配置下接口描述符数量
' F, `' Q+ ?% \) q1 {bConfigurationValue:作为Set Configuration的一个参数选择配置值8 j5 e9 W; Z2 G% U- K
iConfiguration:用于描述该配置字符串描述符的索引! i( E" {6 E7 r& W$ ~
bmAttributes:供电模式选择,Bit4-0保留,D7:总线供电,D6:自供电,D5:远程唤醒
, P" \" S% M. l5 [( NMaxPower:总线供电的USB设备的最大消耗电流.以2mA为单位8 ^4 \5 J' x3 b$ F
" Q+ Z, d8 P- n1 G9 h4 g) R
4.3 接口描述符
6 G2 q* x/ F5 x9 U: b4 S+ S接口描述符说明了接口所提供的配置,一个配置所拥有的接口数量通过配置描述符的bNumInterfaces决定,摘取STM32的MSC设备类的接口描述符:, Q" P7 L2 Z! j2 [ }5 V
6 j) g6 H" r/ c6 k# {0 c( J. D4 L
6 }8 t5 J: R% f$ d6 B
" ] ?& D! T: K, a/ |/ V% E用C语言组合就是这样的一个结构:/ u; F2 E; S& Z1 |1 q! Z
: @% l z$ H5 J. c! [5 s- typedef struct _USB_INTERFACE_DESCRIPTOR_
* w3 h9 s3 M ~* S. I0 s% m) y - {
7 l9 v$ x J) K. K: \3 _! O - BYTE bLength,
. e$ m) _. R2 u( M8 U; r% d1 k - BYTE bDescriptorType,
5 h" x5 \. ?0 O+ _ - BYTE bInterfaceNumber,
H1 I! Z1 o' q" ~. Z - BYTE bAlternateSetting,' h4 a) X2 N7 O, p
- BYTE bNumEndpoint,
8 E+ [1 c7 k0 o9 O3 J- J - BYTE bInterfaceClass,6 b) Y% }; S0 d" p5 `" B3 a: i* }* x
- BYTE bInterfaceSubClass,
& ? w# e& A. ~( T- X - BYTE bInterfaceProtocol,
0 a. A9 k7 l+ |$ D$ [ - BYTE iInterface
8 U: T2 H" K. F* H3 f( _ - }USB_INTERFACE_DESCRIPTOR;7 K0 O( c+ t' ~4 u+ h
复制代码 - u$ O6 h: U- ]' J- k
每个字段含义如下:
( |/ B5 Q2 v8 R' _& M
5 p8 E+ e4 u3 n; b8 P2 b9 KbLength : 描述符大小.固定为0x09.1 b' ]3 Z* r/ z7 w {
bDescriptorType : 接口描述符类型.固定为0x04.% S# p% a! t) X' ]$ D0 P! U% P
bInterfaceNumber: 该接口的编号.
# e5 {, |# R6 U; LbAlternateSetting : 用于为上一个字段选择可供替换的位置.即备用的接口描述符标号.' u) m. S; y( l9 S% z( M2 `
bNumEndpoint : 使用的端点数目.端点0除外.5 }; T9 K4 j" l
bInterfaceClass : 类型代码(由USB分配).$ u6 }* u7 A$ j$ P: d6 d) C; o! [
bInterfaceSubClass : 子类型代码(由USB分配).0 A$ ~2 ~. |: |: @/ J( l5 v" m5 a
bInterfaceProtocol : 协议代码(由USB分配).# `, A/ x1 w- L" K8 m& H
iInterface : 字符串描述符的索引! E& F) I t4 x" u
9 c+ @7 Q8 W' L+ ?4.4 端点描述符
& {) `- r2 v- x1 TUSB设备中的每个端点都有自己的端点描述符,由接口描述符中的bNumEndpoint决定其数量。摘取STM32的MSC设备类的端口描述符: o/ c+ z5 Y q; e" k7 |/ r
& x# O) c+ f+ n6 }: X
& h& b. y8 X) w, X
' a" C8 p' K: Q+ J' p
用C语言组合就是这样的一个结构:
H4 J P# c* m/ w: w6 o6 K1 n9 e6 c$ |: s
- typedef struct _USB_ENDPOINT_DESCRIPTOR_
. r8 m0 ?' c" V! q - {
( {6 k0 t* f! \4 w" x - BYTE bLength,9 l- R: u# r8 T7 v* x, q
- BYTE bDescriptorType,) ]0 t' u+ o3 a
- BYTE bEndpointAddress,' y& m3 G5 S# p5 U& D5 l$ Z. i, x
- BYTE bmAttributes,
0 ?! I! Q+ X0 J; y- T. h - uint16_t wMaxPacketSize,
! C$ M/ N. c- r/ ^2 b/ B8 x - BYTE bInterval
- D0 f$ K6 V9 o; m8 @8 e+ s - }USB_ENDPOINT_DESCRIPTOR;6 j& O% I- p- s& b
复制代码
; Q: I+ ~! |- R+ e3 M每个字段含义如下:' _( R- W# I; w& [% x/ t4 u/ M
8 u$ e! E/ U: k! H$ d' \9 p8 d
bLength : 描述符大小.固定为0x07( W; o3 I, k6 U7 X/ R( T5 s$ x
bDescriptorType : 接口描述符类型.固定为0x05
% [/ ^; E& ?$ F/ s% zbEndpointType : USB设备的端点地址.Bit7决定方向,1为IN端点,0为OUT端点,对于控制端点可以忽略;Bit6-4,保留;BIt3-0:端点号
8 x9 H7 _8 Z) Q2 S0 }; ]6 EbmAttributes : 端点属性.Bit7-2,保留.BIt1-0:00控制,01同步,02批量,03中断8 n. R% o' x6 u" A2 o* h9 O; P
wMaxPacketSize : 本端点接收或发送的最大信息包大小
8 o7 \* D! {3 L6 JbInterval : 轮训数据传送端点的时间间隔.对于批量传送和控制传送的端点忽略.对于同步传送的端点,必须为1,对于中断传送的端点,范围为1-255
: A* ~1 e8 j0 h+ ~3 b3 P
( Y& K# `( O9 L( q. F- d+ |( C- d6 P6 `4 o
4.5 字符串描述符
+ R2 y1 w+ [: ?0 Q% O. |; s8 _字符串描述符是可选的,如果不支持字符串描述符,其设备描述符、配置描述符、接口描述符内的所有字符串描述符索引都必须为0。
: s# p, W- h, P4 h+ q' r; S) H' t4 n9 y3 L8 G6 J3 I1 N- L# `
字符串描述符结构如下:" P6 W& S- L( [1 s
8 Y7 l' Q d; s+ `- typedef struct _USB_STRING_DESCRIPTION_! f1 J3 I* `. M u, Q" Q# m7 w- p
- {( d1 a$ h% I- w7 d |; L& R
- BYTE bLength,
' A) ?6 z+ p. ~3 Q/ ~) a - BYTE bDescriptionType,
1 w* b2 r! s5 Q1 D4 | - BYTE bString[1];0 M8 q# u4 I E5 R8 Y+ B+ z i
- }USB_STRING_DESCRIPTION;
复制代码 6 Q7 x/ \/ H! j' \/ S2 A, e9 X
各个字段含义:4 D6 a8 C) M- H6 m# ?* C
% i7 a5 l3 i) z+ J6 g3 WbLength : 描述符大小.由整个字符串的长度加上bLength和bDescriptorType的长度决定.
k* J/ H. U z0 @: l6 obDescriptorType : 接口描述符类型,固定为0x03.+ C1 ^' X0 w. s/ V5 N
bString[1] : Unicode编码字符串* x7 ]% q- s4 Q0 u1 @# N
4 J; v4 _. a( e& [1 E
4.6 IAD描述符
8 O9 p7 N6 h8 R) `: b2 |0 _USB组合设备一般用Interface Association Descriptor(IAD)实现,就是在要合并的接口前加上IAD描述符。例如你想用一个硬件USB接口实现两个功能,又能到U盘又能当虚拟串口,那么在USB配置描述符中就需要加上IAD描述符来指明。; ~0 J( v+ P! k$ f/ V
2 {/ e) L% \. T' J
- typedef struct _USBInterfaceAssociationDescriptor
7 D& E7 w" j7 U1 E, W k; L - {
1 I2 M- b9 t. j j5 U/ r - BYTE bLength: 0x08 //描述符大小,固定
+ V4 f% V3 U6 T& A. Y - BYTE bDescriptorType: 0x0B //IAD描述符类型,固定
$ u" b+ C" C* k/ W) q. C& {5 T+ W0 Z - BYTE bFirstInterface: 0x00 //起始接口编号
7 W8 I& y4 x4 z+ v4 A - BYTE bInterfaceCount: 0x02 //本个IAD下设备类的接口数量5 t4 I U% O* p
- BYTE bFunctionClass: 0x0E //类型代码,本个IAD指示的是什么类型的设备,例如CDC是0X02,MSC是0X08
0 N/ S% z, V& I* `5 s2 \9 o: ?) l - BYTE bFunctionSubClass: 0x03 //子类型代码( Q- E! s* P) x J5 U, ~. I
- BYTE bFunctionProtocol: 0x00 //协议代码3 q: S# m0 J6 b+ I5 R5 f, H
- BYTE iFunction: 0x04 //描述字符串索引
+ T! g+ _* v7 ?6 M - }
复制代码
2 W) ~( T" A8 g5 W4 d以MSC+CDC为例,他的配置描述符结构就是这样的:
% n" c' K% s2 w6 u* J
$ s% @5 L9 e( l9 M5 r3 i/ a* G- 配置描述符' ~$ W1 d) g& { m! I8 t
- {) ?7 u# i4 `! D
- IAD描述符1(CDC)
# w' C* q" ?0 c1 {( c" [ - {7 d3 ~4 b9 I' a4 K2 t" `: H
- 接口描述符1(通信接口): P/ \) o- `# X
- {
# v3 b0 g, y6 m4 O9 y - 其他描述符(特殊描述符)( B. D" P+ w$ Z7 {' c
- {! u! W/ |, X" @5 c. Q
- /*Header Functional Descriptor*/% Z3 B! c/ s: B8 L
- /*Call Management Functional Descriptor*/+ ?0 Z5 |7 ~, D9 C$ g1 J" e
- /*ACM Functional Descriptor*/
% ^5 ?# v; A4 K5 o8 I9 y: a - /*Union Functional Descriptor*/7 a- @& a8 ?6 T6 P: y' g+ ~) F
- }* x8 c n! G6 w% n* K3 g* w; K
- 端点描述符(命令端点)& q1 v% u, X( N' T
- {: T- @1 _2 g1 i* \
-
7 ~4 A1 ` ^ e6 q* l: j) c - }, S! e7 ~) K( O' [7 T0 g6 @
- }
# \, d) Y9 Y) b% }# ~& L4 U6 Y - 接口描述符2(数据接口)
+ X o- T4 T- _2 i- c) x5 I - {
" E7 k8 M0 ]1 ] - 端点描述符1(输出端口)0 x3 g; c9 {0 O; I2 `
- {! [5 Z6 F& A# b8 n8 L* n
- ( M0 Q" e& a& ~
- }
, i6 J: S' z9 f9 j& S0 z - 端点描述符2(输入端口)5 a7 q, k% K4 I v' P
- {
: b0 r3 Y' _, n) ~: {6 Z -
; ^, t; {# }) ]- Y0 h* H - }
- `5 n, a, t' |2 S - }
7 I2 E/ _- r# _/ \0 d - } O1 [/ ]. i& U9 Y2 J4 U
- f# Z0 ~; C+ _5 d( I" x- IAD描述符(MSC)
s$ {* ]0 g6 `: ^ _" Y( Q% [* { - {$ l6 P- f" J2 ?! E1 D U2 ?( A
- 接口描述符1
1 g* i# L: }0 g - {
8 j X Y J3 _& t \ - 端点描述符16 k) {0 B8 F9 r9 t) O
- {
8 `& f V% y7 y8 @" k -
" J6 c, K$ ?: ?+ h - }: s8 p% C' x6 W. w* c
- 端点描述符2/ S9 c* u2 l" q" W* x
- {
( L5 ~5 @) w# Z+ h& M! d -
( i+ \0 }( r9 U - }
- n2 n) |0 D$ ?& f$ Z7 K - }! T2 ]% g4 ]0 T* u
- }( ~$ ?( J$ P) A6 I- }2 @
- }
复制代码 ) V' I( T8 S. I( ^' F$ a; ^/ Y, V
5、STM32-USB详解
`( E# i& ~4 w. S1 Q3 N/ r
/ t. S0 D8 E) W
; ~8 i& I# X' {& Z! v4 `% h. ~
# A x O. Q4 i4 g6 f( e. i- [这个512字节SRAM叫做Packet Buffer Memory Area(简称PMA),这个很重要,后面会详细讲解。4 Q* q* `; |$ B7 c& w
' v- d$ s& o6 V2 h9 ? @ w
D* A1 g! [- w. v* y+ `" s: R( Z3 L: {2 j% J3 C; d
根据描述可以,一共有8个端点,16个寄存器,一个端点关联两个寄存器,所以我们可以将他们规划为8个输入端点(0x80-0X87)+8个输出端点(0X00-0X07)。
% X+ D, i8 q" j" a9 ]! W7 o0 y3 H# m7 n: V& L, |4 u* L
6、STM32-PMA详解( O j' [* w1 ]$ h& _
先说一下USB的数据包大小,全速设备的最大包大小为64字节,高速最大为1024字节。- z: m) m5 }& k1 s0 Z* q% s
A8 a6 ~0 ]$ `& ^+ {5 }$ y
Packet Buffer Memory Area(简称PMA)
5 k2 x. p" ?. p/ G! Z' m
6 l/ [3 ~) O' i+ dSTM32F1/F3/L1系列都有且结构相同(其他系列暂未考证),译过来就是包数据缓存区,大小为512字节,按2字节进行寻址。
: M: @4 X% ]. U' i- }# Y+ k- \ s9 S% P9 p. w& W
这个PMA的作用就是USB设备模块用来实现MCU与主机进行数据通信的一个专门的数据缓冲区,我们称之为USB硬件缓冲区。
( W8 ]* Q" Y, g% r+ {3 c& V7 u5 M, U" g/ y# x' C$ D
说得具体点就是USB模块把来自主机的数据接收进来后先放到PMA,然后再被拷贝到用户数据缓存区;或者MCU要发送到主机的数据,先从用户数据缓存区拷贝进PMA,再通过USB模块负责发送给主机。% H8 _% I* h- E! a
& d0 ^2 q- K* R$ b
很多人利用ST官方的USB库修改自己的USB应用时候卡住,获取改完之后懵懵懂懂出现错误,估计大多数原因就在此处的修改!3 Z0 i' N# D: @6 i
/ a- X5 J% } R' C摘取一下STM32F1参考手册里的PMA描述表:( v$ A3 x7 L0 H+ b" y# V) T
: Y1 i9 f3 L* }' |. B& \
) b. [0 a% G6 Y
; Y+ Y' o" [, F# t' p/ q2 }" i
名称含义:" a% t/ s" K7 m
/ ^% z: U7 x2 j% }
ADDR0_TX:输出端点0发送缓冲区地址9 A; a& _6 y3 f
COUNT0_TX:输出端点0发送缓冲区大小
0 h4 y7 N3 \- g9 t4 B3 vADDR0_RX:输入端点0发送缓冲区地址
. ~# |/ p. o$ U. s# XCOUNT0_RX:输入端点0发送缓冲区大小0 X5 U# Z7 U) t9 [. [8 q& T1 D
; C: ]( y/ \7 M
可以看到一个完整的端点描述包括:缓冲区地址+缓冲区大小。
9 F' a. X* Q4 S& k8 w3 N; T- y r5 J6 x! S7 D
PMA的头部为端点的描述,每个端点占8个字节,实际使用了几个端点就有几个描述头,例如使用了0、1、2这三个连续端点(这里注意是连续端点),那么PMA头部的3x8=24(十六进制的0X18)字节就是描述,倘若你使用的是0、1、3这三个端点,其中编号为2的端点虽然没使用但是占用空间,那么PMA头部的端点描述就是4x8=32字节,编号为2的端点8字节的空间就浪费了(严格来说没浪费,就是不方便使用)。, h3 {5 \* m$ v$ U- d- v/ z( [
+ [/ f! J; Z0 Y4 P% V* j头部的端点描述之后就是各个端点的缓冲区了,例如使用了0、1、2三个端点,占用了PMA头部3x8=24字节的空间,那么这三个端点的缓冲区地址就是从PMA偏移24字节开始的,当然只要是大于24就都可以,这里就是最关键的地方了,很多人修改ST官方库实现自己USB应用时候就是没改这里的地址,导致缓冲区的使用覆盖了PMA头部的端点描述从而出错!
; n8 E. p" y6 t; M
0 w9 d: H& l- x& s, m下面摘取一个STM32官方MSC设备的实例进行分析:6 s% \, m4 M5 x) |0 E+ l
% Z, A' m6 d; _% G
1 D5 B r8 C8 _* y0 T% l! [" g7 Z
$ c9 c5 k' U/ V
可以看到一共用到了4个端点,分别是输入端点0X80和0X81,输出端点0X00和0X01,其中0X00端点和0X80端点是供USB使用必须有的,0X81和0X01端点则是MSC设备输入输出端点。% F: b1 W* h7 l; e2 a
; r, k+ a) M% c& k& `$ [0 O( ]
那么一共使用了4个端点,按理来说PMA头部的端点描述大小应该是4X8=32(十六进制的0X20)字节,0X20之后的才是各个端点缓冲区,但是ST这里的却是从0X18开始,也就是说使用了三个端点,这个地方我还没有搞明白为什么,欢迎各位补充!- f1 v- F4 ?+ h* S1 v
) V( Z9 R5 P' @% U; O
至于为什么0X18之后是0X58,是因为USB全速设备的最大包是64字节(十进制的0X40),所以这里PMA的划分就是:, ?0 \$ h) d- L' ~8 G
; r2 z. q v4 {' ?% k' W/ ?头部0X18字节为各个端点的描述
! c* j1 [* @' |6 z9 e0X18地址开始的64字节为输出端点0的缓冲区$ b# K! }! q0 e; l! f
0X58地址开始的64字节为输入端点0的缓冲区
8 {9 \- Z" A5 B( k7 E" d0X98地址开始的64字节为输出端点1的缓冲区 D! O5 ^; J# p" H9 e1 G
0XD8地址开始的64字节为输入端点1的缓冲区
+ j5 v" `& C6 l1 F+ D8 W. c( q6 f, \4 `$ T! F3 }' Z) @
7 X1 j- N _) p2 O' x) ^& P
这里注意一点,缓冲区分配好之后访问是不会溢出的,也就是说缓冲区之间完全隔离。
' g& `4 L3 Q+ v8 r2 V% F, Q
1 ^- |/ ^/ v9 d! y7 w4 k |