STMCU小助手
发布时间:2022-2-9 21:48
|
最近发现很多人对STM32的USB通信很感兴趣。要将USB的通信协议搞懂确实是一个比较漫长的过程。但是USB的HID通信无论是上位机的设计还是STM32程序的编程都非常的简单。只是我想很多人都不知道而已。这篇文章的目的是让大家以最短的时间将USB加到你的设备中。如果想学得更深就靠大家。 HID只是适合低速传输,其理论上可以达到64KB/S,但多由于windows系统和硬件的关系一般达不到这个传输数度。但这个速度对于一般系统的控制和数据传输都已经足够了,而且是免驱,省去了很多麻烦。2 Q+ T1 R+ ]) k' i% C' k 6 \# j s- o5 R$ C1 J 8 |* _3 |* f0 u# p$ J% ]9 ~8 k: S- s 一、安装完MDK后请打开C:/Keil/ARM/Examples/ST/STM32F10xUSBLib/Demos路径,将Custom_HID在同一个目录下复制一份,如果你要放到其他路径你需要在MDK Options for target的C/C++中添加USB的头文件路径(MDK下的/INC/ST/STM32F10x/USB)。 二、打开usb_desc.c文件,该文件主要包含的端点描述符、设备描述符、配置描述符和字符描述符等。具体请大家参考其他资料了,这里主要说几个常用1 n" Y0 K% i, I4 {) y) k u8 DeviceDescriptor[SIZ_DEVICE_DESC]为USB设备描述符。当中的 0x83, /*idVendor (0x0483)*/ ]! ~2 ^. I4 w1 U( Z, T6 q! a! K + i. k3 r" @) P% E. T$ x1 n, t 0x04, 2 }( A/ w: W9 d% w/ w& p& [ 0x50, /*idProduct = 0x5750*/ 0x57, " A4 |9 N" R% S //idVender字段。厂商ID号,我们这里取0x0483,仅供实验用。3 M0 ~4 K$ x# [) U3 t9 S% g //实际产品不能随便使用厂商ID号,必须跟USB协会申请厂商ID号。% s- V& {; Z$ |3 q: D% d; }8 J* z # M' w Q8 l( z6 x* {/ L* n //注意小端模式,低字节在先。 //idProduct字段。产品ID号,我们这里取0x5750。 / c+ Z; q/ c6 Y9 u //注意小端模式,低字节应该在前。 const u8 ConfigDescriptor[SIZ_CONFIG_DESC]是配置描述符,注意如下 Q/ [. Y% e* q, o( H / k* f8 b. ` b3 l9 X USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */$ @% a |8 b8 ?. k/ y0 w 0 x0 m" ?: j1 L3 n4 g+ l 0x81, /* bEndpointAddress: Endpoint Address (IN) */ : w( @7 w f- j l) |1 u6 H5 L 0x03, /* bmAttributes: Interrupt endpoint */ , u# E. o6 X. \! ^+ u8 b 0x02, /* wMaxPacketSize: 2 Bytes max */ 0x00, & j/ _: I" K# A3 I/ b$ a$ [ 0x20, /* bInterval: Polling Interval (32 ms) */ 2 W8 o% j/ @2 [, @ /* 34 *// v4 e7 \& y1 S, G. x9 w 8 L" C4 I8 B s- Z" f$ M 0x07, /* bLength: Endpoint Descriptor size */% h6 k6 |1 m" X% O, ] USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */. y+ d& N- \+ F, A$ R2 Q6 N /* Endpoint descriptor type */ 0x01, /* bEndpointAddress: */ /* Endpoint Address (OUT) *// ^; D" y: g% l( l: A & ]* t; b5 g# p+ R7 e* d 0x03, /* bmAttributes: Interrupt endpoint */ 4 N8 X! R l6 Y* {0 E6 i7 p- |3 | 0x02, /* wMaxPacketSize: 2 Bytes max */ 0x00,3 g+ d' H3 H) B' V8 p7 }5 R 8 l7 o; l5 @* y) J0 U 0x20, /* bInterval: Polling Interval (20 ms) */ ( E! @; \2 G% c+ _ 4 V! ?% F3 F; ?/ r% L: E1 K# \ 上面包含了“输入端点描述符”和“输出端点描述符”。2 m& f& V0 a# y5 T: B 9 k; M5 E6 X$ U4 H% U //wMaxPacketSize字段。该端点的最大包长。' Y1 @. P( Y: n8 [' j9 g ^! L //bInterval字段。端点查询的时间, 7 Z- ?2 q+ C7 M0 {* i 1 s/ J3 r; h8 X9 J 为了实现更高速的通信我们修改如下:( N! q- Q- s$ C% Y6 A 8 I% f0 h% M( K$ o /******************** Descriptor of endpoint ********************/* Y9 o& R6 {8 N+ t: L& K9 n /* 27 */# I! Z( k3 I# U 0 w8 D" o3 h/ k8 f. x2 W 0x07, /*bLength: Endpoint Descriptor size*/. a [' o# g, T. ]+ p4 ~ USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/7 b. f1 u- U2 K/ [ 3 Z8 [5 i; ^. D 0x81, /*bEndpointAddress: Endpoint Address (IN)*/ ) i* e2 K i. v( [* o 0x03, /*bmAttributes: Interrupt endpoint*/7 ]5 l1 X2 M' `2 l( }9 M0 r5 V 4 v4 m2 `! g, p$ y& ~ 0x40, /*wMaxPacketSize: 64 Byte max */ 0x00, 0x0A, /*bInterval: Polling Interval (10 ms)*/ /* 34 */& Z3 g2 N4 @) k# K /******************** Descriptor of endpoint ********************/) j2 D( o0 v$ L5 o- u ; f; U! ]- [- N" h /* 27 */; W( d6 ~$ ~ I2 A 0x07, /*bLength: Endpoint Descriptor size*/& ]# h9 P4 Z( i USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/ # U( C2 y2 T" `# j' Y 3 a' ~% |; \% ?7 x' m& N$ G9 T 0x01, /*bEndpointAddress: Endpoint Address (OUT)*/# q3 J; M9 U# ~ 0x03, /*bmAttributes: Interrupt endpoint*/ 0x40, /*wMaxPacketSize: 64 Byte max */6 S6 p8 P. Q/ z & G8 f7 O& m( R1 \5 j 0x00, 0 |( r. |2 B8 I- U4 M' c 0x0A, /*bInterval: Polling Interval (10 ms)*/3 K7 B. h0 P# m7 R - M9 {- G, b; ? y2 |: A1 ^4 O4 b) _, y const u8 ReportDescriptor[SIZ_REPORT_DESC]为HID专用的报告描述符,具体的大家就参考资料了,这里可以直接复制了。 const u8 CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] = { 1 \; v4 x. t- n5 }, z* @ 0x05, 0xFF, // USAGE_PAGE(User define) 0x09, 0xFF, // USAGE(User define)& D- |- G, _& I! M- m 1 \, X6 E5 j2 f7 k 0xa1, 0x01, // COLLECTION (Application) ) f: \+ i8 N' L 0x05, 0x01, // USAGE_PAGE(1)7 i$ D- j- R, G! E 5 k2 C: o6 N1 m4 E 0x19, 0x00, // USAGE_MINIMUM(0) 0x29, 0xFF, // USAGE_MAXIMUM(255). F% I, g" I; R$ K/ ~ }/ | ~$ C6 G 0x15, 0x00, // LOGICAL_MINIMUM (0)/ q& U; i5 X8 ]- b, J5 B7 b2 g8 G 0x25, 0xFF, // LOGICAL_MAXIMUM (255) 0x75, 0x08, // REPORT_SIZE (8) 0x95, 0x40, // REPORT_COUNT (64) 4 E$ C$ C- C# t7 a8 X9 P 0x81, 0x02, // INPUT (Data,Var,Abs) 0x05, 0x02, // USAGE_PAGE(2) + m' l% x2 d3 T, Y7 _ 0x19, 0x00, // USAGE_MINIMUM (0) 0x29, 0xFF, // USAGE_MAXIMUM (255) 0x15, 0x00, // LOGICAL_MINIMUM (0)* a2 J, t ^* A 0x25, 0xFF, // LOGICAL_MAXIMUM (255) 0x95, 0x08, // REPORT_COUNT (8) 2 R6 C) d% J$ x% X 0x75, 0x40, // REPORT_SIZE (64)8 W6 S G: ~6 i" `3 Z9 A, j, o7 u ! m$ t y+ Z4 v0 A 0x91, 0x02, // OUTPUT (Data,Var,Abs) 6 D/ u& p- x \ 0xc0 // END_COLLECTION }; /* ReportDescriptor */ % t6 p( _9 C; t9 \# {9 I const u8 CustomHID_StringVendor[CUSTOMHID_SIZ_STRING_VENDOR] const u8 StringProduct[SIZ_STRING_PRODUCT] ! n! s& z9 C+ b$ O* J- r1 Z const u8 StringSerial[SIZ_STRING_SERIAL] 分别是“厂商字符”、“产品字符”、“产品序列号”,这些将在USB HID设备加载的时候显示。但是这需要这些字符要求为Unicode编码,你需要将你要显示的字符先转为Unicode编码。3 \% ~8 h6 n: w4 y- ]; Y #define CUSTOMHID_SIZ_REPORT_DESC 398 K& A. g, K6 a) c+ N/ R9 g & w* [' e( Y5 C+ W. d% ^4 R5 x #define CUSTOMHID_SIZ_STRING_VENDOR 64 #define CUSTOMHID_SIZ_STRING_PRODUCT 28 #define CUSTOMHID_SIZ_STRING_SERIAL 26) \4 K4 h4 a% a7 p! m 8 ^) U4 s- F* {: T; X 三、打开hw_config.c文件,将那些没有的函数删除,只保留如下函数+ W9 W! @: M% `' L7 T5 v a) Set_System(void)% k( m& S, T3 | S1 c- Q+ s 1 P" u' R( Z+ Z, W; t, o b) void Set_USBClock(void) c) void USB_Interrupts_Config(void) d) void USB_Cable_Config (FunctionalState NewState) ; t* Z, `' F; R$ ?. | 特别要注意最后一个函数,其主要作用是控制USB的上拉电阻,让电脑检测USB设备是否连接的。2 @1 j1 F, r9 x% z8 q1 o 0 i( E, j7 c: r) [9 _+ P3 [ 四、打开stm32f10x_it.c文件,把EXTI15_10_IRQHandler等中断内的代码删除。+ V- S" g/ {: _2 h4 ~ 2 j" s( ]& O& r7 B2 G& [1 A+ ~! h# V' Z: f 打开usb_prop.c文件,修改如下: void CustomHID_Reset(void)2 P; v9 G( E9 c' d; J. ~ {$ V9 K1 X y S6 P /* Set Joystick_DEVICE as not configured */ pInformation->Current_Configuration = 0;7 ^( Q8 {+ u& R9 o1 w7 l7 { pInformation->Current_Interface = 0;/*the default Interface*/ SetBTABLE(BTABLE_ADDRESS); ; q. c$ b/ U4 K: G+ Z9 v /* Initialize Endpoint 0 */ 7 S; N0 t% ~: ^; A3 q% B! W SetEPType(ENDP0, EP_CONTROL);% y* z/ y8 b' y/ y SetEPTxStatus(ENDP0, EP_TX_STALL); ! h0 `3 z; T$ g _ SetEPRxAddr(ENDP0, ENDP0_RXADDR);6 R' h9 F' h' t8 v) ^$ m SetEPTxAddr(ENDP0, ENDP0_TXADDR);! k0 v3 K1 }1 m' E / ?$ d t, I1 T/ N) r Clear_Status_Out(ENDP0); 6 R) u# V( W( C: U SetEPRxCount(ENDP0, Device_Property.MaxPacketSize); 1 |6 L+ V0 D' L0 ?$ Z SetEPRxValid(ENDP0); 2 P) z8 x. G+ Y) \1 T: t : ]0 V) V1 f1 t3 _$ d1 x/ ]8 m /* Initialize Endpoint 1 */9 n: g* r6 c1 R8 R9 D3 y SetEPType(ENDP1, EP_INTERRUPT); + L1 _3 N. c7 `9 L SetEPTxAddr(ENDP1, ENDP1_TXADDR);3 ^; Q& z4 x# U4 v7 _. P F' Z : m+ f. U9 X6 {8 f SetEPTxCount(ENDP1, 64);, ~1 ^% X, l( s/ a" ? ( O& Q, F7 v' S2 w ]" l SetEPRxStatus(ENDP1, EP_RX_DIS);$ g7 K) T1 i# p9 X0 k5 D $ ?% m- ~' w0 e! g SetEPTxStatus(ENDP1, EP_TX_NAK);. j H' Z4 B8 Q8 U7 | " y# X6 e/ _* @& w) q* t /* Initialize Endpoint 1 */ 9 L; \ T6 |0 e) \ // SetEPType(ENDP1, EP_INTERRUPT); SetEPRxAddr(ENDP1, ENDP1_RXADDR); SetEPRxCount(ENDP1, 64);7 g+ L3 X- `3 m9 t // SetEPTxStatus(ENDP1, EP_TX_DIS); 5 ]6 V1 `# b$ T3 |' G SetEPRxStatus(ENDP1, EP_RX_VALID); ( n4 ~' t7 F) s" M /* Set this device to response on default address */0 o' P/ }* f( C& Y1 C ) E7 ~. @+ h; c% K' {- j) x- a: F2 C" u SetDeviceAddress(0);+ q7 T7 ]3 ?" C) y& ?3 u $ i" m ]& | u" C2 B } 五、usb_endp.c文件0 M2 T4 {5 P. m, E3 ?/ N& w* M$ ` void EP1_OUT_Callback(void) ; U4 O4 w: o+ b' c* Z {: ?8 s D" D1 _ . _" U P2 J2 E1 c) e 这些写接收代码 . E4 E, ?* ?- O3 N }* m* u5 M& ^5 ^3 H3 B 9 {0 q1 J2 E; g8 W8 y # @5 K0 J0 a# m, m! F& D B 六、数据发送和接收,举例说明 % L% L, K( B B& |4 c! w/ T 1、数据接收 1 E$ R% Z: v" O% W5 S- f. I u8 DataLen;: V: k" z* C! n' c8 Q 5 \0 z& Q2 p% v X# u DataLen = GetEPRxCount(ENDP1);0 }, M( c: `$ a# M PMAToUserBufferCopy(TX1_buffer, ENDP1_RXADDR, DataLen); SetEPRxValid(ENDP1);: Q% G: i/ R7 x* H + Z6 E) G" p5 L. [7 X% ? USART1_Send(DataLen); count_out = 1; 2 t) G) m3 Y- T" w/ i, _/ f 2、数据发送4 m | d) P+ K, c- O) `7 _ UserToPMABufferCopy(InBuffer, GetEPTxAddr(ENDP1), 64); SetEPTxCount(ENDP1, 64); SetEPTxValid(ENDP1); 如果你发送数据较为频繁,每次发送前应使用GetEPTxStatus(ENDP1)检测上次发送是否完成。如果端点状态处于EP_TX_VALID,说明发送未结束,如果端点状态处于EP_TX_NAK,说明发送结束。 |
实战经验 | 基于STM32G0B1的全桥移相+同步整流PWM解决方案
DMA 原理从 0 到 1:为什么它能解放 CPU?一文讲透 DMA 的工作流程
【福利三:逢7发帖赢大礼】8、基于STM32G070板子的Ymodem串口协议IAP Bootloader开发
【福利三:逢7发帖赢大礼】7、基于STM32G070板子TF卡的FATFS移植
【福利三:逢7发帖赢大礼】6、基于STM32G070板子spiflash的FATFS移植
【福利三:逢7发帖赢大礼】5、基于STM32G070板子IRDA红外遥控接收解码
【福利三:逢7发帖赢大礼】4、基于STM32G070板子SPI flash 移植SFUD库
【福利三:逢7发帖赢大礼】基于STM32G070板子的uart shell移植设计
【福利三:逢7发帖赢大礼】3、基于STM32G070板子的YModem串口协议通信
【福利三:逢7发帖赢大礼】2、基于STM32G070板子的OLED移植U8G2库
微信公众号
手机版