STM32 USB HID键盘例程
# C( p# B2 c; t; c9 X4 a5 t; o8 Y, n, n
/ L7 y. M3 s/ h8 n, f最全USB HID开发资料,悉心整理一个月,亲自测试
: n5 W8 A- a1 C/ @2 W5 L" v4 s; ]; r% x
通过STM32CUbeMX建立USB HID的双向通讯实验成功& k: V+ S/ J" P9 t$ F
1 x; u5 B7 m. Q! P% O) T
4 J4 Y6 ?6 p- b, p$ x8 a
发现很多人对STM32的USB通信很感兴趣。要将USB的通信协议搞懂确实是一个比较漫长的过程。但是USB的HID通信无论是上位机的设计还是STM32程序的编程都非常的简单。只是我想很多人都不知道而已。这篇文章的目的是让大家以最短的时间将USB加到你的设备中。如果想学得更深就靠大家。
% V9 R# I& q; f$ M; f' Z3 M) @* L2 y: K; N6 W1 t# c* W3 e2 D" K4 {
HID只是适合低速传输,其理论上可以达到64KB/S,但多由于windows系统和硬件的关系一般达不到这个传输数度。但这个速度对于一般系统的控制和数据传输都已经足够了,而且是免驱,省去了很多麻烦。如果您需要高速传输可参考我的另外一篇文章《STM32的USB例程修改步骤》文章在
! k+ }- B; I, d! w+ W- q: ?2 [) x6 }+ u/ S: ~
一、安装完MDK后请打开C:/Keil/ARM/Examples/ST/STM32F10xUSBLib/Demos路径,将Custom_HID在同一个目录下复制一份,如果你要放到其他路径你需要在MDK Options for target的C/C++中添加USB的头文件路径(MDK下的/INC/ST/STM32F10x/USB)。$ I3 a% Y6 b: r/ n6 }: x3 V2 X
$ H/ h& Y( Q$ b$ e4 h$ G7 g二、打开usb_desc.c文件,该文件主要包含的端点描述符、设备描述符、配置描述符和字符描述符等。具体请大家参考其他资料了,这里主要说几个常用。8 Q5 s: @8 J- w O
8 V+ G% J$ q! y% x& Zu8 DeviceDescriptor[SIZ_DEVICE_DESC]为USB设备描述符。当中的
9 D( t4 \4 Q- i8 ?% a5 p0x83, /*idVendor (0x0483)*/3 Y" Y7 _3 R1 G, D
0x04," @ U" M" R( ?& {
0x50, /*idProduct = 0x5750*/
7 \+ d& I5 N7 W1 W W0x57,
/ b. ?: z" f6 u8 s//idVender字段。厂商ID号,我们这里取0x0483,仅供实验用。5 o t4 w$ T$ j
//实际产品不能随便使用厂商ID号,必须跟USB协会申请厂商ID号。$ P/ F% E4 m5 |% f
//注意小端模式,低字节在先。
6 d1 S, w7 \* i! f8 Y K, |4 G//idProduct字段。产品ID号,我们这里取0x5750。
8 O, B0 k1 C0 F% B6 K) ~//注意小端模式,低字节应该在前。
0 ?) C- K! M% p) S8 b( f6 A& `2 z: h1 ~* z7 X
# \2 W" f8 z) X( ?! b- l
const u8 ConfigDescriptor[SIZ_CONFIG_DESC]是配置描述符,注意如下8 b4 E2 C: S" @( E
- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */# i2 ^4 n. d6 A; k
- 0x81, /* bEndpointAddress: Endpoint Address (IN) */
( P4 z$ j8 j) J P - 0x03, /* bmAttributes: Interrupt endpoint *// i. Z# \: v) N7 I9 h
- 0x02, /* wMaxPacketSize: 2 Bytes max */' j* J! E' }' G+ z9 `+ {7 ~# ]3 L: u$ B
- 0x00,3 s/ C) H/ D4 W, M
- 0x20, /* bInterval: Polling Interval (32 ms) */, z" d2 s/ H# r4 I6 O& L9 r
- /* 34 */, q( Y' D! ]( g4 [
- 0x07, /* bLength: Endpoint Descriptor size */* A+ _0 X" ]0 b( R y' |& B" `
- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */1 e3 h9 A8 w8 F: H' u
- /* Endpoint descriptor type */
( Z( R! `8 h0 w0 ]0 q - 0x01, /* bEndpointAddress: */
; ~0 O- m) c6 h) ~7 p - /* Endpoint Address (OUT) */
* n3 M! D% H0 ]0 O! \/ P8 y3 j# t - 0x03, /* bmAttributes: Interrupt endpoint */3 A0 |4 x6 P9 T% r2 F% C
- 0x02, /* wMaxPacketSize: 2 Bytes max */
, t2 p0 I, i" F" X* B - 0x00,6 |9 Q) p9 ?- s0 J
- 0x20, /* bInterval: Polling Interval (20 ms) */
复制代码 $ D$ J) R7 m4 ^8 n5 e2 l$ p4 ]
上面包含了“输入端点描述符”和“输出端点描述符”。
% i5 p- ^: e7 ^7 g4 \( ^//wMaxPacketSize字段。该端点的最大包长。
& ~& f6 e: w" k* W5 f//bInterval字段。端点查询的时间,
6 ]% ~0 h8 y! y* H2 x
9 Z# B7 r4 r5 x# J& L为了实现更高速的通信我们修改如下:
; W; B# w8 r" [7 p/ L5 l- /******************** Descriptor of endpoint ********************/ @& ?1 `6 A3 q r
- /* 27 */
0 S: {* n& ?8 s% \" h - 0x07, /*bLength: Endpoint Descriptor size*/0 b4 F* t9 U" R( x" J: K1 D
- USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/6 H: N1 W' z! s9 V9 r. A. Y/ Y
- 0x81, /*bEndpointAddress: Endpoint Address (IN)*/
- \, N% ]8 R; q8 `8 D6 ~ F3 Y6 i - 0x03, /*bmAttributes: Interrupt endpoint*/
8 Z% X; a5 q! W7 g& h& o. N. ? - 0x40, /*wMaxPacketSize: 64 Byte max */. H- e$ ^! o- \3 O3 M. {
- 0x00,
2 _, `) d4 k5 ^8 a3 d. } ?! h5 ]. `+ { - 0x0A, /*bInterval: Polling Interval (10 ms)*/7 u3 w1 c4 P& D
- /* 34 */1 A$ c( P" h7 ]2 A1 V
- /******************** Descriptor of endpoint ********************/
. E7 x& ^& Q* E- N2 X, A0 t - /* 27 */# D& U e; P9 f3 F
- 0x07, /*bLength: Endpoint Descriptor size*/ L1 ]2 H( U3 j# u# L
- USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/
, d2 D7 T" _& b* b, J - 0x01, /*bEndpointAddress: Endpoint Address (OUT)*/) W" N8 Z- Q, h) L
- 0x03, /*bmAttributes: Interrupt endpoint*/
* e$ p& h3 W/ h+ O$ v5 I K - 0x40, /*wMaxPacketSize: 64 Byte max */ o3 u3 R: x* |# [; C3 F* Q/ h
- 0x00,
( y0 b3 Y/ K O( p% m$ |7 L - 0x0A, /*bInterval: Polling Interval (10 ms)*/
/ I7 J$ M# J) [2 X! M6 j
% o2 {+ L7 O% x: Z; K/ |/ H- const u8 ReportDescriptor[SIZ_REPORT_DESC]为HID专用的报告描述符,具体的大家就参考资料了,这里可以直接复制了。
' |; ^) D5 x9 U* b$ c* P - const u8 CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =; k3 R. h) |+ Z5 g- \
- {: n- [4 p% X! o- s- N1 A' }; D
- 0x05, 0xFF, // USAGE_PAGE(User define): L; _/ `% P. Y* e) |' y D
- 0x09, 0xFF, // USAGE(User define)
( T* D0 [4 c/ \( l$ w0 s% x - 0xa1, 0x01, // COLLECTION (Application)% `$ h( J" K0 T
- 0x05, 0x01, // USAGE_PAGE(1)/ m7 \5 \) {) x" e6 q$ u
- 0x19, 0x00, // USAGE_MINIMUM(0)& R* X( y9 p# A
- 0x29, 0xFF, // USAGE_MAXIMUM(255)7 J8 b# ]# x; Q3 P' [
- 0x15, 0x00, // LOGICAL_MINIMUM (0)$ t; a) i1 E9 Y: n3 v7 T$ } f
- 0x25, 0xFF, // LOGICAL_MAXIMUM (255)
( B E X; l9 G - 0x75, 0x08, // REPORT_SIZE (8)
; G& O; P( w) F8 D6 w8 N - 0x95, 0x40, // REPORT_COUNT (64)5 ?' i3 T" N' ~! \
- 0x81, 0x02, // INPUT (Data,Var,Abs)
+ {2 z% A* U( h8 n - 0x05, 0x02, // USAGE_PAGE(2)
9 t( L M4 o# j0 d7 ? - 0x19, 0x00, // USAGE_MINIMUM (0), Q8 ^+ s# }' W9 {; J- ]; H% x
- 0x29, 0xFF, // USAGE_MAXIMUM (255)
6 [: _# u% |5 q5 H T+ x - 0x15, 0x00, // LOGICAL_MINIMUM (0)
( u3 r# S) R3 K! ^/ k - 0x25, 0xFF, // LOGICAL_MAXIMUM (255)
1 | | S a3 _' ?7 ` - 0x95, 0x08, // REPORT_COUNT (8)
- y# v' z& ?$ F4 D - 0x75, 0x40, // REPORT_SIZE (64)
6 ~" o$ K( |7 v* d6 {1 E: ]6 A - 0x91, 0x02, // OUTPUT (Data,Var,Abs)
* L8 k. f+ Q6 _6 N% {0 p - 0xc0 // END_COLLECTION
* S1 X/ P6 i' _ - }; /* ReportDescriptor */, k8 _% n* X1 }* v- K1 v
- . U0 g1 t _- C" z3 ^
- const u8 CustomHID_StringVendor[CUSTOMHID_SIZ_STRING_VENDOR]5 ]2 b/ T6 _$ U6 }1 P/ x9 @
- const u8 StringProduct[SIZ_STRING_PRODUCT]
+ d, i% [6 ~' Z* F - const u8 StringSerial[SIZ_STRING_SERIAL]
复制代码
: \5 x" _0 @3 @5 g h分别是“厂商字符”、“产品字符”、“产品序列号”,这些将在USB HID设备加载的时候显示。但是这需要这些字符要求为Unicode编码,你需要将你要显示的字符先转为Unicode编码。最好大家还要根据各个数组的长度修改如下定义。
! u/ |# z$ H1 S- w1 c$ w+ Z# W/ s8 _4 }+ t5 y! o
- #define CUSTOMHID_SIZ_REPORT_DESC 39( H9 V, L! T6 t, g6 g* T( C8 S
- #define CUSTOMHID_SIZ_STRING_VENDOR 64
0 d. Y: E4 d u' q9 D - #define CUSTOMHID_SIZ_STRING_PRODUCT 28( U4 O. ? r& Z: x5 V2 C+ M
- #define CUSTOMHID_SIZ_STRING_SERIAL 26
复制代码 $ A5 A4 V5 z- R7 D- M
三、打开hw_config.c文件,将那些没有的函数删除,只保留如下函数3 G2 U' {9 F8 D% H( P
a) Set_System(void): d! }/ q4 `+ M* v9 O0 |# {" Q
b) void Set_USBClock(void) 1 r; s7 z2 X2 |$ V$ ?" w8 c
c) void USB_Interrupts_Config(void)
6 Q t( ?/ m/ H7 P5 }( md) void USB_Cable_Config (FunctionalState NewState) e O7 d- |+ D$ ?1 D \$ U
特别要注意最后一个函数,其主要作用是控制USB的上拉电阻,让电脑检测USB设备是否连接的。) R# l) c1 }% c0 V5 k. |
]+ z1 m+ x! y) _ ^; z
2 q9 c) o8 w& Q9 a( Z四、打开stm32f10x_it.c文件,把EXTI15_10_IRQHandler等中断内的代码删除。5 S3 `% F2 j9 t: x% P
打开usb_prop.c文件,修改如下:
0 b9 y0 G7 i; R0 D- void CustomHID_Reset(void) l( \& s# ^. ^
- {
& r: U1 P& k5 k1 b: v - /* Set Joystick_DEVICE as not configured */2 ?& b: h% o- L8 e( ^: o; j+ @
- pInformation->Current_Configuration = 0;) Q0 \$ S4 G/ b+ U q c. C
- pInformation->Current_Interface = 0;/*the default Interface*/& H, \# i) X$ w, x K9 B
- SetBTABLE(BTABLE_ADDRESS);
/ Q: h7 o! e+ q/ _, a2 c7 _ - 9 T: ` A* |& q
- /* Initialize Endpoint 0 */) {* V [; L8 e' F' G( Z: F
- SetEPType(ENDP0, EP_CONTROL);
1 v) @6 H% u- a& }& w! @5 g - SetEPTxStatus(ENDP0, EP_TX_STALL);
9 Z7 Q) R" f( ?- R- h - SetEPRxAddr(ENDP0, ENDP0_RXADDR);$ s7 o9 }+ ?8 P. ]2 @# h, j4 H9 f
- SetEPTxAddr(ENDP0, ENDP0_TXADDR);" e3 [8 G! J3 N
- Clear_Status_Out(ENDP0);7 o! N" r$ a2 Y7 q1 D- s
- SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);
' q. s- }; H* T1 L0 O - SetEPRxValid(ENDP0);* W- G9 V0 I* @5 d- y3 m* d5 m
" v' t6 |; p: l5 P4 Y ^- /* Initialize Endpoint 1 */7 w0 k4 n5 F0 |; J0 |
- SetEPType(ENDP1, EP_INTERRUPT);
2 v8 d' i5 N* \3 F S+ f - SetEPTxAddr(ENDP1, ENDP1_TXADDR);
. c. `2 P/ k" u$ ] - SetEPTxCount(ENDP1, 64);
- M$ I( v d7 A% x - SetEPRxStatus(ENDP1, EP_RX_DIS);
/ E; x1 d0 P% ]( o - SetEPTxStatus(ENDP1, EP_TX_NAK);, `+ W3 [, i" S$ e
; ]% Y% L2 T! b7 ^7 }- /* Initialize Endpoint 1 */
7 Z3 a5 S- r' O: ^ - // SetEPType(ENDP1, EP_INTERRUPT);
- H" d! L7 |, ` O& S5 R. w$ b4 \! D - SetEPRxAddr(ENDP1, ENDP1_RXADDR);
& a" }1 {( R: }4 ^; [! ~ - SetEPRxCount(ENDP1, 64);& y1 @6 [8 K( \2 ? o9 K. Q
- // SetEPTxStatus(ENDP1, EP_TX_DIS);& A0 c* L; A9 ]+ o( x( @
- SetEPRxStatus(ENDP1, EP_RX_VALID);
# z, D- j) r L - /* Set this device to response on default address */
3 u1 i' W% q5 {6 i' ^! k* K; z - SetDeviceAddress(0);* J3 L# P4 ]. f/ d/ d; M% U$ ?
- }
复制代码
' y& D0 U0 |, G( w- e5 `五、usb_endp.c文件
& t: R$ Z; x: l- void EP1_OUT_Callback(void), }6 j% A8 _: A
- {
2 H; J' K* c* g5 I$ [* F - 这些写接收代码7 q/ x+ [9 C# f2 r" ^. n6 {# J
- }
复制代码 ( W2 I a$ Q" Y5 l+ w
六、数据发送和接收,举例说明
- k! b& s9 U5 \2 E5 A Q1、数据接收* K* u+ y' F1 a& R
- u8 DataLen;
2 g4 M# D; j/ M- H) H9 L" d7 I - DataLen = GetEPRxCount(ENDP1);
% L- [- g! f5 @( o1 h - PMAToUserBufferCopy(TX1_buffer, ENDP1_RXADDR, DataLen);; X3 C# S9 T/ t' Q* O. l
- SetEPRxValid(ENDP1);: ~ g0 M/ q x& c: K* U3 k$ L
- USART1_Send(DataLen);
& [* }! D* f; e& S* v8 }0 Z$ E - count_out = 1;
复制代码 # _4 l8 Y8 u f
2、数据发送, `6 y8 d% j0 c
- UserToPMABufferCopy(InBuffer, GetEPTxAddr(ENDP1), 64);
, I6 R' o# w5 K3 l9 L - SetEPTxCount(ENDP1, 64);
+ D5 }9 ]8 x( _/ w" S - SetEPTxValid(ENDP1);
复制代码 ; O- _, L2 V3 ?) }* L7 C$ t5 z
如果你发送数据较为频繁,每次发送前应使用GetEPTxStatus(ENDP1)检测上次发送是否完成。如果端点状态处于EP_TX_VALID,说明发送未结束,如果端点状态处于EP_TX_NAK,说明发送结束。 i: A1 N; F2 w
- W& }9 u/ u3 q3 ?% c+ \% ^
* D; s# M6 d; i% |- z* P ]& T
(by xidongs)
: \: G9 \8 H7 Z |