前言
2 D% t8 \. w( {/ n% s( j0 k理解 STM32F103 上 USB 模块的端点资源,灵活在应用中的配置。6 ]' C$ Z5 ^6 q8 P# M
4 R i; V7 G$ Y _% A1 h
问题
8 K w' l% y. o; O1 a某客户使用 STM32F103 的 USB 模块做设备时和上位机 PC 连接时碰到一个问题:PC 端驱动已经固. y) s G8 c5 r# J# p
定好,是对下位机 USB 设备上的地址编号为 0x0A 和 0x0B 的两个端点通信,从 0x0A 端点读取数据,
1 ^: Q4 P3 k$ ~2 s+ [7 T; ^向 0x0B 端点写数据。而 STM32F103 的 USB 模块只有 8 个双向端点,能否支持这样的寻址。
( r% R' Q& Z& `9 _! ^2 }6 G# f9 @$ x3 O X
1. 问题调研
, V5 h6 Y& S3 ]' Q* H 我们先来看看 STM32F103 上的 USB 端点资源。从 STM32F103 参考手册(RM0008)可知,一共有8 个双向端点,对应 8 个寄存器来控制其属性和表征其状态。如下图,可知每一对端点必须配置成相同的端点地址,这个地址位域是 4 位,取值从 0x0 到 0x0F 范围。# t& j; Z, [7 |& r6 a+ @
. q& \6 s1 c6 g
- d/ ?$ ^0 q/ k; J8 U0 K: d; G3 b& X+ y7 p+ \( P' q
和以下摘录的 USB 规范符合:
& b* c; H0 \; }' W" G3 ^
1 {! k8 m: `3 L+ o. f
/ z1 L# V# b1 ?% j+ U% N- }
; D: E/ H3 p0 Y' H- i: P4 [9 g1 U 客户使用的是 STSW-STM32121(STM32F10x, STM32L1xx and STM32F3xx 全速 USB 设备库),那么应该修改哪些代码呢?
4 a- \% `# W, ]8 x# j, _+ t u2. 问题分析
: W" W- p4 D O3 o0 L, M 首先,USB 设备通过端点描述符向主机 PC 报告它所使用的端点有哪些:每个端点的地址(即 USB 规范里,以及参考手册的寄存器中规定的那 4 位地址域)、传输方向、传输类型、最大包长等。以STSW-STM32121 库中的 Mass_Storage 例程为例,需要把中的端点描述符做如下修改:0x0A 地址的端点作为 IN 端点(PC 从它读取数据),0x0B 地址的端点作为 OUT 端点(PC 向它写据)。% K# m# F: ?, x, `& ~
- const uint8_t MASS_ConfigDescriptor[MASS_SIZ_CONFIG_DESC] =
k1 x) [. p( ]) l( u7 J$ H - {
+ b0 [4 v& J7 |. ?4 i# z - 。。。。。。
7 _9 ~1 S7 x9 M7 A - /* 18 */+ y5 w6 n% x- ]! T% c0 P
- 0x07, /*Endpoint descriptor length = 7*/! t U7 P9 k' x/ ^* O1 m
- 0x05, /*Endpoint descriptor type */
5 Z; |3 l5 f; g0 g% B4 i - 0x8A, //0x81, /*Endpoint address (IN, address 1) */
/ g3 E5 A0 N8 I( c+ d - 0x02, /*Bulk endpoint type */
# A! n- Z. p' Q1 u% k3 u - 0x40, /*Maximum packet size (64 bytes) */9 T6 g, ]% r/ Z5 I3 T1 ]$ o
- 0x00,' M e% {6 y# i% I! k% `# C) z
- 0x00, /*Polling interval in milliseconds */
/ ]/ U8 }7 ~8 J/ O0 ? - /* 25 */( ]8 z6 z$ N4 k# ]
- 0x07, /*Endpoint descriptor length = 7 */
1 T* j8 A9 O6 K, G3 a - 0x05, /*Endpoint descriptor type */
4 M3 `0 P' d, o: M$ a8 Q, H - 0x0B, //0x02, /*Endpoint address (OUT, address 2) */* U1 v7 Q2 ?3 s) t1 t6 K8 d+ p2 W
- 0x02, /*Bulk endpoint type */& {& S# [0 B7 s# q
- 0x40, /*Maximum packet size (64 bytes) */
$ u( |5 ~& n& c - 0x00,$ S6 p8 d5 T m! b/ V" Z, R$ D6 W
- 0x00 /*Polling interval in milliseconds*/
6 B0 p) t( X& c, J- H - /*32*/$ p+ y5 B# H1 d# T$ `" Z& m* @
- };
/ u2 m9 y& U6 @; r: R- v
复制代码 ! f# M2 K5 V3 ?' k0 K4 o
接下来就是考虑使用 STM32F103 USB 模块提供的 8 个双向端点的哪个端点了。我们刚才从参考手册
4 ]" k; o9 l' k0 X- T, B( g; U关于寄存器描述的截图中看到,每一对端点具有相同的地址。在库函数里,对端点寄存器的地址位域& i" |' G' q5 D5 T2 U0 @$ c1 {
的操作在这里:
4 g8 R7 Y3 e8 ~( ]* k& U, T4 D- void SetDeviceAddress(uint8_t Val)- p: H1 D. K, [* R
- {
+ u: t2 j9 G" g5 | - uint32_t i;
4 q1 `5 j9 ~6 h2 U! ]' W - uint32_t nEP = Device_Table.Total_Endpoint;% I w: ^' w! z* l% I5 W8 b( m& a
- /* set address in every used endpoint */1 B! k; ~9 @- T: F& W7 e" v8 g
- for (i = 0; i < nEP; i++)* k2 P# }! u0 q, r
- {5 U/ E! i9 M9 G: G
- _SetEPAddress((uint8_t)i, (uint8_t)i);
/ V# z$ Y, L# x* T - } /* for */
" f1 V* P& ]9 y - _SetDADDR(Val | DADDR_EF); /* set device address and enable function */& i% _" t! l2 ]/ q" _; O
- }& P0 ]0 ^) q) r* o6 Z9 i2 Z
复制代码 这个函数的名称是“设置(USB)设备地址”,但是其中除了最后一句是在设置 USB 设备的地址,前面2 Z0 f- i3 d% `7 z+ W3 g5 S- T
的 for 循环是在设置该设备内的端点地址。3 s1 m5 i8 {" k, J8 d) y
: u- \) y) A& K 从以上绿色标注的代码段可以看到,库代码固定给 1 号端点”0x01”这个地址,2 号端点”0x02”这个地址,以此类推。这里的”1 号”、”2 号”指的是端点的编号,对应的就是之前提到的 8 个寄存器的编号,即下图中的 n=0~7。n 在这里就是端点的编号。
8 c$ ^5 p; N. X: x3 O
* |% Q: c ~! P' H7 ~/ s
/ R+ e, I v- u9 b, B Q+ C- K1 Y( n; d! S+ }) s
那么在这个应用中,需要用到地址为 0x0A 和 0x0B 的两个端点,但是端点编号最多只能到 7,因此需要修改库代码中关于端点地址设置的地方如下:这里,我们使用编号为 1 和 2 的两个端点。# b# B+ T* r. |6 f- `. ^1 z* p. L
为啥不用编号 0?因为编号 0 默认给双向 0 端点,即用于控制传输的 0 端点。
6 j1 ~& X$ }6 k# b; X 为啥用 2 个编号?因为这里需要 2 个不同的端点地址,必须用 2 个编号。一个编号对应的 2 个端点必须共享同样的端点地址。
/ B, y- ~9 A3 r0 K( R( x- void SetDeviceAddress(uint8_t Val)! V. R' q; P$ P* a, h w) r# }& K
- {
6 C: F. y& R! Z3 O& M - /* set address in every used endpoint */9 L+ ~( W! P& l. b" V
- _SetEPAddress((uint8_t)0, (uint8_t)0);
3 p' u9 N7 o& A7 Y& t9 o# } - _SetEPAddress((uint8_t)1, (uint8_t)0x0A); // 1 号端点是 0x8A,即地址为 A 的 IN 端点
: `) L) L9 p$ k+ f1 ^# z# R3 d# { - _SetEPAddress((uint8_t)2, (uint8_t)0x0B); // 2 号端点是 0x0B,即地址位 B 的 OUT 端点
8 @. e2 p7 y' `4 s: o5 P& ~( ~ - _SetDADDR(Val | DADDR_EF); /* set device address and enable function */
% Y* u. a `; F. F ~ - }
复制代码 既然这里指定了使用编号 1 和编号 2 的端点,那么需要在中设置这两个端点的硬件收发缓冲区地址
8 g: r' S9 R; x! N# e+ O: u9 g- /* EP0 */: Q! s; o7 }( |7 V
- /* rx/tx buffer base address */
7 c) W7 N$ k! e9 E) ~& [! V1 @6 a - #define ENDP0_RXADDR (0x18)
) M6 |" J, W. Z( ?8 | - #define ENDP0_TXADDR (0x58)' C) m5 t2 l& D# ]0 I2 P9 @0 }
- /* 1 号端点,IN 端点,发送缓冲区如下 */+ }. i" L# h8 E$ O9 ]% a
- #define ENDP1_TXADDR (0x98)
* b+ Z- E3 [) W, G: ^* u - /* 2 号端点,OUT 端点,接收缓冲区如下 */
复制代码 & b: g; C- w# }) s2 U
当然如果你很任性,一定要使用编号为 6 和 7 的端点,也可以,那么代码就如下修改:3 g# B/ J' R& F6 h/ Z, Y3 t) P
- void SetDeviceAddress(uint8_t Val)- j. Y9 B6 C' q& M# j9 Q% `# K
- {
/ U. e0 t2 E# N - /* set address in every used endpoint */
' F& F6 D+ G: P0 K - _SetEPAddress((uint8_t)0, (uint8_t)0);
7 T& o: x$ R, K; K O% Q - _SetEPAddress((uint8_t)6, (uint8_t)0x0A);
; k3 y0 n8 P" D8 d- z0 G6 k - _SetEPAddress((uint8_t)7, (uint8_t)0x0B);
* U& N% I3 G' k6 U. g3 ~% z$ a0 I - _SetDADDR(Val | DADDR_EF); /* set device address and enable function */% a% `0 m- ]2 u& H9 i7 u
- }
复制代码 , f( K% B6 ?! v
相应地,需要在中指明编号为 6 和 7 的这两个端点的硬件收发缓冲区地址。那么如法炮% Z6 [8 [5 s: m4 S4 b8 ^* j( n) v
制做如下修改,就可以了吗?就可以了吗?就可以了吗?+ A/ g! E, m) F5 h+ `4 `
- /* EP0 */
+ x$ X: n& P) N0 D3 j; a F! B - /* rx/tx buffer base address */
* ]" O* E4 k1 v) u. p. h - #define ENDP0_RXADDR (0x18)- V1 n1 T6 B0 I/ c3 B( Q
- #define ENDP0_TXADDR (0x58)
5 f: |6 P7 Q! v; F. E - /* 6 号端点,IN 端点,发送缓冲区如下 */
7 T/ }7 X1 [' a4 |, m1 \* e" R+ ~ - #define ENDP6_TXADDR (0x98)8 u* J5 [! K' [5 r% L1 n2 r& h& w
- /* 7 号端点,OUT 端点,接收缓冲区如下 */" ]4 e% T, s- J6 }. E
- #define ENDP7_RXADDR (0xD8)
复制代码
6 T7 w9 I- s0 I$ d( k 答案是否定的!以下的代码才 OK。欲知详情,请参考下一条应用技巧《STM32F103 上 USB 模块的$ N- M* @9 H: B/ D+ [* q
包缓冲区详解》. G3 s) ]) {9 N9 G
- /* EP0 */1 ~- H* i- S* l6 P& ?9 } Q R
- /* rx/tx buffer base address */9 L0 u. c7 Q" k$ {/ ^; @1 J. v
- #define ENDP0_RXADDR (0x40)
7 d2 ^) m" F+ | - #define ENDP0_TXADDR (0x80) }+ P0 X& Q, s4 \
- /* 6 号端点,IN 端点,发送缓冲区如下 */! G) K5 f# Q, A. C( |% f
- #define ENDP6_TXADDR (0xC0)
5 D. Y/ w3 `; G; _1 H/ c S" N/ D& l - /* 7 号端点,OUT 端点,接收缓冲区如下 */
复制代码
3 [- z* K: ]# V
$ H) v" T, ~, G0 p |