STM32 USB HID键盘例程; R# u: M5 l0 d$ k
! {) e' U( B l9 `( @% t& o. k最全USB HID开发资料,悉心整理一个月,亲自测试
2 C2 i$ ~/ G+ F$ r/ [& D2 h- H! O! \ f6 N/ F6 W
通过STM32CUbeMX建立USB HID的双向通讯实验成功& K0 I! A9 z) U$ F
' ~; u1 e' d, J2 X9 T
# m$ }6 j' V% Q; B' g发现很多人对STM32的USB通信很感兴趣。要将USB的通信协议搞懂确实是一个比较漫长的过程。但是USB的HID通信无论是上位机的设计还是STM32程序的编程都非常的简单。只是我想很多人都不知道而已。这篇文章的目的是让大家以最短的时间将USB加到你的设备中。如果想学得更深就靠大家。
5 p) E# q! W$ X' r8 d5 S) c- p
2 U+ X4 Z7 W* e# y0 iHID只是适合低速传输,其理论上可以达到64KB/S,但多由于windows系统和硬件的关系一般达不到这个传输数度。但这个速度对于一般系统的控制和数据传输都已经足够了,而且是免驱,省去了很多麻烦。如果您需要高速传输可参考我的另外一篇文章《STM32的USB例程修改步骤》文章在2 f! e/ z: y: h
, L4 f* c" x$ G; r8 G. x% Y- J
一、安装完MDK后请打开C:/Keil/ARM/Examples/ST/STM32F10xUSBLib/Demos路径,将Custom_HID在同一个目录下复制一份,如果你要放到其他路径你需要在MDK Options for target的C/C++中添加USB的头文件路径(MDK下的/INC/ST/STM32F10x/USB)。
( x* q7 {0 z9 p0 s# V) B: z
$ b5 ] E- O6 ?- I5 I G二、打开usb_desc.c文件,该文件主要包含的端点描述符、设备描述符、配置描述符和字符描述符等。具体请大家参考其他资料了,这里主要说几个常用。0 d* s, n; G. B; D
/ q. E9 n- y) D9 t0 n8 t6 X
u8 DeviceDescriptor[SIZ_DEVICE_DESC]为USB设备描述符。当中的
" t' _, t0 _) j+ e$ w* M0x83, /*idVendor (0x0483)*/# Y+ l% c* f% i
0x04,
5 l* ^4 I' F7 n- M! p0x50, /*idProduct = 0x5750*/. I0 m. U6 I4 |, h, D# D7 |, i
0x57,
0 K" k. v4 c) N7 [//idVender字段。厂商ID号,我们这里取0x0483,仅供实验用。
/ D# F2 `+ B- i# Z//实际产品不能随便使用厂商ID号,必须跟USB协会申请厂商ID号。
1 y& g- D6 S+ l9 w. ^' L( f//注意小端模式,低字节在先。3 \( a' m0 k2 b9 I& L' x
//idProduct字段。产品ID号,我们这里取0x5750。
! \0 G" B- E, T& \//注意小端模式,低字节应该在前。
' s, ?5 _$ P: ]+ r8 b4 I( R. ~- C" t9 i% F
: d) R, m, e" Z* N3 Nconst u8 ConfigDescriptor[SIZ_CONFIG_DESC]是配置描述符,注意如下, a0 w6 [0 R0 k. z& c" \+ j9 ~
- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */7 O$ r7 F' N) r# H$ s$ |' I. {; K4 K, X
- 0x81, /* bEndpointAddress: Endpoint Address (IN) *// z7 J- E/ D4 H9 |7 Z: p
- 0x03, /* bmAttributes: Interrupt endpoint */
4 P/ t& S; G0 b& K a6 C0 f - 0x02, /* wMaxPacketSize: 2 Bytes max */
, k9 V/ P8 N8 g# g. V4 ~( \ - 0x00,
/ I/ R- Z% ]) S" p3 E8 C - 0x20, /* bInterval: Polling Interval (32 ms) */
/ V9 J9 o% r4 b8 d- G2 t - /* 34 */% f2 ]& G( A% w3 A9 T
- 0x07, /* bLength: Endpoint Descriptor size */7 H( l4 ^ @+ j9 ]* s) G! H2 J3 M
- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
7 k/ G; }& k- a! _; ?% h - /* Endpoint descriptor type */8 i! z, j: n: b! M3 X
- 0x01, /* bEndpointAddress: */
% u4 a( q e( {: U* a - /* Endpoint Address (OUT) */; R0 }" S7 i& ~5 b# ?5 `+ y1 E
- 0x03, /* bmAttributes: Interrupt endpoint */
5 v0 e! g' q' n1 L - 0x02, /* wMaxPacketSize: 2 Bytes max */
# ?* `: J1 O. _, _ - 0x00,
& j! B' T# T+ e8 @ - 0x20, /* bInterval: Polling Interval (20 ms) */
复制代码
9 d( B N" s+ Y/ p$ z; W上面包含了“输入端点描述符”和“输出端点描述符”。8 U: |+ U7 D6 m+ Z1 W
//wMaxPacketSize字段。该端点的最大包长。7 q7 x$ T. y7 A" Y
//bInterval字段。端点查询的时间,: O; v! e7 r0 \. o! J
# r. `! h# d) H! z为了实现更高速的通信我们修改如下:5 l6 u: i5 T* @* [6 W/ `
- /******************** Descriptor of endpoint ********************/. @9 o' H. G b0 Q" [# I
- /* 27 */
2 ]/ w' E6 b2 B( q4 n9 z - 0x07, /*bLength: Endpoint Descriptor size*/
8 o1 l. l" u n. e* { - USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/4 H _- q4 P+ K1 ]5 V
- 0x81, /*bEndpointAddress: Endpoint Address (IN)*// \: I2 s3 i, w) \& m: N
- 0x03, /*bmAttributes: Interrupt endpoint*/5 p2 k8 u: I& U0 Q! D* n% x
- 0x40, /*wMaxPacketSize: 64 Byte max */
1 z7 l9 C" W4 l) n0 L& o# \ - 0x00,% h9 ~9 {: @5 p, s" C6 m
- 0x0A, /*bInterval: Polling Interval (10 ms)*/+ G* @/ y. C8 }) }; q2 V) {
- /* 34 *// w: a8 X$ i% w" G
- /******************** Descriptor of endpoint ********************/' J! k& v! A7 @9 {- e v
- /* 27 */+ g4 _9 g3 p# ]2 {7 p; F) k O
- 0x07, /*bLength: Endpoint Descriptor size*/
/ i: t. w/ U1 u+ r3 B* l; n- M - USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/- F2 c3 W9 {# r0 }) E, E
- 0x01, /*bEndpointAddress: Endpoint Address (OUT)*/' d) i H0 U/ y7 b9 L" _8 K
- 0x03, /*bmAttributes: Interrupt endpoint*/5 s2 V" o- j# e2 r @3 c( v
- 0x40, /*wMaxPacketSize: 64 Byte max */, V/ M* H+ a+ L' ~ s
- 0x00,
; p a: P& g7 s( Q$ F1 Z Q) C - 0x0A, /*bInterval: Polling Interval (10 ms)*/+ L. F$ K$ z5 H7 Q% |
- & y* _/ l9 D: }) b$ x4 o$ O% I
- const u8 ReportDescriptor[SIZ_REPORT_DESC]为HID专用的报告描述符,具体的大家就参考资料了,这里可以直接复制了。
6 Y6 ^$ b3 \+ w3 S% G2 e+ g: ^! I - const u8 CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =) L O$ V6 a3 Y
- {6 r+ X K% K7 m# t- R K
- 0x05, 0xFF, // USAGE_PAGE(User define)) s( r, m, S5 q4 P4 m( O. |. l
- 0x09, 0xFF, // USAGE(User define)' \5 Y7 J, N; `' \1 C" x$ U3 T
- 0xa1, 0x01, // COLLECTION (Application)2 C- ]( v; ~9 i1 l
- 0x05, 0x01, // USAGE_PAGE(1)) X2 I( R2 K: h3 k) `$ Q% `. b
- 0x19, 0x00, // USAGE_MINIMUM(0)
& H) j4 j9 C& P4 f, O2 d0 X - 0x29, 0xFF, // USAGE_MAXIMUM(255)
( |: \) x* y8 X+ Y - 0x15, 0x00, // LOGICAL_MINIMUM (0)
) P) i# s$ ?. [+ F1 P; Z - 0x25, 0xFF, // LOGICAL_MAXIMUM (255)
& V1 q5 r+ {! T5 l- Q" j - 0x75, 0x08, // REPORT_SIZE (8)- j6 I+ D: O3 |' }9 Y. U: M7 s
- 0x95, 0x40, // REPORT_COUNT (64)
- N/ Z: X! u8 O - 0x81, 0x02, // INPUT (Data,Var,Abs): _# C$ k, U! i
- 0x05, 0x02, // USAGE_PAGE(2)
% ^& p' S& }# ]) {& F+ S1 s - 0x19, 0x00, // USAGE_MINIMUM (0) ^$ A& t6 ?$ H" ^
- 0x29, 0xFF, // USAGE_MAXIMUM (255)
; U$ ~: r' B) \ - 0x15, 0x00, // LOGICAL_MINIMUM (0): K8 _5 U: p: ^" a$ r) H$ c
- 0x25, 0xFF, // LOGICAL_MAXIMUM (255)1 B& [2 s& A$ p a1 {8 E: I4 I
- 0x95, 0x08, // REPORT_COUNT (8)$ K# [0 \) q1 I: K x2 T
- 0x75, 0x40, // REPORT_SIZE (64)% y! |; L4 Y( ]4 G4 X8 y8 f
- 0x91, 0x02, // OUTPUT (Data,Var,Abs)# }8 t: ^* ]- y( O; t
- 0xc0 // END_COLLECTION
2 j! W1 n, w" K - }; /* ReportDescriptor */
3 B+ p' N+ c' u1 Z0 m* G( V/ v - # H1 |$ q7 Y4 @ H4 M! e1 a
- const u8 CustomHID_StringVendor[CUSTOMHID_SIZ_STRING_VENDOR]
. m+ v6 ]6 o; f" _8 j7 I - const u8 StringProduct[SIZ_STRING_PRODUCT]
; F; ~, H% ^& L" F! b0 ^5 P' D - const u8 StringSerial[SIZ_STRING_SERIAL]
复制代码 1 E, p! i% y5 i
分别是“厂商字符”、“产品字符”、“产品序列号”,这些将在USB HID设备加载的时候显示。但是这需要这些字符要求为Unicode编码,你需要将你要显示的字符先转为Unicode编码。最好大家还要根据各个数组的长度修改如下定义。
9 o. k2 k( t1 g: o
! J, V$ q+ B0 J* N9 f5 E- #define CUSTOMHID_SIZ_REPORT_DESC 39
5 K8 X4 ?- Z+ K. L+ q8 e' d r% n" e: F - #define CUSTOMHID_SIZ_STRING_VENDOR 64. X9 z' O6 q1 _, H
- #define CUSTOMHID_SIZ_STRING_PRODUCT 28: U; \9 a( P4 z; r4 _
- #define CUSTOMHID_SIZ_STRING_SERIAL 26
复制代码
$ [% \" \: O$ R/ j5 \, \/ L! y三、打开hw_config.c文件,将那些没有的函数删除,只保留如下函数. g" [7 H- {6 k$ ~
a) Set_System(void)! v) }/ s0 @# X4 o
b) void Set_USBClock(void) - R8 H0 `2 s1 P @
c) void USB_Interrupts_Config(void)' L( @( q$ W0 p4 c! v+ g4 l9 m
d) void USB_Cable_Config (FunctionalState NewState)$ u2 x/ A3 a, u4 m/ @
特别要注意最后一个函数,其主要作用是控制USB的上拉电阻,让电脑检测USB设备是否连接的。
& \, E5 ~6 X* L: w& R. e) J
/ K9 x% @( ]" R* e1 y
- [) V, l5 Y1 d8 h四、打开stm32f10x_it.c文件,把EXTI15_10_IRQHandler等中断内的代码删除。
+ D1 a5 e; O7 E' g打开usb_prop.c文件,修改如下:
4 R9 s( M: c/ }" q- void CustomHID_Reset(void)& M' l0 V2 E: U( s( M9 ~- K; a
- {
) M) I5 }% q$ m" U - /* Set Joystick_DEVICE as not configured */
! v% H E p% W' ^4 n- k8 | - pInformation->Current_Configuration = 0;% U; D0 S6 l3 @ O
- pInformation->Current_Interface = 0;/*the default Interface*/
) E' p( X7 O3 g' P6 D5 _9 b. W - SetBTABLE(BTABLE_ADDRESS);
# b) U, t! E( S5 P# K& G. v; x) w - 7 r: A5 N8 ]/ Z! l& r
- /* Initialize Endpoint 0 */
( @1 K3 z; ^2 x) Y+ P# T8 N7 K - SetEPType(ENDP0, EP_CONTROL);
% y" u2 x! u# n* u0 V2 A - SetEPTxStatus(ENDP0, EP_TX_STALL);! C2 L# {: t* f; h5 |" R5 C# T7 {
- SetEPRxAddr(ENDP0, ENDP0_RXADDR);. m5 [. l3 k6 r# ~
- SetEPTxAddr(ENDP0, ENDP0_TXADDR);
! w) z I( o. a) r+ V/ D2 F2 Z - Clear_Status_Out(ENDP0);
+ `6 F# X$ U' Q% b - SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);
: i" J" B6 y. J# a: [$ u, V - SetEPRxValid(ENDP0);9 G& M8 z0 L' t( {( U* q
7 V* N [+ b! H) k4 z) K- /* Initialize Endpoint 1 */* ]4 C5 ~- d* H0 ~' v+ K
- SetEPType(ENDP1, EP_INTERRUPT);% x9 F% Z* z9 c5 q) e7 }
- SetEPTxAddr(ENDP1, ENDP1_TXADDR);
" o; z( t0 Z& k1 V - SetEPTxCount(ENDP1, 64);. j/ ?3 t9 y3 q% |3 @
- SetEPRxStatus(ENDP1, EP_RX_DIS);
( E* d& n6 j; x7 z2 Z - SetEPTxStatus(ENDP1, EP_TX_NAK);
; R) Z- q, X" \5 e$ }9 V0 f
& z! t( u! q2 l. I$ n0 u( E5 \- /* Initialize Endpoint 1 */
1 n+ {* U( n5 U* l - // SetEPType(ENDP1, EP_INTERRUPT);
& T/ f) X/ w, m2 Z' u! y - SetEPRxAddr(ENDP1, ENDP1_RXADDR);
' X! o; K7 t# g+ `* k% Q - SetEPRxCount(ENDP1, 64);8 p! U! R; S; P9 G- J; p- P
- // SetEPTxStatus(ENDP1, EP_TX_DIS);
; Y* y& j1 y2 _) u1 ?4 |; ^ - SetEPRxStatus(ENDP1, EP_RX_VALID);6 X0 A& S. S( x: ^- H' y* u% d' Y
- /* Set this device to response on default address */
3 v4 u$ Y- C' |. m! l( s Y - SetDeviceAddress(0);. @$ q0 D( K, D5 H& I
- }
复制代码
# _5 B+ U9 Y# k5 Z+ o五、usb_endp.c文件9 `3 R! v. _5 {4 X! D& P
- void EP1_OUT_Callback(void)2 I+ `( \ f$ ~- U& \- p! o; O
- {' D- L ~! W' E7 o; V
- 这些写接收代码1 Y2 [8 Z6 L9 P
- }
复制代码 9 n8 Z. y6 A3 t- C8 m
六、数据发送和接收,举例说明
! g: \$ c8 L- |6 I) n& h1、数据接收
1 X4 [; z, D3 G- u8 DataLen;
8 e9 ]+ [* k! q' q+ I& _: C5 U - DataLen = GetEPRxCount(ENDP1);
2 p; P; R$ H$ C/ V |3 m9 A$ l - PMAToUserBufferCopy(TX1_buffer, ENDP1_RXADDR, DataLen);, \ ?+ u& y: `" N' @- b3 Y# d
- SetEPRxValid(ENDP1);- P0 k& }+ j& @% E
- USART1_Send(DataLen);3 o# U4 Q _! e2 ]5 g4 y
- count_out = 1;
复制代码 6 }$ Z8 X# }! t' b3 s( v4 O. c: P
2、数据发送. q" \7 U t2 O) q) Y
- UserToPMABufferCopy(InBuffer, GetEPTxAddr(ENDP1), 64); h0 z6 H" m" h$ N% Z4 }
- SetEPTxCount(ENDP1, 64);
9 v# K# H: t" s/ W+ h6 Z - SetEPTxValid(ENDP1);
复制代码
" ^& U; H, f& u如果你发送数据较为频繁,每次发送前应使用GetEPTxStatus(ENDP1)检测上次发送是否完成。如果端点状态处于EP_TX_VALID,说明发送未结束,如果端点状态处于EP_TX_NAK,说明发送结束。2 S& `/ P6 N1 o a% {! q9 K" K
4 e# o l, x: x; Q% x h T; e& s! X
(by xidongs)* P. m9 x" G: ]/ `2 e
|