STM32 USB HID键盘例程
4 R/ E9 ~. l1 f' c7 _
/ t7 r& j' a' S+ G4 l; P% }最全USB HID开发资料,悉心整理一个月,亲自测试& Z# j5 x4 q3 W7 F% `
" c: w& S. s7 M6 L
通过STM32CUbeMX建立USB HID的双向通讯实验成功: L+ J" g# V! G5 I5 g; @9 K5 U+ X3 P
/ G0 a+ |- p. H7 h; ?% C
7 W* V$ F- {% m
发现很多人对STM32的USB通信很感兴趣。要将USB的通信协议搞懂确实是一个比较漫长的过程。但是USB的HID通信无论是上位机的设计还是STM32程序的编程都非常的简单。只是我想很多人都不知道而已。这篇文章的目的是让大家以最短的时间将USB加到你的设备中。如果想学得更深就靠大家。
3 [( A( K6 b6 @% R; ^2 l' [) Q; M" m, q. V @0 k# I
HID只是适合低速传输,其理论上可以达到64KB/S,但多由于windows系统和硬件的关系一般达不到这个传输数度。但这个速度对于一般系统的控制和数据传输都已经足够了,而且是免驱,省去了很多麻烦。如果您需要高速传输可参考我的另外一篇文章《STM32的USB例程修改步骤》文章在
! j g! W: [& G9 }. A5 o, h+ ], y8 J8 G: Q
一、安装完MDK后请打开C:/Keil/ARM/Examples/ST/STM32F10xUSBLib/Demos路径,将Custom_HID在同一个目录下复制一份,如果你要放到其他路径你需要在MDK Options for target的C/C++中添加USB的头文件路径(MDK下的/INC/ST/STM32F10x/USB)。+ H4 N5 U7 ~: B+ [/ ?
& D; W( {$ f* j0 R二、打开usb_desc.c文件,该文件主要包含的端点描述符、设备描述符、配置描述符和字符描述符等。具体请大家参考其他资料了,这里主要说几个常用。+ W8 f0 L: M4 D. s
# T4 ^% \. @. e6 Bu8 DeviceDescriptor[SIZ_DEVICE_DESC]为USB设备描述符。当中的
5 Z& q1 P1 P8 S$ g0x83, /*idVendor (0x0483)*/
7 s5 D+ o) N$ z2 i* E0x04,7 \, E5 q' g4 s' q; z2 \7 F) ]
0x50, /*idProduct = 0x5750*/+ f$ ]4 d) s& j' Q
0x57, h# t6 k* H3 f
//idVender字段。厂商ID号,我们这里取0x0483,仅供实验用。' o+ [4 T, F/ F' S! t* m
//实际产品不能随便使用厂商ID号,必须跟USB协会申请厂商ID号。
# P4 z3 F5 {% q+ X//注意小端模式,低字节在先。$ j# `# N7 _- w# E8 F; J7 E6 p, c
//idProduct字段。产品ID号,我们这里取0x5750。6 g+ w2 g* m) P2 J! ~( d
//注意小端模式,低字节应该在前。
/ ]" k3 Y4 }1 M* k+ S2 J! |2 q0 Z- c. K6 u: @: D2 i
& h4 |* ]6 c5 ?8 z; Oconst u8 ConfigDescriptor[SIZ_CONFIG_DESC]是配置描述符,注意如下* i% f. A0 V$ Q: Y% z
- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
# _# @* ]! W7 D/ N8 h+ C$ \ - 0x81, /* bEndpointAddress: Endpoint Address (IN) */
$ N# Q' o- I( a2 b; [ - 0x03, /* bmAttributes: Interrupt endpoint */" V. m1 O+ ]) h7 S
- 0x02, /* wMaxPacketSize: 2 Bytes max */" Q9 z I! Q- x/ C+ x
- 0x00,
; I; j. M/ L6 v; x1 Y1 Q7 B0 d7 Y - 0x20, /* bInterval: Polling Interval (32 ms) */4 ?( @2 L5 M4 H9 w- R
- /* 34 */& E' U7 _ G$ `& _& p' K
- 0x07, /* bLength: Endpoint Descriptor size */! k- Y4 c1 W: P( P0 Y! K
- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
4 e! V8 w6 z& H3 `/ l* P - /* Endpoint descriptor type */
# H% \5 J* }: H9 e+ P - 0x01, /* bEndpointAddress: */5 @$ m, [! z- H0 D
- /* Endpoint Address (OUT) */" @* S8 S+ w2 ]# A' D8 j! `: `3 [
- 0x03, /* bmAttributes: Interrupt endpoint */
" Y- G x% f3 {* N/ D$ j- W - 0x02, /* wMaxPacketSize: 2 Bytes max *// W; z3 \5 g% N+ v4 T5 d
- 0x00,
8 i2 s7 ~$ I2 j# Z - 0x20, /* bInterval: Polling Interval (20 ms) */
复制代码
% G7 F1 I+ U: w' S* U5 V8 j; z9 W上面包含了“输入端点描述符”和“输出端点描述符”。. e5 t% L7 ~9 B9 z
//wMaxPacketSize字段。该端点的最大包长。6 D- J# O7 X6 u0 B0 n0 q/ ?$ o
//bInterval字段。端点查询的时间,# b+ B3 P& \, {6 L. O# N9 |- y
6 [8 R |+ |! M# H8 B8 r
为了实现更高速的通信我们修改如下:
) a' u5 _! }, N7 M5 P2 R$ j1 M# A- /******************** Descriptor of endpoint ********************/
$ B4 d' g4 f: }9 `7 A Z0 C0 N - /* 27 */
8 R$ `) g3 `& H - 0x07, /*bLength: Endpoint Descriptor size*/; E7 K7 \- v: U* C
- USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/' d; Z/ [; {+ [0 _9 z6 G: r/ M
- 0x81, /*bEndpointAddress: Endpoint Address (IN)*/
3 o& `- x+ ~" \. c* }0 w6 h - 0x03, /*bmAttributes: Interrupt endpoint*/: {2 s, f' ~3 B+ j
- 0x40, /*wMaxPacketSize: 64 Byte max */( z4 Z5 Q2 o* e6 j7 x( Y
- 0x00,
( f; y6 r+ D# o4 T - 0x0A, /*bInterval: Polling Interval (10 ms)*/& z/ Q7 v5 f- A" ]$ C( ?
- /* 34 */ C( b3 X2 }- T( }) r9 b
- /******************** Descriptor of endpoint ********************/! m# D2 z1 _4 `5 n
- /* 27 */8 {. T) X _+ B. {) b6 z- H; ~( m
- 0x07, /*bLength: Endpoint Descriptor size*/- i/ g& ^" w9 z5 Z8 S/ {
- USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/3 m0 e% {1 V3 o( |1 o6 h% [
- 0x01, /*bEndpointAddress: Endpoint Address (OUT)*/+ b. W: }1 {1 Z, j, q. J: A
- 0x03, /*bmAttributes: Interrupt endpoint*/- S5 Z/ g6 z# J
- 0x40, /*wMaxPacketSize: 64 Byte max */
2 I/ V1 z* A2 n7 { - 0x00,
! X4 v9 H4 I0 b5 I9 w! W3 S9 d) x - 0x0A, /*bInterval: Polling Interval (10 ms)*/
2 g. J; M+ e' R+ ^1 h
) L# I6 t, T9 G) z/ f- const u8 ReportDescriptor[SIZ_REPORT_DESC]为HID专用的报告描述符,具体的大家就参考资料了,这里可以直接复制了。
, J! D8 B4 W( {6 {- Z - const u8 CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =
2 s! I$ }4 m+ C" R j" k( U( G* n - {0 s0 S- j2 b: Z% h
- 0x05, 0xFF, // USAGE_PAGE(User define)$ k! U% D8 N8 a7 \# E7 L! p
- 0x09, 0xFF, // USAGE(User define)
* o# N0 ^- D% A9 z+ e - 0xa1, 0x01, // COLLECTION (Application)
D7 r+ V: o0 D; O& f5 t) A - 0x05, 0x01, // USAGE_PAGE(1): H/ n# @% E5 h
- 0x19, 0x00, // USAGE_MINIMUM(0)2 v3 O2 b; t+ k x, J5 G1 S; L( Y
- 0x29, 0xFF, // USAGE_MAXIMUM(255)/ a( D$ G$ ~8 }6 e: k! N
- 0x15, 0x00, // LOGICAL_MINIMUM (0)+ Y. P4 a" m- z; I, C
- 0x25, 0xFF, // LOGICAL_MAXIMUM (255)
# z4 a+ g$ `: D( W) f - 0x75, 0x08, // REPORT_SIZE (8)* U3 o; U* ]& Y D
- 0x95, 0x40, // REPORT_COUNT (64)6 G# r# N" T. \
- 0x81, 0x02, // INPUT (Data,Var,Abs)6 m ?8 N$ F8 W6 r/ j3 _
- 0x05, 0x02, // USAGE_PAGE(2)
. S# I) t4 n0 q( f5 Q. A - 0x19, 0x00, // USAGE_MINIMUM (0)
~! E0 ~5 u' C Z& C& K3 B* _$ A - 0x29, 0xFF, // USAGE_MAXIMUM (255)2 t3 L+ t" e; x" T/ r% g; s, {
- 0x15, 0x00, // LOGICAL_MINIMUM (0)
1 ^' y; W0 E4 F; [5 B - 0x25, 0xFF, // LOGICAL_MAXIMUM (255)
3 C3 Z$ p" G) ~' {8 U - 0x95, 0x08, // REPORT_COUNT (8): I( L S3 u) Q4 o% ?" H8 m: _/ N
- 0x75, 0x40, // REPORT_SIZE (64)' w' c. D7 T2 |* l. g. n
- 0x91, 0x02, // OUTPUT (Data,Var,Abs)
# }* e- U. W, C+ u$ F- h/ w - 0xc0 // END_COLLECTION; i5 S# P1 c2 f7 g2 s
- }; /* ReportDescriptor */. {, w7 O" v! V9 G% M5 Z0 }% X
- % z6 ~/ g, l% j9 R
- const u8 CustomHID_StringVendor[CUSTOMHID_SIZ_STRING_VENDOR]6 V4 {6 @# _3 _$ ?6 I7 p" ]
- const u8 StringProduct[SIZ_STRING_PRODUCT]5 j+ C+ P) v! e* I" W
- const u8 StringSerial[SIZ_STRING_SERIAL]
复制代码
% [' v6 P1 r3 O3 ]* Z分别是“厂商字符”、“产品字符”、“产品序列号”,这些将在USB HID设备加载的时候显示。但是这需要这些字符要求为Unicode编码,你需要将你要显示的字符先转为Unicode编码。最好大家还要根据各个数组的长度修改如下定义。
) ^' |9 I5 t7 i$ D) G/ g9 O9 g0 h! P3 C9 f8 V, h z, l8 v5 K
- #define CUSTOMHID_SIZ_REPORT_DESC 39- p* Y0 z% H2 Z4 n
- #define CUSTOMHID_SIZ_STRING_VENDOR 642 P/ b" [9 ?+ T5 ?; A' D9 k
- #define CUSTOMHID_SIZ_STRING_PRODUCT 28* E* D/ S. c& T- t
- #define CUSTOMHID_SIZ_STRING_SERIAL 26
复制代码
. n* H6 Z% @% L三、打开hw_config.c文件,将那些没有的函数删除,只保留如下函数* p8 C8 P' V4 s! k) i- Q4 g
a) Set_System(void). k* {! O5 I5 o6 L8 k) \2 A! \
b) void Set_USBClock(void) # z) y* C- E( U! ~
c) void USB_Interrupts_Config(void)
: @8 X7 r% B0 v& [4 r8 ^d) void USB_Cable_Config (FunctionalState NewState)
( Z% U# D8 s) u特别要注意最后一个函数,其主要作用是控制USB的上拉电阻,让电脑检测USB设备是否连接的。- N o- P0 h. r: C7 R% K( l
3 N* O: u6 |$ z
9 Q u' W3 P7 Y+ ~- n4 \2 c2 L四、打开stm32f10x_it.c文件,把EXTI15_10_IRQHandler等中断内的代码删除。) \5 [! Q; M7 p i4 @( [( R: [
打开usb_prop.c文件,修改如下:8 ]2 r: W- H0 B- W; L1 h8 w( S6 X
- void CustomHID_Reset(void), k+ D! S1 R0 f7 I
- {+ n* m4 F* t9 p5 [% H- k7 Z
- /* Set Joystick_DEVICE as not configured */
& Y, t# E4 h0 i" f( ?# I - pInformation->Current_Configuration = 0;
. f' o2 b2 [! m% n - pInformation->Current_Interface = 0;/*the default Interface*/ S, V( u1 `( f; U9 G
- SetBTABLE(BTABLE_ADDRESS);2 v9 a# F4 D5 k) @
- ; `3 k c- ^- j) C; R4 Z
- /* Initialize Endpoint 0 */, ^; ?1 c m6 L* E/ X
- SetEPType(ENDP0, EP_CONTROL);. [, G d% S$ k& E+ l) X9 S" T
- SetEPTxStatus(ENDP0, EP_TX_STALL);
9 G3 B9 @3 L' |7 V - SetEPRxAddr(ENDP0, ENDP0_RXADDR);3 p: \* u' @; U& ?: S k' L
- SetEPTxAddr(ENDP0, ENDP0_TXADDR);
4 V; w( |1 ~( T/ K+ J5 S - Clear_Status_Out(ENDP0);
9 E, h4 Z! {- A! E% Q7 Z - SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);
% B6 \" E5 Q2 a# ` - SetEPRxValid(ENDP0);
( I3 }7 e6 ~4 B' F0 G3 N4 n4 q
* ?! t+ l7 C4 S: X3 }8 ^- /* Initialize Endpoint 1 */: g" G/ W7 S2 E. ^# S- f% W, Z
- SetEPType(ENDP1, EP_INTERRUPT);/ h4 ]' ~2 R3 B+ t* B+ S7 o# r
- SetEPTxAddr(ENDP1, ENDP1_TXADDR);( B1 ?1 m0 |9 X5 z& E5 m
- SetEPTxCount(ENDP1, 64);: w. k3 N& @8 ?4 b$ l* k0 h
- SetEPRxStatus(ENDP1, EP_RX_DIS);
; C6 V) G* x+ }% R3 F - SetEPTxStatus(ENDP1, EP_TX_NAK);
: B1 G; ]! R4 U! V. {, y8 W - * F4 I, n5 h; k9 D( [$ N
- /* Initialize Endpoint 1 */7 ]# Y/ J. N! o8 ^* f
- // SetEPType(ENDP1, EP_INTERRUPT);: K7 |# M; X. b0 i4 P9 g9 ?
- SetEPRxAddr(ENDP1, ENDP1_RXADDR);
- a# r" E4 ]+ \ - SetEPRxCount(ENDP1, 64);
* t3 f3 m7 x! E( n! _6 Q - // SetEPTxStatus(ENDP1, EP_TX_DIS);) ]% g4 ~0 M7 M
- SetEPRxStatus(ENDP1, EP_RX_VALID);+ G' r- a$ o+ W# _/ f+ x
- /* Set this device to response on default address */5 v2 B# s5 ~9 J3 i" I! b$ P& q- E
- SetDeviceAddress(0);
- K/ A9 F" Y e5 Y( n - }
复制代码 O- @2 Q' k% B) B$ }* }& i
五、usb_endp.c文件# q( Z( K, E! g5 Q+ w' [/ r7 Q
- void EP1_OUT_Callback(void)) K9 ~' e( v! Z) R) d
- {
2 ^" \6 D; [0 L8 m* `! V# S - 这些写接收代码
& `+ u7 n0 P' D - }
复制代码
+ M8 e [- R1 q( G; d: U9 q六、数据发送和接收,举例说明4 {5 E: M# i; M5 Z3 X
1、数据接收5 F' Y _ s) N9 \; h
- u8 DataLen;
$ ?; Z' h+ O& W4 G - DataLen = GetEPRxCount(ENDP1);. z2 W7 O% n5 o$ T" {: k% D8 L. `4 t' i
- PMAToUserBufferCopy(TX1_buffer, ENDP1_RXADDR, DataLen);
5 f, d& i) U" M5 B% @ - SetEPRxValid(ENDP1);
9 j# W$ g. Q' f0 }- X# ~7 Y - USART1_Send(DataLen);& \/ T/ ^7 ^: i& ~/ }, R9 B
- count_out = 1;
复制代码
5 z+ P. k4 O5 L2 \3 M2、数据发送( l9 O( m# C( D" S
- UserToPMABufferCopy(InBuffer, GetEPTxAddr(ENDP1), 64);: d& g3 d1 p5 J7 ?, j y( ~2 B
- SetEPTxCount(ENDP1, 64);
. H' I5 C& u2 H8 O3 K$ v6 k - SetEPTxValid(ENDP1);
复制代码
1 I1 j0 C/ h2 s+ I+ K" n如果你发送数据较为频繁,每次发送前应使用GetEPTxStatus(ENDP1)检测上次发送是否完成。如果端点状态处于EP_TX_VALID,说明发送未结束,如果端点状态处于EP_TX_NAK,说明发送结束。& C3 @0 z( w% q7 w1 \8 h
& X7 n+ F1 b4 @5 U, k1 K
I% |2 Q9 R3 n$ }# b: n# z
(by xidongs)
& C |4 t7 y# C1 ?9 V1 ~ |