STM32 USB HID键盘例程
. W: m" k y1 H9 D1 N; r$ f5 |- ]# x/ Y+ O' D# j! u @1 H4 y
最全USB HID开发资料,悉心整理一个月,亲自测试
$ ?. O/ j0 j/ u2 R& A) W D8 l* a/ |# A( T9 k) P; [
通过STM32CUbeMX建立USB HID的双向通讯实验成功2 ?, I2 q6 A9 b3 _ H Y8 i
, o; B7 P O: Q' `! g( @4 b( P3 n; |, a9 l! z$ o. Z
发现很多人对STM32的USB通信很感兴趣。要将USB的通信协议搞懂确实是一个比较漫长的过程。但是USB的HID通信无论是上位机的设计还是STM32程序的编程都非常的简单。只是我想很多人都不知道而已。这篇文章的目的是让大家以最短的时间将USB加到你的设备中。如果想学得更深就靠大家。
+ p0 y& E9 N, L, |6 U' e# J; }1 u9 S2 r3 D# ~/ d1 w, X% V. q# o- N
HID只是适合低速传输,其理论上可以达到64KB/S,但多由于windows系统和硬件的关系一般达不到这个传输数度。但这个速度对于一般系统的控制和数据传输都已经足够了,而且是免驱,省去了很多麻烦。如果您需要高速传输可参考我的另外一篇文章《STM32的USB例程修改步骤》文章在
! t" ]* [# Z( t4 v, H( F$ P2 K
g2 z8 E2 |/ V7 H: j一、安装完MDK后请打开C:/Keil/ARM/Examples/ST/STM32F10xUSBLib/Demos路径,将Custom_HID在同一个目录下复制一份,如果你要放到其他路径你需要在MDK Options for target的C/C++中添加USB的头文件路径(MDK下的/INC/ST/STM32F10x/USB)。
|. F2 U: k2 D( X2 G
+ Y+ d& M/ D; D* {' H, G; y二、打开usb_desc.c文件,该文件主要包含的端点描述符、设备描述符、配置描述符和字符描述符等。具体请大家参考其他资料了,这里主要说几个常用。" c: d& r4 C' D% [. Y6 j) b- ]
h+ e5 c- q# i5 B
u8 DeviceDescriptor[SIZ_DEVICE_DESC]为USB设备描述符。当中的+ G8 C& H' @8 c8 B1 u
0x83, /*idVendor (0x0483)*/" w" Z* L- T1 R
0x04,+ a% M( L2 W. k
0x50, /*idProduct = 0x5750*/
/ C7 p/ c/ F9 d3 ?( m' Y- l% f, J0x57,6 B. X2 Q: o# `1 f
//idVender字段。厂商ID号,我们这里取0x0483,仅供实验用。
+ N5 }" z4 Z% ~7 B% N( n//实际产品不能随便使用厂商ID号,必须跟USB协会申请厂商ID号。
& ?/ @- O2 b0 @* v& i//注意小端模式,低字节在先。
. y9 C. \7 V* Y, S4 k7 q//idProduct字段。产品ID号,我们这里取0x5750。
: B+ V& t" G0 T( z7 }//注意小端模式,低字节应该在前。
2 ^6 D# E$ a' {, q& c, K! j" D8 b1 V Q* ~* V+ _
& k. s8 b+ A" M4 }* ]6 e# D' C
const u8 ConfigDescriptor[SIZ_CONFIG_DESC]是配置描述符,注意如下9 E" J# Y9 Y/ P3 u. W8 L; p
- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */. I5 T, E" G, k
- 0x81, /* bEndpointAddress: Endpoint Address (IN) */
# b/ c1 L) c) _% ] - 0x03, /* bmAttributes: Interrupt endpoint */
/ q3 H: n* I0 b - 0x02, /* wMaxPacketSize: 2 Bytes max */
! v) S& W H- ^" F- C - 0x00,, o- |( H9 Q& F p8 D& b: s
- 0x20, /* bInterval: Polling Interval (32 ms) */& W! e I2 n! D6 c) j. Y; l
- /* 34 */: s* N4 p2 O8 P5 R
- 0x07, /* bLength: Endpoint Descriptor size */
6 l# i0 a9 q$ S6 e2 m - USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */9 X3 ]) [4 _- b5 h6 l
- /* Endpoint descriptor type */
8 {0 o& q; j; K" P( h% X8 n - 0x01, /* bEndpointAddress: */
T9 I- f. u! d- A - /* Endpoint Address (OUT) */5 ^, U( R, n' @' C
- 0x03, /* bmAttributes: Interrupt endpoint */' q# S5 z' S7 h( c b0 f
- 0x02, /* wMaxPacketSize: 2 Bytes max */* k+ U O, r# n1 C
- 0x00,# E) L, _& w/ ]
- 0x20, /* bInterval: Polling Interval (20 ms) */
复制代码 : o. ]/ E4 R) |% F; t
上面包含了“输入端点描述符”和“输出端点描述符”。8 z) ?* v6 }! Z* @
//wMaxPacketSize字段。该端点的最大包长。
, ]7 }- A- n4 W$ n" H, o: r6 l6 h//bInterval字段。端点查询的时间,
6 g0 N, _' u8 L. j
% E9 R( {2 f% f' k. x为了实现更高速的通信我们修改如下:
3 v4 M" t( [* X' o+ v- /******************** Descriptor of endpoint ********************/+ O, n$ g0 i' U2 q* q) r1 P4 R
- /* 27 */0 Z4 D# V& C7 ]' g' K2 m
- 0x07, /*bLength: Endpoint Descriptor size*/2 e; R, R; u/ o) q
- USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/# I' K; h% K1 ?. l' H
- 0x81, /*bEndpointAddress: Endpoint Address (IN)*/; I4 m6 ?# _$ Z p- S/ ^
- 0x03, /*bmAttributes: Interrupt endpoint*/" c7 s) n' }" c+ m# b
- 0x40, /*wMaxPacketSize: 64 Byte max */) N$ [, q3 {. @( V
- 0x00,
" O, C2 y7 u8 H" x8 w: S - 0x0A, /*bInterval: Polling Interval (10 ms)*/5 G' O, w }- H, ^
- /* 34 */2 ^: j3 L6 p! T& J
- /******************** Descriptor of endpoint ********************/
$ A* B9 I8 |- m1 A7 z- x - /* 27 */
s) O' O5 L" e4 V0 o" ~! @* s, J - 0x07, /*bLength: Endpoint Descriptor size*/' v8 e* L [1 {3 m1 Q! N! E
- USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/
( M. u. l) J8 o - 0x01, /*bEndpointAddress: Endpoint Address (OUT)*/% A) }8 n- o& z
- 0x03, /*bmAttributes: Interrupt endpoint*/
0 |; z+ K8 M# M! n. h - 0x40, /*wMaxPacketSize: 64 Byte max */) F& N! J o4 i3 ?# A
- 0x00,
% u* [0 p% N/ |: _ - 0x0A, /*bInterval: Polling Interval (10 ms)*/
$ {* r4 Q9 S3 l& q( d1 H2 X
# `+ Z9 F7 P) F k1 B9 m; ?) }; m- const u8 ReportDescriptor[SIZ_REPORT_DESC]为HID专用的报告描述符,具体的大家就参考资料了,这里可以直接复制了。
i; D {: L% _0 z% _ - const u8 CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =- w! K: A/ ^5 ]* N
- {8 N, A+ b4 T3 b, p3 R8 k
- 0x05, 0xFF, // USAGE_PAGE(User define)
3 n. x6 J- w2 ?% | `4 } - 0x09, 0xFF, // USAGE(User define)" I" e7 Q. u2 j* w
- 0xa1, 0x01, // COLLECTION (Application)/ r0 Q/ @" x" O: X6 f! X9 A
- 0x05, 0x01, // USAGE_PAGE(1)
- m) Z# M( F7 @4 E* `% \ - 0x19, 0x00, // USAGE_MINIMUM(0)
3 r6 S, [# ?9 U. K* e; n) X, R8 b% P - 0x29, 0xFF, // USAGE_MAXIMUM(255)
6 T" B( Z! ~; {# M; n$ h - 0x15, 0x00, // LOGICAL_MINIMUM (0)
& ~( {- o5 y. @( `+ I. }$ g$ | - 0x25, 0xFF, // LOGICAL_MAXIMUM (255)3 M. W& `6 r* a4 Y9 H2 ?0 ]
- 0x75, 0x08, // REPORT_SIZE (8)% O, Y8 T" y! ~0 H; e
- 0x95, 0x40, // REPORT_COUNT (64)
4 l9 r2 ^& T: J& C! ^5 a- g% Z - 0x81, 0x02, // INPUT (Data,Var,Abs)5 d/ Y: R7 u9 B3 A+ i2 x* z
- 0x05, 0x02, // USAGE_PAGE(2)4 D& a Z: y2 q k5 M5 E, M7 x
- 0x19, 0x00, // USAGE_MINIMUM (0)7 T$ ?2 z- h/ X, y6 f9 d* D
- 0x29, 0xFF, // USAGE_MAXIMUM (255)- E" T0 l# g! D7 E) J8 r+ J
- 0x15, 0x00, // LOGICAL_MINIMUM (0)+ y! j6 [; w; H) v: O
- 0x25, 0xFF, // LOGICAL_MAXIMUM (255)1 Z$ |, s' }& E- @8 X* J% \
- 0x95, 0x08, // REPORT_COUNT (8)
7 t4 R O( \- P2 ^0 S - 0x75, 0x40, // REPORT_SIZE (64)* n4 E4 ~' V; i( I- S3 {+ J! _ A
- 0x91, 0x02, // OUTPUT (Data,Var,Abs)9 ?* R! {5 h) [4 l1 L
- 0xc0 // END_COLLECTION
( O7 K; l2 }3 _' m8 _& I - }; /* ReportDescriptor */
; x% o7 i# ] q7 `
2 A% |& A) X# m! i6 D- const u8 CustomHID_StringVendor[CUSTOMHID_SIZ_STRING_VENDOR]
3 ]% l9 G! O' ]5 P I( X3 [% c - const u8 StringProduct[SIZ_STRING_PRODUCT] m% _/ V0 d- R( P
- const u8 StringSerial[SIZ_STRING_SERIAL]
复制代码
; \# N% d- W& g, T5 f+ j3 q分别是“厂商字符”、“产品字符”、“产品序列号”,这些将在USB HID设备加载的时候显示。但是这需要这些字符要求为Unicode编码,你需要将你要显示的字符先转为Unicode编码。最好大家还要根据各个数组的长度修改如下定义。
1 }) j+ L U0 ^4 h1 y. n) Z, X
1 B/ G6 p3 ]' ^, F) g/ ]1 H- #define CUSTOMHID_SIZ_REPORT_DESC 39
8 I. j/ y$ ~: D4 v, U! L - #define CUSTOMHID_SIZ_STRING_VENDOR 64
) {- Q4 g, V' s - #define CUSTOMHID_SIZ_STRING_PRODUCT 28
0 V$ Z0 H! \/ [0 B - #define CUSTOMHID_SIZ_STRING_SERIAL 26
复制代码 ; R( K/ g; O7 J/ t7 u
三、打开hw_config.c文件,将那些没有的函数删除,只保留如下函数
+ s6 R# h6 h- g) u( T; e; W7 z; x8 da) Set_System(void)
5 b- D* y2 U8 r' M% _, u( Rb) void Set_USBClock(void) * G e* c7 E+ M* f1 r6 j
c) void USB_Interrupts_Config(void)
* [4 b' o1 H9 bd) void USB_Cable_Config (FunctionalState NewState)
5 D* v# z* U1 w+ s6 v特别要注意最后一个函数,其主要作用是控制USB的上拉电阻,让电脑检测USB设备是否连接的。
3 f+ f. w, B; q; o; }7 m l ^" [( k; e' H7 Y8 ~
7 \1 Q& u3 F7 _+ a4 ^# K四、打开stm32f10x_it.c文件,把EXTI15_10_IRQHandler等中断内的代码删除。
3 y, Q) F$ d _9 h+ ?" V打开usb_prop.c文件,修改如下:( L. N" ^, ?" Z) N, w- W8 w5 q/ O
- void CustomHID_Reset(void)! S- h* d& C; k& N$ L. G+ c$ n
- {
) [* Z ]! `# N) S& N/ J8 n - /* Set Joystick_DEVICE as not configured */0 h3 n1 b; u7 W0 d, m' k$ t( p
- pInformation->Current_Configuration = 0;
- }- G0 Q: l" d* B2 [) ? - pInformation->Current_Interface = 0;/*the default Interface*/
( o4 s; C8 N1 F5 Y, P - SetBTABLE(BTABLE_ADDRESS);
/ v) L- d& T- w
% @2 p# Q. `: V* G3 l) s% `+ P- /* Initialize Endpoint 0 */1 }5 `- v0 ^0 c# d9 f& I( P
- SetEPType(ENDP0, EP_CONTROL);. ?3 `3 t- w; z+ _; B' v( B
- SetEPTxStatus(ENDP0, EP_TX_STALL);4 n, d* C' z! u) }% J& A1 [* g
- SetEPRxAddr(ENDP0, ENDP0_RXADDR);
9 }, O% n. e2 a! o, n - SetEPTxAddr(ENDP0, ENDP0_TXADDR);
9 M3 d+ v3 f: n6 A - Clear_Status_Out(ENDP0);; r4 u- m7 b6 O& Q8 P. P/ P& U
- SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);
" b# }/ { H' n4 _4 n, y - SetEPRxValid(ENDP0);
( c" f7 H" A5 [
1 N; y( s: v T6 b# w4 J' l- /* Initialize Endpoint 1 */
( q- Q" l4 M2 l% v) u; C. J4 K3 f - SetEPType(ENDP1, EP_INTERRUPT);. H9 H& Z& ^( _
- SetEPTxAddr(ENDP1, ENDP1_TXADDR);
/ b7 ?! o) h) M: q- N3 H9 e - SetEPTxCount(ENDP1, 64);, Z/ u O9 s, R3 }% b
- SetEPRxStatus(ENDP1, EP_RX_DIS);$ x1 N6 Y- |4 M0 Q
- SetEPTxStatus(ENDP1, EP_TX_NAK);4 q8 W4 C M( b$ |
/ C+ Y: k3 ^$ C- /* Initialize Endpoint 1 */
8 c- R6 K9 e! G - // SetEPType(ENDP1, EP_INTERRUPT);" ]5 _8 t9 y2 T. _: g4 ?
- SetEPRxAddr(ENDP1, ENDP1_RXADDR);' o1 w4 h. [9 p- d" ]0 n5 ~+ o
- SetEPRxCount(ENDP1, 64); t) F5 J1 ]2 L# M. ~4 s1 w% \9 S W
- // SetEPTxStatus(ENDP1, EP_TX_DIS);, \6 _$ k0 z! \) L, Y1 y) x! Y/ ]
- SetEPRxStatus(ENDP1, EP_RX_VALID);! [- [7 v* {1 f+ H
- /* Set this device to response on default address */* N3 N7 O! U" S1 G/ s0 Q3 w
- SetDeviceAddress(0);- O) X# ~2 p) x
- }
复制代码
# h; ]: v/ D" X: M1 i3 h8 _' O五、usb_endp.c文件
4 H; B7 w5 X6 t' B! m, V8 I/ n: x' d- void EP1_OUT_Callback(void)# `( f3 r( W4 ?9 F. ]* I, M
- {
% b- s" S" _, H g. ~# ^' w, h - 这些写接收代码
0 x( V% r% }" I! | - }
复制代码
; |, S2 W. ~, C5 M; _- M六、数据发送和接收,举例说明
$ F6 C/ ^8 z- E$ M2 i4 |/ V1、数据接收1 R( n( C+ @# a# U
- u8 DataLen;
+ I' a: ~1 d2 R( e - DataLen = GetEPRxCount(ENDP1);+ k9 \* V h3 c
- PMAToUserBufferCopy(TX1_buffer, ENDP1_RXADDR, DataLen);
/ t) D- ^7 i, w, ]" |! y" x d - SetEPRxValid(ENDP1);
1 S6 E4 b: _0 O7 b, S5 R* y - USART1_Send(DataLen);& j$ `+ B. k6 B, N4 b
- count_out = 1;
复制代码 1 `% u5 r' [1 z# s5 F% I8 x
2、数据发送
3 D0 N" Z* x; K, \# I- UserToPMABufferCopy(InBuffer, GetEPTxAddr(ENDP1), 64);
2 r/ v+ V! D6 _8 J4 w- I: H& { - SetEPTxCount(ENDP1, 64); 8 G+ `4 r( {3 F! ^
- SetEPTxValid(ENDP1);
复制代码
) T" j m: s: h. X9 L7 [如果你发送数据较为频繁,每次发送前应使用GetEPTxStatus(ENDP1)检测上次发送是否完成。如果端点状态处于EP_TX_VALID,说明发送未结束,如果端点状态处于EP_TX_NAK,说明发送结束。 G+ z8 g: e+ c4 C- u6 e, U
% f! r/ L# b: d# V/ Z2 C: S
, k* B& P3 y( W
(by xidongs), H/ @! e5 [5 G' l- S) G
|