STM32 USB HID键盘例程5 ~2 H$ }: A! |5 H/ t
4 y' V# o& O, G R
最全USB HID开发资料,悉心整理一个月,亲自测试
/ z5 H, o! u# j0 [+ y& |" B* g6 E' ^$ z' p
通过STM32CUbeMX建立USB HID的双向通讯实验成功
. f6 T1 k5 A+ x. V6 M6 M
( a* ]4 |. j9 _
; C8 L# g; I/ A% ~! ~+ s发现很多人对STM32的USB通信很感兴趣。要将USB的通信协议搞懂确实是一个比较漫长的过程。但是USB的HID通信无论是上位机的设计还是STM32程序的编程都非常的简单。只是我想很多人都不知道而已。这篇文章的目的是让大家以最短的时间将USB加到你的设备中。如果想学得更深就靠大家。
! c9 I% t( Q4 @7 u5 i4 ]5 f" m" }1 @5 A6 o8 w! F/ b: b0 p! ~+ S1 Q6 l
HID只是适合低速传输,其理论上可以达到64KB/S,但多由于windows系统和硬件的关系一般达不到这个传输数度。但这个速度对于一般系统的控制和数据传输都已经足够了,而且是免驱,省去了很多麻烦。如果您需要高速传输可参考我的另外一篇文章《STM32的USB例程修改步骤》文章在
$ J5 M1 h+ t4 u# X# B+ h* Z) D
0 v$ ]1 a; ]- l, z一、安装完MDK后请打开C:/Keil/ARM/Examples/ST/STM32F10xUSBLib/Demos路径,将Custom_HID在同一个目录下复制一份,如果你要放到其他路径你需要在MDK Options for target的C/C++中添加USB的头文件路径(MDK下的/INC/ST/STM32F10x/USB)。
2 W4 c8 q- ?& m* @. L) L( b
7 |! V" n% ?8 y9 F8 t* |二、打开usb_desc.c文件,该文件主要包含的端点描述符、设备描述符、配置描述符和字符描述符等。具体请大家参考其他资料了,这里主要说几个常用。
. ~! O$ K$ N$ r% f" u" I
7 ~, @* @' b4 V, `2 eu8 DeviceDescriptor[SIZ_DEVICE_DESC]为USB设备描述符。当中的4 L& D$ E- H. }3 [1 E
0x83, /*idVendor (0x0483)*/
: o. g6 U4 ~# X; N3 Z* B* v! s, z0x04,3 }9 u) o* r, A
0x50, /*idProduct = 0x5750*/
- J8 A, d; M+ S; W8 _, f- J2 H0x57,; N' `: \, q3 ]0 |
//idVender字段。厂商ID号,我们这里取0x0483,仅供实验用。: ^2 ?: v- ^2 ]4 E* n3 B
//实际产品不能随便使用厂商ID号,必须跟USB协会申请厂商ID号。
( d! r0 \' I; i: Y. o* A//注意小端模式,低字节在先。
" i8 F- c. }% r3 @0 ^& }//idProduct字段。产品ID号,我们这里取0x5750。
9 n6 H# Z1 N0 p$ F! m7 x//注意小端模式,低字节应该在前。
, K% r+ E; L, ~# p/ t' R! R9 f4 S R4 P r
; n2 [' s% S9 _1 C
const u8 ConfigDescriptor[SIZ_CONFIG_DESC]是配置描述符,注意如下
( f( S- c! D5 n7 z) w; W* m- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */) R& J0 o* y, Y1 T
- 0x81, /* bEndpointAddress: Endpoint Address (IN) */
7 W6 V5 Q6 N6 r8 \ - 0x03, /* bmAttributes: Interrupt endpoint */
9 X/ T# u4 R/ v3 a4 m3 R0 Y - 0x02, /* wMaxPacketSize: 2 Bytes max */* q0 O" E2 w7 a+ T) s+ T$ W% p" D
- 0x00,% p4 Q; I. ~4 E, y% y0 |
- 0x20, /* bInterval: Polling Interval (32 ms) */
, F2 c; d3 P6 g: A) s3 t2 k4 b - /* 34 */2 c/ V% h8 j% N
- 0x07, /* bLength: Endpoint Descriptor size */+ | t! x/ X( y6 C/ J3 d0 k
- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */8 x& }2 q2 W$ Q5 a9 u* k
- /* Endpoint descriptor type */5 V5 @' g$ v' O4 M5 H f
- 0x01, /* bEndpointAddress: */
7 \/ B- L m8 e( |# D - /* Endpoint Address (OUT) */: V( F2 R I. Y( @: {
- 0x03, /* bmAttributes: Interrupt endpoint */
* ?7 k9 |( |( H. I7 B% o9 ? - 0x02, /* wMaxPacketSize: 2 Bytes max */
1 ]! ~, d$ u& J- ^; | - 0x00,8 P4 Z. c+ q- c8 ]7 p; [
- 0x20, /* bInterval: Polling Interval (20 ms) */
复制代码 * u) r3 }3 C4 h0 R& M
上面包含了“输入端点描述符”和“输出端点描述符”。
+ S4 K1 Q/ X5 W; g0 [2 l \//wMaxPacketSize字段。该端点的最大包长。
# _# N7 g+ Q0 ^" i, U//bInterval字段。端点查询的时间,* n0 y8 V+ Q# v4 n3 v
B0 M3 Q& `$ I l. _( x0 l为了实现更高速的通信我们修改如下:9 e7 a4 M# R) X2 E0 p O& s2 C
- /******************** Descriptor of endpoint ********************/
$ j& V% N. e- X - /* 27 */
; Z ]: X: J' O6 ~8 n3 ]" I - 0x07, /*bLength: Endpoint Descriptor size*/' S( v$ v! c' }5 S( a( c& m v/ a' N
- USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/
# d/ U! l I/ C' R - 0x81, /*bEndpointAddress: Endpoint Address (IN)*/
" }& k# _ c. P% O0 \6 F - 0x03, /*bmAttributes: Interrupt endpoint*/9 U; h; X1 g5 @! H( e7 B
- 0x40, /*wMaxPacketSize: 64 Byte max */
, ~( n* Z" n7 L# _+ a2 t - 0x00,
; x3 I) t/ x4 W+ z6 e - 0x0A, /*bInterval: Polling Interval (10 ms)*/
: P- S x. b7 d" \1 ? a: m& O% u - /* 34 */
+ H t z2 n. C3 [0 U - /******************** Descriptor of endpoint ********************/
0 t, y3 X& O/ l4 o/ N; ? - /* 27 */
7 U. z# B t) @( I - 0x07, /*bLength: Endpoint Descriptor size*/
p& ]# \( m) A3 j9 \5 F; F/ N5 Z9 K7 u - USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/
+ B0 N% Z/ K% [, N& a6 o - 0x01, /*bEndpointAddress: Endpoint Address (OUT)*/
- n( n: h( d0 J4 U7 G - 0x03, /*bmAttributes: Interrupt endpoint*/
/ ?& U/ T' a8 f0 L# ~/ u! B - 0x40, /*wMaxPacketSize: 64 Byte max */# i; D6 c% ]& q( `5 g( t4 I
- 0x00,3 Z4 Z2 L0 ?3 s% z5 g' \/ v1 b
- 0x0A, /*bInterval: Polling Interval (10 ms)*/$ A% h7 f4 T3 y0 a9 q6 |
2 z; w% q) g" g5 r$ [- const u8 ReportDescriptor[SIZ_REPORT_DESC]为HID专用的报告描述符,具体的大家就参考资料了,这里可以直接复制了。6 i3 C( h" a7 ~* Z4 M" f
- const u8 CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =
/ E, }4 i, q% C6 q; Q& M( z - {
0 X6 i& x6 g2 X - 0x05, 0xFF, // USAGE_PAGE(User define)
" ], q" x& w+ B( ]& b( W - 0x09, 0xFF, // USAGE(User define)
/ N& K2 d; t7 `7 d8 x% H+ p' h - 0xa1, 0x01, // COLLECTION (Application)) }4 o; N- M) c8 h4 O/ \; N5 _
- 0x05, 0x01, // USAGE_PAGE(1)' {' E( x2 A: U2 t- n8 s; m
- 0x19, 0x00, // USAGE_MINIMUM(0)
3 j- F+ J: Y* v/ i/ t: N& n5 x. |8 P - 0x29, 0xFF, // USAGE_MAXIMUM(255) q; N Y' H# I3 M5 e
- 0x15, 0x00, // LOGICAL_MINIMUM (0)1 B9 F5 y. P+ A7 g v
- 0x25, 0xFF, // LOGICAL_MAXIMUM (255)* U; L9 c4 ]3 }1 j' n- {
- 0x75, 0x08, // REPORT_SIZE (8)- W! y- E) U9 |! j( r1 ?6 y
- 0x95, 0x40, // REPORT_COUNT (64)
) W6 n$ t" w* A: b, A5 H3 d - 0x81, 0x02, // INPUT (Data,Var,Abs)$ Z1 K2 F6 K$ V6 Y: C
- 0x05, 0x02, // USAGE_PAGE(2)' e1 e; ~& B6 P7 G+ b! J
- 0x19, 0x00, // USAGE_MINIMUM (0)
7 d( s, h/ @( z0 b - 0x29, 0xFF, // USAGE_MAXIMUM (255)! j' J3 Q8 f2 \3 Z4 ^8 B
- 0x15, 0x00, // LOGICAL_MINIMUM (0)9 s" T/ i% Z* x; I3 R
- 0x25, 0xFF, // LOGICAL_MAXIMUM (255)9 Y& u" _+ |2 g
- 0x95, 0x08, // REPORT_COUNT (8)8 a4 ^. s P2 C( J5 ?, W
- 0x75, 0x40, // REPORT_SIZE (64)- e, F3 l- n( V2 Y( V7 E
- 0x91, 0x02, // OUTPUT (Data,Var,Abs)3 f! \& V. X9 e: U5 `. G
- 0xc0 // END_COLLECTION
+ ~) _# x7 {% w' N$ k9 U+ | - }; /* ReportDescriptor */2 J1 D' b! k2 B, c, Z
6 Z, f" I% p0 a3 x9 L5 r6 w8 Y- const u8 CustomHID_StringVendor[CUSTOMHID_SIZ_STRING_VENDOR]
& Z* y" _ J* L3 _ E - const u8 StringProduct[SIZ_STRING_PRODUCT]' P0 I9 F" F, {& V+ U+ e
- const u8 StringSerial[SIZ_STRING_SERIAL]
复制代码 0 A& P+ t& s2 T& V( {
分别是“厂商字符”、“产品字符”、“产品序列号”,这些将在USB HID设备加载的时候显示。但是这需要这些字符要求为Unicode编码,你需要将你要显示的字符先转为Unicode编码。最好大家还要根据各个数组的长度修改如下定义。( ~ o7 b# L3 j1 ^" N/ C u
. @& p. W9 q0 t
- #define CUSTOMHID_SIZ_REPORT_DESC 39' l* P# E: G' j9 B/ B
- #define CUSTOMHID_SIZ_STRING_VENDOR 64
9 ?" u1 G5 e- i2 W" ] - #define CUSTOMHID_SIZ_STRING_PRODUCT 28
2 u. j6 p1 p7 }9 N0 v - #define CUSTOMHID_SIZ_STRING_SERIAL 26
复制代码
% d/ W% ^% b$ V0 Z) w. v" J Y三、打开hw_config.c文件,将那些没有的函数删除,只保留如下函数1 m/ t- |( L. e! m
a) Set_System(void)
3 _$ {7 X+ T: H; k$ O @: d5 e) ^b) void Set_USBClock(void)
Y& @5 Y' e# p( p1 _# Qc) void USB_Interrupts_Config(void)
: r5 ]3 S6 T3 ]: A k; Pd) void USB_Cable_Config (FunctionalState NewState)
+ o; e* m- c/ Q; }- A' Y) h0 A/ ~# v特别要注意最后一个函数,其主要作用是控制USB的上拉电阻,让电脑检测USB设备是否连接的。
. q8 |' @- k- I
* `- j' b7 E$ Y: ~: C
+ Y- p; b6 I' m. Y# c( J四、打开stm32f10x_it.c文件,把EXTI15_10_IRQHandler等中断内的代码删除。8 k" s1 C1 Z+ O& x9 C; O
打开usb_prop.c文件,修改如下:, x$ v4 q. J0 s- G; `
- void CustomHID_Reset(void); U" G2 P3 |" S
- {: C7 T. W3 O* ?& y6 _3 i- K. W
- /* Set Joystick_DEVICE as not configured */
, X8 Y! F: _( c$ Y! l9 Q/ [ - pInformation->Current_Configuration = 0;" h3 u: h9 Z( h& W. t7 |
- pInformation->Current_Interface = 0;/*the default Interface*/, V, D, l4 d0 U4 p# Z
- SetBTABLE(BTABLE_ADDRESS);- _7 z" }) Q- g
9 t# @% V: v) V$ I/ |1 f% B- /* Initialize Endpoint 0 */4 ~/ e, g5 w$ b9 `0 s
- SetEPType(ENDP0, EP_CONTROL);- x' z$ L! K! {
- SetEPTxStatus(ENDP0, EP_TX_STALL);& \& W( d1 c% C
- SetEPRxAddr(ENDP0, ENDP0_RXADDR);
$ V7 h8 a! k9 {# O5 ~& ?% J - SetEPTxAddr(ENDP0, ENDP0_TXADDR);
G5 c R: E3 ~8 a1 i( q - Clear_Status_Out(ENDP0);
1 I$ g. `0 {& Z3 t9 G- l7 w - SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);
* y' f l( q' E - SetEPRxValid(ENDP0);
; o0 G& z* Q0 g: C - - ?9 N! F( F6 ?! W4 Y
- /* Initialize Endpoint 1 */7 f# M5 w& S6 n/ _/ e# y9 ]- d
- SetEPType(ENDP1, EP_INTERRUPT);; e0 h+ }2 Y7 D$ F7 l8 }
- SetEPTxAddr(ENDP1, ENDP1_TXADDR);9 n' n) |2 |. J8 `
- SetEPTxCount(ENDP1, 64);8 U& H, J' y- v$ J
- SetEPRxStatus(ENDP1, EP_RX_DIS);6 @0 j+ H9 B0 }1 y% N
- SetEPTxStatus(ENDP1, EP_TX_NAK);
8 a& W8 ^$ Y2 K S1 c4 _% p6 q ^" B v
1 k( s7 j/ B7 ?' ~9 r/ O+ O. e- /* Initialize Endpoint 1 */ L3 T0 b1 d. [; b% i' c
- // SetEPType(ENDP1, EP_INTERRUPT);9 x( O' q0 ^) W/ e, h7 e0 a5 K
- SetEPRxAddr(ENDP1, ENDP1_RXADDR);
8 r% ~4 s/ {- Q - SetEPRxCount(ENDP1, 64);6 s; T' E! |& M
- // SetEPTxStatus(ENDP1, EP_TX_DIS);
$ x. k) _2 s5 K: m8 Z) ` - SetEPRxStatus(ENDP1, EP_RX_VALID);
" ?4 }( g( o; f- h - /* Set this device to response on default address */
* v8 ?# H- m2 X- `0 R6 o - SetDeviceAddress(0);# a7 }) b" U/ z/ S5 `3 C2 t0 P
- }
复制代码 0 Q% S7 Y; \. X1 X) M
五、usb_endp.c文件: m5 j. N! g8 \8 i: i* o
- void EP1_OUT_Callback(void)
H5 o- ]6 X" g9 O - {
4 }% f1 ~( C& X5 v8 |- D8 f. P9 t - 这些写接收代码
, x" N0 V$ b8 z1 O2 @8 I - }
复制代码 9 S5 g* V8 ]. _, }0 q+ m( M
六、数据发送和接收,举例说明: S6 i P$ W# S* d7 p, t
1、数据接收
- \* Y1 Y. C: N4 x& A" w8 J- u8 DataLen;, L' X1 C" k9 c0 w+ o+ x# b
- DataLen = GetEPRxCount(ENDP1);+ g6 w' x7 g) |" g* D$ Z. T8 P
- PMAToUserBufferCopy(TX1_buffer, ENDP1_RXADDR, DataLen);
" ~. i1 x& _ k+ F9 {2 E% e - SetEPRxValid(ENDP1);4 k& J% c2 B0 A. D; N
- USART1_Send(DataLen);
: P9 p6 O& {1 Y' r2 z - count_out = 1;
复制代码 # }9 a7 x- a, h& `
2、数据发送 ^' M5 N+ |- {2 }# r) ]( B
- UserToPMABufferCopy(InBuffer, GetEPTxAddr(ENDP1), 64);6 I% J W) k- M
- SetEPTxCount(ENDP1, 64);
1 o; y; m9 O4 p/ i - SetEPTxValid(ENDP1);
复制代码
9 ~2 i' F; g8 U/ E如果你发送数据较为频繁,每次发送前应使用GetEPTxStatus(ENDP1)检测上次发送是否完成。如果端点状态处于EP_TX_VALID,说明发送未结束,如果端点状态处于EP_TX_NAK,说明发送结束。
8 \+ a: o% D. ]% k! ~7 Q$ k. x) K! ~1 L9 v5 F1 p
; R7 ^7 {# ?5 N8 r3 \) G/ p
(by xidongs)
, O V8 q( h4 g0 Y1 m; a |