STM32 USB HID键盘例程
/ k/ ~ g3 N1 A2 l
2 j6 L: D5 n1 w. D9 g$ k+ V' N最全USB HID开发资料,悉心整理一个月,亲自测试* ]9 _: S- T9 m/ k; m- S( D' w
9 D0 P. m1 S5 a7 t# v4 s6 q) V9 ]通过STM32CUbeMX建立USB HID的双向通讯实验成功
9 a- \7 }. \5 G: Q. A" t# j' q# ^3 X
# B* Y! U, ^6 g& O/ i
发现很多人对STM32的USB通信很感兴趣。要将USB的通信协议搞懂确实是一个比较漫长的过程。但是USB的HID通信无论是上位机的设计还是STM32程序的编程都非常的简单。只是我想很多人都不知道而已。这篇文章的目的是让大家以最短的时间将USB加到你的设备中。如果想学得更深就靠大家。9 X, L/ K! q5 G7 u, b
, S3 w$ b5 D6 w# l( w) lHID只是适合低速传输,其理论上可以达到64KB/S,但多由于windows系统和硬件的关系一般达不到这个传输数度。但这个速度对于一般系统的控制和数据传输都已经足够了,而且是免驱,省去了很多麻烦。如果您需要高速传输可参考我的另外一篇文章《STM32的USB例程修改步骤》文章在
' v! b9 Z) T# ~( H1 I) t2 @; `0 w2 g+ F6 i& v2 d$ C9 V
一、安装完MDK后请打开C:/Keil/ARM/Examples/ST/STM32F10xUSBLib/Demos路径,将Custom_HID在同一个目录下复制一份,如果你要放到其他路径你需要在MDK Options for target的C/C++中添加USB的头文件路径(MDK下的/INC/ST/STM32F10x/USB)。
8 O& Q. n) O8 O, p
7 `! u& f: W% n# z二、打开usb_desc.c文件,该文件主要包含的端点描述符、设备描述符、配置描述符和字符描述符等。具体请大家参考其他资料了,这里主要说几个常用。
4 }( }8 R' z I) |& j; D
0 m1 b! } j' Q! a9 J8 |- Ju8 DeviceDescriptor[SIZ_DEVICE_DESC]为USB设备描述符。当中的) }( s: C2 s6 a1 K: U2 B7 r
0x83, /*idVendor (0x0483)*/
. J# v! O; ~9 a4 R0x04,3 m l# O8 ]) i2 {0 ^, ]1 T
0x50, /*idProduct = 0x5750*/
0 s* q/ h6 r; t3 }. D0x57,
7 `2 m6 q1 P& E1 t, l( x; l% N/ W//idVender字段。厂商ID号,我们这里取0x0483,仅供实验用。8 [1 a8 i, H4 n. ^( G1 l' z. T% ?
//实际产品不能随便使用厂商ID号,必须跟USB协会申请厂商ID号。
0 c5 _2 \; D: U A) `0 I6 c: M' _//注意小端模式,低字节在先。& [' q' n9 R& c" L# ~) x1 z
//idProduct字段。产品ID号,我们这里取0x5750。+ o9 @* A8 \5 k3 {8 C
//注意小端模式,低字节应该在前。
" c* F$ c) |, f" m+ a Q; O- s E: }9 U. _2 {# e6 ^9 v# E+ Y+ k
- X9 x( E0 j9 d" f, |6 x( n/ Jconst u8 ConfigDescriptor[SIZ_CONFIG_DESC]是配置描述符,注意如下
1 Q2 ^5 s+ w" c% U- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
( U- w* B5 v4 E, I' L - 0x81, /* bEndpointAddress: Endpoint Address (IN) */0 m; n! J' R" M2 `( q
- 0x03, /* bmAttributes: Interrupt endpoint */$ R( x0 a' }& ?9 U1 C
- 0x02, /* wMaxPacketSize: 2 Bytes max */: K8 `" j( c$ s S9 `/ n
- 0x00,
' _$ @" l* |5 }0 |' z3 Z! y5 E+ D - 0x20, /* bInterval: Polling Interval (32 ms) */
" i3 d. e* f3 d+ O* z - /* 34 */* a) q8 @4 S) k: p( d
- 0x07, /* bLength: Endpoint Descriptor size */! E7 h1 R* O3 ~5 l- M
- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
$ ]; ^/ d0 d6 w. F7 g" u- C - /* Endpoint descriptor type */9 S. w# u# I5 f0 ?& H( Y7 ~
- 0x01, /* bEndpointAddress: */
5 B- v% N( ~, s1 y2 b6 X - /* Endpoint Address (OUT) */
M+ |$ o6 S) C5 @7 _7 k3 H - 0x03, /* bmAttributes: Interrupt endpoint */0 b) W" d% ~* P$ a2 T4 i. [
- 0x02, /* wMaxPacketSize: 2 Bytes max */ N) l; P% K: Z) H/ N# v' z
- 0x00,
& p& F5 I; ]; F; w) E& r3 A0 x - 0x20, /* bInterval: Polling Interval (20 ms) */
复制代码
+ }& O# ^0 a- @# @6 B上面包含了“输入端点描述符”和“输出端点描述符”。
$ n% o6 y R4 L1 W I//wMaxPacketSize字段。该端点的最大包长。# L9 K* x& k* X$ C4 D
//bInterval字段。端点查询的时间,
- ?( N/ E, [" S7 B8 F5 _5 _3 R
; a8 }+ e9 B0 z/ O) A$ Y- j+ L为了实现更高速的通信我们修改如下: K) H; n4 s2 L( f
- /******************** Descriptor of endpoint ********************/
0 P2 R$ \( b3 T6 e - /* 27 */
9 d1 t) h1 j! J% X - 0x07, /*bLength: Endpoint Descriptor size*/8 G: |$ L* o2 Q7 q4 m$ w
- USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/3 U1 H5 H# x; ]" h
- 0x81, /*bEndpointAddress: Endpoint Address (IN)*/
j9 s; G8 g3 O4 i$ f6 [) S - 0x03, /*bmAttributes: Interrupt endpoint*/
5 c& y' ? Q) [ - 0x40, /*wMaxPacketSize: 64 Byte max */5 J, q/ K: E$ u1 H$ n1 n% ^* u8 {
- 0x00,
7 w6 v6 Y( t' K! Q( \1 p$ H - 0x0A, /*bInterval: Polling Interval (10 ms)*/! k6 x% |+ W" i8 B( W3 N
- /* 34 */, |. r0 n4 Q6 g* U4 I" B% N
- /******************** Descriptor of endpoint ********************/1 R9 w6 s! X! V- t' d& z9 n
- /* 27 */+ W: k/ C2 k9 ]% z0 z8 P6 x Q
- 0x07, /*bLength: Endpoint Descriptor size*/9 |4 t3 t& A( }6 w& M
- USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/
8 H0 P g. A2 c" T - 0x01, /*bEndpointAddress: Endpoint Address (OUT)*/
7 ~7 i# Y$ R; ?7 x: ^! Y+ y0 r C - 0x03, /*bmAttributes: Interrupt endpoint*/! U4 T$ V4 D; J4 F) v6 M: }& v
- 0x40, /*wMaxPacketSize: 64 Byte max */
; N7 Q+ Y6 ~4 T$ Y9 b& n - 0x00,1 |6 J0 u/ T, V& B, c% T
- 0x0A, /*bInterval: Polling Interval (10 ms)*/
' Q9 ]# t- J0 F- u. @- {, e - 7 R. |8 P5 Z) w
- const u8 ReportDescriptor[SIZ_REPORT_DESC]为HID专用的报告描述符,具体的大家就参考资料了,这里可以直接复制了。
# R5 n6 n' I" ]. {- d& p - const u8 CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =3 l6 H p% a1 t3 I6 B
- {
* ~2 `( P1 t7 j - 0x05, 0xFF, // USAGE_PAGE(User define)
8 b/ F1 I: `8 f% x( P% ]" z - 0x09, 0xFF, // USAGE(User define)
: U/ U5 p- d/ } - 0xa1, 0x01, // COLLECTION (Application)4 J' u8 j& V, H3 k* N* x
- 0x05, 0x01, // USAGE_PAGE(1)
9 p5 ]9 X% D$ ^( n! K - 0x19, 0x00, // USAGE_MINIMUM(0)9 @/ i; `, c; @ p
- 0x29, 0xFF, // USAGE_MAXIMUM(255)
' D5 v3 \8 A+ @ - 0x15, 0x00, // LOGICAL_MINIMUM (0)
3 C- Z2 H9 S* b2 [# U - 0x25, 0xFF, // LOGICAL_MAXIMUM (255)
, z! w* C$ G) V N# m1 ^: z9 ~' E5 N - 0x75, 0x08, // REPORT_SIZE (8)
6 i4 x' F2 D& D2 f0 ^ - 0x95, 0x40, // REPORT_COUNT (64)
, F' b9 ~- J. k - 0x81, 0x02, // INPUT (Data,Var,Abs)
5 F5 c' U* q7 f* A( H) [ - 0x05, 0x02, // USAGE_PAGE(2)
. k( X8 {3 o) n. A - 0x19, 0x00, // USAGE_MINIMUM (0)
" X. q4 O& u: Z2 j- w* k$ t - 0x29, 0xFF, // USAGE_MAXIMUM (255)2 N$ F/ [' |2 h7 s8 G6 v3 y7 l
- 0x15, 0x00, // LOGICAL_MINIMUM (0)4 J7 J% k& \' H
- 0x25, 0xFF, // LOGICAL_MAXIMUM (255)
( O8 I9 Q% ?9 r1 x l - 0x95, 0x08, // REPORT_COUNT (8)
% F$ t/ K+ d8 s& F$ v# T - 0x75, 0x40, // REPORT_SIZE (64)+ [" i! R, L. E. \) o- m! t
- 0x91, 0x02, // OUTPUT (Data,Var,Abs)
# ~: U* _: w* r' b2 i6 Y - 0xc0 // END_COLLECTION
$ O3 g1 n6 X8 D - }; /* ReportDescriptor */
5 q8 h- k6 [* {7 R: }6 R/ F# N - " p& A4 a6 K P- P" ~ s2 s5 ^
- const u8 CustomHID_StringVendor[CUSTOMHID_SIZ_STRING_VENDOR]
+ K/ X) i. b% P$ l - const u8 StringProduct[SIZ_STRING_PRODUCT], Y/ ^9 R& Z& v7 p, L S+ q5 @
- const u8 StringSerial[SIZ_STRING_SERIAL]
复制代码 * G7 E/ C6 B1 X! ~9 c7 C
分别是“厂商字符”、“产品字符”、“产品序列号”,这些将在USB HID设备加载的时候显示。但是这需要这些字符要求为Unicode编码,你需要将你要显示的字符先转为Unicode编码。最好大家还要根据各个数组的长度修改如下定义。/ s. R2 a. W# | L
, ?8 Q2 R& R* i5 {5 g, \
- #define CUSTOMHID_SIZ_REPORT_DESC 395 j& w* Q+ n4 S( [) B {/ J
- #define CUSTOMHID_SIZ_STRING_VENDOR 64( n0 y! G9 i% F2 w) c
- #define CUSTOMHID_SIZ_STRING_PRODUCT 28& {+ c8 X% l" L' `3 _( |) ?7 _$ O
- #define CUSTOMHID_SIZ_STRING_SERIAL 26
复制代码
) F' Z: Y, [' |/ w/ r* @% t7 z; Q三、打开hw_config.c文件,将那些没有的函数删除,只保留如下函数
6 p( T1 q; V1 U9 N( ~a) Set_System(void)
1 z* M" x/ \" P/ Z8 _* L/ Nb) void Set_USBClock(void) 3 U( }0 g) g) l& Y; Z$ _6 j
c) void USB_Interrupts_Config(void)6 K( d: X( B u$ T X ?7 j
d) void USB_Cable_Config (FunctionalState NewState)4 ]0 ^, P; A9 f5 j
特别要注意最后一个函数,其主要作用是控制USB的上拉电阻,让电脑检测USB设备是否连接的。; j$ O7 V: B8 z
# G7 w. S5 t1 A* u% p" \# m0 I- z6 n3 N8 o$ V
四、打开stm32f10x_it.c文件,把EXTI15_10_IRQHandler等中断内的代码删除。6 D; N s/ n' }
打开usb_prop.c文件,修改如下:/ A0 O% J* l* Y {4 ]. A
- void CustomHID_Reset(void)
5 L+ X( x3 p- Z2 d - {7 g' m) A& i5 k( V. i& |3 R4 A
- /* Set Joystick_DEVICE as not configured */
$ K- V+ r6 ^/ `; L% t1 v - pInformation->Current_Configuration = 0;
3 Z: K- K- Y) ^ - pInformation->Current_Interface = 0;/*the default Interface*/
, I$ o. {, j. ^- l; N* a, T - SetBTABLE(BTABLE_ADDRESS);7 H" ?! @$ }! }4 i2 E7 m6 p! H: X
- 1 z% E& W$ l: |% G0 `7 O
- /* Initialize Endpoint 0 */
3 Y( K6 W. r( R' ^ - SetEPType(ENDP0, EP_CONTROL);
. X2 G2 k u) |2 U0 x( B# I) L - SetEPTxStatus(ENDP0, EP_TX_STALL);
6 Z( A6 t# t& S! @ - SetEPRxAddr(ENDP0, ENDP0_RXADDR);+ m5 \8 }( R3 o# X7 Y5 [
- SetEPTxAddr(ENDP0, ENDP0_TXADDR);
6 l3 I) R, b5 [9 Z/ I* ]9 S - Clear_Status_Out(ENDP0);
# \1 V; f+ o# | - SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);. E' P3 S4 w9 Z% F2 s
- SetEPRxValid(ENDP0);- C3 S5 d5 |3 O" Z& ^, U( y
4 J0 X! Z( h5 T7 y- /* Initialize Endpoint 1 */4 M' f5 t) H* v1 j# }7 s- h0 {
- SetEPType(ENDP1, EP_INTERRUPT);+ I. {% z# l3 r& w A
- SetEPTxAddr(ENDP1, ENDP1_TXADDR);4 j# K, ^" `4 a2 n+ j
- SetEPTxCount(ENDP1, 64);
7 V% n+ e7 ^+ u% f5 ^ c - SetEPRxStatus(ENDP1, EP_RX_DIS);% L! @* Y4 A8 u/ H, a4 G! h
- SetEPTxStatus(ENDP1, EP_TX_NAK);
+ u3 e* a# I5 C1 T - 4 T4 P |+ j6 q
- /* Initialize Endpoint 1 */
" h4 W1 S. J( q5 k& j$ @, y, T) @ - // SetEPType(ENDP1, EP_INTERRUPT);
" `/ w# c7 R% u1 |+ \: | - SetEPRxAddr(ENDP1, ENDP1_RXADDR);& v) c: ]6 j, y6 V' [
- SetEPRxCount(ENDP1, 64);
$ B0 [6 |! f, L - // SetEPTxStatus(ENDP1, EP_TX_DIS);
! q* M: w3 I, u. D1 P$ F ~4 V - SetEPRxStatus(ENDP1, EP_RX_VALID);
7 `, f2 Y T: @+ M - /* Set this device to response on default address */$ u a7 Q( M! G3 T' s
- SetDeviceAddress(0);
! {" J% |" _7 n" W: j* A - }
复制代码 : u( u7 o6 j8 b; a& ?4 W
五、usb_endp.c文件
* G3 O: S1 v" T( P6 }. M8 S T- void EP1_OUT_Callback(void)
- r, W8 i! t; D6 a% w( s! Q; K - {
% c6 T# N3 j9 Z* a/ I! x - 这些写接收代码
0 ?2 u; ~& j( U0 ]; D d6 P - }
复制代码 ( ~) N; N3 u( t3 k* I# t
六、数据发送和接收,举例说明
( f" {4 p8 i- v* r6 D1、数据接收3 @6 K' K+ o( Y& D6 s+ W, Z4 g
- u8 DataLen;
1 L, H# o# ]7 \6 X% P - DataLen = GetEPRxCount(ENDP1);
' Y9 t5 a; O5 B) n4 z - PMAToUserBufferCopy(TX1_buffer, ENDP1_RXADDR, DataLen);) ^, Q6 i7 {# A
- SetEPRxValid(ENDP1);8 `/ M9 \, j4 [4 c" D( ~
- USART1_Send(DataLen);5 e0 Z" {4 D' c# X% G( V- X$ G
- count_out = 1;
复制代码
0 S: g' Y' ~; ]2、数据发送
7 ` W; }" E- S/ ?) s8 ~# v$ ^0 b- UserToPMABufferCopy(InBuffer, GetEPTxAddr(ENDP1), 64);
' k- A3 b% }4 s2 c! X5 |! d* [ - SetEPTxCount(ENDP1, 64); + D" j' c ?7 X+ w6 y! q
- SetEPTxValid(ENDP1);
复制代码
: k K: b- E! j" b如果你发送数据较为频繁,每次发送前应使用GetEPTxStatus(ENDP1)检测上次发送是否完成。如果端点状态处于EP_TX_VALID,说明发送未结束,如果端点状态处于EP_TX_NAK,说明发送结束。* Q0 h7 q9 X$ e0 u: A- u) k8 ~7 `
2 d, m" G1 x( [4 j2 |: D$ p1 h& s* a: y& l3 ^8 ~7 w
(by xidongs)$ ]8 R# q5 W0 a! v. X, t8 |
|