前言
+ X2 n0 W! \! i- L; W9 O理解 STM32F103 上 USB 模块的端点资源,灵活在应用中的配置。: c0 x* `( k( @) c3 e
, ~. u4 L% u: e- x& I7 F
问题; r* j a& {% ?3 `( t5 H
某客户使用 STM32F103 的 USB 模块做设备时和上位机 PC 连接时碰到一个问题:PC 端驱动已经固# c4 |9 p a1 b: {. X6 k
定好,是对下位机 USB 设备上的地址编号为 0x0A 和 0x0B 的两个端点通信,从 0x0A 端点读取数据,0 j8 n+ l6 K1 z( v& ]/ j2 K7 s
向 0x0B 端点写数据。而 STM32F103 的 USB 模块只有 8 个双向端点,能否支持这样的寻址。; u, F0 f+ s- B; y, p* y
+ T' |+ e! M O& Y1 @6 S5 P1. 问题调研
1 ~# j/ ~: `7 F6 g0 @% s8 _6 W 我们先来看看 STM32F103 上的 USB 端点资源。从 STM32F103 参考手册(RM0008)可知,一共有8 个双向端点,对应 8 个寄存器来控制其属性和表征其状态。如下图,可知每一对端点必须配置成相同的端点地址,这个地址位域是 4 位,取值从 0x0 到 0x0F 范围。& G0 D" H; e9 k% D9 z7 L3 n7 z
( B) k. x( l" K5 j$ V8 d
5 w/ ?$ G* f; k! k
3 K1 ~- }2 n/ {: W0 t% m 和以下摘录的 USB 规范符合:
* m( W) x4 V$ x. d, D& J! [3 [" O. p* A1 A, T9 R
. w$ p1 q& D5 ]: ?$ C. N! v/ k
1 U6 F* w0 |5 M" J3 h 客户使用的是 STSW-STM32121(STM32F10x, STM32L1xx and STM32F3xx 全速 USB 设备库),那么应该修改哪些代码呢?
3 x* w% `3 {" ?/ c }: i$ |' J1 o9 X2. 问题分析
5 U. O' C- j+ o7 i/ G# i 首先,USB 设备通过端点描述符向主机 PC 报告它所使用的端点有哪些:每个端点的地址(即 USB 规范里,以及参考手册的寄存器中规定的那 4 位地址域)、传输方向、传输类型、最大包长等。以STSW-STM32121 库中的 Mass_Storage 例程为例,需要把中的端点描述符做如下修改:0x0A 地址的端点作为 IN 端点(PC 从它读取数据),0x0B 地址的端点作为 OUT 端点(PC 向它写据)。+ M. h& O( m5 d( V" o; ^/ m
- const uint8_t MASS_ConfigDescriptor[MASS_SIZ_CONFIG_DESC] =. s9 |# g. I, }( o6 B$ ~& O
- { K' L R6 h B' |' v3 I
- 。。。。。。% n% ]6 b9 E1 r' I
- /* 18 */5 W, {/ D" Z, q
- 0x07, /*Endpoint descriptor length = 7*/
! ]6 d) V- U a3 A - 0x05, /*Endpoint descriptor type */
7 N6 X, v! L: V3 k5 { - 0x8A, //0x81, /*Endpoint address (IN, address 1) */3 H i! K- E5 n9 s
- 0x02, /*Bulk endpoint type */
& g+ D' z* ]1 ], U5 P9 j - 0x40, /*Maximum packet size (64 bytes) */
8 Q6 b( u5 v0 b$ C - 0x00,
# l* J+ n; g; }/ S+ `- \ - 0x00, /*Polling interval in milliseconds */
& T. X4 U! j- C - /* 25 */, k. S: W% m) f' [
- 0x07, /*Endpoint descriptor length = 7 */! {% i. _/ @; ?: v+ W
- 0x05, /*Endpoint descriptor type */
0 A4 V& R& A+ z3 o5 a( ] - 0x0B, //0x02, /*Endpoint address (OUT, address 2) */# i1 T8 Y& l' D/ p! `
- 0x02, /*Bulk endpoint type */, z8 x; h7 ]8 x% j5 P: P
- 0x40, /*Maximum packet size (64 bytes) */1 q7 H% [1 U m( {, k3 P7 u: z
- 0x00,: T& f/ {* |. R) t- f
- 0x00 /*Polling interval in milliseconds*/
9 a! U) e- ^$ u: `( D2 K - /*32*/7 M$ v! d$ f3 }3 q6 {1 Q: e* y8 ]
- };
! ]9 r$ }& P& k. z) S4 V
复制代码
* P, {4 _! ~2 G接下来就是考虑使用 STM32F103 USB 模块提供的 8 个双向端点的哪个端点了。我们刚才从参考手册# V7 @9 S5 I6 v+ p/ E7 P6 U
关于寄存器描述的截图中看到,每一对端点具有相同的地址。在库函数里,对端点寄存器的地址位域9 u/ b' r# H2 O
的操作在这里:
% M4 P' b- C) E- void SetDeviceAddress(uint8_t Val)
/ p) V' J9 p ~# E) l, ?7 ? - {
+ p" D' q' O- n5 S- c/ }) w - uint32_t i;( r4 I) t/ H; B5 X
- uint32_t nEP = Device_Table.Total_Endpoint;
8 z- E: ` w- o3 b; |/ Z2 r1 y - /* set address in every used endpoint */6 u! B8 q6 h; n
- for (i = 0; i < nEP; i++)
, b! ?8 o! P: q4 Y/ @6 Q - {
: r( v8 w' f( T$ i$ y - _SetEPAddress((uint8_t)i, (uint8_t)i);1 `, g0 y4 |* {0 k
- } /* for */1 }5 J9 t# y, L" l
- _SetDADDR(Val | DADDR_EF); /* set device address and enable function */
1 |5 p' Q3 g0 F7 b7 ~# t' Z! x - }
9 v4 L9 k9 m5 [$ `) k
复制代码 这个函数的名称是“设置(USB)设备地址”,但是其中除了最后一句是在设置 USB 设备的地址,前面% }4 D0 s: `- Y$ G
的 for 循环是在设置该设备内的端点地址。 f, V, u3 r, Q, L k
% [. x0 ] y" _0 A3 v' E3 l- \8 g 从以上绿色标注的代码段可以看到,库代码固定给 1 号端点”0x01”这个地址,2 号端点”0x02”这个地址,以此类推。这里的”1 号”、”2 号”指的是端点的编号,对应的就是之前提到的 8 个寄存器的编号,即下图中的 n=0~7。n 在这里就是端点的编号。
# E( A" `5 P$ q, O8 t0 J' X$ x! f. Y
* | [4 ]1 o& z1 I! Z$ ? \- |+ w3 n; c$ t1 N
那么在这个应用中,需要用到地址为 0x0A 和 0x0B 的两个端点,但是端点编号最多只能到 7,因此需要修改库代码中关于端点地址设置的地方如下:这里,我们使用编号为 1 和 2 的两个端点。
) ^1 z$ j2 x5 h% G 为啥不用编号 0?因为编号 0 默认给双向 0 端点,即用于控制传输的 0 端点。! V7 z4 _9 b3 e) N+ w6 U1 ]
为啥用 2 个编号?因为这里需要 2 个不同的端点地址,必须用 2 个编号。一个编号对应的 2 个端点必须共享同样的端点地址。3 E+ b7 B) Z6 W. B6 v
- void SetDeviceAddress(uint8_t Val)
" r- D0 l9 h6 j' H - {+ ` T5 p: r9 j2 {' R2 c( Q# N* ^8 M
- /* set address in every used endpoint */# i3 [) v ~! p
- _SetEPAddress((uint8_t)0, (uint8_t)0);
% B. u' P/ |: ?$ q6 i/ ]# [: U$ B - _SetEPAddress((uint8_t)1, (uint8_t)0x0A); // 1 号端点是 0x8A,即地址为 A 的 IN 端点# J; n) I8 \, o' h+ b
- _SetEPAddress((uint8_t)2, (uint8_t)0x0B); // 2 号端点是 0x0B,即地址位 B 的 OUT 端点, R2 j, G C2 g: g, ] [5 j+ y5 y
- _SetDADDR(Val | DADDR_EF); /* set device address and enable function */0 T3 z; [- G! o6 y6 S
- }
复制代码 既然这里指定了使用编号 1 和编号 2 的端点,那么需要在中设置这两个端点的硬件收发缓冲区地址
4 `- ?; v G7 `: t7 u" l- /* EP0 */
3 B( I8 G2 @- Y) `! }8 D' G6 }* b - /* rx/tx buffer base address */
- ?# w* p! l* ~. p) c2 C - #define ENDP0_RXADDR (0x18)
3 y# m) l" k! F1 [ - #define ENDP0_TXADDR (0x58)0 z$ K7 [8 ~% e5 s! j# X9 l
- /* 1 号端点,IN 端点,发送缓冲区如下 */
( A& q$ B* Q+ a. O8 `: ` - #define ENDP1_TXADDR (0x98)
& l" }8 t3 ]+ S0 ^4 F g7 D' g$ S$ z - /* 2 号端点,OUT 端点,接收缓冲区如下 */
复制代码 $ ^: I4 h% \+ C: b
当然如果你很任性,一定要使用编号为 6 和 7 的端点,也可以,那么代码就如下修改:4 G* G4 t0 v8 t4 G' o; o( F
- void SetDeviceAddress(uint8_t Val)
: N9 T) O% u$ l, j - {
I6 g: |2 x( }- I6 \ - /* set address in every used endpoint */& v5 Z; J, i& X9 u: `: o- n9 y7 n
- _SetEPAddress((uint8_t)0, (uint8_t)0);
4 L- P6 y Q1 v2 A) A# F - _SetEPAddress((uint8_t)6, (uint8_t)0x0A);
& N; K- A9 [ v* p - _SetEPAddress((uint8_t)7, (uint8_t)0x0B);& P0 t( U v; E% @& V4 _( b
- _SetDADDR(Val | DADDR_EF); /* set device address and enable function */
) j. F" w$ v$ j) G% g - }
复制代码
. V, |/ r' {6 S' z 相应地,需要在中指明编号为 6 和 7 的这两个端点的硬件收发缓冲区地址。那么如法炮 f p" q, B5 t X
制做如下修改,就可以了吗?就可以了吗?就可以了吗?
# V: d) o$ K2 g9 j3 M7 @$ o! y- /* EP0 */" D% \# W1 e! ?
- /* rx/tx buffer base address */
1 |( v" u0 m$ h, { A - #define ENDP0_RXADDR (0x18)
' Z. D e3 _3 Z' m - #define ENDP0_TXADDR (0x58)8 E; W; f, Z- E/ ^0 h; G
- /* 6 号端点,IN 端点,发送缓冲区如下 */8 ]* J" q* N4 C3 L( u5 `
- #define ENDP6_TXADDR (0x98)
2 O0 Q! |" k V7 P" w! u, [6 w - /* 7 号端点,OUT 端点,接收缓冲区如下 */
/ |4 X) Q# c4 K& d" Z' @ - #define ENDP7_RXADDR (0xD8)
复制代码 / a; W' k& A, Q% I. ~% B
答案是否定的!以下的代码才 OK。欲知详情,请参考下一条应用技巧《STM32F103 上 USB 模块的4 [, G: n+ {2 y. n
包缓冲区详解》( {/ v3 [3 l0 l D
- /* EP0 */% V) R4 e1 j" o$ [# ~7 S* D
- /* rx/tx buffer base address *// p: |( N. k. ^6 U/ y4 U' O
- #define ENDP0_RXADDR (0x40)
# P7 o% r5 J, @2 J. ]- V1 O6 k- a" U - #define ENDP0_TXADDR (0x80)) \; u* n" y6 c) I* C o$ z
- /* 6 号端点,IN 端点,发送缓冲区如下 */% H& x/ o; N; I' I, X
- #define ENDP6_TXADDR (0xC0)) |. ]2 \/ z5 k
- /* 7 号端点,OUT 端点,接收缓冲区如下 */
复制代码
2 r( u7 O J0 q, u/ a' P
h4 h* z) Z: I. |; o2 q |