STM32 USB HID键盘例程+ x* ?* g, D4 l0 C
$ ?" k. s- O- P Z4 D- s2 p$ j最全USB HID开发资料,悉心整理一个月,亲自测试4 M9 g# @2 f; D4 R
- L& T# x/ L* w2 l+ [0 k+ }
通过STM32CUbeMX建立USB HID的双向通讯实验成功
/ D5 `9 y* y+ u2 n" l$ W, h7 S% A
) K& ?& B/ P+ o发现很多人对STM32的USB通信很感兴趣。要将USB的通信协议搞懂确实是一个比较漫长的过程。但是USB的HID通信无论是上位机的设计还是STM32程序的编程都非常的简单。只是我想很多人都不知道而已。这篇文章的目的是让大家以最短的时间将USB加到你的设备中。如果想学得更深就靠大家。9 v6 s$ M( ~0 K* `8 f4 I
7 l' E% f# p" u1 U- z" P# n, oHID只是适合低速传输,其理论上可以达到64KB/S,但多由于windows系统和硬件的关系一般达不到这个传输数度。但这个速度对于一般系统的控制和数据传输都已经足够了,而且是免驱,省去了很多麻烦。如果您需要高速传输可参考我的另外一篇文章《STM32的USB例程修改步骤》文章在0 N+ c4 U9 s- w% }- p2 b( ]
% F9 U4 s* I0 J9 F- L一、安装完MDK后请打开C:/Keil/ARM/Examples/ST/STM32F10xUSBLib/Demos路径,将Custom_HID在同一个目录下复制一份,如果你要放到其他路径你需要在MDK Options for target的C/C++中添加USB的头文件路径(MDK下的/INC/ST/STM32F10x/USB)。, i. I$ ? E% Z1 {, Q3 C
. H3 x5 T: n0 W6 [& F+ p. @二、打开usb_desc.c文件,该文件主要包含的端点描述符、设备描述符、配置描述符和字符描述符等。具体请大家参考其他资料了,这里主要说几个常用。
( u; }: ^! O! k6 L
0 W2 U& u6 s7 N. bu8 DeviceDescriptor[SIZ_DEVICE_DESC]为USB设备描述符。当中的
: Y. h6 h! g2 r9 O7 P0x83, /*idVendor (0x0483)*/2 O/ ?/ [2 T s/ X+ @
0x04,
* A0 b1 B7 M8 ?; h3 i0x50, /*idProduct = 0x5750*/
8 i" h+ A( s3 r+ d0x57,1 _: q, D: M0 _, M: V; D
//idVender字段。厂商ID号,我们这里取0x0483,仅供实验用。
. y; x4 r" V0 k( f3 }8 ~$ _ o//实际产品不能随便使用厂商ID号,必须跟USB协会申请厂商ID号。7 f1 n8 }$ }: l$ {# e6 T% S( a7 |
//注意小端模式,低字节在先。- \- |# s0 h. C; g& C' G/ z% C
//idProduct字段。产品ID号,我们这里取0x5750。) ^& l! a" Q( E" b G7 i+ ]1 o9 r* d0 F
//注意小端模式,低字节应该在前。
& F! {8 x& K) f5 W6 S- p8 m; Q8 e
9 [+ I) i# B+ X" Q! H K( {+ u- g U8 _1 ^6 P- w5 R
const u8 ConfigDescriptor[SIZ_CONFIG_DESC]是配置描述符,注意如下& u" ^, }/ V( n6 P3 o; ^
- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
. d8 [4 \6 U# b; \6 E; x - 0x81, /* bEndpointAddress: Endpoint Address (IN) */! O; \1 \6 C$ w: U# E& D8 ]5 d* X
- 0x03, /* bmAttributes: Interrupt endpoint */$ G; C+ ]! B/ n, @' X, Q
- 0x02, /* wMaxPacketSize: 2 Bytes max */+ Q$ b8 |2 U8 ^) d2 ]8 Z! I$ r1 K
- 0x00,. x D; ^; A2 Z
- 0x20, /* bInterval: Polling Interval (32 ms) */
1 S% W# N; l6 J, p) [ - /* 34 */6 L& u. T6 }" C7 K$ F2 m3 m
- 0x07, /* bLength: Endpoint Descriptor size */7 C' s# Y) P3 f" y& Y% g
- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: *// @4 B' w' J- m. O
- /* Endpoint descriptor type */
3 Q, J( f& f9 ^0 i9 c A2 O' k7 R3 m - 0x01, /* bEndpointAddress: */
- m4 R0 F7 M/ u) F P - /* Endpoint Address (OUT) */, M1 w& M; f* f6 u% A
- 0x03, /* bmAttributes: Interrupt endpoint */
2 D: z8 q* e$ u/ @2 ? - 0x02, /* wMaxPacketSize: 2 Bytes max */
) {# B( n/ y1 [+ h% \1 y7 B7 p - 0x00,$ \' ?0 m! w- K
- 0x20, /* bInterval: Polling Interval (20 ms) */
复制代码
# o7 R, ^/ M1 ~" W& X$ Z, r上面包含了“输入端点描述符”和“输出端点描述符”。
( Q m' w$ i ~4 Q! e* @1 e//wMaxPacketSize字段。该端点的最大包长。" l6 `2 M9 m1 H$ e
//bInterval字段。端点查询的时间,
) M* j ?; h" Y4 Q s
8 p2 b* |* @' T" {% `8 ] `1 q; s为了实现更高速的通信我们修改如下:6 u: y: S% \) D
- /******************** Descriptor of endpoint ********************/
! {9 H( ~ U# r. }; O- e - /* 27 */5 v( K" _- [$ |5 }2 b6 o9 K+ _+ J
- 0x07, /*bLength: Endpoint Descriptor size*/
1 |% |4 s% ?6 o# o3 ` - USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/- g6 M' }0 j ?
- 0x81, /*bEndpointAddress: Endpoint Address (IN)*/
* d( j1 y. V j6 y# }& @; E - 0x03, /*bmAttributes: Interrupt endpoint*/
4 Q" V( _9 ~* Y& p& ^+ n - 0x40, /*wMaxPacketSize: 64 Byte max */
c' Z& m3 `" e- R - 0x00,3 v- e5 k2 B: f
- 0x0A, /*bInterval: Polling Interval (10 ms)*/! @! _3 w2 ]* F
- /* 34 */' H8 B* `1 t4 ~6 K
- /******************** Descriptor of endpoint ********************/
+ D, Q/ b0 a, f9 R. Y! D - /* 27 */
y1 S0 E g* f# x4 n6 t" p - 0x07, /*bLength: Endpoint Descriptor size*// q6 s' Q3 O9 [' Q0 x' W. b( p' a
- USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/: Q5 J- o2 N4 ]/ ?8 @9 o
- 0x01, /*bEndpointAddress: Endpoint Address (OUT)*/5 y& ~7 {. e8 J$ W; t+ o/ Q+ `
- 0x03, /*bmAttributes: Interrupt endpoint*/
8 a/ K! r7 e3 U0 `5 B8 ^ - 0x40, /*wMaxPacketSize: 64 Byte max */: g( N1 B7 l; F8 a
- 0x00,% p6 J5 ^8 V2 Q" F" Z0 r
- 0x0A, /*bInterval: Polling Interval (10 ms)*/ t* H' p d$ W% T) C3 I# Q3 p: a
- % C7 N+ }3 F% B/ M, o2 q, H
- const u8 ReportDescriptor[SIZ_REPORT_DESC]为HID专用的报告描述符,具体的大家就参考资料了,这里可以直接复制了。2 ?6 F- _5 B7 c* a. I3 ]! T# ~' B+ s+ _
- const u8 CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =( a( z8 B2 q8 I0 a+ @
- {
. g) A6 A4 d* t& }- n) I4 V8 w - 0x05, 0xFF, // USAGE_PAGE(User define)+ ]& s* ~2 ?: e0 Z+ U$ _4 r" D
- 0x09, 0xFF, // USAGE(User define). U' N6 i! O( w& C, I% c4 B% \5 g
- 0xa1, 0x01, // COLLECTION (Application)2 I) b5 T% ]0 n4 q
- 0x05, 0x01, // USAGE_PAGE(1)
3 a1 D2 G9 C. a1 Z( V4 N - 0x19, 0x00, // USAGE_MINIMUM(0)
+ q1 K% f0 x6 _, X& a9 A3 S - 0x29, 0xFF, // USAGE_MAXIMUM(255), B4 M1 X3 b) N" d( D! V
- 0x15, 0x00, // LOGICAL_MINIMUM (0)' r* ]/ I; E [. x5 Q: c: O
- 0x25, 0xFF, // LOGICAL_MAXIMUM (255)
\0 d3 q# _9 i/ |1 y( i4 @ - 0x75, 0x08, // REPORT_SIZE (8)/ G y4 z4 J8 T+ k
- 0x95, 0x40, // REPORT_COUNT (64)
. r; D0 Z& [- r q3 \+ B2 { - 0x81, 0x02, // INPUT (Data,Var,Abs)1 f- g) |9 O" @- q# s3 N
- 0x05, 0x02, // USAGE_PAGE(2)1 R" ^4 U% W5 J' E) O& g
- 0x19, 0x00, // USAGE_MINIMUM (0)
3 p6 w/ I$ a/ B. w - 0x29, 0xFF, // USAGE_MAXIMUM (255)$ ~7 c) I* p# u
- 0x15, 0x00, // LOGICAL_MINIMUM (0)
' J" S0 v6 D9 H& Z3 c6 R - 0x25, 0xFF, // LOGICAL_MAXIMUM (255)$ @. V0 W: S+ e
- 0x95, 0x08, // REPORT_COUNT (8)( d% M w$ a5 e4 N7 a/ C: V, C
- 0x75, 0x40, // REPORT_SIZE (64)
! i- X7 [# F. w' M2 C - 0x91, 0x02, // OUTPUT (Data,Var,Abs); ?% e, b; O' J& l, k
- 0xc0 // END_COLLECTION
# P6 _' a0 i2 t ? - }; /* ReportDescriptor */7 }6 n" D8 s; ?
; _1 `3 i' X* x' }" @5 M+ u* K$ y- const u8 CustomHID_StringVendor[CUSTOMHID_SIZ_STRING_VENDOR]8 z" P- P5 \4 h4 d% U/ W+ t
- const u8 StringProduct[SIZ_STRING_PRODUCT]
4 w8 Z7 q1 v- \& y, S1 z& \ - const u8 StringSerial[SIZ_STRING_SERIAL]
复制代码
( ~0 U9 Z3 I t r% K/ V- I- p3 [分别是“厂商字符”、“产品字符”、“产品序列号”,这些将在USB HID设备加载的时候显示。但是这需要这些字符要求为Unicode编码,你需要将你要显示的字符先转为Unicode编码。最好大家还要根据各个数组的长度修改如下定义。/ e4 [5 E1 R( Z
/ S; A( {/ H* z1 l. T- #define CUSTOMHID_SIZ_REPORT_DESC 39
) j3 E4 [+ Q& K; j - #define CUSTOMHID_SIZ_STRING_VENDOR 64: p2 _1 l( U- a/ ~4 g9 I
- #define CUSTOMHID_SIZ_STRING_PRODUCT 28
$ z1 y2 K7 Q! k% N3 U! r* L b4 v - #define CUSTOMHID_SIZ_STRING_SERIAL 26
复制代码 ' o) ~* @ n% ~" q$ }5 }5 d
三、打开hw_config.c文件,将那些没有的函数删除,只保留如下函数
" S! j. `9 w4 V+ n0 E4 Ta) Set_System(void)0 W7 Z5 E4 t. O4 W2 r+ _
b) void Set_USBClock(void)
$ b1 p* q" @7 g4 N: j+ Kc) void USB_Interrupts_Config(void)
0 g& {8 J. K) X Y# T5 l; g& d& ~d) void USB_Cable_Config (FunctionalState NewState)* m6 {# E& x" h* f
特别要注意最后一个函数,其主要作用是控制USB的上拉电阻,让电脑检测USB设备是否连接的。# u. t8 z _, g
5 ~$ [. L8 H0 U7 ]1 i6 z4 I1 A6 m! y @! f$ c% J0 k* o5 f3 T
四、打开stm32f10x_it.c文件,把EXTI15_10_IRQHandler等中断内的代码删除。
$ B8 s. ~6 N4 }9 f9 q打开usb_prop.c文件,修改如下:/ \) X+ j* E, _# `) U
- void CustomHID_Reset(void)3 a5 t0 x3 o; A& f8 \2 a! `
- {
% a! O+ F- ]: ~- _6 _( z - /* Set Joystick_DEVICE as not configured */
+ p) ^( K, n& {8 J: ?7 k% x - pInformation->Current_Configuration = 0;
: q, ^: ^3 U& f' o8 X9 s( O% Y - pInformation->Current_Interface = 0;/*the default Interface*/
9 k' t' V+ J! |$ e5 x7 e - SetBTABLE(BTABLE_ADDRESS);
; r$ ^$ r) ?# y& O! ~ - 9 R: a9 o& n) u* j4 U' Z3 ?
- /* Initialize Endpoint 0 */. }; V7 p# i Q9 \2 U; Q4 f
- SetEPType(ENDP0, EP_CONTROL);
; i2 `& L5 ~* P: t- Q0 e - SetEPTxStatus(ENDP0, EP_TX_STALL);/ v' J" [% Q2 j" W
- SetEPRxAddr(ENDP0, ENDP0_RXADDR);
4 V: W2 M0 z- q( _3 b - SetEPTxAddr(ENDP0, ENDP0_TXADDR);
% q% @3 z5 T& @# Z8 O9 r1 H - Clear_Status_Out(ENDP0);. I! l0 r& p ]6 ]& A' {
- SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);
. q/ o4 l' c9 F2 |$ ]: b8 K - SetEPRxValid(ENDP0);
5 {# P I7 D5 E1 e
. z) y1 q. u: J' ]- /* Initialize Endpoint 1 */
- K5 E3 H& p8 ^ - SetEPType(ENDP1, EP_INTERRUPT);0 e ^+ j' C9 s, v
- SetEPTxAddr(ENDP1, ENDP1_TXADDR);
5 F0 _9 d2 L7 a/ ]3 Q+ Q - SetEPTxCount(ENDP1, 64);
: w- O7 p; I1 r6 k1 d0 G$ [ - SetEPRxStatus(ENDP1, EP_RX_DIS);
) P, I" ?1 _0 C5 n3 k2 F - SetEPTxStatus(ENDP1, EP_TX_NAK);
4 c# s. u( \6 U b3 o, q: r
2 F7 L- u. u: h" ~" D; U) |4 B- /* Initialize Endpoint 1 */
# x- U6 G. }2 r8 X0 N6 Q - // SetEPType(ENDP1, EP_INTERRUPT);
3 ?& `! e, J# V5 R8 T+ L3 y - SetEPRxAddr(ENDP1, ENDP1_RXADDR);: t) D& q) D" _# K
- SetEPRxCount(ENDP1, 64);
0 ?0 x4 `7 T" ~2 K - // SetEPTxStatus(ENDP1, EP_TX_DIS);
" l: Z/ I; J2 o/ \2 ] - SetEPRxStatus(ENDP1, EP_RX_VALID);
6 m ~3 X* w* v( c% _ - /* Set this device to response on default address */
9 `% g" R$ ~; G; D - SetDeviceAddress(0);0 R" D- P5 o5 p2 {7 J: o) [
- }
复制代码
9 i) X5 u* M& N% w# E; [五、usb_endp.c文件
6 T- Q5 ?. }% ^. M" `- void EP1_OUT_Callback(void)
/ m3 H( H8 B+ n% }7 T3 v - {
$ w. a5 z' x9 [: j% j - 这些写接收代码$ q: o' x) e( ?, n
- }
复制代码
$ `/ ~; G0 I0 O# a# b六、数据发送和接收,举例说明5 n- _7 x' v5 t3 I. |7 \' B! `
1、数据接收
5 w b+ p" R' o; {$ |. J- u8 DataLen;
+ S7 f+ y: O/ ^0 X+ ~ C# l - DataLen = GetEPRxCount(ENDP1);0 z& L0 W9 Y5 ^: ?3 {3 C9 H
- PMAToUserBufferCopy(TX1_buffer, ENDP1_RXADDR, DataLen);
- A# Z3 P5 Y$ z$ _4 O, d - SetEPRxValid(ENDP1);
) [9 J9 ?5 c4 F - USART1_Send(DataLen);" o5 N3 T8 q/ L# f" W
- count_out = 1;
复制代码
0 S, z8 b8 W0 P* V2、数据发送
& U$ v D% u9 i- UserToPMABufferCopy(InBuffer, GetEPTxAddr(ENDP1), 64);
2 b& X+ f& z5 e( l: K% I" ~ - SetEPTxCount(ENDP1, 64); * X) b0 ~$ z7 ^+ p
- SetEPTxValid(ENDP1);
复制代码
6 l% @: `5 ]- h1 K' \) u1 W, H7 f如果你发送数据较为频繁,每次发送前应使用GetEPTxStatus(ENDP1)检测上次发送是否完成。如果端点状态处于EP_TX_VALID,说明发送未结束,如果端点状态处于EP_TX_NAK,说明发送结束。7 T- ~. o1 W8 K; u6 C' i
0 E$ R4 {4 H9 \* H% r
( L4 X: s! s; ]' Z6 ?
(by xidongs)
8 S) X. O+ t: z) G |