STM32 USB HID键盘例程2 Q/ @3 w% h" {7 L P# S
" n# f: M8 r+ N1 i3 U
最全USB HID开发资料,悉心整理一个月,亲自测试7 C8 O% C D, U3 w$ A
4 B' A! Z8 N! K$ ~( v0 A通过STM32CUbeMX建立USB HID的双向通讯实验成功" s; U) ~- K( e @8 b
2 E4 F+ [. B9 q7 L0 v7 y" X6 l# y8 c) r* v5 N- E
发现很多人对STM32的USB通信很感兴趣。要将USB的通信协议搞懂确实是一个比较漫长的过程。但是USB的HID通信无论是上位机的设计还是STM32程序的编程都非常的简单。只是我想很多人都不知道而已。这篇文章的目的是让大家以最短的时间将USB加到你的设备中。如果想学得更深就靠大家。! x) d/ O) M4 h, X3 C
5 W4 |. s5 N& W, ]& `; F4 C* ^( J! h
HID只是适合低速传输,其理论上可以达到64KB/S,但多由于windows系统和硬件的关系一般达不到这个传输数度。但这个速度对于一般系统的控制和数据传输都已经足够了,而且是免驱,省去了很多麻烦。如果您需要高速传输可参考我的另外一篇文章《STM32的USB例程修改步骤》文章在
3 _9 ?! h3 x8 K; w; _& s7 g8 Z4 v# q7 Z: ]* j
一、安装完MDK后请打开C:/Keil/ARM/Examples/ST/STM32F10xUSBLib/Demos路径,将Custom_HID在同一个目录下复制一份,如果你要放到其他路径你需要在MDK Options for target的C/C++中添加USB的头文件路径(MDK下的/INC/ST/STM32F10x/USB)。8 u/ P8 K4 b* P9 |( }
# t9 a6 v' l! K2 |+ w _
二、打开usb_desc.c文件,该文件主要包含的端点描述符、设备描述符、配置描述符和字符描述符等。具体请大家参考其他资料了,这里主要说几个常用。1 ^' T1 C, X' h% V5 \* |
; t! J8 G H! D+ B& Ju8 DeviceDescriptor[SIZ_DEVICE_DESC]为USB设备描述符。当中的
/ g; e7 s' x6 v' w& L2 W0x83, /*idVendor (0x0483)*/
5 D. O! s! a- S( J" d) d+ E- d0x04,+ y# E% c( T" h
0x50, /*idProduct = 0x5750*/
3 v2 G! v! x, M; @1 D0x57,% f- N6 m: f0 A4 I
//idVender字段。厂商ID号,我们这里取0x0483,仅供实验用。
' K/ s9 H) c/ q0 l6 j, r! y//实际产品不能随便使用厂商ID号,必须跟USB协会申请厂商ID号。
7 ^: P i+ l8 m6 x2 Q! q) N//注意小端模式,低字节在先。; X% w* L) N ?6 e/ C* O+ Z
//idProduct字段。产品ID号,我们这里取0x5750。0 { y1 O0 u: U, N* y& {4 \3 {
//注意小端模式,低字节应该在前。
1 h0 a2 u# _; s5 @ l- j1 d4 y/ d6 _! G; O; M: C8 ?. t
- F1 k9 u# F3 N0 O' R3 G2 I
const u8 ConfigDescriptor[SIZ_CONFIG_DESC]是配置描述符,注意如下
3 e1 u; N/ Y- u; x0 A- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */! F3 }! j+ s5 p# \/ L
- 0x81, /* bEndpointAddress: Endpoint Address (IN) */ S' Y. [7 ~+ u# g2 E) f
- 0x03, /* bmAttributes: Interrupt endpoint */
" c5 j6 O8 y/ t+ d3 ] - 0x02, /* wMaxPacketSize: 2 Bytes max */
$ g# [6 N8 q+ ^- J; Y0 N/ b! }( n - 0x00,5 ?3 H+ Q5 r1 a7 E" R' x3 O4 r. N
- 0x20, /* bInterval: Polling Interval (32 ms) */
4 ~5 @' I5 J( ]; [% G - /* 34 */
2 s/ l* a h1 u& [0 U - 0x07, /* bLength: Endpoint Descriptor size */$ ]- g$ j( l& ]. ]3 T/ g- n+ ?$ S. \
- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
6 o+ w- t" c3 I3 G - /* Endpoint descriptor type */
# `8 g0 K6 Y% A$ S( f - 0x01, /* bEndpointAddress: */
" p6 R! [6 O! p0 f! g w) e - /* Endpoint Address (OUT) */( W& e" [' y* C+ O
- 0x03, /* bmAttributes: Interrupt endpoint */) U0 z9 `- O7 h( w/ t! m
- 0x02, /* wMaxPacketSize: 2 Bytes max */* V2 _- D7 r) I% A( o
- 0x00,
: j7 j! S8 D& o# N/ v( O" M - 0x20, /* bInterval: Polling Interval (20 ms) */
复制代码
9 b$ C r8 F2 z) [# u5 m- \" d上面包含了“输入端点描述符”和“输出端点描述符”。4 d4 C& Y7 z5 s6 {
//wMaxPacketSize字段。该端点的最大包长。
& D. i. n. H6 v& l0 P2 F//bInterval字段。端点查询的时间,* T3 C1 n2 G* q- v
2 d: [7 P5 n* T9 s0 k
为了实现更高速的通信我们修改如下:! G9 C9 E5 d% d1 w/ l; G
- /******************** Descriptor of endpoint ********************/
% e- t3 g0 L# T2 ^' h - /* 27 */) | ?% {( e6 s F" ^% l: l2 F) v$ K
- 0x07, /*bLength: Endpoint Descriptor size*/2 ]- H8 u: g8 z' l+ ~' F$ r. d
- USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/
" H; x5 w1 M7 e E% ? - 0x81, /*bEndpointAddress: Endpoint Address (IN)*/
9 ^1 i; I4 W" r9 u; J4 i7 D5 m - 0x03, /*bmAttributes: Interrupt endpoint*/
$ y% q: [7 u4 g+ q4 {3 m! _, g - 0x40, /*wMaxPacketSize: 64 Byte max */: x; @) l- d) u0 k) ]6 H
- 0x00,
" ^ a/ O4 q" q E0 B1 } - 0x0A, /*bInterval: Polling Interval (10 ms)*/
2 ~% L# T! u+ j - /* 34 */% r6 w) W* x$ y3 e4 m/ x! ~4 O* J
- /******************** Descriptor of endpoint ********************/
" o. c0 f7 ]0 n+ k2 S - /* 27 */
/ R9 G, N6 B. \) a1 _ - 0x07, /*bLength: Endpoint Descriptor size*/
# Y' n: e; `0 \3 V1 Q9 s% d" Q - USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/0 w: S. k2 V( _5 y
- 0x01, /*bEndpointAddress: Endpoint Address (OUT)*/
+ F+ l, C7 g: q - 0x03, /*bmAttributes: Interrupt endpoint*/
% t9 z9 b9 ~3 ^8 y) ]2 M - 0x40, /*wMaxPacketSize: 64 Byte max */
/ u. `- c6 }4 X: U! J - 0x00,2 u1 d4 l. G) d4 Y& ~% }
- 0x0A, /*bInterval: Polling Interval (10 ms)*/& \& ~9 @/ c* I& L$ h! i
$ C7 l/ h$ O& c0 g0 a- const u8 ReportDescriptor[SIZ_REPORT_DESC]为HID专用的报告描述符,具体的大家就参考资料了,这里可以直接复制了。
3 ^* x Z' \: M7 `2 \$ x% b5 L - const u8 CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =
; d( d3 O3 j) ^1 g- j1 E) ~ - {: E6 Y- Q0 M8 S5 h
- 0x05, 0xFF, // USAGE_PAGE(User define)
: c* h$ }& ~2 A' e. w - 0x09, 0xFF, // USAGE(User define)8 y0 N: z \$ \ d
- 0xa1, 0x01, // COLLECTION (Application)
9 F$ f1 P9 z5 @8 m9 H' v% T2 t - 0x05, 0x01, // USAGE_PAGE(1)! p) Y/ A* \" p+ J3 i
- 0x19, 0x00, // USAGE_MINIMUM(0)
6 t' k% {. M& c$ I0 o - 0x29, 0xFF, // USAGE_MAXIMUM(255)
: E k: b& H& Q! e0 X - 0x15, 0x00, // LOGICAL_MINIMUM (0): }8 R( Z E* G$ {% @3 [, [1 P
- 0x25, 0xFF, // LOGICAL_MAXIMUM (255)! t7 t# ~* S# }- i5 s, k5 s
- 0x75, 0x08, // REPORT_SIZE (8)
, A, n! z2 M0 Y _( O2 Z2 t, f3 U - 0x95, 0x40, // REPORT_COUNT (64)
. v3 |1 o9 f& g3 d% | - 0x81, 0x02, // INPUT (Data,Var,Abs)
8 L6 j* l( J" W/ N- p - 0x05, 0x02, // USAGE_PAGE(2)6 E) ?/ x; h# x1 D
- 0x19, 0x00, // USAGE_MINIMUM (0)
3 x/ Q/ n' \6 A" l( J5 n - 0x29, 0xFF, // USAGE_MAXIMUM (255)! N- ?$ c- L R- v! X0 \
- 0x15, 0x00, // LOGICAL_MINIMUM (0)
3 m" A2 I5 T) s8 T8 W - 0x25, 0xFF, // LOGICAL_MAXIMUM (255)5 ]" p4 Q! c# o+ z) e
- 0x95, 0x08, // REPORT_COUNT (8)5 B& \+ K, _: E2 B
- 0x75, 0x40, // REPORT_SIZE (64)
1 A# @) E. O1 m K, n: H - 0x91, 0x02, // OUTPUT (Data,Var,Abs)
, }4 [- }& \! D( G) i" s9 Q - 0xc0 // END_COLLECTION
8 l3 T* x J. ~; ~ - }; /* ReportDescriptor */
w" f; Q! Q8 b1 b1 a" Z - N, p& k6 c; O; j
- const u8 CustomHID_StringVendor[CUSTOMHID_SIZ_STRING_VENDOR]' N9 x/ n- q/ A/ Q: [
- const u8 StringProduct[SIZ_STRING_PRODUCT]3 S1 |* C) \+ t1 x
- const u8 StringSerial[SIZ_STRING_SERIAL]
复制代码 " {& V' K4 U2 y
分别是“厂商字符”、“产品字符”、“产品序列号”,这些将在USB HID设备加载的时候显示。但是这需要这些字符要求为Unicode编码,你需要将你要显示的字符先转为Unicode编码。最好大家还要根据各个数组的长度修改如下定义。0 y/ p& e# }) p$ k5 \' G) d: Q# r
7 \% K, {5 G0 C1 W- r# ?
- #define CUSTOMHID_SIZ_REPORT_DESC 39) k1 F% z9 V% o" i+ J
- #define CUSTOMHID_SIZ_STRING_VENDOR 646 g, d$ j9 H; G/ B {
- #define CUSTOMHID_SIZ_STRING_PRODUCT 28
, [! ?+ u1 w) x; f9 ^ - #define CUSTOMHID_SIZ_STRING_SERIAL 26
复制代码 & |1 H g$ @0 E' p1 @& G' x% y
三、打开hw_config.c文件,将那些没有的函数删除,只保留如下函数
4 G& K) @/ I+ u$ K7 e& N4 O5 Oa) Set_System(void)
- _ X9 r' E* q% L) ob) void Set_USBClock(void)
! [& ?) `' e# }/ ?+ rc) void USB_Interrupts_Config(void)" ^# V" b0 j' o% A1 F+ b: L
d) void USB_Cable_Config (FunctionalState NewState), ]( @ X9 ?% N
特别要注意最后一个函数,其主要作用是控制USB的上拉电阻,让电脑检测USB设备是否连接的。+ r" l" @ m8 ]: i
$ F/ r$ @' H8 ` X8 V, }! y$ ^% }7 E
四、打开stm32f10x_it.c文件,把EXTI15_10_IRQHandler等中断内的代码删除。
2 p4 }( L' v0 S打开usb_prop.c文件,修改如下:' {# \: B1 p1 Z O( g P' u, y
- void CustomHID_Reset(void)* V( \) A2 v D e
- {
/ K0 M; s' M8 I& o9 U2 W - /* Set Joystick_DEVICE as not configured */
5 k: H2 o8 I6 p% `! R - pInformation->Current_Configuration = 0;
' a) i; n! ~$ k* x% r [( R - pInformation->Current_Interface = 0;/*the default Interface*/
6 A7 F) ~% d; v - SetBTABLE(BTABLE_ADDRESS);
9 @/ f8 G2 N+ T8 i/ t) O
7 h! ^, M' R3 _5 B* E- /* Initialize Endpoint 0 */( t9 S, L+ o4 N5 \5 ~
- SetEPType(ENDP0, EP_CONTROL);
; A/ d8 X- w1 g* \2 B4 S' T* M' N& P - SetEPTxStatus(ENDP0, EP_TX_STALL); L3 g$ m8 ?6 }5 D- i. H
- SetEPRxAddr(ENDP0, ENDP0_RXADDR);
, |1 m5 U4 F' ~, h5 G* k - SetEPTxAddr(ENDP0, ENDP0_TXADDR);
& a5 R6 t+ V) K( P3 ` - Clear_Status_Out(ENDP0);7 q% U% f! T* e
- SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);
3 C' s' Y2 K6 c ~, E - SetEPRxValid(ENDP0);& y: E: Z" y3 K9 L' F
- 0 `5 U" q- Z" S
- /* Initialize Endpoint 1 */1 y- r5 w& a9 A/ |, \
- SetEPType(ENDP1, EP_INTERRUPT);8 _% w3 o- @- n1 L
- SetEPTxAddr(ENDP1, ENDP1_TXADDR);
. G6 U {2 {5 t' ], o( `8 A; V - SetEPTxCount(ENDP1, 64);
* P/ W" X. G M& ` - SetEPRxStatus(ENDP1, EP_RX_DIS);
, s5 T. [2 L# d1 _" s! A - SetEPTxStatus(ENDP1, EP_TX_NAK);9 W8 H& n" t, v4 g% H
: K9 E" t# k9 J; N- /* Initialize Endpoint 1 */6 E) _1 v, z* n' E
- // SetEPType(ENDP1, EP_INTERRUPT);" `: n( A8 d% Y8 K6 o( \( |" D; z0 V
- SetEPRxAddr(ENDP1, ENDP1_RXADDR);( ^2 B& I. a" H! J) b
- SetEPRxCount(ENDP1, 64);0 p5 q2 @7 h8 B$ V' ~9 D- J
- // SetEPTxStatus(ENDP1, EP_TX_DIS);5 }: }' |/ E( {; G
- SetEPRxStatus(ENDP1, EP_RX_VALID);
" D4 G) w3 t/ n! q - /* Set this device to response on default address */* @" N4 P2 p* S
- SetDeviceAddress(0);
# v0 `$ g7 w, b1 J - }
复制代码
2 F2 D, O7 a# N. }" X M+ a) N! F7 T五、usb_endp.c文件$ d: r+ |+ ~! s& |5 f2 p* `
- void EP1_OUT_Callback(void)1 n# k3 g! E! {$ q
- {
( k, L0 B/ F, ?6 ?7 D - 这些写接收代码
1 {* k& D% V k5 ? - }
复制代码 1 Q+ y1 P" ?' o2 A' }# }$ V% ]
六、数据发送和接收,举例说明
4 q) V% [4 y8 m; j3 n; i1、数据接收
$ S+ f6 N3 y6 d- }& [- u8 DataLen;) o4 u: [! s; Q; C" N; X: [
- DataLen = GetEPRxCount(ENDP1);
% N6 @ @1 E4 t5 W: q - PMAToUserBufferCopy(TX1_buffer, ENDP1_RXADDR, DataLen);
9 g _# k9 A# L5 B# V: h4 q- Y. z - SetEPRxValid(ENDP1);$ g' e( x- ^' i
- USART1_Send(DataLen); V: {( C' h. T& Z
- count_out = 1;
复制代码 6 q% U* s* o( r! O" v0 T
2、数据发送( L0 I- k$ K8 [* \
- UserToPMABufferCopy(InBuffer, GetEPTxAddr(ENDP1), 64);- G$ i( a2 w8 C4 R% ^9 q: {8 j8 x6 C
- SetEPTxCount(ENDP1, 64); ( {& c+ d- y; K9 z
- SetEPTxValid(ENDP1);
复制代码
# ?6 F8 K9 X! K( M7 ]! c9 U2 c5 A如果你发送数据较为频繁,每次发送前应使用GetEPTxStatus(ENDP1)检测上次发送是否完成。如果端点状态处于EP_TX_VALID,说明发送未结束,如果端点状态处于EP_TX_NAK,说明发送结束。; [2 {: R% Y f2 Z
9 m) a4 m0 e5 P
/ R# M- S4 T9 A. w
(by xidongs)
I) g' d% N! a2 z+ U6 ? |