STM32 USB HID键盘例程! ~9 V; S% Y' q# n) h0 ^2 k
& {" X- O& q% t2 ^% f& \$ r
最全USB HID开发资料,悉心整理一个月,亲自测试
$ u& n6 L7 C u) c
- p6 W2 W1 V z6 \4 a M通过STM32CUbeMX建立USB HID的双向通讯实验成功2 C* ]! P. f/ u. l0 E3 L
0 _% W. H- ?# u' {2 n: G+ h9 h! n, Q9 D$ A- W3 S8 o
发现很多人对STM32的USB通信很感兴趣。要将USB的通信协议搞懂确实是一个比较漫长的过程。但是USB的HID通信无论是上位机的设计还是STM32程序的编程都非常的简单。只是我想很多人都不知道而已。这篇文章的目的是让大家以最短的时间将USB加到你的设备中。如果想学得更深就靠大家。0 u e8 F- v* F8 c7 `3 P8 l, n
* h& Y9 z4 y3 X2 W- t
HID只是适合低速传输,其理论上可以达到64KB/S,但多由于windows系统和硬件的关系一般达不到这个传输数度。但这个速度对于一般系统的控制和数据传输都已经足够了,而且是免驱,省去了很多麻烦。如果您需要高速传输可参考我的另外一篇文章《STM32的USB例程修改步骤》文章在9 o0 V7 \* }9 y- a Z K$ i! G/ L
1 q9 j' C5 T; O
一、安装完MDK后请打开C:/Keil/ARM/Examples/ST/STM32F10xUSBLib/Demos路径,将Custom_HID在同一个目录下复制一份,如果你要放到其他路径你需要在MDK Options for target的C/C++中添加USB的头文件路径(MDK下的/INC/ST/STM32F10x/USB)。% |/ S2 C; v- l3 _
( f: m x5 ^" {$ q. c' x/ U9 o二、打开usb_desc.c文件,该文件主要包含的端点描述符、设备描述符、配置描述符和字符描述符等。具体请大家参考其他资料了,这里主要说几个常用。
+ c3 `/ [% B% x
* [: s( X/ A! a3 v5 Gu8 DeviceDescriptor[SIZ_DEVICE_DESC]为USB设备描述符。当中的. y7 I% ^ F+ Z. } l: a
0x83, /*idVendor (0x0483)*/
/ M& l v/ l6 T A. f! @0x04,- Z$ | u6 C I3 o+ ~3 c
0x50, /*idProduct = 0x5750*/
3 m, Y2 e" [+ ]# U, u$ P0x57,
! c% D1 M& I5 [$ v$ P! H$ U) t//idVender字段。厂商ID号,我们这里取0x0483,仅供实验用。7 X" H! q5 ]9 L! B- ?. J" a7 n
//实际产品不能随便使用厂商ID号,必须跟USB协会申请厂商ID号。
* Y! E8 X5 z9 O9 v- N//注意小端模式,低字节在先。
2 z+ P/ R9 F8 D; G9 p//idProduct字段。产品ID号,我们这里取0x5750。
8 F4 b. R( l0 J; a" p4 j2 r//注意小端模式,低字节应该在前。
7 U$ c/ ~& x+ B
, v% W: K. K0 n e7 w$ t9 U$ N( R4 X# e R% @# H4 M+ `- t' V
const u8 ConfigDescriptor[SIZ_CONFIG_DESC]是配置描述符,注意如下: k, Q' m- ?6 K3 N3 K! C& W
- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
5 E N% c7 i# n. g7 a8 ^8 R - 0x81, /* bEndpointAddress: Endpoint Address (IN) */
, v; X! H/ F1 } Q/ B3 a - 0x03, /* bmAttributes: Interrupt endpoint */
9 R/ @" C* g9 j5 r' t7 N - 0x02, /* wMaxPacketSize: 2 Bytes max */
, s" d) g" W8 g - 0x00,
9 k% N" h- l- l) p0 x8 C - 0x20, /* bInterval: Polling Interval (32 ms) */4 x9 P* g" M1 V
- /* 34 */7 ?: h( E* N+ t
- 0x07, /* bLength: Endpoint Descriptor size */- U+ `" d4 @3 Z$ @& x8 ` R5 Q
- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */+ P5 }# e6 |2 l! b& L! z) ?
- /* Endpoint descriptor type */, \% n m" T/ |6 ?
- 0x01, /* bEndpointAddress: */6 V0 y+ J! j5 n# ]* A
- /* Endpoint Address (OUT) */
' a5 ~% m& G9 ]6 D - 0x03, /* bmAttributes: Interrupt endpoint */+ N' _2 u; |& c, h; O4 k( w4 B
- 0x02, /* wMaxPacketSize: 2 Bytes max */8 S# E: X" A, `
- 0x00,3 m5 o0 E8 \% e8 N" Z% ?9 j
- 0x20, /* bInterval: Polling Interval (20 ms) */
复制代码 ! j- K7 L: E0 {/ T9 J9 A
上面包含了“输入端点描述符”和“输出端点描述符”。
# C( \4 `9 i! \8 K% i* Z//wMaxPacketSize字段。该端点的最大包长。
5 \2 Y p( l% K; X' [//bInterval字段。端点查询的时间,; N- g) m- ~+ e
7 M& B: C* H1 h* g; N; o
为了实现更高速的通信我们修改如下:
" T6 X' \; J+ N1 Q- /******************** Descriptor of endpoint ********************/
1 S4 z ]7 F3 p* a - /* 27 */
4 w4 g/ c4 u& V$ r9 a% G - 0x07, /*bLength: Endpoint Descriptor size*/
% f t/ j# K" Y- J# Z( w5 \ - USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/
: s, B7 ^2 M' V1 \2 X; a - 0x81, /*bEndpointAddress: Endpoint Address (IN)*/
b6 l/ W: J1 F7 f0 l8 J* }+ w0 V' m - 0x03, /*bmAttributes: Interrupt endpoint*/
) `3 v2 T7 h* |3 M0 V0 c: {- A - 0x40, /*wMaxPacketSize: 64 Byte max */
6 h* o% v9 g) K: V+ @; F - 0x00,' m1 b! i ?9 a7 j& A
- 0x0A, /*bInterval: Polling Interval (10 ms)*/+ E( t$ P! X2 V
- /* 34 */5 C$ X) l$ X! @/ d& C
- /******************** Descriptor of endpoint ********************/
3 v7 q' D9 b. T# \ - /* 27 */6 o4 B! _( D. `: s$ B: t
- 0x07, /*bLength: Endpoint Descriptor size*/) J* h) l' K+ Z; q h& j
- USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/
5 Q( H3 g& N% ~7 V$ V - 0x01, /*bEndpointAddress: Endpoint Address (OUT)*/
$ X9 B [. {# n' F q) Q# p - 0x03, /*bmAttributes: Interrupt endpoint*/
$ p0 |9 }. H- A5 `4 | - 0x40, /*wMaxPacketSize: 64 Byte max */' z; f* b2 U; u# u* ^
- 0x00,
5 I7 J# a+ K! J: R5 o/ j4 K0 N - 0x0A, /*bInterval: Polling Interval (10 ms)*/4 b% r! d( B7 R& [' T g! A
7 e/ k K0 A/ j' |- const u8 ReportDescriptor[SIZ_REPORT_DESC]为HID专用的报告描述符,具体的大家就参考资料了,这里可以直接复制了。, U( ~: b. I0 L; r3 R8 {5 G
- const u8 CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =6 E" F, B* x; J" H
- {
, g, \5 x8 {9 ^6 E# T7 a( u( n# O - 0x05, 0xFF, // USAGE_PAGE(User define)4 s+ e1 X0 a: A3 E
- 0x09, 0xFF, // USAGE(User define)
! y" P: D9 ~7 j8 O; f, U - 0xa1, 0x01, // COLLECTION (Application). A* f% i8 U7 \# d' d
- 0x05, 0x01, // USAGE_PAGE(1); c1 g: H5 }) M$ R" K6 e p- |1 h
- 0x19, 0x00, // USAGE_MINIMUM(0)7 T+ A V1 f: M
- 0x29, 0xFF, // USAGE_MAXIMUM(255): q2 z6 Q) y6 M, S
- 0x15, 0x00, // LOGICAL_MINIMUM (0)
2 u6 _3 R: J2 \ t( X6 J7 x - 0x25, 0xFF, // LOGICAL_MAXIMUM (255)
- }9 ?+ l6 l0 V" w7 z' ]' C - 0x75, 0x08, // REPORT_SIZE (8)
* [+ ?( s( {( g+ l) I - 0x95, 0x40, // REPORT_COUNT (64)
7 @! ^3 I) D/ r- L - 0x81, 0x02, // INPUT (Data,Var,Abs)+ @# s" d# w+ F# p: q
- 0x05, 0x02, // USAGE_PAGE(2)# L9 l9 x9 U# @( K
- 0x19, 0x00, // USAGE_MINIMUM (0)
1 ] W/ Q5 y6 j - 0x29, 0xFF, // USAGE_MAXIMUM (255)
! p$ c6 i) t9 Q; D- z4 C m$ A- B - 0x15, 0x00, // LOGICAL_MINIMUM (0)
' b s$ d( f; y( }0 D- \; q0 v - 0x25, 0xFF, // LOGICAL_MAXIMUM (255)
: \! q J O" U+ ` - 0x95, 0x08, // REPORT_COUNT (8)6 b# Z, ~6 j. }# ~* }- R' {
- 0x75, 0x40, // REPORT_SIZE (64)* t# `) u1 e, f# c: D9 m8 v
- 0x91, 0x02, // OUTPUT (Data,Var,Abs)$ g# \# g. Y& m/ U1 s4 e+ z# t& I$ B
- 0xc0 // END_COLLECTION& y6 ~( d9 V; t
- }; /* ReportDescriptor */
; U4 r2 M& M6 v8 g% c
+ Y, o# Y$ ]3 U0 f- S/ V/ V# ?; M- const u8 CustomHID_StringVendor[CUSTOMHID_SIZ_STRING_VENDOR]0 y4 W) O% S3 a! w& A
- const u8 StringProduct[SIZ_STRING_PRODUCT]
. }5 D- k' O2 I+ s( O - const u8 StringSerial[SIZ_STRING_SERIAL]
复制代码 , i0 C4 h! b1 g6 A' x+ |& i
分别是“厂商字符”、“产品字符”、“产品序列号”,这些将在USB HID设备加载的时候显示。但是这需要这些字符要求为Unicode编码,你需要将你要显示的字符先转为Unicode编码。最好大家还要根据各个数组的长度修改如下定义。
" x! D" h2 p. c# [/ {: U5 G9 {- B: }& h7 }" Q
- #define CUSTOMHID_SIZ_REPORT_DESC 39
$ i# G0 r1 D6 g1 m/ l( u# G6 {3 l% N- H - #define CUSTOMHID_SIZ_STRING_VENDOR 64
0 b/ }: w' I4 ~* V, \9 A8 b - #define CUSTOMHID_SIZ_STRING_PRODUCT 286 I& o2 V! S- `2 }2 r2 o/ s
- #define CUSTOMHID_SIZ_STRING_SERIAL 26
复制代码 , [, }- f' \ A) O, s1 D; [
三、打开hw_config.c文件,将那些没有的函数删除,只保留如下函数7 U& S# R5 ?/ P0 D9 n
a) Set_System(void)
$ Z) K4 l+ e$ @# C0 n. @9 _b) void Set_USBClock(void)
. E- |0 B8 @: Gc) void USB_Interrupts_Config(void)
1 |! t9 u, U; J( F2 ~d) void USB_Cable_Config (FunctionalState NewState)
9 U9 B* ^$ Y ~# ]3 }5 g4 u. W4 w9 F特别要注意最后一个函数,其主要作用是控制USB的上拉电阻,让电脑检测USB设备是否连接的。9 d: Y( P0 J; v- |( p" x# H
4 Z0 ^1 ]6 H( d% v* |* k$ ~
, q6 }( `% m) H8 D9 k# ~; Y/ S四、打开stm32f10x_it.c文件,把EXTI15_10_IRQHandler等中断内的代码删除。
6 ~6 P: _$ U) i* _6 G打开usb_prop.c文件,修改如下:
* |2 @8 l4 d# u% r- void CustomHID_Reset(void). j( C" W7 S! n9 H$ V' A% [* a Q
- {
' @& X% [+ ~& }; Z* ` - /* Set Joystick_DEVICE as not configured */
# k* H8 _6 k( q, m - pInformation->Current_Configuration = 0;
s) O4 `. F7 j( `: W% X - pInformation->Current_Interface = 0;/*the default Interface*/
6 B8 [! `# Z" X4 T' L) z - SetBTABLE(BTABLE_ADDRESS);
6 Z; b M% C! I
4 g' M4 {1 o% J( H! E' o+ I9 Q- /* Initialize Endpoint 0 */2 ~5 |( o, m2 c0 m# C3 D
- SetEPType(ENDP0, EP_CONTROL);& C. _) o0 o) C6 C
- SetEPTxStatus(ENDP0, EP_TX_STALL);
0 C1 s2 f# a; o& `2 E - SetEPRxAddr(ENDP0, ENDP0_RXADDR);
/ L) T% t' Y5 a. P. U. g - SetEPTxAddr(ENDP0, ENDP0_TXADDR);
* M4 g# L0 _: K: C: k - Clear_Status_Out(ENDP0);
. u" B+ \! n- @! J" {9 K' z - SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);# H. V0 G V! T; h6 R8 s7 a) L
- SetEPRxValid(ENDP0);$ h. x% N$ M! K0 E. v4 l
- ! B! O; A. c6 K# ~, |
- /* Initialize Endpoint 1 */
, I D, Y- [: q. [ - SetEPType(ENDP1, EP_INTERRUPT);
+ P' E4 {4 [3 Y+ Y3 E# u$ ?6 } - SetEPTxAddr(ENDP1, ENDP1_TXADDR);% m# C2 l. M- T( i& Z" J
- SetEPTxCount(ENDP1, 64);% S9 w. q$ r4 r+ X9 i8 ]- l( y
- SetEPRxStatus(ENDP1, EP_RX_DIS);# z T1 u- ] y( M+ C( G4 C
- SetEPTxStatus(ENDP1, EP_TX_NAK);5 {* ]# {8 ]6 b9 z
X0 Z9 J5 S) v: o- /* Initialize Endpoint 1 */
$ u2 `3 d" g! _ - // SetEPType(ENDP1, EP_INTERRUPT);! ]9 P8 d: J* f0 w( t* }# b3 k4 R5 |1 C
- SetEPRxAddr(ENDP1, ENDP1_RXADDR);
, U* v! B: c3 w$ P - SetEPRxCount(ENDP1, 64);
' `8 w( D) a, q- y - // SetEPTxStatus(ENDP1, EP_TX_DIS);
/ [0 c3 D) c$ Y" P9 z - SetEPRxStatus(ENDP1, EP_RX_VALID);7 G: \% q! O! }) S' W& j* Y
- /* Set this device to response on default address */# e4 h0 ]* c9 l! e0 K) c
- SetDeviceAddress(0);
' r; s9 V9 M* j' J3 k, |8 i, h' Q) Y - }
复制代码 - L9 V2 X6 A$ t# Q% k$ {' p* O1 k( Z
五、usb_endp.c文件7 w9 u6 v- D! ?1 D6 h
- void EP1_OUT_Callback(void). u8 w D( k" [2 S% T+ O
- {, J$ p- m, z" H
- 这些写接收代码
* \9 D# r6 A( N. O - }
复制代码
! U* V# ]4 K$ j. g六、数据发送和接收,举例说明
9 Z' K Y! N5 D. h1、数据接收
+ N$ t) D+ v0 R8 E0 c- ^- u8 DataLen;, ]- L% R* I& N1 b! @. u8 t
- DataLen = GetEPRxCount(ENDP1);' l2 V! d2 w8 w! R3 `5 L
- PMAToUserBufferCopy(TX1_buffer, ENDP1_RXADDR, DataLen);' V/ H7 V$ b1 _+ P
- SetEPRxValid(ENDP1);) L4 Z, w& k9 O8 [2 Y
- USART1_Send(DataLen);
9 a5 e0 i) b) c# K" ? - count_out = 1;
复制代码
2 E5 j1 f' T' k7 M' V# M2、数据发送
: c- h" Z2 u6 j6 a& g7 `' y9 S- UserToPMABufferCopy(InBuffer, GetEPTxAddr(ENDP1), 64);
! E9 N+ S4 [7 m3 I - SetEPTxCount(ENDP1, 64);
7 W) D( a/ W4 D. K3 w - SetEPTxValid(ENDP1);
复制代码
9 z- B, v8 n% g" @% [如果你发送数据较为频繁,每次发送前应使用GetEPTxStatus(ENDP1)检测上次发送是否完成。如果端点状态处于EP_TX_VALID,说明发送未结束,如果端点状态处于EP_TX_NAK,说明发送结束。
* p- Y/ c* Q4 i; C3 s& C4 V. f; y0 F) t3 Q
2 U9 o. G& {0 c4 F0 \
(by xidongs)
5 ^+ I% {: G ?. n, D4 A' U |