STM32 USB HID键盘例程
: y+ {9 M3 K! k; P! h4 P
. X$ R) K7 z$ h: w) ]1 p最全USB HID开发资料,悉心整理一个月,亲自测试
3 c# G) ~, ^* }$ e
# _6 \% ]! a) @8 Z% g3 X% c' I- p通过STM32CUbeMX建立USB HID的双向通讯实验成功, ?6 m3 R7 z X& y- |$ q
+ w4 y. M: F" \9 ~% k# ~" _6 U& R" \1 [" n" d
发现很多人对STM32的USB通信很感兴趣。要将USB的通信协议搞懂确实是一个比较漫长的过程。但是USB的HID通信无论是上位机的设计还是STM32程序的编程都非常的简单。只是我想很多人都不知道而已。这篇文章的目的是让大家以最短的时间将USB加到你的设备中。如果想学得更深就靠大家。
5 j4 h: j& Y7 s- e" j$ |* P0 a
6 g3 N! v' U% }2 p( VHID只是适合低速传输,其理论上可以达到64KB/S,但多由于windows系统和硬件的关系一般达不到这个传输数度。但这个速度对于一般系统的控制和数据传输都已经足够了,而且是免驱,省去了很多麻烦。如果您需要高速传输可参考我的另外一篇文章《STM32的USB例程修改步骤》文章在1 ]- r) D. M7 y" ]
9 S# }: {0 C2 Q( y2 ~一、安装完MDK后请打开C:/Keil/ARM/Examples/ST/STM32F10xUSBLib/Demos路径,将Custom_HID在同一个目录下复制一份,如果你要放到其他路径你需要在MDK Options for target的C/C++中添加USB的头文件路径(MDK下的/INC/ST/STM32F10x/USB)。/ ~6 y. M4 M0 O. h& L- J
0 z& t2 l, J) G% p0 ^9 f二、打开usb_desc.c文件,该文件主要包含的端点描述符、设备描述符、配置描述符和字符描述符等。具体请大家参考其他资料了,这里主要说几个常用。/ |3 E9 j( A- y/ R" U( s5 V% S8 Q
' h# j: v( V9 i! h: }# Lu8 DeviceDescriptor[SIZ_DEVICE_DESC]为USB设备描述符。当中的
7 R% Q$ e% ~3 E8 i. N0x83, /*idVendor (0x0483)*/' e/ L8 A, N" ~" G# f* k' @+ S
0x04,
9 W* @# U2 P, @$ A% J0x50, /*idProduct = 0x5750*/
- o7 _4 G) c0 e% K2 U) c2 w0x57,) |+ n; y$ V4 U |5 o
//idVender字段。厂商ID号,我们这里取0x0483,仅供实验用。& g/ E2 @! c, g Y' u
//实际产品不能随便使用厂商ID号,必须跟USB协会申请厂商ID号。
9 r* e C9 G+ ^5 ^' I4 L//注意小端模式,低字节在先。
7 S: c/ p, f" W* m& ^1 q$ W//idProduct字段。产品ID号,我们这里取0x5750。8 R1 I4 {) K% m; y3 W, I) t
//注意小端模式,低字节应该在前。
) N x" ?. t3 m* {$ k. v# }/ N# _( r8 @- ]% P- B
' x8 B+ L* F3 A
const u8 ConfigDescriptor[SIZ_CONFIG_DESC]是配置描述符,注意如下
& u, y; @( G/ g( U( q! _- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
! M2 }: g8 Q+ P/ \ - 0x81, /* bEndpointAddress: Endpoint Address (IN) */
3 P" L9 r* @/ G& p( C7 |1 x" _ - 0x03, /* bmAttributes: Interrupt endpoint */0 R! L% j) ?6 E0 V; ~# S
- 0x02, /* wMaxPacketSize: 2 Bytes max */
6 j& S: g$ a# ? - 0x00,. v* j) z' h/ ]; ]$ ]" [
- 0x20, /* bInterval: Polling Interval (32 ms) */3 _+ {$ K2 [5 a0 i* {8 d
- /* 34 */
. M, _0 i' k0 W# o - 0x07, /* bLength: Endpoint Descriptor size */
) d; X* A: c0 e. ~ - USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */) G" |9 `2 q. p# _. r, i
- /* Endpoint descriptor type */
+ g% {% a/ t" P# W; e0 q - 0x01, /* bEndpointAddress: */
/ `0 G2 H( n# S. g - /* Endpoint Address (OUT) */
) `& E6 y6 K/ i - 0x03, /* bmAttributes: Interrupt endpoint */
Q7 u* M( J. Q* K: W. C - 0x02, /* wMaxPacketSize: 2 Bytes max */2 ?* P0 w! Y' N: k5 G7 \
- 0x00,, ^, w1 v. [7 I" T" m$ N4 N
- 0x20, /* bInterval: Polling Interval (20 ms) */
复制代码 5 ? ^4 W% Q, v+ Y# H
上面包含了“输入端点描述符”和“输出端点描述符”。
9 `4 H+ c" o6 }: C//wMaxPacketSize字段。该端点的最大包长。% c3 ?+ k( M9 c: F
//bInterval字段。端点查询的时间,* O2 \" ]3 N6 z( b7 Q
* s7 \" S& T: N为了实现更高速的通信我们修改如下:7 H) x$ h9 R3 j8 m8 l" K
- /******************** Descriptor of endpoint ********************/7 N7 F5 w5 D: O* G( Z8 [0 A
- /* 27 */
" Y1 A/ B( h! L6 |. @. g% K - 0x07, /*bLength: Endpoint Descriptor size*/& a0 { e+ S; A' l/ X
- USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/
4 a: Q9 A6 T" T2 i - 0x81, /*bEndpointAddress: Endpoint Address (IN)*/2 J* x8 q' f* v1 @0 |# `# f% x: i
- 0x03, /*bmAttributes: Interrupt endpoint*/: M$ ^$ }$ o9 a3 O* C
- 0x40, /*wMaxPacketSize: 64 Byte max */
( v, h- Q' r$ q. |' S# ~ - 0x00,
' _+ _( ?- V! _ - 0x0A, /*bInterval: Polling Interval (10 ms)*/
! H$ R3 D- |. B4 j - /* 34 */! c7 J" ~8 b# |5 H. k0 V
- /******************** Descriptor of endpoint ********************/
) \" N# t+ X" f: D: R/ p. P - /* 27 */
( i% u8 P- n7 w: e - 0x07, /*bLength: Endpoint Descriptor size*/- b9 z* R+ Z2 l8 i- z4 N9 @
- USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/
; d. f8 U" E9 [- f- ~ - 0x01, /*bEndpointAddress: Endpoint Address (OUT)*// b2 u) L# z9 E$ h& z
- 0x03, /*bmAttributes: Interrupt endpoint*/
2 n7 d; o9 } j/ |- t - 0x40, /*wMaxPacketSize: 64 Byte max */
( E$ t) u+ `- i$ ]! w - 0x00,3 R3 [4 _2 V1 F$ R$ k
- 0x0A, /*bInterval: Polling Interval (10 ms)*/
- C7 u2 u& x3 i6 G6 Y% | - 1 [# x; @1 A. i- Y) A
- const u8 ReportDescriptor[SIZ_REPORT_DESC]为HID专用的报告描述符,具体的大家就参考资料了,这里可以直接复制了。: Y5 O' z5 @( a
- const u8 CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =$ X v0 E1 [/ v( j: W& R# o1 ~0 Z
- {
8 o- l& J E- v# `/ s$ i1 N+ B - 0x05, 0xFF, // USAGE_PAGE(User define)7 V) Y' W1 d# D) C
- 0x09, 0xFF, // USAGE(User define)
& X! m! }' g1 f - 0xa1, 0x01, // COLLECTION (Application)& ]% B7 p( j& b# N/ {+ S; ? x
- 0x05, 0x01, // USAGE_PAGE(1)$ _5 @9 q. n" h- D7 v2 X- i, L& T5 m
- 0x19, 0x00, // USAGE_MINIMUM(0)
" r" s" n0 l; W6 y1 N - 0x29, 0xFF, // USAGE_MAXIMUM(255)
& m. L; r& o5 Y/ e - 0x15, 0x00, // LOGICAL_MINIMUM (0)
- e' F" ]5 Q. k( T8 |+ Y - 0x25, 0xFF, // LOGICAL_MAXIMUM (255)
' ~6 D8 x# |$ y* S' X7 b - 0x75, 0x08, // REPORT_SIZE (8)
( `! l& I( E# e A/ \. w - 0x95, 0x40, // REPORT_COUNT (64)# r& B' z9 t* Z6 J+ v
- 0x81, 0x02, // INPUT (Data,Var,Abs)5 h& c& R6 ^9 g z) c
- 0x05, 0x02, // USAGE_PAGE(2)7 p0 q! X. b2 m7 O
- 0x19, 0x00, // USAGE_MINIMUM (0)) c. o& ^& v! V# M
- 0x29, 0xFF, // USAGE_MAXIMUM (255)
8 u" y. T* t8 U. ~/ w- @ - 0x15, 0x00, // LOGICAL_MINIMUM (0)2 ^% R1 I$ C; m5 m9 Y6 P( Z
- 0x25, 0xFF, // LOGICAL_MAXIMUM (255)" F4 q$ N& \, H3 {: U
- 0x95, 0x08, // REPORT_COUNT (8)8 n( Y, H4 C I q
- 0x75, 0x40, // REPORT_SIZE (64)
/ h, |% d9 d) | - 0x91, 0x02, // OUTPUT (Data,Var,Abs)+ B# J( r4 o' B% `$ O( \3 I
- 0xc0 // END_COLLECTION
6 X' C* g7 x0 f# g: u - }; /* ReportDescriptor */
- @7 v; O/ L1 H7 ^) s+ s
% d4 l$ M( q3 I) a- const u8 CustomHID_StringVendor[CUSTOMHID_SIZ_STRING_VENDOR]
1 N6 \* y8 `7 Z$ T V - const u8 StringProduct[SIZ_STRING_PRODUCT]4 L) j8 i5 V" l+ p+ D
- const u8 StringSerial[SIZ_STRING_SERIAL]
复制代码 % T7 Y, u2 x2 T6 b1 c6 S
分别是“厂商字符”、“产品字符”、“产品序列号”,这些将在USB HID设备加载的时候显示。但是这需要这些字符要求为Unicode编码,你需要将你要显示的字符先转为Unicode编码。最好大家还要根据各个数组的长度修改如下定义。
+ Y8 T4 U9 w$ y+ Y# I! c- V' q, U9 C) q) q( S. D! u& u8 z
- #define CUSTOMHID_SIZ_REPORT_DESC 39
3 ?+ W9 c" P3 j- s/ B" A' X - #define CUSTOMHID_SIZ_STRING_VENDOR 64" }" v9 c$ t- _" S m
- #define CUSTOMHID_SIZ_STRING_PRODUCT 282 f% N) I( s6 O. I
- #define CUSTOMHID_SIZ_STRING_SERIAL 26
复制代码
/ x8 d: o: ]- I, e. _2 u" h三、打开hw_config.c文件,将那些没有的函数删除,只保留如下函数7 ?' }9 Y6 w$ t5 x( x" N
a) Set_System(void)9 S z7 |8 j) c/ S; x4 {4 b
b) void Set_USBClock(void) ) Z) q$ K7 l- H2 f9 X4 s
c) void USB_Interrupts_Config(void)% C4 i: o6 p6 J* A9 f5 V% a7 I
d) void USB_Cable_Config (FunctionalState NewState)# B. W2 j9 Y {7 h
特别要注意最后一个函数,其主要作用是控制USB的上拉电阻,让电脑检测USB设备是否连接的。8 m v6 Z% ?; e4 Z* L1 v8 u+ E
# Z) e! W8 k E# B7 m8 Y1 ?% {# Q
四、打开stm32f10x_it.c文件,把EXTI15_10_IRQHandler等中断内的代码删除。
$ o: r% `1 n7 l( J0 X7 Y- r! D打开usb_prop.c文件,修改如下:
& r- w1 R X$ O& w( F8 n5 H- void CustomHID_Reset(void)! \0 X( ]! F9 A' @
- {
2 |; B3 m5 {; X3 L& }& ]+ }% l - /* Set Joystick_DEVICE as not configured */
" D. n$ }9 O, k/ M& j5 w% F9 m# a - pInformation->Current_Configuration = 0;
* n5 P/ I9 Q/ n6 \ c' f - pInformation->Current_Interface = 0;/*the default Interface*/
; W# v! N# t7 u; e8 \" U, D8 Z - SetBTABLE(BTABLE_ADDRESS);) u6 g7 H/ L) N4 v; e
- * m" {- H: v7 w2 [
- /* Initialize Endpoint 0 */
* h7 l( C1 A$ b6 v# E - SetEPType(ENDP0, EP_CONTROL);
# l' b/ z ~3 s) b - SetEPTxStatus(ENDP0, EP_TX_STALL);
8 s2 `/ u9 ~0 t - SetEPRxAddr(ENDP0, ENDP0_RXADDR);4 s; v- `! q, I [
- SetEPTxAddr(ENDP0, ENDP0_TXADDR); ]( G; r( C1 A
- Clear_Status_Out(ENDP0);$ M" K; j: K# t. F) a
- SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);
+ ]: N* o& m& G3 _ - SetEPRxValid(ENDP0);, T+ J* j! L" z
. }/ ^. p0 ~; n; K2 c: h* l- /* Initialize Endpoint 1 */
$ P8 }+ Z( r! y$ V& z: v. E/ t: M a - SetEPType(ENDP1, EP_INTERRUPT);
2 Z1 _& Z) A4 Q! n - SetEPTxAddr(ENDP1, ENDP1_TXADDR);
- w1 [# I6 F4 z& g1 ]% { - SetEPTxCount(ENDP1, 64);
* I% P0 `' r* o8 u - SetEPRxStatus(ENDP1, EP_RX_DIS);
( i* @' G3 |9 f* ^ - SetEPTxStatus(ENDP1, EP_TX_NAK);
4 x' E$ S+ S: q: l7 c/ `) d i - " {) d+ ]4 _4 {. c5 n4 c
- /* Initialize Endpoint 1 */9 P9 \+ H2 t- |
- // SetEPType(ENDP1, EP_INTERRUPT);6 C: b2 o( S) z5 k0 c5 Z; r
- SetEPRxAddr(ENDP1, ENDP1_RXADDR);
7 r1 Z8 C( z) i8 M; n& [ - SetEPRxCount(ENDP1, 64);
1 }, N. j7 o9 f! K9 O9 l - // SetEPTxStatus(ENDP1, EP_TX_DIS);$ G! j5 L% i+ J1 \5 K' [7 t
- SetEPRxStatus(ENDP1, EP_RX_VALID);
! [% S% P- a' `, t6 M' X - /* Set this device to response on default address */
8 D5 u9 a3 y x: r3 C9 {2 S" _ - SetDeviceAddress(0);
/ P, [! l' m' I/ b0 _ - }
复制代码
" v$ f# }2 z( e9 _. A' p五、usb_endp.c文件- a! J8 _! z7 G4 u2 U
- void EP1_OUT_Callback(void)
/ A2 n* u7 `4 E. j6 T- `, ] - {
8 j6 l& m7 N5 g: B, j - 这些写接收代码5 @ s) U M+ U% {8 ^; ~" i% {
- }
复制代码
0 [9 ]' M" d& A4 H7 l( k3 q六、数据发送和接收,举例说明$ n0 m M# F- a' X! k# v
1、数据接收6 G( j& o8 n. u! R& k4 U& r
- u8 DataLen; p3 Z4 M4 f; e6 q' ?0 A# e
- DataLen = GetEPRxCount(ENDP1);7 y3 y) |% _' }, k+ V
- PMAToUserBufferCopy(TX1_buffer, ENDP1_RXADDR, DataLen);$ I8 c8 Z/ w8 I
- SetEPRxValid(ENDP1);
( q: c# p, O8 B2 |& r; m) P3 j# E - USART1_Send(DataLen);
* H- D! z% M `$ j% G H8 w# {% n - count_out = 1;
复制代码 2 d: u/ z7 U; B/ |- L$ b& M
2、数据发送: f$ L- B" @2 R* b" \4 l
- UserToPMABufferCopy(InBuffer, GetEPTxAddr(ENDP1), 64);
6 m& `; R9 f/ ^% x; q - SetEPTxCount(ENDP1, 64); 6 [; s1 r- `: I3 I9 O
- SetEPTxValid(ENDP1);
复制代码
' v9 m$ g6 a$ v如果你发送数据较为频繁,每次发送前应使用GetEPTxStatus(ENDP1)检测上次发送是否完成。如果端点状态处于EP_TX_VALID,说明发送未结束,如果端点状态处于EP_TX_NAK,说明发送结束。, {. A$ V! l& d6 V
. g Y' \# P; T; Z
; d3 \2 a+ l* z. {& I(by xidongs)
3 Z/ A8 O j* W1 E9 d0 y. g% ~ |