通过STM32CubeMX生成HID双向通讯工程之示例
% R. l8 b' a) t1 ?1 |0 W[下面内容转载于STM32单片机微信公众号] 前言 客户在做USB通讯的时候,基本的需求就是发送某些数据到USB host端,同时接收一些数据从USB Host端,那么如何快速的建立一个工程并验证数据是否正确呢?下边我们就结合STM32F072的评估板(其他的STM32xx系列的实现方式都是类似的)来快速实现一个简单的数据收发实验。 下面是具体操作和一些基本的解说。 USBHost软件 的准备 PC端软件使用ST免费提供的Usb Hid Demonstrator。这个软件可以在ST官网上免费下载到。连接地址:STSW-STM32084,此软件调用的是windows标准的HID类驱动,所以无需安装任何驱动程序及可运行。 下载安装完这个软件之后,我们就可以开始开发STM32的USB 从机程序了。 首先,打开STM32CubeMX,新建工程,选择STM32F072B-DISCOVERY开发板。 其次,在Pinout选项中,开打USB的device功能。 并在Middleware中选择开启classfor IP中的 custom Human Interface Device(HID) 点击“保存”后直接生成工程。我们这里以生成IAR工程为例,项目名叫做HID。 这样我们的工程就基本成功了,但是还缺少最最关键的一步,就是USB主机和从机的通讯“协议”,这个协议在那里实现呢?因为我们Host端软件已经是Usb Hid Demonstrator,那么这边的协议就已经固定了(其实在实际的开发中大多是主机端和从机相互沟通后,软件自行修改的),从机只需要对应这套协议即可。 将如下代码复制,替换掉usbd_custom_hid_if.c文件中的同名数组。 - __ALIGN_BEGIN static uint8_tCUSTOM_HID_ReportDesc_FS [USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
& k# `6 R& T' ?. q - {9 Z q2 A& a% L' \) o- [% R! A
- 0x06, 0xFF, 0x00, /* USAGE_PAGE(Vendor Page: 0xFF00) */( `. {0 Q Z0 L% _$ R% L, Y
- 0x09, 0x01, /* USAGE (Demo Kit) */
, Q0 L$ P% @' S; v( ]" O) Z - 0xa1, 0x01, /* COLLECTION(Application) */
2 }" M! u7 n0 Z - /* 6 */ b6 |4 D! d2 Z+ k
- /* LED1 */
7 Z( X# S3 R2 X6 u - 0x85, LED1_REPORT_ID, /* REPORT_ID(1) */9 n+ ]9 _* g K8 ~
- 0x09, 0x01, /* USAGE (LED 1) */* H3 N% G, }5 M) \9 a7 e/ z. q$ K( |
- 0x15, 0x00, /* LOGICAL_MINIMUM (0)*/! j; Y- a; s& V: g. ~' s% j2 L8 W
- 0x25, 0x01, /* LOGICAL_MAXIMUM (1)*/8 R5 J F; ?0 ^, ?$ Y/ C# H
- 0x75, 0x08, /* REPORT_SIZE (8) */
( y* Z0 K$ a b, R/ }. i: f - 0x95, LED1_REPORT_COUNT, /*REPORT_COUNT (1) */
' S f0 S8 y7 M8 } - 0xB1, 0x82, /* FEATURE(Data,Var,Abs,Vol) */
h; z2 k+ a+ v7 }! b - 0x85, LED1_REPORT_ID, /* REPORT_ID(1) */
5 O o% Y' M' K4 M5 F - 0x09, 0x01, /* USAGE (LED 1) */$ ^0 }2 {0 L! V% [% a+ S9 R
- 0x91, 0x82, /* OUTPUT(Data,Var,Abs,Vol) */1 z8 U( T8 H ^" H* w* ~9 G
- /* 26 */
6 w9 ]* W) Q- A% j% s3 J) l - /* LED2 */2 P0 m5 P1 F6 o' ~" n
- 0x85, LED2_REPORT_ID, /* REPORT_ID 2*/8 X) s5 i( y- x8 d
- 0x09, 0x02, /* USAGE (LED 2) */
& w- }- f6 Q) }+ x7 }4 h: T - 0x15, 0x00, /* LOGICAL_MINIMUM (0)*// ?$ S! x! h. m) r' l/ F
- 0x25, 0x01, /* LOGICAL_MAXIMUM (1)*/
+ C8 r- V5 f, D) o+ m; N3 p - 0x75, 0x08, /* REPORT_SIZE (8) */
: C* F, Z, {; L0 D - 0x95, LED2_REPORT_COUNT, /*REPORT_COUNT (1) */% C( W( y! N: S* `7 w5 [! l
- 0xB1, 0x82, /* FEATURE(Data,Var,Abs,Vol) */
7 p* L+ o; n+ y+ C' d - 0x85, LED2_REPORT_ID, /* REPORT_ID(2) */
' G; ^ y C* E' @1 ?6 H/ j - 0x09, 0x02, /* USAGE (LED 2) */
V6 y4 j% h9 }! k! w0 F - 0x91, 0x82, /* OUTPUT(Data,Var,Abs,Vol) */+ `& k2 N2 f( Z" Y; R
- /* 46 */
4 r4 e# \ w: ^' E }3 C - /* LED3 */
, a, O( Y4 }9 S) k- Q/ v - 0x85, LED3_REPORT_ID, /* REPORT_ID(3) */( m0 ]+ A5 @. w8 d% ^1 \! ?
- 0x09, 0x03, /* USAGE (LED 3) */+ i5 {/ ^9 P& J# L
- 0x15, 0x00, /* LOGICAL_MINIMUM (0)*/4 r. j% w/ D5 ]9 d; }! N O6 r
- 0x25, 0x01, /* LOGICAL_MAXIMUM (1)*/
. M4 j* w0 l$ p; T$ C, k1 V - 0x75, 0x08, /* REPORT_SIZE (8) */
" C T: S. p* A) C/ w4 M7 w - 0x95, LED3_REPORT_COUNT, /*REPORT_COUNT (1) */
; c8 U( o2 w. I1 }& ~ - 0xB1, 0x82, /* FEATURE(Data,Var,Abs,Vol) */
6 F/ P' N! c/ J$ ]) T, H" u( Y - 0x85, LED3_REPORT_ID, /* REPORT_ID(3) */
2 C4 [' `8 M; [/ Z7 x, o7 X. I - 0x09, 0x03, /* USAGE (LED 3) */
S' |8 W% R0 G: h+ ~$ o6 q - 0x91, 0x82, /* OUTPUT (Data,Var,Abs,Vol) */
- D: k$ w& x# ]" k; k& h u* Y - /* 66 */
+ d, f. X& \7 o' `5 @; s6 g: h3 ?1 H - /* LED4 */
: X* |- N- ?; C. @# k6 F - 0x85, LED4_REPORT_ID, /* REPORT_ID4) */* ~$ y' E0 f1 `6 a
- 0x09, 0x04, /* USAGE (LED 4) */
9 W2 W5 u: u8 j1 P5 I1 @ - 0x15, 0x00, /* LOGICAL_MINIMUM (0)*/4 T; |. Y( N! o+ d( c6 _, b# F
- 0x25, 0x01, /* LOGICAL_MAXIMUM (1)*/
( G2 d1 u( [1 X! w3 ?8 H - 0x75, 0x08, /* REPORT_SIZE (8) */
3 o# e6 P6 o& M9 s9 Q& `! } - 0x95, LED4_REPORT_COUNT, /*REPORT_COUNT (1) */
" a: `" e) f% G. C% i; ^, ] - 0xB1, 0x82, /* FEATURE(Data,Var,Abs,Vol) *// v2 O) j) b6 X- I
- 0x85, LED4_REPORT_ID, /* REPORT_ID(4) */7 p. o C. N" X" H7 r: B/ J% h F
- 0x09, 0x04, /* USAGE (LED 4) */
* e! t2 `7 O' I4 I; l - 0x91, 0x82, /* OUTPUT(Data,Var,Abs,Vol) */6 C8 v' b6 C+ h
- /* 86 */
; n# U1 W- N; u - /* key Push Button */' R; r& J3 @! r* V% } X
- 0x85, KEY_REPORT_ID, /* REPORT_ID(5) */
- p8 y+ Z8 R- Q$ z/ b9 w0 r - 0x09, 0x05, /* USAGE (Push Button)*/! q, u8 W7 X1 ?$ U
- 0x15, 0x00, /* LOGICAL_MINIMUM (0)*/
6 |8 j6 R2 p" A, |; r9 N/ B - 0x25, 0x01, /* LOGICAL_MAXIMUM (1)*/
# R% t; R2 \5 K* n; V- e - 0x75, 0x01, /* REPORT_SIZE (1) */
& c8 |9 v' H H" ~) }4 }3 K - 0x81, 0x82, /* INPUT(Data,Var,Abs,Vol) */
5 p' H- m2 B7 M; V% S - 0x09, 0x05, /* USAGE (Push Button)*/
3 @% d4 v7 C0 I' r; _$ z# c% Q9 e - 0x75, 0x01, /* REPORT_SIZE (1) */; [* Q7 Q! x& ~
- 0xb1, 0x82, /* FEATURE(Data,Var,Abs,Vol) */: _5 S3 R/ w X+ h
- 0x75, 0x07, /* REPORT_SIZE (7) */% F6 e1 J2 g/ ]3 M" @
- 0x81, 0x83, /* INPUT(Cnst,Var,Abs,Vol) */% ]% M n. o! h# `1 }9 u# P
- 0x85, KEY_REPORT_ID, /* REPORT_ID(2) */; W0 t0 V1 P* g" `1 P
- 0x75, 0x07, /* REPORT_SIZE (7) */
6 q3 e1 r3 ?8 U$ _" A - 0xb1, 0x83, /* FEATURE (Cnst,Var,Abs,Vol)*/! j2 a/ s6 G& d
- /* 114 */3 Y* Z8 F6 H) K8 o2 M
- /* Tamper Push Button */; N. w8 v3 k" F+ G7 m- w' N9 A
- 0x85, TAMPER_REPORT_ID,/* REPORT_ID(6) */# y, |( N( i+ T B
- 0x09, 0x06, /* USAGE (Tamper PushButton) */
. V5 g% C/ B: ~8 J' F2 p4 \ - 0x15, 0x00, /* LOGICAL_MINIMUM (0)*/" }! k/ S8 H$ u
- 0x25, 0x01, /* LOGICAL_MAXIMUM (1)*/& g- l% a L4 z O. j! f5 K2 H
- 0x75, 0x01, /* REPORT_SIZE (1) */5 z! O& ?) m" u+ s! H
- 0x81, 0x82, /* INPUT(Data,Var,Abs,Vol) */
5 _0 Z, w, I. d8 M2 P: o& y" U5 t - 0x09, 0x06, /* USAGE (Tamper PushButton) */ b# A# X: Z: D1 s, r
- 0x75, 0x01, /* REPORT_SIZE (1) */
. p4 E# ^) q; h& H$ J6 J - 0xb1, 0x82, /* FEATURE(Data,Var,Abs,Vol) */5 L! o( }4 o1 X P8 h% ?
- 0x75, 0x07, /* REPORT_SIZE (7) */
! S6 @. B u2 n+ ]; O5 T - 0x81, 0x83, /* INPUT (Cnst,Var,Abs,Vol)*/* V+ }* d: [8 J k% Q8 z& o1 h+ h0 @
- 0x85, TAMPER_REPORT_ID,/* REPORT_ID(6) */
$ U* T2 `6 N! h* w/ W" u( \; F' d - 0x75, 0x07, /* REPORT_SIZE (7) */
% y% f' a' ~5 Q8 J( ~; o - 0xb1, 0x83, /* FEATURE(Cnst,Var,Abs,Vol) */+ o x! x) x$ f7 V$ u3 x+ S% x
- /* 142 */$ `/ s0 p p, g( E7 U
- /* ADC IN */( v% b7 C$ g% y/ Z! c
- 0x85, ADC_REPORT_ID, /* REPORT_ID */5 k4 `5 q1 \* }( r; M1 c9 U8 q
- 0x09, 0x07, /* USAGE (ADC IN) */
3 n( M- |* A" e) {8 k - 0x15, 0x00, /* LOGICAL_MINIMUM (0)*/
- Z0 }5 Z8 n2 J7 _ - 0x26, 0xff, 0x00, /* LOGICAL_MAXIMUM(255) */
! N; g+ {0 s) \' x% A3 B4 j+ d - 0x75, 0x08, /* REPORT_SIZE (8) */# W5 _1 `- G4 `# R; _
- 0x81, 0x82, /* INPUT(Data,Var,Abs,Vol) */) J0 E- O1 G+ A. L
- 0x85, ADC_REPORT_ID, /* REPORT_ID(7) */$ m& ?/ f9 \0 |/ V
- 0x09, 0x07, /* USAGE (ADC in) */9 v6 }; X6 \7 {) l8 x. ?" ?
- 0xb1, 0x82, /* FEATURE (Data,Var,Abs,Vol)*/+ e& w7 k/ D" T: L. E% F$ o; I. _
- /* 161 */
0 w1 @0 }1 E/ w: C6 P0 V - 0xc0 /* END_COLLECTION */
" Z6 O' b" Q/ E* u: U8 J - };
复制代码 ; Z* |! U( P& e( z! a$ a. |
注意:这里一定要覆盖“同名”数组,千万不要覆盖错了。 之后将如下代码复制到usbd_custom_hid_if_if.h中。 - #define LED1_REPORT_ID 0x01
+ I$ F( U6 \0 v - #define LED1_REPORT_COUNT 0x011 v% w3 [5 @1 @$ v
- #define LED2_REPORT_ID 0x025 k' H, p- a. O8 U* x0 S c) _. l/ x
- #define LED2_REPORT_COUNT 0x012 `7 P! X0 W$ {: i- V& R! h
- #define LED3_REPORT_ID 0x036 L+ G X9 N4 {0 u A( H [6 B
- #define LED3_REPORT_COUNT 0x01
1 J+ i" I7 h; e1 E - #define LED4_REPORT_ID 0x04
- h) M% G9 Q7 z! S9 o - #define LED4_REPORT_COUNT 0x014 E( m2 N. E4 a) o
- #define KEY_REPORT_ID 0x053 c: W. ]( s( X; o/ c+ M
- #define TAMPER_REPORT_ID 0x06
/ h( n. A9 `3 p6 Z! p$ R - #define ADC_REPORT_ID 0x07
复制代码
1 q+ k, {6 g5 H4 o5 _ 最后在usbd_conf.h文件中将USBD_CUSTOM_HID_REPORT_DESC_SIZE的定义值修改 为163(默认值是2) - #defineUSBD_CUSTOM_HID_REPORT_DESC_SIZE 163 //2
复制代码
& E+ |* a- p/ n1 ~ ^为什么这样修改呢? 简单说一下其中关键值的含义。 这个HID 的报文描述符其实定义了8个部分(条目)的功能定义,分为LED1,LED2,LED3, LED4,按键输入,篡改按键输入和ADC输入。每部分的基本格式都是固定的。以LED1为例(其他条目可自行对照文档解析): 0x85, LED1_REPORT_ID, 含义是这个功能的ID号是LED1_REPORT_ID(宏定义为0x01) 这个ID号是每次报文发送的时候最先被发送出去的(USB都是LSB)字节,之后跟着的才是我们实际有效的数据/指令,到底是数据还是指令,就看你的应用程序如何去解析这个数据了。 0x09, 0x01, 这个功能序号为1,后边的序号依次递加。 0x15, 0x00, 这个是规定逻辑最小值为0 。 0x25, 0x01, 这个是规定逻辑最大值为1 。 上边的这两条语句规定了跟在报文ID后边的数据范围,最大值是1,最小值是0.(因为我们的LED也就只有灭和亮两种状态) 0x75, 0x08, 这个是报文的大小为8,只要别写错就行了。 0x95, LED1_REPORT_COUNT, 这个是说下边有LED1_REPORT_COUNT (宏定义为1)个项目会被添加,即这个功能的数量是1个 。 0xB1, 0x82, 这个是规定能够发送给从机设备的数据信息。 0x91, 0x82, 这个规定了该功能的数据方向是输出(传输方向以主机为参照)。 总结一下,通过这个报文描述符,我们就告诉了主机,在HID中有一个功能ID为1的功能,其方向是从主机到从机,每次发送1个有效数据(前边的ID是都要含有的),这个数据可以是0或者是1. 关于HID 报文描述符的详细信息,您可以在下边的网址下载一篇叫做《Device Class Definitionfor HID》的文档来参考。 http://www.usb.org/developers/hidpage 这样基本的程序框架就已经成功了。此时我们可以先编译一下,看看是否有任何遗漏的或者笔误。如果编译是正确的,那么我们就可以先下载到硬件开发板上,连接到PC端,看看是否可以枚举出设备。如果您前边的修改都是正确的,那么在PC的设备管理器中会看到如下图所示的内容。 注意:开发板上有两个一模一样的Mini USB接口,一个是USB USER,另 一个是USB ST-link,下载代码的时候用USB ST-Link,连接电脑运行程序的时候要用USB USER。 此时我们的USB枚举就完成了,这个是USB通讯的关键步骤,之后的应用通讯内容都是通过这个枚举工程来进行“规划”的。 数据发送 就类似串口通讯,我们首先做一个数据的发送工作。 在Main.c文件中,我们在while(1)的主循环中增加我们的发送函数,主要就是调用发送报文的API:USBD_CUSTOM_HID_SendReport() - /* USER CODE BEGIN 2 */$ J5 J6 v. E6 O) a
- uint8_t i=0;
! ` U9 b% R4 \/ i6 J - sendbuffer[0]=0x07; //这个是report ID,每次发送报文都需要以这个为开始,这样主机才能正确//解析后边的数据含义: X9 Q- ^& `. V2 @& x
- sendbuffer[1]=0x01; //这个是实际发送的数据,可以自由定义,只要不超过报文描述符的限制
8 R) j; z/ @2 m# { - /* USER CODE END 2 */
) i$ [0 n/ o2 ^" R - /* Infinite loop */* F& y% H+ h5 m3 Z
- /* USER CODE BEGIN WHILE */* Z3 [0 K" B4 i5 k
- while (1)
. Z# \& q* A2 Q8 e - {
@& W$ p7 F1 f8 T1 Z Y: h' ` - HAL_Delay(100); //延迟100ms0 i* b5 N, n* W% {0 F( @
- sendbuffer[1]++; //每次发送都将变量自加1
9 G! W( E2 |( H" q% L* l - USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS,sendbuffer,2);//发送报文
3 \4 d h a5 f5 z9 ]( M0 x d - /* USER CODE END WHILE */
* z) D3 `$ p+ ]. E! ?8 @ - /* USER CODE BEGIN 3 */3 D2 \4 X' F7 U7 V: F) b
- }' j- x( X, L# S
- /* USER CODE END 3 */
复制代码 . Y) o* }/ J+ ` _3 e- [/ L7 ^" i
编译后下载到MCU内,连接上位机软件即可看到如下所示的进度条在不断的增长。 这个就是我们上传到的数据在上位机的图形显示,你也可以看Input/outputtransfer里的数据变化。 这样看起来是不是更像是串口调试助手了?嘿嘿本来机制就差不多的。 数据接收 MCU的USB数据是如何接收的呢?是不是调用一个类似于串口接收的API呢? 不是的!USB的数据接收都是在中断中完成的,在新建的工程中,我们在函数CUSTOM_HID_OutEvent_FS内增加如下代码。 - static int8_t CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state)
" f( t+ [' H. l, C - {
, V. j5 O4 D% F3 _8 F - /* USER CODE BEGIN 6 */switch(event_idx)( `7 {, M* O0 ~/ W' f
- {
' R) e, K2 @0 a9 P - case 1: /* LED3 */8 p: u: v' }3 p$ i1 A* ^
- (state == 1) ?HAL_GPIO_WritePin(LD3_GPIO_Port,LD3_Pin,GPIO_PIN_SET) :" W+ X5 q$ L5 ]7 @; @
- HAL_GPIO_WritePin(LD3_GPIO_Port,LD3_Pin,GPIO_PIN_RESET);
) C6 m4 A: \" L2 m7 A% ]2 X% @ - break;
1 Q2 {% K1 t0 c. A- x - case 2: /* LED4 */
6 d4 m( \3 O# H - (state == 1) ?HAL_GPIO_WritePin(LD4_GPIO_Port,LD4_Pin,GPIO_PIN_SET) :
9 G! h/ P; o0 M- }1 t; G - HAL_GPIO_WritePin(LD4_GPIO_Port,LD4_Pin,GPIO_PIN_RESET);
& h* e7 `# k) \9 n% Q" V, G: g - break;# p2 x8 K( Q6 w* R6 w7 b
- case 3: /* LED5 */2 e& Z" S9 F3 Z8 x
- (state == 1) ?HAL_GPIO_WritePin(LD5_GPIO_Port,LD5_Pin,GPIO_PIN_SET) :
/ m# B, b' G& q4 }$ t8 \7 W - HAL_GPIO_WritePin(LD5_GPIO_Port,LD5_Pin,GPIO_PIN_RESET);8 C& |& ?: h. Q$ s
- break;: f N9 a8 D; `$ B% n) m& n# }
- case 4: /* LED6 */ X$ R2 H6 ]# {2 J: `. x
- (state == 1) ?HAL_GPIO_WritePin(LD6_GPIO_Port,LD6_Pin,GPIO_PIN_SET) :( G( B. r0 U8 i
- HAL_GPIO_WritePin(LD6_GPIO_Port,LD6_Pin,GPIO_PIN_RESET);
$ w9 H/ _& I, H' J9 i' }- s - break;1 _1 f8 w/ F- t& f3 I$ @+ T2 x
- default:# M: R( J% d' S
- break;+ m0 D, d" p$ y; D: z
- }
, }& d; A4 e9 y6 {7 C - return (0); G: `1 H1 ~! R0 R& J
- /* USER CODE END 6 */0 Z @% a6 Q3 J- q i/ i8 b
- }
复制代码 ' ]+ A) i1 [2 i& B6 k* {/ r
编译之后下载到MCU内,通过USBUSER连接到PC端,打开UsbHidDemonstrator,我们可以通过勾选右下角的图形界面来实现控制开发板上的LED电量或者关闭。 当然,这个是通过图像化的界面来进行控制,你也可以通过Input/outputtransfer中的写入对话框来完成这个操作。注意,写入的第一个字节是ID,表示你想控制的是哪个LED;第二个字节是0或者是1,表示你想让这个LDE的状态变成灭还是亮。 总结: 本范例程序是为了快速实现USB 从机设备与主句设备双向通讯目的,其初始化代码是用STM32CubeMX来生成的,大大降低了工程师开发USB设备的难度(尤其是是入门阶段的难度)。从这个工程的基础上,工程师可以比较方便的建立好框架工程并,对其中的代码进行研究,进而移植或增加自己的应用代码。 6 ~( a1 C+ m8 M. o
文章出处: 茶话MCU
, I5 I W* u$ c/ i2 K( p. @ |