STM32 USB HID键盘例程! d. {/ d% J; n& |
0 ]8 y, a2 w& ~3 x/ r# m
最全USB HID开发资料,悉心整理一个月,亲自测试
' W6 F2 h: q/ `5 ^; U4 H- {- h# S
& t& @6 s! l8 p7 c6 e; E通过STM32CUbeMX建立USB HID的双向通讯实验成功
( O' r$ R9 n3 w3 \3 T* X, w; h2 C# e; J
" \% p1 i {' `发现很多人对STM32的USB通信很感兴趣。要将USB的通信协议搞懂确实是一个比较漫长的过程。但是USB的HID通信无论是上位机的设计还是STM32程序的编程都非常的简单。只是我想很多人都不知道而已。这篇文章的目的是让大家以最短的时间将USB加到你的设备中。如果想学得更深就靠大家。
1 m# Z* M5 z% n
& \; R4 R/ @0 Q. lHID只是适合低速传输,其理论上可以达到64KB/S,但多由于windows系统和硬件的关系一般达不到这个传输数度。但这个速度对于一般系统的控制和数据传输都已经足够了,而且是免驱,省去了很多麻烦。如果您需要高速传输可参考我的另外一篇文章《STM32的USB例程修改步骤》文章在 V1 o- P0 P5 d8 X0 o/ K
# n; M. ]9 Z' h! r& |# f
一、安装完MDK后请打开C:/Keil/ARM/Examples/ST/STM32F10xUSBLib/Demos路径,将Custom_HID在同一个目录下复制一份,如果你要放到其他路径你需要在MDK Options for target的C/C++中添加USB的头文件路径(MDK下的/INC/ST/STM32F10x/USB)。+ a( a7 _5 i0 \
& D. Z& K9 z/ ~+ }+ \, x
二、打开usb_desc.c文件,该文件主要包含的端点描述符、设备描述符、配置描述符和字符描述符等。具体请大家参考其他资料了,这里主要说几个常用。
% Z9 f x8 ~& M& s9 b. H$ M) K
6 z3 t! V1 ^$ k1 U) zu8 DeviceDescriptor[SIZ_DEVICE_DESC]为USB设备描述符。当中的
" k/ a' c9 F$ j& ?" b0 R7 Z0x83, /*idVendor (0x0483)*/
7 J, D4 E1 _6 q0 \0x04, Q$ \! y) X4 A! \# U4 B5 c4 N. d
0x50, /*idProduct = 0x5750*/
1 C( R- S# b0 B0x57,
: w* E, E \ n/ \4 h, ?//idVender字段。厂商ID号,我们这里取0x0483,仅供实验用。
7 u8 t2 X% h6 u. z//实际产品不能随便使用厂商ID号,必须跟USB协会申请厂商ID号。
# i! F- i( J$ L: R( C//注意小端模式,低字节在先。
- R3 r! k# E$ z4 P//idProduct字段。产品ID号,我们这里取0x5750。& K d& e9 a8 C
//注意小端模式,低字节应该在前。
9 m" m3 a, |1 T, {) D
4 V$ H1 @3 p v0 k1 n3 l, E. Y4 |& f- e! _! d
const u8 ConfigDescriptor[SIZ_CONFIG_DESC]是配置描述符,注意如下1 z; e5 c( s& a) J& x
- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */ X/ B A# t& f: V( e- W( i8 b
- 0x81, /* bEndpointAddress: Endpoint Address (IN) */ ~1 S3 r1 [5 e8 O! @9 d# _$ E
- 0x03, /* bmAttributes: Interrupt endpoint */
& V# z1 r: |1 e( v7 y! U1 T1 f6 r - 0x02, /* wMaxPacketSize: 2 Bytes max */4 t4 E+ ?* L7 `. Z: N
- 0x00,/ k0 c: J6 v/ A/ L7 w! i- m
- 0x20, /* bInterval: Polling Interval (32 ms) */
$ _1 L- m9 b8 x7 Q9 t) t - /* 34 */
: t3 f4 j' B) F/ [/ [, l3 f- t - 0x07, /* bLength: Endpoint Descriptor size */
) L0 B3 s+ w) ]0 `, r4 x - USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
|6 k' @/ H" e) G: N+ E, w - /* Endpoint descriptor type */4 f# k+ @0 N3 a( v2 b) e
- 0x01, /* bEndpointAddress: */" q: g' g: o! `8 b4 ~4 G
- /* Endpoint Address (OUT) */
: R `$ b- V. u/ }2 { - 0x03, /* bmAttributes: Interrupt endpoint */3 ^% B- R E5 M) ~4 S# `0 L- p0 R
- 0x02, /* wMaxPacketSize: 2 Bytes max */
9 A0 [% {! U4 I# E# ^ - 0x00,
* Q5 D! v" I! j! T - 0x20, /* bInterval: Polling Interval (20 ms) */
复制代码 & Q9 ]7 L- h! [6 c* O1 w( J, x. s
上面包含了“输入端点描述符”和“输出端点描述符”。
2 D+ B6 z. {: B5 S( Y//wMaxPacketSize字段。该端点的最大包长。0 ?2 ?$ \9 d9 m. h$ S6 L3 Y3 T
//bInterval字段。端点查询的时间,+ K9 h/ E7 W `; |3 C
& L# [) E/ n" l" M/ V为了实现更高速的通信我们修改如下:8 u* B- W/ K6 r( R' w
- /******************** Descriptor of endpoint ********************/0 t, ]" z3 U9 k s" C- S1 }
- /* 27 */
' c2 u( o) F" q/ D# } - 0x07, /*bLength: Endpoint Descriptor size*/" r+ H7 K- c7 h/ M. }
- USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/
% [1 X7 M' b1 K6 A - 0x81, /*bEndpointAddress: Endpoint Address (IN)*/( Z. K$ m# w7 N0 O
- 0x03, /*bmAttributes: Interrupt endpoint*/
! L" r$ y& C4 U - 0x40, /*wMaxPacketSize: 64 Byte max */
+ ^. W. @/ g0 Y5 v$ ?. o - 0x00,0 e& C0 U* H# z/ ?! X1 p/ `0 {
- 0x0A, /*bInterval: Polling Interval (10 ms)*/5 [; b4 I! |9 I4 w0 w6 X
- /* 34 */
. u* W' T! i% X8 n9 z - /******************** Descriptor of endpoint ********************/
3 r0 a G' a. r3 y8 O - /* 27 *// M+ A" f# y P# x4 Y$ h5 ?. q- \
- 0x07, /*bLength: Endpoint Descriptor size*/
# I9 y# U8 a j. B: C+ e, v( u - USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/! s) B j1 ^' W+ ]6 B0 N0 C
- 0x01, /*bEndpointAddress: Endpoint Address (OUT)*/) F) i5 M' @; K$ w
- 0x03, /*bmAttributes: Interrupt endpoint*/" E9 S3 \3 [% r; P8 k. u9 w
- 0x40, /*wMaxPacketSize: 64 Byte max */
2 }# U5 q. x& s0 {) ] - 0x00,2 T5 o: E. S7 N. U" G
- 0x0A, /*bInterval: Polling Interval (10 ms)*/
2 J9 n! A, Y3 M! V
0 O: [( L$ e# R( z/ v7 F0 p7 Y& p$ U4 Z- const u8 ReportDescriptor[SIZ_REPORT_DESC]为HID专用的报告描述符,具体的大家就参考资料了,这里可以直接复制了。
9 y% ]5 O* m4 z) {* [ w0 F3 o/ t' W e - const u8 CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =
; I* r2 Z' k7 L+ y* L - {2 N+ t* M+ W4 F2 Z% Q( }, X* r7 o
- 0x05, 0xFF, // USAGE_PAGE(User define): N) L$ z' `* s) E
- 0x09, 0xFF, // USAGE(User define)
6 J1 W- h. R* P! _$ O: e5 n+ @ - 0xa1, 0x01, // COLLECTION (Application)
B' x, d% V f; U) @ - 0x05, 0x01, // USAGE_PAGE(1)! i3 ?# o6 q [6 P5 w& n; N; i
- 0x19, 0x00, // USAGE_MINIMUM(0)% t7 Y7 [/ _/ P- N% H$ ~7 i
- 0x29, 0xFF, // USAGE_MAXIMUM(255)& S0 R) V# u, i0 C+ G/ w3 v. E
- 0x15, 0x00, // LOGICAL_MINIMUM (0), l9 T; x5 Q2 y1 E" a
- 0x25, 0xFF, // LOGICAL_MAXIMUM (255)
$ f6 D" D$ ^& D6 |, I& D - 0x75, 0x08, // REPORT_SIZE (8)- x8 _- ]5 I+ n$ S! N9 ?/ E
- 0x95, 0x40, // REPORT_COUNT (64)
0 Q8 F. b6 p7 |* L2 a - 0x81, 0x02, // INPUT (Data,Var,Abs)
( U$ s' a! i. q# T - 0x05, 0x02, // USAGE_PAGE(2)
( y; I7 y; l& U% D! b& J- ]% j1 F7 H - 0x19, 0x00, // USAGE_MINIMUM (0)
+ N/ u# c" G6 V3 g* }6 l) I- e - 0x29, 0xFF, // USAGE_MAXIMUM (255)2 {# g7 h4 G& n, R4 k
- 0x15, 0x00, // LOGICAL_MINIMUM (0)& w1 y4 S1 F" Y( s
- 0x25, 0xFF, // LOGICAL_MAXIMUM (255)
, S* V, q0 J2 d; G8 K - 0x95, 0x08, // REPORT_COUNT (8)
/ r$ p4 M8 i% g% B8 v" c. s - 0x75, 0x40, // REPORT_SIZE (64)5 H' m t0 ^0 z
- 0x91, 0x02, // OUTPUT (Data,Var,Abs)" l; g( b# w1 S+ `
- 0xc0 // END_COLLECTION# U2 Q7 R7 k! L0 |
- }; /* ReportDescriptor */& z' _. i J* |/ _) j2 S, j& h; g3 P
, \8 K8 U( m2 W6 s+ e3 \$ N- const u8 CustomHID_StringVendor[CUSTOMHID_SIZ_STRING_VENDOR]
" t3 @% Q, b/ s! B - const u8 StringProduct[SIZ_STRING_PRODUCT]" k/ [' ~9 s" D& M# p
- const u8 StringSerial[SIZ_STRING_SERIAL]
复制代码 4 g8 }- k0 R S9 Y
分别是“厂商字符”、“产品字符”、“产品序列号”,这些将在USB HID设备加载的时候显示。但是这需要这些字符要求为Unicode编码,你需要将你要显示的字符先转为Unicode编码。最好大家还要根据各个数组的长度修改如下定义。
- N3 y+ x* V9 k. @% k8 N7 f
7 B$ s( K+ U% \3 n' y8 O1 c( j- #define CUSTOMHID_SIZ_REPORT_DESC 39" E- [, A$ D0 ~! Y
- #define CUSTOMHID_SIZ_STRING_VENDOR 64
% i* K$ ?( [ s/ k7 G9 L+ M - #define CUSTOMHID_SIZ_STRING_PRODUCT 28
0 p7 E0 Q& z9 F1 |( z - #define CUSTOMHID_SIZ_STRING_SERIAL 26
复制代码 % P, K7 [; E% L/ T3 Q9 P& H
三、打开hw_config.c文件,将那些没有的函数删除,只保留如下函数) X' R( \- o, g7 K J
a) Set_System(void)' ^% i3 ^1 c0 H* y. r# Y
b) void Set_USBClock(void)
) o# Z+ ^+ N* X4 Q6 ~( O" `) a. Hc) void USB_Interrupts_Config(void)7 R% g: g4 p. g: \/ q
d) void USB_Cable_Config (FunctionalState NewState)( N8 O( S/ j: k. l( G3 X
特别要注意最后一个函数,其主要作用是控制USB的上拉电阻,让电脑检测USB设备是否连接的。
& X8 U5 |# p2 M t: E3 l
2 s0 r" W: _5 B. U& i: M, _4 c7 G+ |6 g* V" i
四、打开stm32f10x_it.c文件,把EXTI15_10_IRQHandler等中断内的代码删除。: Q6 O! ?( R8 `3 H) z$ i! F- L
打开usb_prop.c文件,修改如下:
% d% _& I( K* e; m. O+ X' u) e- void CustomHID_Reset(void)
' I/ A7 a* P* ` - {
: P) v& }2 ]: D2 t j6 q$ r - /* Set Joystick_DEVICE as not configured */$ k: X' U/ C) `6 t# c1 M, L: j
- pInformation->Current_Configuration = 0;0 d$ f+ R h5 p
- pInformation->Current_Interface = 0;/*the default Interface*/0 ^! [- ~# \+ S: m& M
- SetBTABLE(BTABLE_ADDRESS);$ F7 n- D1 K6 O3 a& Z5 }6 d# ]
- ! c" A- b9 u- H! l
- /* Initialize Endpoint 0 */( b Z. \+ O0 D- E, f
- SetEPType(ENDP0, EP_CONTROL);4 p1 Y; S2 P- J" y3 c
- SetEPTxStatus(ENDP0, EP_TX_STALL);" B8 s) Q& {2 o3 m" k! p
- SetEPRxAddr(ENDP0, ENDP0_RXADDR);
. y, O+ |$ w) {, c2 f - SetEPTxAddr(ENDP0, ENDP0_TXADDR);2 j" e }1 l- a
- Clear_Status_Out(ENDP0);
' a* `0 j: I4 v' C* Q) |& q - SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);
! V4 w+ y" l/ M: Z - SetEPRxValid(ENDP0);
" R2 R: u0 k9 h( l
- ], |: _% n1 V5 Y- /* Initialize Endpoint 1 */
( T" g3 \2 F$ _* _ Z4 P0 l7 D( R - SetEPType(ENDP1, EP_INTERRUPT);
* ]1 d/ z% U W - SetEPTxAddr(ENDP1, ENDP1_TXADDR);
2 W* q( k) K( E( m0 y0 m8 Z x - SetEPTxCount(ENDP1, 64);7 v6 p/ ^# E/ j: L5 ]1 u8 K4 D
- SetEPRxStatus(ENDP1, EP_RX_DIS);
+ n6 {$ a* K( I& ?3 D+ j - SetEPTxStatus(ENDP1, EP_TX_NAK);* J( h( a+ P. ]1 G) Q' Z" g
- ; L; l2 `: X, e7 I
- /* Initialize Endpoint 1 */+ v) T! e3 O! J
- // SetEPType(ENDP1, EP_INTERRUPT);
- T# W/ ^5 b: L! a! X - SetEPRxAddr(ENDP1, ENDP1_RXADDR);
- w( \8 {+ L6 A" c) y3 ] - SetEPRxCount(ENDP1, 64);
6 ?. p# D3 u4 O - // SetEPTxStatus(ENDP1, EP_TX_DIS);
: T& [* P2 E3 B7 @. S - SetEPRxStatus(ENDP1, EP_RX_VALID);: y! K1 S& Y( R3 t( {" c, o
- /* Set this device to response on default address */
5 @6 M2 d; t1 Q8 j# ]8 A N - SetDeviceAddress(0);
* X+ g2 B2 x& T9 c - }
复制代码
. O2 j/ p: a& O& L+ l1 [) k五、usb_endp.c文件
& r$ P0 h7 V. j( [# k- void EP1_OUT_Callback(void)
6 l( B6 q9 ]2 V# U) y; P3 e - {! b0 o, r0 Y; k+ I# h! @- p/ {, g
- 这些写接收代码
4 }5 c* p: R& O( Y/ m. ?. B- z - }
复制代码
; F3 Q0 D; q9 ~六、数据发送和接收,举例说明
1 e; {: D% s O& C9 G9 P. s3 {1、数据接收7 ]5 L* L& X! [7 N5 m& s8 T e
- u8 DataLen;
# S# k8 ]3 u! k1 T# k5 D- r" y - DataLen = GetEPRxCount(ENDP1);8 ?$ O* Y: {, J; Y
- PMAToUserBufferCopy(TX1_buffer, ENDP1_RXADDR, DataLen);, V) y, C) r t# L; n, T- e0 k/ k
- SetEPRxValid(ENDP1);6 ^8 H' X) K- {! O: |6 x
- USART1_Send(DataLen);' S* \+ `2 F! s! v# B1 x& I- o
- count_out = 1;
复制代码
4 j$ M) s; F" | ^- D: y2、数据发送
# @4 V6 U# g6 Y% C6 L# E- UserToPMABufferCopy(InBuffer, GetEPTxAddr(ENDP1), 64);% g' |5 E; g- O1 i2 `9 B
- SetEPTxCount(ENDP1, 64); * t2 B- ~1 q R- M
- SetEPTxValid(ENDP1);
复制代码
& w$ m) v0 l n+ d: G如果你发送数据较为频繁,每次发送前应使用GetEPTxStatus(ENDP1)检测上次发送是否完成。如果端点状态处于EP_TX_VALID,说明发送未结束,如果端点状态处于EP_TX_NAK,说明发送结束。
6 i2 ~) g$ A I/ l
" ~+ y3 z$ Z1 b' X+ n" i" [3 z( ~3 ]: Y7 [5 w3 [& x
(by xidongs). i( `4 a5 M; k/ H! s
|