STM32 USB HID键盘例程
+ a% V$ s n' ^8 p$ e
) I2 \4 R; A7 b3 o, P6 k最全USB HID开发资料,悉心整理一个月,亲自测试
& }1 q0 m/ K# |* i4 E
" X" n7 l$ }; z) J' S) K: O通过STM32CUbeMX建立USB HID的双向通讯实验成功' {" `- M7 b8 k G) V/ V* i. W) s
9 y" Q" P# U+ X' x4 `: B
' `: u5 `( C$ ]发现很多人对STM32的USB通信很感兴趣。要将USB的通信协议搞懂确实是一个比较漫长的过程。但是USB的HID通信无论是上位机的设计还是STM32程序的编程都非常的简单。只是我想很多人都不知道而已。这篇文章的目的是让大家以最短的时间将USB加到你的设备中。如果想学得更深就靠大家。8 ?2 A* |, } o
$ \, J$ J5 l$ Z
HID只是适合低速传输,其理论上可以达到64KB/S,但多由于windows系统和硬件的关系一般达不到这个传输数度。但这个速度对于一般系统的控制和数据传输都已经足够了,而且是免驱,省去了很多麻烦。如果您需要高速传输可参考我的另外一篇文章《STM32的USB例程修改步骤》文章在4 b" D* ?- z! z$ D, w# T$ v
& ]5 j7 M4 `" o
一、安装完MDK后请打开C:/Keil/ARM/Examples/ST/STM32F10xUSBLib/Demos路径,将Custom_HID在同一个目录下复制一份,如果你要放到其他路径你需要在MDK Options for target的C/C++中添加USB的头文件路径(MDK下的/INC/ST/STM32F10x/USB)。& }% f# G8 \2 d9 y* t$ ]
. p; p6 n+ v& V& C
二、打开usb_desc.c文件,该文件主要包含的端点描述符、设备描述符、配置描述符和字符描述符等。具体请大家参考其他资料了,这里主要说几个常用。
3 ~7 B/ s, b$ ~3 V) t2 w: y
" m2 ^( s3 Y: f5 ^' _/ |( b" ]' lu8 DeviceDescriptor[SIZ_DEVICE_DESC]为USB设备描述符。当中的
S; U2 W8 a b2 X: H$ F0x83, /*idVendor (0x0483)*/' M4 v$ F q7 L9 \1 R" z+ U2 h
0x04,5 }. m; Y$ J0 I0 d4 e
0x50, /*idProduct = 0x5750*/& U' ]4 z" _/ U! P0 _
0x57,2 _: H, }& J6 N+ W
//idVender字段。厂商ID号,我们这里取0x0483,仅供实验用。* X9 D- E. o" M4 w
//实际产品不能随便使用厂商ID号,必须跟USB协会申请厂商ID号。5 X$ V3 K+ k5 E6 j p( A
//注意小端模式,低字节在先。5 r2 y& e! |+ P9 Y6 `' F
//idProduct字段。产品ID号,我们这里取0x5750。, s1 X* S/ M5 ? D I5 \4 [ R
//注意小端模式,低字节应该在前。
" A) ?1 ^1 C9 }2 N; D1 q+ y5 m% V1 i, S/ O9 X+ Y& n
4 |: c6 p3 N8 d" s9 Z+ {2 G+ ^
const u8 ConfigDescriptor[SIZ_CONFIG_DESC]是配置描述符,注意如下
0 J8 s9 V/ a# e3 U- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */# U, `: a, j% d5 j& g
- 0x81, /* bEndpointAddress: Endpoint Address (IN) */. i4 V0 W* X" `! n
- 0x03, /* bmAttributes: Interrupt endpoint */' i6 B1 Y; v2 F0 F( R. U
- 0x02, /* wMaxPacketSize: 2 Bytes max */' L. ^* v/ I2 K3 Y4 K
- 0x00,
! V9 @) L9 a; G, g* e - 0x20, /* bInterval: Polling Interval (32 ms) */8 {0 e3 a# r2 G3 L9 @$ n
- /* 34 */
$ |/ v6 j) p2 T) }8 E - 0x07, /* bLength: Endpoint Descriptor size */
: R2 e5 ^4 m8 F7 s3 {$ Q - USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
1 d) S$ E$ q5 g, s& n - /* Endpoint descriptor type */
" M, |; v( h1 p3 y0 f6 i$ y; { - 0x01, /* bEndpointAddress: */- |6 B( G/ J6 x0 P. E. |7 w3 K
- /* Endpoint Address (OUT) */
& i9 a; V7 ?% M! I - 0x03, /* bmAttributes: Interrupt endpoint */2 D5 B: G6 ]# v
- 0x02, /* wMaxPacketSize: 2 Bytes max */
' E9 K9 t p8 E/ W1 a - 0x00,* E, H9 |% {7 J" H/ L1 `$ k" r1 e
- 0x20, /* bInterval: Polling Interval (20 ms) */
复制代码
2 Y2 i, {5 k% a& |1 C上面包含了“输入端点描述符”和“输出端点描述符”。
8 t4 |/ t: `- j//wMaxPacketSize字段。该端点的最大包长。0 P2 ^4 s! p, C) o& U
//bInterval字段。端点查询的时间,
! s8 M2 I3 R S9 n# }3 G' g7 g
1 \" O0 Y2 T' J' |8 J5 q+ e为了实现更高速的通信我们修改如下:; h8 [2 R& i5 Q
- /******************** Descriptor of endpoint ********************/) p# E2 _# |- @. d5 d/ V
- /* 27 */
e5 y5 N% }6 J/ ?+ F - 0x07, /*bLength: Endpoint Descriptor size*/) {3 V0 [6 A/ u/ Y
- USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/
} O. J0 C! K9 o7 h' F1 p& X2 E$ p - 0x81, /*bEndpointAddress: Endpoint Address (IN)*/
, f" I2 n; _. ?0 o- O3 T! G - 0x03, /*bmAttributes: Interrupt endpoint*/
7 I5 Q8 l* U0 U - 0x40, /*wMaxPacketSize: 64 Byte max */
5 I3 I* b. U! t( ?$ Z5 h. k" o/ ? - 0x00,
- {' l& {4 W0 T$ K2 V9 O3 d# j - 0x0A, /*bInterval: Polling Interval (10 ms)*/
$ f+ S8 x5 _6 w( T1 a - /* 34 */2 a3 z; \' |: o0 |# @. i, {
- /******************** Descriptor of endpoint ********************/
/ F$ T4 S, _9 N4 E2 f - /* 27 */
; g8 u) b5 U6 A h$ K# h/ F - 0x07, /*bLength: Endpoint Descriptor size*/
: L% b& G' Q3 g# {4 _, M5 R - USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/' b" ?1 f4 R: a# Q4 _6 v' _! D
- 0x01, /*bEndpointAddress: Endpoint Address (OUT)*/; z" @& B9 u# h; D
- 0x03, /*bmAttributes: Interrupt endpoint*/
% b6 {3 M. P; _" {9 G% T( I - 0x40, /*wMaxPacketSize: 64 Byte max */1 E) n3 C O, t6 P2 F! L; |0 ^
- 0x00,* X$ V U3 A( z! z7 b
- 0x0A, /*bInterval: Polling Interval (10 ms)*/! ]# U. b0 Z) G9 E$ j! E
- " p* K( v/ g& n- A! x% b/ r
- const u8 ReportDescriptor[SIZ_REPORT_DESC]为HID专用的报告描述符,具体的大家就参考资料了,这里可以直接复制了。
* P* H0 q0 Y, S5 L - const u8 CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =
: u" y$ C" [8 r6 ~ - {) \+ @/ H, V0 O. ]9 ?. A! E( v
- 0x05, 0xFF, // USAGE_PAGE(User define)
) I: [8 ^! }% n7 r1 u% _ - 0x09, 0xFF, // USAGE(User define)
. k1 W/ E/ ]) G- F) R( Z9 s - 0xa1, 0x01, // COLLECTION (Application)# B5 B0 H8 r0 ]6 ~) m" _" n
- 0x05, 0x01, // USAGE_PAGE(1)
0 B j; V0 [* r - 0x19, 0x00, // USAGE_MINIMUM(0)
5 U) q7 ?- C( e7 R& T1 J. _ - 0x29, 0xFF, // USAGE_MAXIMUM(255)/ }9 q E& i2 _# F8 G; N: k1 U
- 0x15, 0x00, // LOGICAL_MINIMUM (0)- H/ `# W' _7 W$ N, S
- 0x25, 0xFF, // LOGICAL_MAXIMUM (255)7 r7 B& x+ I8 [6 {; V
- 0x75, 0x08, // REPORT_SIZE (8)1 O1 a, Y. H1 p6 r m, g# w4 Q
- 0x95, 0x40, // REPORT_COUNT (64)
/ q5 r/ \, g3 j% s' t: @ - 0x81, 0x02, // INPUT (Data,Var,Abs)- ?, c/ W/ h" K7 \
- 0x05, 0x02, // USAGE_PAGE(2)
4 p9 h; B6 w( g3 f+ S - 0x19, 0x00, // USAGE_MINIMUM (0); H1 \5 I! O5 |' h% Q
- 0x29, 0xFF, // USAGE_MAXIMUM (255)8 E& X& C+ f5 l$ |" t8 J2 A8 Q+ o
- 0x15, 0x00, // LOGICAL_MINIMUM (0)
0 }' f# N# o4 `/ ]/ ~ - 0x25, 0xFF, // LOGICAL_MAXIMUM (255); P! |: s2 ^/ C2 W. ^. |9 q
- 0x95, 0x08, // REPORT_COUNT (8)7 t U( q# t/ e6 S- m* t% m
- 0x75, 0x40, // REPORT_SIZE (64)5 W& b% j( U& K
- 0x91, 0x02, // OUTPUT (Data,Var,Abs) ]* k0 Y: \, r) k
- 0xc0 // END_COLLECTION
2 ]8 c3 W! G3 r' }! i6 R5 A9 p, h2 V - }; /* ReportDescriptor */! x9 [9 z/ U1 L: J+ @
9 j" h# R$ p- q- const u8 CustomHID_StringVendor[CUSTOMHID_SIZ_STRING_VENDOR] |% A: Z0 e* X9 ]& Q# d
- const u8 StringProduct[SIZ_STRING_PRODUCT]5 k' d+ x7 a3 D! B2 R" N
- const u8 StringSerial[SIZ_STRING_SERIAL]
复制代码 1 Q3 \ P2 A$ p8 r8 m- j* [
分别是“厂商字符”、“产品字符”、“产品序列号”,这些将在USB HID设备加载的时候显示。但是这需要这些字符要求为Unicode编码,你需要将你要显示的字符先转为Unicode编码。最好大家还要根据各个数组的长度修改如下定义。; A: l ]/ y$ `* D s' c3 ~+ f
$ T' \- L$ |; w# u( x- t" |
- #define CUSTOMHID_SIZ_REPORT_DESC 39. `6 I* [6 V4 R9 V! a* x: C+ |) e
- #define CUSTOMHID_SIZ_STRING_VENDOR 642 b. O& o3 q7 b* v' U; V
- #define CUSTOMHID_SIZ_STRING_PRODUCT 286 ]2 D% b6 \8 M: s
- #define CUSTOMHID_SIZ_STRING_SERIAL 26
复制代码
+ U9 t8 r+ o2 o0 Z, L4 T三、打开hw_config.c文件,将那些没有的函数删除,只保留如下函数
( Z. i, Q% w- j6 \7 ` La) Set_System(void)
$ f( l. A+ p. ~; ~$ Sb) void Set_USBClock(void)
. a* G; T, k5 k1 d; @3 m) xc) void USB_Interrupts_Config(void); ~7 V& f3 W/ t+ Q
d) void USB_Cable_Config (FunctionalState NewState)9 L- q' P' R2 {# N6 f: K& T
特别要注意最后一个函数,其主要作用是控制USB的上拉电阻,让电脑检测USB设备是否连接的。$ \5 s1 y; K& P% I
1 X5 c8 O0 G9 J
) G7 s+ c' I9 G$ z! b, ?四、打开stm32f10x_it.c文件,把EXTI15_10_IRQHandler等中断内的代码删除。& Q d6 A; C7 R/ R' E/ E
打开usb_prop.c文件,修改如下:1 }; P( M* A- K% t
- void CustomHID_Reset(void)
" D; f- `" Q |+ v6 O% ` - {
5 r% V) ]3 U- _3 y' D% @6 l - /* Set Joystick_DEVICE as not configured */* }$ @0 z9 @/ u, l. @* y- R; ]
- pInformation->Current_Configuration = 0;
$ _6 g. `, v R# X9 y4 x! l1 n - pInformation->Current_Interface = 0;/*the default Interface*/
; e! t1 v0 A5 P3 j' p6 W, Z( t - SetBTABLE(BTABLE_ADDRESS);1 f" D' K. U2 Q. t, }1 C
* ~/ H( G! ?4 t3 `( m/ o4 Y1 A. i- /* Initialize Endpoint 0 */
% T8 k- ], C% d: C( d0 V - SetEPType(ENDP0, EP_CONTROL);8 X" D/ I y# W; j
- SetEPTxStatus(ENDP0, EP_TX_STALL);4 H2 ]6 K3 G% v% q6 b! q! T7 K0 V8 A7 {
- SetEPRxAddr(ENDP0, ENDP0_RXADDR);
5 @+ K& y, D8 h6 b - SetEPTxAddr(ENDP0, ENDP0_TXADDR);
" f: b* q, F6 B3 F - Clear_Status_Out(ENDP0);
- G1 U5 `2 h7 c H+ j* C+ K/ ?) | - SetEPRxCount(ENDP0, Device_Property.MaxPacketSize); x) Y/ H) p, q, a1 h7 `
- SetEPRxValid(ENDP0);: l0 d8 T( ^% G( m
* m9 t/ `% P5 i" Y- /* Initialize Endpoint 1 */
, M+ S% ]7 s5 _& r0 m( j - SetEPType(ENDP1, EP_INTERRUPT);
# Q! f) {: C* h2 ~( F; n7 r - SetEPTxAddr(ENDP1, ENDP1_TXADDR);5 P) e% [8 N# K
- SetEPTxCount(ENDP1, 64);
+ I2 f/ {8 A; R# t - SetEPRxStatus(ENDP1, EP_RX_DIS);
9 z) b: z) o' T; ]: N7 d - SetEPTxStatus(ENDP1, EP_TX_NAK);! i- x& m! k; T9 Z* r: ^& X& D
- * K" i; W( K7 D' T+ G7 T5 l
- /* Initialize Endpoint 1 */
3 t; R, u1 X5 i, p! Q2 w - // SetEPType(ENDP1, EP_INTERRUPT);
/ n; V' L8 Z) v5 o - SetEPRxAddr(ENDP1, ENDP1_RXADDR);
3 ?( ?2 H: q& o& f - SetEPRxCount(ENDP1, 64);
8 p. [' g- }. P( F, E$ F( i/ p - // SetEPTxStatus(ENDP1, EP_TX_DIS);, _# W8 m0 L; X& {8 C" s. R
- SetEPRxStatus(ENDP1, EP_RX_VALID);/ ^# |$ X/ U \% o Y
- /* Set this device to response on default address */: h$ _4 p0 }) H) ?
- SetDeviceAddress(0);9 l% f- Y1 Z4 f# q) `/ D7 T' z1 K1 e
- }
复制代码 8 G6 }& e: f( D, b5 `; {
五、usb_endp.c文件9 V1 G7 v/ N6 D, z
- void EP1_OUT_Callback(void)
9 Z- ]5 \5 p# t0 P4 r - {
& q. `4 r9 [- t. s$ Z/ N# A - 这些写接收代码
# y, X( T, X/ Q - }
复制代码 ! X& v6 R# m# S
六、数据发送和接收,举例说明
7 G! Z1 O5 ^& _7 z+ ~6 e1、数据接收( u% _( _' V4 Z
- u8 DataLen;
3 V7 B- n& `- m) `; Y* H# ? - DataLen = GetEPRxCount(ENDP1);
( X- d8 l% {2 [, d - PMAToUserBufferCopy(TX1_buffer, ENDP1_RXADDR, DataLen);1 E8 ~) k% b) `* }! J4 e' c
- SetEPRxValid(ENDP1);
/ P0 q) H1 ^8 k3 L5 p - USART1_Send(DataLen);% U2 O: I$ c- Y" z" s3 b' S
- count_out = 1;
复制代码
. v: E& T1 n. N+ d; K& ~( V) Q2、数据发送) v' H6 D5 z/ @: W, T
- UserToPMABufferCopy(InBuffer, GetEPTxAddr(ENDP1), 64);
0 C2 ?- J9 {4 r, {; }) {; c* r - SetEPTxCount(ENDP1, 64); # _0 a# E8 _* `& O) _
- SetEPTxValid(ENDP1);
复制代码
- f% T& R9 y1 L6 _! v5 x; @如果你发送数据较为频繁,每次发送前应使用GetEPTxStatus(ENDP1)检测上次发送是否完成。如果端点状态处于EP_TX_VALID,说明发送未结束,如果端点状态处于EP_TX_NAK,说明发送结束。, a# G2 R) j! L( { Q* y) U2 e: h L2 o
& S" g! \3 C; C7 `, v
% b5 i) @4 X2 f1 Q7 F(by xidongs)
! z5 B) D7 o0 [9 p: P v5 D d |