STM32 USB HID键盘例程
: ~2 i, w0 C" U
, O. _* Y1 g' u4 H+ I% r- T最全USB HID开发资料,悉心整理一个月,亲自测试7 Z E* z, Y n- j9 t/ P9 V1 }
: s* o9 r7 b6 z1 E
通过STM32CUbeMX建立USB HID的双向通讯实验成功. U* s6 J3 L+ ]5 a" b
5 @/ K$ M$ S6 ?) t% x0 U% D6 @1 `3 ?+ {6 M9 p2 \
发现很多人对STM32的USB通信很感兴趣。要将USB的通信协议搞懂确实是一个比较漫长的过程。但是USB的HID通信无论是上位机的设计还是STM32程序的编程都非常的简单。只是我想很多人都不知道而已。这篇文章的目的是让大家以最短的时间将USB加到你的设备中。如果想学得更深就靠大家。$ `, y8 p2 c5 k$ `. U! l7 C) I8 L
- ^, o; D/ V/ v3 a* kHID只是适合低速传输,其理论上可以达到64KB/S,但多由于windows系统和硬件的关系一般达不到这个传输数度。但这个速度对于一般系统的控制和数据传输都已经足够了,而且是免驱,省去了很多麻烦。如果您需要高速传输可参考我的另外一篇文章《STM32的USB例程修改步骤》文章在1 D3 Y( Z+ u* d! H( K& H
: ?. {* d5 [: o+ D2 y0 F
一、安装完MDK后请打开C:/Keil/ARM/Examples/ST/STM32F10xUSBLib/Demos路径,将Custom_HID在同一个目录下复制一份,如果你要放到其他路径你需要在MDK Options for target的C/C++中添加USB的头文件路径(MDK下的/INC/ST/STM32F10x/USB)。
& k2 W& Z3 v( q: ?# W) K/ F; S0 k8 R
二、打开usb_desc.c文件,该文件主要包含的端点描述符、设备描述符、配置描述符和字符描述符等。具体请大家参考其他资料了,这里主要说几个常用。! o. z7 V6 V4 a* e" `0 a6 Z
" R: k( p. O' f" d6 o' ]
u8 DeviceDescriptor[SIZ_DEVICE_DESC]为USB设备描述符。当中的& z! t8 p n, C8 H. g, L
0x83, /*idVendor (0x0483)*/
& D' N1 `) _% o6 n# x& T( ?0x04,
3 r2 ~, l! W0 s' {/ \0x50, /*idProduct = 0x5750*/
1 S; ?4 v1 }" `3 C0x57,
. [. m7 }1 V4 L. b2 Y& b//idVender字段。厂商ID号,我们这里取0x0483,仅供实验用。! A4 a( m3 |! O1 w% r/ w
//实际产品不能随便使用厂商ID号,必须跟USB协会申请厂商ID号。' Z+ Z6 F' h" j9 y+ k/ A6 {& F
//注意小端模式,低字节在先。% E5 u# S" q' }* R; {+ D
//idProduct字段。产品ID号,我们这里取0x5750。
) D- _( n. R. y! _' L//注意小端模式,低字节应该在前。6 H! \2 |+ K+ ^7 M
5 D7 r! T0 R0 e2 W7 [
8 r5 S1 R$ H2 mconst u8 ConfigDescriptor[SIZ_CONFIG_DESC]是配置描述符,注意如下
0 y6 R( z5 f. E, @0 Z- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: *// s6 m" c2 x9 Q! u8 G7 }. y
- 0x81, /* bEndpointAddress: Endpoint Address (IN) */! j9 J4 A0 ^6 \7 m+ {4 F
- 0x03, /* bmAttributes: Interrupt endpoint */& X1 ^; `7 U" x% p+ G
- 0x02, /* wMaxPacketSize: 2 Bytes max */
& E9 {/ _/ R3 ~9 b+ t4 N - 0x00,
' ^9 i( S0 s( C& J. i - 0x20, /* bInterval: Polling Interval (32 ms) */
; f! z, E u" G- y3 w2 K; \3 ? - /* 34 */
7 ]+ F' k. {0 i+ I# X- ` - 0x07, /* bLength: Endpoint Descriptor size */$ a$ A9 A) `& a! _& }+ n
- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
" A, t3 L' }' N - /* Endpoint descriptor type */& z- E+ ]7 q0 F* U6 K0 n& Y3 R
- 0x01, /* bEndpointAddress: */
! f7 ?1 N" D/ @9 v9 I - /* Endpoint Address (OUT) */
5 n, }/ j. r4 W% O' T+ W% G - 0x03, /* bmAttributes: Interrupt endpoint */
% N4 g) d2 d! w# ~5 @ - 0x02, /* wMaxPacketSize: 2 Bytes max */
0 b+ {- j3 z b - 0x00,/ E& c* \5 a% ?1 K+ _% g9 C' [
- 0x20, /* bInterval: Polling Interval (20 ms) */
复制代码
2 o" M& W8 _: D+ N上面包含了“输入端点描述符”和“输出端点描述符”。
/ @% w9 K# r: p3 H% q& c//wMaxPacketSize字段。该端点的最大包长。) T" V5 o# {# v N5 B
//bInterval字段。端点查询的时间,3 s- N* q, S2 ^4 E( Z
! K0 Y- i) ~ W
为了实现更高速的通信我们修改如下:
9 J3 J9 m# U3 L* Y n; d) I6 N# D- /******************** Descriptor of endpoint ********************/
" i- v* V7 E. [" H: q! \2 [( Z - /* 27 */
9 p( |, x W. I2 g( `5 n - 0x07, /*bLength: Endpoint Descriptor size*/
' k$ a% l1 n; b# S2 A/ h% F - USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/
% i- q$ \, V U* T# w - 0x81, /*bEndpointAddress: Endpoint Address (IN)*/3 V Z r+ E6 I& J" r
- 0x03, /*bmAttributes: Interrupt endpoint*/& I4 K) g8 W d; R
- 0x40, /*wMaxPacketSize: 64 Byte max */
- f" S, c2 c/ I* v - 0x00,. Y# @# a' z7 W4 X9 Z0 R2 n) ]- {* t0 z
- 0x0A, /*bInterval: Polling Interval (10 ms)*/1 c; w# n* m! Y0 }
- /* 34 */2 u) H2 B8 N3 z) y
- /******************** Descriptor of endpoint ********************/
1 T5 `; p! ^; G; C9 I8 X+ C' t. s, Y. K - /* 27 */
- O# n) M5 B ?; x+ O - 0x07, /*bLength: Endpoint Descriptor size*/) h) c+ Y; U% o# Y+ |9 @( W: h9 O
- USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/& j( X8 D% C$ S# l
- 0x01, /*bEndpointAddress: Endpoint Address (OUT)*/1 A1 `# n. `# R0 H) E" V
- 0x03, /*bmAttributes: Interrupt endpoint*/8 ?$ h& y- \# Z K; `
- 0x40, /*wMaxPacketSize: 64 Byte max */
; _- N+ C* x) w - 0x00,
- E6 P4 J$ i( K% g* c - 0x0A, /*bInterval: Polling Interval (10 ms)*/
, L, X7 l( ~* r1 Q& {
, v8 `- E* U5 Q0 x- const u8 ReportDescriptor[SIZ_REPORT_DESC]为HID专用的报告描述符,具体的大家就参考资料了,这里可以直接复制了。
* c: @: J: R1 X - const u8 CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =9 I4 Y0 S. R/ O" m1 m5 T k
- {
/ Y9 i+ k/ f# W& b: k( M1 I/ }9 Y/ q) J - 0x05, 0xFF, // USAGE_PAGE(User define)4 H" v, l8 C0 e( |. d; r8 |- [
- 0x09, 0xFF, // USAGE(User define)
" j1 ~2 b! @0 Q( L# E - 0xa1, 0x01, // COLLECTION (Application)
6 R+ A0 Y( C1 {7 f6 G9 b' a - 0x05, 0x01, // USAGE_PAGE(1)
3 j1 V( ]1 Z& w! c' y: B - 0x19, 0x00, // USAGE_MINIMUM(0)
1 ?; a8 Q$ Z0 w9 g - 0x29, 0xFF, // USAGE_MAXIMUM(255)
M; ^: R9 X! r3 A' e' C. j1 V - 0x15, 0x00, // LOGICAL_MINIMUM (0)
3 k! C* m6 ?: V& X* j: {6 Z+ |3 E/ g - 0x25, 0xFF, // LOGICAL_MAXIMUM (255)# Z6 k4 T3 p( Y: Q
- 0x75, 0x08, // REPORT_SIZE (8)# Q* s! I3 H. E* i1 E0 l9 M5 w
- 0x95, 0x40, // REPORT_COUNT (64)7 P) T: O- Y N! \+ ]
- 0x81, 0x02, // INPUT (Data,Var,Abs)7 x- w, K' }8 |2 {
- 0x05, 0x02, // USAGE_PAGE(2)% z: R# g5 a! N- n8 S- Q
- 0x19, 0x00, // USAGE_MINIMUM (0)6 e P" m- U/ F- P
- 0x29, 0xFF, // USAGE_MAXIMUM (255)
6 V6 A6 E# U. n& w0 ~" P - 0x15, 0x00, // LOGICAL_MINIMUM (0)* x+ R$ e( M) d" R8 l
- 0x25, 0xFF, // LOGICAL_MAXIMUM (255)
* r# R% g) k5 n! X. M5 X7 Q! O - 0x95, 0x08, // REPORT_COUNT (8)
# B3 f! ~# b& X8 M. x! ?* Q$ F3 ?, z - 0x75, 0x40, // REPORT_SIZE (64)
5 B/ T4 a$ Q8 E; \7 G - 0x91, 0x02, // OUTPUT (Data,Var,Abs)
+ s7 U2 j7 d" e4 A/ _3 X6 `& k - 0xc0 // END_COLLECTION
) P/ _, y( H5 W% i - }; /* ReportDescriptor */
: L: j- y6 K/ \% E
* B3 p2 S. t! s' Y( e- const u8 CustomHID_StringVendor[CUSTOMHID_SIZ_STRING_VENDOR]8 O: B4 r$ Y2 l- i& G. i0 e! h3 h: u
- const u8 StringProduct[SIZ_STRING_PRODUCT]
2 D& s. b6 g) |, b0 B6 R2 a - const u8 StringSerial[SIZ_STRING_SERIAL]
复制代码
2 n! s; K, M1 g2 m& f& R分别是“厂商字符”、“产品字符”、“产品序列号”,这些将在USB HID设备加载的时候显示。但是这需要这些字符要求为Unicode编码,你需要将你要显示的字符先转为Unicode编码。最好大家还要根据各个数组的长度修改如下定义。" D8 ^- r7 r K3 J
$ Q4 W) ?( J, `# v- #define CUSTOMHID_SIZ_REPORT_DESC 39
8 \0 z5 X& R0 |: ?( i/ l8 J - #define CUSTOMHID_SIZ_STRING_VENDOR 645 l1 b5 ? W8 q$ z* y
- #define CUSTOMHID_SIZ_STRING_PRODUCT 28& H& Y: d6 U2 W: D* _$ ~# ^
- #define CUSTOMHID_SIZ_STRING_SERIAL 26
复制代码
" w# G$ `+ `7 v- s三、打开hw_config.c文件,将那些没有的函数删除,只保留如下函数! g3 [" r/ D$ p; E8 ~6 y: B N; L
a) Set_System(void)/ v* W* o, Z+ t6 w3 I1 t
b) void Set_USBClock(void) 4 q; @) k! o0 Y( d/ O8 E
c) void USB_Interrupts_Config(void)0 N9 A: q0 n; _. `
d) void USB_Cable_Config (FunctionalState NewState)
. e' `$ }& k$ O: U' }# \特别要注意最后一个函数,其主要作用是控制USB的上拉电阻,让电脑检测USB设备是否连接的。
6 |9 h9 D5 D3 l& ]* Q6 h( {7 ?4 a/ }* w+ ?5 V& _; K
# n7 L' v1 x$ K9 S
四、打开stm32f10x_it.c文件,把EXTI15_10_IRQHandler等中断内的代码删除。
' a9 X: n: \1 X0 p: w打开usb_prop.c文件,修改如下:: C" K$ I: m1 i0 a
- void CustomHID_Reset(void)
" x J$ l3 O) S3 \8 t - {! \1 ]) D8 `( w. M# s5 s$ y
- /* Set Joystick_DEVICE as not configured */
Y" g* w- H. ^3 D2 A2 v - pInformation->Current_Configuration = 0;
8 R6 k. [: e5 I3 V# H) a - pInformation->Current_Interface = 0;/*the default Interface*/! m, J2 t% | y6 U% ~
- SetBTABLE(BTABLE_ADDRESS);' k3 m5 `( W8 \9 B) N8 d G1 s
% u6 F! B8 |4 H* p8 r& t- /* Initialize Endpoint 0 */
9 z, B5 m/ Z! }0 h1 k* S - SetEPType(ENDP0, EP_CONTROL);
+ x V* |' c9 r2 V3 }7 L5 {6 p - SetEPTxStatus(ENDP0, EP_TX_STALL);3 _! @' C" k& C" l) Y- A" b
- SetEPRxAddr(ENDP0, ENDP0_RXADDR);) F# [( b% \7 M
- SetEPTxAddr(ENDP0, ENDP0_TXADDR);
4 w' z" E& m7 K: y - Clear_Status_Out(ENDP0);0 A% R; P. z* e$ D! @" X. z
- SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);* q/ `' ]0 s/ p( C% l3 C! ~
- SetEPRxValid(ENDP0);0 H5 l2 z( S- W$ c3 `) P( Y" _
6 C" T8 }8 ~+ ^4 V: W& r- /* Initialize Endpoint 1 */; {9 p. V+ [, P: @
- SetEPType(ENDP1, EP_INTERRUPT);
+ `5 }# o4 \$ H2 S4 P! J4 U - SetEPTxAddr(ENDP1, ENDP1_TXADDR);/ `* Q2 f' d( B8 V2 e
- SetEPTxCount(ENDP1, 64);7 s% U1 U( R- L& J- ]
- SetEPRxStatus(ENDP1, EP_RX_DIS);; I# m9 Q; F8 g0 W: @: f+ S& c
- SetEPTxStatus(ENDP1, EP_TX_NAK);. Q2 f- @6 Q0 U* T
9 x; N& a/ A- L. M* j9 b6 y i- /* Initialize Endpoint 1 */
& ~! Q# A0 z7 U+ d6 n, U - // SetEPType(ENDP1, EP_INTERRUPT);
0 N+ I0 |5 z' d8 Q) F8 b - SetEPRxAddr(ENDP1, ENDP1_RXADDR);9 N: j, R7 v1 f; y, F7 B
- SetEPRxCount(ENDP1, 64);
; K A0 w) N; g6 Q" D - // SetEPTxStatus(ENDP1, EP_TX_DIS);
G% v; Z& [: e1 }1 e3 |- @5 p - SetEPRxStatus(ENDP1, EP_RX_VALID);. R' E4 x) h' G& {. S
- /* Set this device to response on default address */( p$ ^$ l; S% K. W
- SetDeviceAddress(0);% [5 j4 \) l9 i/ o
- }
复制代码 3 h/ l% f. B6 g# o
五、usb_endp.c文件
" ~$ P# h8 A4 o$ w: s$ D$ C; e* F- void EP1_OUT_Callback(void)
0 v4 r4 b0 E* Z' B; w. p - {
, K( W# S6 o/ d/ s% q - 这些写接收代码
; x0 R7 K$ G6 O0 Z& Q - }
复制代码
' |# N- s, \: S2 K3 h' S, L六、数据发送和接收,举例说明1 K! T+ O! I/ l. ?, z
1、数据接收" k4 P: F8 e2 U, Y& Y
- u8 DataLen;
& E0 i5 I( o1 A" v' q - DataLen = GetEPRxCount(ENDP1);, Z, W6 i2 x4 n3 x4 W
- PMAToUserBufferCopy(TX1_buffer, ENDP1_RXADDR, DataLen);
. \) }* e7 D* Z: {9 j; ~# R - SetEPRxValid(ENDP1);& r# O s" z* @% Q
- USART1_Send(DataLen);! L7 m/ O( o3 G. Q7 E
- count_out = 1;
复制代码
; @5 I* r" L. |9 P+ y* T8 T6 v% c2、数据发送6 I( ^: ?0 O1 ^/ g- z
- UserToPMABufferCopy(InBuffer, GetEPTxAddr(ENDP1), 64);$ A# @0 q8 F# K8 v% `6 m2 i
- SetEPTxCount(ENDP1, 64);
$ X, _0 X8 k/ C& D8 W - SetEPTxValid(ENDP1);
复制代码 + W3 g" O9 s, F |" l' L
如果你发送数据较为频繁,每次发送前应使用GetEPTxStatus(ENDP1)检测上次发送是否完成。如果端点状态处于EP_TX_VALID,说明发送未结束,如果端点状态处于EP_TX_NAK,说明发送结束。: o4 B, j7 v- N8 Z6 b% `
6 K M% t0 k* G. z* K9 ^
& f% k5 d# Z2 \2 w& b(by xidongs)
* n( r+ N0 O# q- d: P1 s* V5 G |