前言. O. N# W T& t3 D( |
理解 STM32F103 上 USB 模块的端点资源,灵活在应用中的配置。6 `5 B2 y/ \9 S" Z8 n8 I
) J; P- }" b: B9 h$ Y+ D: R
问题# s2 q" x {/ j2 w. I/ [
某客户使用 STM32F103 的 USB 模块做设备时和上位机 PC 连接时碰到一个问题:PC 端驱动已经固2 U$ ^- Z, q% P% f+ c
定好,是对下位机 USB 设备上的地址编号为 0x0A 和 0x0B 的两个端点通信,从 0x0A 端点读取数据,
- c. V+ f: e+ i! @, Q; }: C向 0x0B 端点写数据。而 STM32F103 的 USB 模块只有 8 个双向端点,能否支持这样的寻址。2 V! K7 t3 A, Z. y! { N- l
8 w: K( c) J7 e7 Z& W$ z
1. 问题调研0 a5 g# n2 `" N
我们先来看看 STM32F103 上的 USB 端点资源。从 STM32F103 参考手册(RM0008)可知,一共有8 个双向端点,对应 8 个寄存器来控制其属性和表征其状态。如下图,可知每一对端点必须配置成相同的端点地址,这个地址位域是 4 位,取值从 0x0 到 0x0F 范围。# c- Y1 e$ R: _6 V* q0 e! S; }$ e
- _+ [# ?0 T0 R
0 B6 u* \0 L. Q# p
; Y/ l" }# o% q: H+ p# U 和以下摘录的 USB 规范符合:
! U1 R1 l8 j. q
# G9 g# Z7 m8 N5 @4 a
0 k2 o$ y+ r, A1 j/ S. r" C- y* I5 k, d! s
客户使用的是 STSW-STM32121(STM32F10x, STM32L1xx and STM32F3xx 全速 USB 设备库),那么应该修改哪些代码呢?) L6 d- T/ \2 J- T- t
2. 问题分析3 q; y, E, }; C- y- _
首先,USB 设备通过端点描述符向主机 PC 报告它所使用的端点有哪些:每个端点的地址(即 USB 规范里,以及参考手册的寄存器中规定的那 4 位地址域)、传输方向、传输类型、最大包长等。以STSW-STM32121 库中的 Mass_Storage 例程为例,需要把中的端点描述符做如下修改:0x0A 地址的端点作为 IN 端点(PC 从它读取数据),0x0B 地址的端点作为 OUT 端点(PC 向它写据)。) k6 N" E8 y7 n' U) y/ S+ Z
- const uint8_t MASS_ConfigDescriptor[MASS_SIZ_CONFIG_DESC] =7 ?; J6 j0 ] g1 ?$ e( D6 ]
- {
1 q. W# u1 a0 X7 Q - 。。。。。。% ?! R( i2 O+ i
- /* 18 */
/ z( H3 a- _2 n( ?2 v9 J' \( m' d - 0x07, /*Endpoint descriptor length = 7*/
: I& b" z' G9 |: l% v6 C& v - 0x05, /*Endpoint descriptor type */& b1 }# U; s" X
- 0x8A, //0x81, /*Endpoint address (IN, address 1) */% ?% f& M4 K9 m0 T0 V) S9 A
- 0x02, /*Bulk endpoint type */
4 @9 W6 n( r# F - 0x40, /*Maximum packet size (64 bytes) */
2 K, V1 @# D3 I" @" ? - 0x00,
4 x% c/ o+ i1 R! }, W - 0x00, /*Polling interval in milliseconds */
8 ^! Q% s S2 I$ L7 v& i! a - /* 25 */' l4 }" A! K: o' K- w
- 0x07, /*Endpoint descriptor length = 7 */
6 i4 r" @8 p! w: l0 R - 0x05, /*Endpoint descriptor type */! Q% x: k1 U0 |4 P
- 0x0B, //0x02, /*Endpoint address (OUT, address 2) */
% I# l' ~7 @* H. v$ D4 t9 e - 0x02, /*Bulk endpoint type */
9 r9 M W; D5 j/ q7 t - 0x40, /*Maximum packet size (64 bytes) */2 V5 _, |2 C8 ^7 I
- 0x00,
2 I+ w) u; r9 ? - 0x00 /*Polling interval in milliseconds*/
& r" y/ m& n4 Q1 v$ D# \ - /*32*/
5 O* }' B! A" }# @# r4 y; c/ _' a1 @ - };
# s# [" h7 ]& m- F. l, J
复制代码 0 Q4 C, h) x6 D7 Z* F+ S
接下来就是考虑使用 STM32F103 USB 模块提供的 8 个双向端点的哪个端点了。我们刚才从参考手册8 d" f% M6 D% Y' T% U1 e% d
关于寄存器描述的截图中看到,每一对端点具有相同的地址。在库函数里,对端点寄存器的地址位域; A% E7 j, [7 y
的操作在这里:1 ~. I' u# U2 z; p4 v
- void SetDeviceAddress(uint8_t Val)
- j r9 v- _: q* v - {6 O- q, ^; q3 h* T
- uint32_t i;
" D6 r6 M: ?1 ?, x - uint32_t nEP = Device_Table.Total_Endpoint;
: u- {, ^7 |6 g; ` - /* set address in every used endpoint */7 b+ u @$ Y$ E$ H1 S$ W
- for (i = 0; i < nEP; i++)
8 y2 N$ j/ _3 p - {- q$ w' ?( q5 m
- _SetEPAddress((uint8_t)i, (uint8_t)i);/ W! F) N9 y" p5 i; ^: r' k
- } /* for */
, U) @6 {1 z3 M% G* P# S# z# V+ x - _SetDADDR(Val | DADDR_EF); /* set device address and enable function */% X/ D; s: ^/ P( @8 {) v8 K+ c' v
- }$ q2 b" `& x5 o2 V4 f' L( |. X) }8 G
复制代码 这个函数的名称是“设置(USB)设备地址”,但是其中除了最后一句是在设置 USB 设备的地址,前面
' C) O" w1 x% o0 W; ~7 P; ~9 L! [: s的 for 循环是在设置该设备内的端点地址。$ y; z' a, G; f1 p( A C$ S3 U R' X' @
# K: {6 }1 j/ O" c8 u
从以上绿色标注的代码段可以看到,库代码固定给 1 号端点”0x01”这个地址,2 号端点”0x02”这个地址,以此类推。这里的”1 号”、”2 号”指的是端点的编号,对应的就是之前提到的 8 个寄存器的编号,即下图中的 n=0~7。n 在这里就是端点的编号。% Y. {7 P0 b& ^$ t: J; Q" C
* N) U9 w+ K7 o A+ q
* J2 }8 t( _ ~& ?* V/ ]) c( B% u
/ U- O$ A9 B; U8 A1 y
那么在这个应用中,需要用到地址为 0x0A 和 0x0B 的两个端点,但是端点编号最多只能到 7,因此需要修改库代码中关于端点地址设置的地方如下:这里,我们使用编号为 1 和 2 的两个端点。
8 E* Z+ A2 H1 q6 y$ k 为啥不用编号 0?因为编号 0 默认给双向 0 端点,即用于控制传输的 0 端点。
0 O, I9 K1 f9 V" h 为啥用 2 个编号?因为这里需要 2 个不同的端点地址,必须用 2 个编号。一个编号对应的 2 个端点必须共享同样的端点地址。
Y! {' V7 E6 o$ b- void SetDeviceAddress(uint8_t Val) o- B; y/ V- m; i; _
- {
& j6 d3 y) t2 b4 {! v7 E, w j$ g - /* set address in every used endpoint */
* C% ^) D- c* I" H9 D2 l - _SetEPAddress((uint8_t)0, (uint8_t)0);3 v( }# ?6 o& w9 a
- _SetEPAddress((uint8_t)1, (uint8_t)0x0A); // 1 号端点是 0x8A,即地址为 A 的 IN 端点
* C! ^8 s1 h7 z+ Q; \" z: V - _SetEPAddress((uint8_t)2, (uint8_t)0x0B); // 2 号端点是 0x0B,即地址位 B 的 OUT 端点
# L. n( W' X9 j: c5 K( i* U - _SetDADDR(Val | DADDR_EF); /* set device address and enable function */
6 a, Q9 v6 X& n# g - }
复制代码 既然这里指定了使用编号 1 和编号 2 的端点,那么需要在中设置这两个端点的硬件收发缓冲区地址
% e6 q* C$ h. j% ~ D( i- /* EP0 */
" q8 ~7 Y2 O/ ^, k: S$ H0 j5 _ x - /* rx/tx buffer base address */0 `8 q4 ^9 `; \8 ]
- #define ENDP0_RXADDR (0x18)
7 A. h* r) G' Q& f$ {1 ^ - #define ENDP0_TXADDR (0x58) M( G1 G! p4 u
- /* 1 号端点,IN 端点,发送缓冲区如下 */+ E+ G* J4 l. K1 m5 `( _& C
- #define ENDP1_TXADDR (0x98)
* n/ [" \ H* U& c - /* 2 号端点,OUT 端点,接收缓冲区如下 */
复制代码
0 S4 W1 q; P; o! c6 G# z% g$ P 当然如果你很任性,一定要使用编号为 6 和 7 的端点,也可以,那么代码就如下修改:& L/ I- d3 j: ` X" R- t
- void SetDeviceAddress(uint8_t Val)
- j6 K) k4 X1 N) n8 M3 y5 t0 z - {
2 I8 f1 A, ]7 x - /* set address in every used endpoint */1 r& g# |5 k# @! Q; O
- _SetEPAddress((uint8_t)0, (uint8_t)0);
, C% z! X: E: p0 K9 f9 I& ` - _SetEPAddress((uint8_t)6, (uint8_t)0x0A);
; g4 n1 A% g8 k) w, g - _SetEPAddress((uint8_t)7, (uint8_t)0x0B);8 g$ t4 O7 Q( n1 O8 i3 l
- _SetDADDR(Val | DADDR_EF); /* set device address and enable function */
3 f( G; g! D1 b" q! r0 }# i$ \ - }
复制代码 5 U8 v3 t6 g6 V
相应地,需要在中指明编号为 6 和 7 的这两个端点的硬件收发缓冲区地址。那么如法炮
2 |: T6 F: O5 y, \9 V4 ? 制做如下修改,就可以了吗?就可以了吗?就可以了吗?
3 s @! o* |/ `5 ~! l- /* EP0 */. l7 W1 {. V. J, U$ o* l$ Y
- /* rx/tx buffer base address */
0 K; ^' _+ U1 L* Q4 C: p) D6 W - #define ENDP0_RXADDR (0x18)" z* q' b" Z1 J6 d
- #define ENDP0_TXADDR (0x58)
7 d9 r, p8 c8 E( Q" w - /* 6 号端点,IN 端点,发送缓冲区如下 */
1 k* X6 X8 g3 R' B! z - #define ENDP6_TXADDR (0x98)6 x4 i- Q7 @" X. V, E; J6 ^" \
- /* 7 号端点,OUT 端点,接收缓冲区如下 */
2 A! V0 ?( ^% R - #define ENDP7_RXADDR (0xD8)
复制代码
, b4 z( T0 z5 m4 u 答案是否定的!以下的代码才 OK。欲知详情,请参考下一条应用技巧《STM32F103 上 USB 模块的
) g: \- B8 V- V, @/ R0 G 包缓冲区详解》
6 B v0 s; t2 R8 \- /* EP0 */
+ ]; k B+ V$ K: `6 N/ g - /* rx/tx buffer base address */9 ]+ G2 N2 h! {1 R! ~6 W
- #define ENDP0_RXADDR (0x40); d1 O: p- }- a& T
- #define ENDP0_TXADDR (0x80)
: N# v% Z2 g! R - /* 6 号端点,IN 端点,发送缓冲区如下 */
' ^: A9 p. D8 a4 ~7 D: A - #define ENDP6_TXADDR (0xC0)
9 w% m, J9 |- Z0 Z, j - /* 7 号端点,OUT 端点,接收缓冲区如下 */
复制代码
4 i; ~" U, w: X3 [; ?. Q" z: U3 q6 D, ^2 T
|