STM32 USB HID键盘例程
3 _6 C& @6 ^- [ X5 k3 P
0 J: D' q" O7 T最全USB HID开发资料,悉心整理一个月,亲自测试- o7 n& h2 y: n
9 l5 y; S5 x: \; g$ _通过STM32CUbeMX建立USB HID的双向通讯实验成功/ A6 ~) m7 v, z0 `1 b1 y% l0 a- G
5 S y; g5 B# a+ R5 m2 a2 E* N
v/ |. A- p l Q发现很多人对STM32的USB通信很感兴趣。要将USB的通信协议搞懂确实是一个比较漫长的过程。但是USB的HID通信无论是上位机的设计还是STM32程序的编程都非常的简单。只是我想很多人都不知道而已。这篇文章的目的是让大家以最短的时间将USB加到你的设备中。如果想学得更深就靠大家。
8 Q2 d ~! m! t! A- a' R4 c9 B$ r- F8 _7 U- j+ @
HID只是适合低速传输,其理论上可以达到64KB/S,但多由于windows系统和硬件的关系一般达不到这个传输数度。但这个速度对于一般系统的控制和数据传输都已经足够了,而且是免驱,省去了很多麻烦。如果您需要高速传输可参考我的另外一篇文章《STM32的USB例程修改步骤》文章在
1 u# n9 e6 M3 c, `
/ W1 l" a+ ^6 d, E一、安装完MDK后请打开C:/Keil/ARM/Examples/ST/STM32F10xUSBLib/Demos路径,将Custom_HID在同一个目录下复制一份,如果你要放到其他路径你需要在MDK Options for target的C/C++中添加USB的头文件路径(MDK下的/INC/ST/STM32F10x/USB)。
" o3 ?; @7 L2 R
+ e2 K! {! @9 _; F二、打开usb_desc.c文件,该文件主要包含的端点描述符、设备描述符、配置描述符和字符描述符等。具体请大家参考其他资料了,这里主要说几个常用。
% s( q! a8 i1 s7 |9 s0 s* P0 b" C" i( c E
u8 DeviceDescriptor[SIZ_DEVICE_DESC]为USB设备描述符。当中的
' O3 O y0 s5 s0x83, /*idVendor (0x0483)*/' X% B5 P5 O, g! e. a& O! H: c: v
0x04,4 T/ q, V9 D' G+ D/ s" g
0x50, /*idProduct = 0x5750*/
& Z5 X! Z. Z$ ?6 K6 U3 O( n& a6 l7 P0x57,
/ a0 j( f5 v. Y) K//idVender字段。厂商ID号,我们这里取0x0483,仅供实验用。
, S% O9 H- Z6 O. W2 n# K: [( k//实际产品不能随便使用厂商ID号,必须跟USB协会申请厂商ID号。/ i" R7 U! E2 S8 z
//注意小端模式,低字节在先。/ H' S- V5 ^ v
//idProduct字段。产品ID号,我们这里取0x5750。
3 `' y" U" W7 k9 ]: B//注意小端模式,低字节应该在前。' q* p G6 A7 U" o: `/ i$ @
, R: @$ y8 Q$ t Q3 V! l1 ], r
, g# x+ T( W' ?6 {( T* Cconst u8 ConfigDescriptor[SIZ_CONFIG_DESC]是配置描述符,注意如下, j" H& n- k; F; c1 B% g
- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */1 R+ i8 u/ |+ n. \' I" J
- 0x81, /* bEndpointAddress: Endpoint Address (IN) */
, x1 [8 ^. L0 m2 o4 D: B; E% k - 0x03, /* bmAttributes: Interrupt endpoint */
( P( v6 i3 Z% y% F; P* ^5 m - 0x02, /* wMaxPacketSize: 2 Bytes max */0 p8 ~. R, t& a: N* }. e9 |8 i/ Q
- 0x00,
. U$ `+ s0 ^) U$ F+ M- o - 0x20, /* bInterval: Polling Interval (32 ms) */1 t/ ^0 ?3 L# h" ]
- /* 34 */9 y/ W( y; Z& k$ A
- 0x07, /* bLength: Endpoint Descriptor size */
# G& q2 ?; \+ l) Y - USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */* F* X2 S: o- v- r+ I) U& ?$ J
- /* Endpoint descriptor type */
. t' M$ f# V% u. f - 0x01, /* bEndpointAddress: */" O, Y% u8 I+ B0 Q
- /* Endpoint Address (OUT) */
- O4 ^8 i- ?/ |) k+ F - 0x03, /* bmAttributes: Interrupt endpoint */
1 f: `5 X- Y) I7 @) w - 0x02, /* wMaxPacketSize: 2 Bytes max */3 Z( M7 t1 U* l: m& D ?6 |! D/ e( ^
- 0x00,' U# K( H+ a3 j, D
- 0x20, /* bInterval: Polling Interval (20 ms) */
复制代码 8 f# K' v6 O- h" t$ T& X( P
上面包含了“输入端点描述符”和“输出端点描述符”。
( ?3 B7 h0 K8 s( I' _& O//wMaxPacketSize字段。该端点的最大包长。
6 l, F! W' }: D8 P8 @4 o2 I//bInterval字段。端点查询的时间,
7 @* c: ?& ]+ a5 g
+ u- Z6 `- P- ]9 m0 F为了实现更高速的通信我们修改如下:4 r5 u2 `8 g0 ] S& k7 z4 N3 f
- /******************** Descriptor of endpoint ********************/3 m8 `( m* E1 w$ {5 f) b' o
- /* 27 */
. T* |) D/ k0 V% I; J - 0x07, /*bLength: Endpoint Descriptor size*/
! y/ s! O" J9 ~ e- T5 | - USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/
$ W m+ K: ^7 Q. {2 {6 n6 \+ h - 0x81, /*bEndpointAddress: Endpoint Address (IN)*/
3 o M; z9 c! t6 { - 0x03, /*bmAttributes: Interrupt endpoint*/
8 N$ l$ P- [6 S" U - 0x40, /*wMaxPacketSize: 64 Byte max */
9 f, g8 P" I, l1 o6 `* r - 0x00,: U* K4 D0 y' n4 ~6 d5 c2 b
- 0x0A, /*bInterval: Polling Interval (10 ms)*/
2 @# ~- X" D+ @# q7 X% K - /* 34 */. M: M0 l% h( F6 _
- /******************** Descriptor of endpoint ********************/, T; m: w4 q# c F+ ?
- /* 27 */
& K# i- k$ c' E K, ]& F7 F4 N - 0x07, /*bLength: Endpoint Descriptor size*/3 }. w3 G" F, D9 I2 J
- USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/
S1 D2 W& L! t3 o3 V. ^* m - 0x01, /*bEndpointAddress: Endpoint Address (OUT)*/1 c# W5 t8 n! u/ _5 C
- 0x03, /*bmAttributes: Interrupt endpoint*/0 U# }8 ~8 W; U7 C- Y. E
- 0x40, /*wMaxPacketSize: 64 Byte max */ W0 Q$ M6 u* l) p9 g
- 0x00,
. A5 S# W) j! k - 0x0A, /*bInterval: Polling Interval (10 ms)*/9 z$ R' j, s' Y
- / T; O# p% j) ` d
- const u8 ReportDescriptor[SIZ_REPORT_DESC]为HID专用的报告描述符,具体的大家就参考资料了,这里可以直接复制了。3 j8 m3 i9 M( I. [6 D
- const u8 CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =
6 R; V4 P2 T* o8 C. s# ]" \4 B - {
) x8 g# Q! K0 s: ^7 [( ` g7 f - 0x05, 0xFF, // USAGE_PAGE(User define)
3 w3 h# p C1 F: `+ e - 0x09, 0xFF, // USAGE(User define)$ m+ A& ?; j+ }4 x) L, {
- 0xa1, 0x01, // COLLECTION (Application)
' X5 F# {3 \/ v! L; X& v - 0x05, 0x01, // USAGE_PAGE(1)( W, D+ ?' r8 Q0 B, n) v
- 0x19, 0x00, // USAGE_MINIMUM(0)
+ y- [: b# y7 {4 T2 C- E o2 X0 o - 0x29, 0xFF, // USAGE_MAXIMUM(255)/ c9 h2 `2 u5 [( G Q& B: o
- 0x15, 0x00, // LOGICAL_MINIMUM (0), w$ Q# [8 |% S2 s# `7 t. h
- 0x25, 0xFF, // LOGICAL_MAXIMUM (255)
: R2 h6 o, T' K% J4 f9 I9 v1 J h3 J+ s - 0x75, 0x08, // REPORT_SIZE (8). Y) M# u/ G- l2 ^1 p% E$ m. S
- 0x95, 0x40, // REPORT_COUNT (64)3 v* A) F1 I/ s
- 0x81, 0x02, // INPUT (Data,Var,Abs)5 i" }" @+ |$ B5 j- w$ t Q+ m
- 0x05, 0x02, // USAGE_PAGE(2); P E1 ?% J$ T+ p3 G
- 0x19, 0x00, // USAGE_MINIMUM (0)/ t; o1 r* I* x# @& V
- 0x29, 0xFF, // USAGE_MAXIMUM (255)
: W% r/ B% M9 g6 o5 R - 0x15, 0x00, // LOGICAL_MINIMUM (0)
7 g- Q4 T; N/ y8 c8 ^ - 0x25, 0xFF, // LOGICAL_MAXIMUM (255)5 ]% F5 [% w5 g+ c4 M
- 0x95, 0x08, // REPORT_COUNT (8)
, O' z' y9 y: R6 l$ E" U1 |& H! i - 0x75, 0x40, // REPORT_SIZE (64)0 ]0 S# f0 j' l' x4 i
- 0x91, 0x02, // OUTPUT (Data,Var,Abs)
$ n* t4 @; u0 ~9 f6 w+ y$ D$ z - 0xc0 // END_COLLECTION
& }2 u' g4 {+ j, a6 [ - }; /* ReportDescriptor */
1 J# G0 C7 s0 U - 5 o# P% d; R" `$ C" E
- const u8 CustomHID_StringVendor[CUSTOMHID_SIZ_STRING_VENDOR]6 A- g3 M0 h: e7 `! t: x( y9 _
- const u8 StringProduct[SIZ_STRING_PRODUCT]
" S$ s' q. |7 A1 C, y# ^( X4 G - const u8 StringSerial[SIZ_STRING_SERIAL]
复制代码
+ F+ T% k7 X. u! M( o! Y分别是“厂商字符”、“产品字符”、“产品序列号”,这些将在USB HID设备加载的时候显示。但是这需要这些字符要求为Unicode编码,你需要将你要显示的字符先转为Unicode编码。最好大家还要根据各个数组的长度修改如下定义。
! k9 Y1 z" ?: P+ H) ^! a) l
6 M" l( l" A1 f V' {- #define CUSTOMHID_SIZ_REPORT_DESC 39' z: {9 l" O) X! y4 n* }
- #define CUSTOMHID_SIZ_STRING_VENDOR 64
: d- q. i2 Y X- S1 V - #define CUSTOMHID_SIZ_STRING_PRODUCT 28
3 n: N9 K9 R* A& L- J8 y; y/ } - #define CUSTOMHID_SIZ_STRING_SERIAL 26
复制代码 : l7 _' A) o+ g( y" \0 L; x B8 {6 e
三、打开hw_config.c文件,将那些没有的函数删除,只保留如下函数
. @4 U8 @6 F* j0 ]; H/ Ka) Set_System(void)
1 f, q) |& Z: T# J. Zb) void Set_USBClock(void) # ?6 f. `; `4 V) k! g( _
c) void USB_Interrupts_Config(void)( q# ?$ f$ d! f* w D o4 A* M
d) void USB_Cable_Config (FunctionalState NewState)8 r6 {: G$ I; m0 K' S! G/ ^4 h6 G
特别要注意最后一个函数,其主要作用是控制USB的上拉电阻,让电脑检测USB设备是否连接的。! o" F+ i# q+ @- T. ^" t+ L. Q
$ p0 V1 e' v0 b6 D9 e
; z9 @5 d/ T2 x. m四、打开stm32f10x_it.c文件,把EXTI15_10_IRQHandler等中断内的代码删除。
/ p# l$ K# w3 F. o! x: P打开usb_prop.c文件,修改如下:
( w9 H" u! o- d% `, r- void CustomHID_Reset(void)
5 W6 S) g/ h2 X$ n# E6 j - {9 D( E3 x5 }& t- K
- /* Set Joystick_DEVICE as not configured */
( h! e) l) [% T - pInformation->Current_Configuration = 0;
: a6 s9 Z- R I! }; K* U - pInformation->Current_Interface = 0;/*the default Interface*/- O+ R* Y+ o' F
- SetBTABLE(BTABLE_ADDRESS);# U- I2 J, W! U$ ?4 L# v- W
- 1 w7 [4 a* C& x0 I+ H
- /* Initialize Endpoint 0 */
/ x8 l, v1 l* L1 m3 m, u7 Q+ Z - SetEPType(ENDP0, EP_CONTROL);! N' s _0 Y0 h/ D5 V
- SetEPTxStatus(ENDP0, EP_TX_STALL);% Z+ r& @" j3 H5 W* N u
- SetEPRxAddr(ENDP0, ENDP0_RXADDR);, Y. w% C. T* Q5 F
- SetEPTxAddr(ENDP0, ENDP0_TXADDR);: O' C8 p$ g) k. {
- Clear_Status_Out(ENDP0);
) Y1 Q4 x7 k0 V% ]; | - SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);& Q1 C6 f( g& E! B
- SetEPRxValid(ENDP0);7 U* W8 F" k7 l
- 9 N& s% [1 Q% t: t( k% e r
- /* Initialize Endpoint 1 */" Z: T; h/ E$ k8 L2 R6 V
- SetEPType(ENDP1, EP_INTERRUPT);
' d- u3 I+ e' B - SetEPTxAddr(ENDP1, ENDP1_TXADDR);6 H6 A5 f# s- {. H
- SetEPTxCount(ENDP1, 64);
* e! q. Z v3 O/ r; a - SetEPRxStatus(ENDP1, EP_RX_DIS);3 A& c* p! M" o) P! I
- SetEPTxStatus(ENDP1, EP_TX_NAK);0 o) n- Y) [/ b% o! E
9 N: p+ T ^: r) h' T- /* Initialize Endpoint 1 */
/ w, a8 A8 w6 S6 }5 v0 R - // SetEPType(ENDP1, EP_INTERRUPT);- z3 \; f" ~; D6 F2 s5 g
- SetEPRxAddr(ENDP1, ENDP1_RXADDR);2 v4 b/ {4 A1 c
- SetEPRxCount(ENDP1, 64);/ @# i0 \/ y6 o% j( t4 k
- // SetEPTxStatus(ENDP1, EP_TX_DIS);
3 I. _% ^8 a. N, r( k! { - SetEPRxStatus(ENDP1, EP_RX_VALID);
1 B# `- _7 z) g - /* Set this device to response on default address */" i; G; H4 L1 I
- SetDeviceAddress(0);
1 R0 x; m4 q# ~! h3 p - }
复制代码 8 s$ o3 |, t/ [- |) ~
五、usb_endp.c文件7 `% Y: q3 N3 b" s/ O
- void EP1_OUT_Callback(void)
6 q% W C1 {2 f2 _ - {
. m/ c g4 `9 I. B* `3 ~ - 这些写接收代码
% z* `: G# E+ U8 m4 Z - }
复制代码 2 G0 j7 @8 Q8 B4 I
六、数据发送和接收,举例说明
) n" T! @# P. a! [1、数据接收
5 L( O9 X2 k; E% j& P# l' u! N- u8 DataLen;0 `9 i) X) o8 `6 ] l Z
- DataLen = GetEPRxCount(ENDP1);
m% A" J$ X( a' `, x - PMAToUserBufferCopy(TX1_buffer, ENDP1_RXADDR, DataLen);
1 X, h9 s1 ]/ _5 o, N- R- p; H- s - SetEPRxValid(ENDP1);: G4 |7 ]: m! k7 E% C/ _2 Y. E
- USART1_Send(DataLen);6 w+ Z6 Z5 c5 ~4 ~9 u
- count_out = 1;
复制代码 ; E8 U. g" g% ], s; ~
2、数据发送" a9 C5 P# m* s% S9 J
- UserToPMABufferCopy(InBuffer, GetEPTxAddr(ENDP1), 64);- N- B# m5 e/ h: b
- SetEPTxCount(ENDP1, 64);
7 A/ d {! c+ p! u. C - SetEPTxValid(ENDP1);
复制代码
* N/ q! I" b( w% s( I5 X如果你发送数据较为频繁,每次发送前应使用GetEPTxStatus(ENDP1)检测上次发送是否完成。如果端点状态处于EP_TX_VALID,说明发送未结束,如果端点状态处于EP_TX_NAK,说明发送结束。* V% N1 ^3 w5 p9 K/ [6 @
% K( Z) z. e" X8 y! u
: M9 c: m! N7 ?3 a4 _2 x% M
(by xidongs) Y( O; b+ Z* [4 j, M$ \- s! p
|