|
通过STM32CubeMX生成HID双向通讯工程之示例
, M# N7 i' Q. ?% `! I[下面内容转载于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 =
1 z$ \7 }6 l8 ^ - {8 x. p7 Z# R* E2 y! ? L6 ?
- 0x06, 0xFF, 0x00, /* USAGE_PAGE(Vendor Page: 0xFF00) */
% b6 {- p0 c' ]+ h% z9 t - 0x09, 0x01, /* USAGE (Demo Kit) */
! j, }* s! c/ M- o/ Y- l - 0xa1, 0x01, /* COLLECTION(Application) */
& V% M# l4 s% e6 } - /* 6 */
( w4 a6 E# L: [0 { - /* LED1 */ v( K; m% _( Z
- 0x85, LED1_REPORT_ID, /* REPORT_ID(1) */
0 | ]& N2 p% Z8 v( ?0 s" N l: z- x - 0x09, 0x01, /* USAGE (LED 1) */1 z8 B5 [ v+ t& Y% ^
- 0x15, 0x00, /* LOGICAL_MINIMUM (0)*/
0 i! h# g6 O* ]8 j - 0x25, 0x01, /* LOGICAL_MAXIMUM (1)*/& Y. l; v# O; U" p2 U1 r
- 0x75, 0x08, /* REPORT_SIZE (8) */
! R/ r9 r! M- f5 x1 ] - 0x95, LED1_REPORT_COUNT, /*REPORT_COUNT (1) */
- [3 ^* i2 z+ D) H - 0xB1, 0x82, /* FEATURE(Data,Var,Abs,Vol) */: ^9 J& K* s' Q
- 0x85, LED1_REPORT_ID, /* REPORT_ID(1) */# ?" Q" t& D; w
- 0x09, 0x01, /* USAGE (LED 1) */! Y% _& n8 x9 [' ^) v+ O* L$ X2 B
- 0x91, 0x82, /* OUTPUT(Data,Var,Abs,Vol) */- z- m- L5 a9 L6 A4 k
- /* 26 */& y2 H* ^: o& @3 B( t: |$ S
- /* LED2 */
5 f: B) }- b6 M3 P4 L3 X# {3 ]3 R; | - 0x85, LED2_REPORT_ID, /* REPORT_ID 2*/7 I& R& o6 C( S5 F
- 0x09, 0x02, /* USAGE (LED 2) */ q1 U7 y" w! y9 w
- 0x15, 0x00, /* LOGICAL_MINIMUM (0)*/
) w6 `: {- y0 ^; i8 e9 ]/ U) F - 0x25, 0x01, /* LOGICAL_MAXIMUM (1)*/! @2 w8 Y; J5 K$ Y& K5 U7 \4 T1 Z, Y
- 0x75, 0x08, /* REPORT_SIZE (8) */" ? s/ [- U* g6 ^
- 0x95, LED2_REPORT_COUNT, /*REPORT_COUNT (1) */' y, E5 O1 ^" V# s; u$ x, l8 R/ r
- 0xB1, 0x82, /* FEATURE(Data,Var,Abs,Vol) */* K: I: l0 t6 {4 i/ A: r0 c
- 0x85, LED2_REPORT_ID, /* REPORT_ID(2) */* `3 W& f( _8 q5 t0 |# I
- 0x09, 0x02, /* USAGE (LED 2) */* S+ v8 [& g0 c5 `/ a
- 0x91, 0x82, /* OUTPUT(Data,Var,Abs,Vol) */
7 W8 C6 `: Y# c6 r - /* 46 */! x' w- Y; h) E6 C
- /* LED3 */
9 ]2 P3 L, m2 }$ `/ ^ - 0x85, LED3_REPORT_ID, /* REPORT_ID(3) */$ j% @1 Q( ~+ Y& V
- 0x09, 0x03, /* USAGE (LED 3) */& v# p, N% {/ \ |7 Z- j' ?
- 0x15, 0x00, /* LOGICAL_MINIMUM (0)*/+ i6 _. o) ^/ p( A. d8 d
- 0x25, 0x01, /* LOGICAL_MAXIMUM (1)*/
B( |% ~5 G- K8 f - 0x75, 0x08, /* REPORT_SIZE (8) */
% x4 r0 x# \8 Z' X5 G - 0x95, LED3_REPORT_COUNT, /*REPORT_COUNT (1) */% N. Q* u4 E0 x. u2 P
- 0xB1, 0x82, /* FEATURE(Data,Var,Abs,Vol) */: z" n& S2 l- D1 H% ]# V+ w, _/ i0 W
- 0x85, LED3_REPORT_ID, /* REPORT_ID(3) */
; Y7 y5 P! w, N" r - 0x09, 0x03, /* USAGE (LED 3) */
% o/ l: m h+ ^# O* o; m r - 0x91, 0x82, /* OUTPUT (Data,Var,Abs,Vol) */, `6 q5 S0 c9 P1 n3 R0 m+ d
- /* 66 */
" v) \+ b; ^ _6 ~/ q) m - /* LED4 */
2 n; N; |& h* i" M" ?) F5 y) y4 a - 0x85, LED4_REPORT_ID, /* REPORT_ID4) */
- i% ?7 ^' T7 _: Z1 V* A2 m% O" h( T - 0x09, 0x04, /* USAGE (LED 4) */
7 S6 T, e& Z( L( o5 N - 0x15, 0x00, /* LOGICAL_MINIMUM (0)*/
8 D% [9 G! a/ d5 E& Q7 x" n0 T - 0x25, 0x01, /* LOGICAL_MAXIMUM (1)*/
& W! h# t. s: J; H$ g - 0x75, 0x08, /* REPORT_SIZE (8) */
! `* X4 B+ }3 d) T% }: S0 H - 0x95, LED4_REPORT_COUNT, /*REPORT_COUNT (1) */* G9 m: {) j' ]$ ^0 `9 N
- 0xB1, 0x82, /* FEATURE(Data,Var,Abs,Vol) */
. L$ z$ j( f1 e0 v - 0x85, LED4_REPORT_ID, /* REPORT_ID(4) */# s: k1 d2 Q4 a9 ?2 ~# P
- 0x09, 0x04, /* USAGE (LED 4) */
1 A! t" h; T" @ E" h- Q) }& c - 0x91, 0x82, /* OUTPUT(Data,Var,Abs,Vol) */$ Q- N! Y7 Z9 Y- a2 E3 I$ S2 _
- /* 86 */
5 @9 n# d0 q! `* H5 b( H. N X5 z - /* key Push Button */
) e0 q9 }# X: w; n - 0x85, KEY_REPORT_ID, /* REPORT_ID(5) */: p+ |# l3 p) h) w9 N
- 0x09, 0x05, /* USAGE (Push Button)*/
B7 @- X& l1 _ A) |9 }) p7 A, L4 _3 P - 0x15, 0x00, /* LOGICAL_MINIMUM (0)*/
8 z3 i* O+ f, v# J5 K! M. b - 0x25, 0x01, /* LOGICAL_MAXIMUM (1)*/
/ ]6 `1 t8 C, o. Z8 T! ` - 0x75, 0x01, /* REPORT_SIZE (1) */4 T; p# r/ A. `) t% f# W) Z- X
- 0x81, 0x82, /* INPUT(Data,Var,Abs,Vol) */9 J1 A! {3 Y8 D: @! `. p
- 0x09, 0x05, /* USAGE (Push Button)*/
& T) ?, g& F% y0 V4 I. m - 0x75, 0x01, /* REPORT_SIZE (1) */: V- A' @# k- N, c/ j3 F
- 0xb1, 0x82, /* FEATURE(Data,Var,Abs,Vol) */
7 `% A0 V8 ^; A/ g& P - 0x75, 0x07, /* REPORT_SIZE (7) */2 i, Y* p- _' |# C: v# U9 p
- 0x81, 0x83, /* INPUT(Cnst,Var,Abs,Vol) */
: L8 K$ e, Y* P+ g! m - 0x85, KEY_REPORT_ID, /* REPORT_ID(2) */
* N% k- i0 g7 ]/ b3 s) W - 0x75, 0x07, /* REPORT_SIZE (7) */6 L. x7 l p1 A6 F9 j v# y' y
- 0xb1, 0x83, /* FEATURE (Cnst,Var,Abs,Vol)*/$ J% J1 d$ d+ _3 p! z
- /* 114 */
9 V) |) V) d3 P8 P3 o2 w' q - /* Tamper Push Button */5 F8 p* |9 e" w6 s* Q& Z% Q6 v
- 0x85, TAMPER_REPORT_ID,/* REPORT_ID(6) */" n: ~$ ~* F- A T' ?
- 0x09, 0x06, /* USAGE (Tamper PushButton) */
6 V& t0 V/ w. P& I% `3 A+ U, a( \ - 0x15, 0x00, /* LOGICAL_MINIMUM (0)*/) _+ J. ?( m8 O) q
- 0x25, 0x01, /* LOGICAL_MAXIMUM (1)*/% c; A" ^( r. Q y
- 0x75, 0x01, /* REPORT_SIZE (1) */1 e0 l" A8 p2 N
- 0x81, 0x82, /* INPUT(Data,Var,Abs,Vol) */
* V) o) m6 i2 k3 R5 {5 Y4 a - 0x09, 0x06, /* USAGE (Tamper PushButton) */( P" v8 a6 H; o# |- R6 l
- 0x75, 0x01, /* REPORT_SIZE (1) */
: F+ C" @3 v9 ?6 p0 h - 0xb1, 0x82, /* FEATURE(Data,Var,Abs,Vol) */2 n, X& F' v& S5 {: N2 K j) v% E
- 0x75, 0x07, /* REPORT_SIZE (7) */! i! I7 f. p. [) V' w
- 0x81, 0x83, /* INPUT (Cnst,Var,Abs,Vol)*/
6 C# P5 M- ]# b1 ^0 s0 |+ ` - 0x85, TAMPER_REPORT_ID,/* REPORT_ID(6) */0 k/ p1 g" j! m% s) Y# j" E
- 0x75, 0x07, /* REPORT_SIZE (7) */
6 v- i6 J7 H. }. E9 Z J6 z - 0xb1, 0x83, /* FEATURE(Cnst,Var,Abs,Vol) */; q* N, w! _/ M1 [# @4 ^
- /* 142 */
) ^+ Z, Z$ P- E) l. {3 A - /* ADC IN */
( H m" D! _8 {" [+ i9 D/ M - 0x85, ADC_REPORT_ID, /* REPORT_ID */9 }3 I: ^: |; Q) Y5 d' A2 B* ?" k2 G+ f
- 0x09, 0x07, /* USAGE (ADC IN) */
, W: Q8 B* i- H. H - 0x15, 0x00, /* LOGICAL_MINIMUM (0)*/) J! `1 @5 _! _
- 0x26, 0xff, 0x00, /* LOGICAL_MAXIMUM(255) */. b7 q4 G4 P0 k5 V7 y- r
- 0x75, 0x08, /* REPORT_SIZE (8) */$ A0 h. B d' t
- 0x81, 0x82, /* INPUT(Data,Var,Abs,Vol) */
% }2 e1 Y4 O, g$ m - 0x85, ADC_REPORT_ID, /* REPORT_ID(7) */$ n D7 e& ~% s0 S6 L$ Y2 S A- x
- 0x09, 0x07, /* USAGE (ADC in) */
8 v+ A9 a6 u7 R% `7 _! z - 0xb1, 0x82, /* FEATURE (Data,Var,Abs,Vol)*/7 R) u9 `" A+ r
- /* 161 */% e, [& Z: g- c7 d
- 0xc0 /* END_COLLECTION */
C; i* |2 }. \4 C7 l - };
复制代码 u% S8 l- |0 u! b7 H$ Y
注意:这里一定要覆盖“同名”数组,千万不要覆盖错了。 之后将如下代码复制到usbd_custom_hid_if_if.h中。 - #define LED1_REPORT_ID 0x01
# {# Z: M9 E2 Z - #define LED1_REPORT_COUNT 0x01
/ `8 Y3 R0 l( m; q1 J - #define LED2_REPORT_ID 0x02
3 _5 }2 ?# m% t - #define LED2_REPORT_COUNT 0x01# y" |9 ^+ \4 `1 P9 g! n$ p
- #define LED3_REPORT_ID 0x03
: `1 |4 G1 q" L" g5 Y - #define LED3_REPORT_COUNT 0x01- m6 Z( ^0 G* V! H# m/ G
- #define LED4_REPORT_ID 0x04
: E" O& W! P+ K0 M+ d2 G - #define LED4_REPORT_COUNT 0x01" Q& b+ T' k, o: [ V- {) h* w
- #define KEY_REPORT_ID 0x05
3 _5 b" F& H, H4 t - #define TAMPER_REPORT_ID 0x06
' j( ]5 l9 z; i9 y" p; h2 d% w - #define ADC_REPORT_ID 0x07
复制代码 % ?+ B. O# X( B) }4 P3 h' o0 y
最后在usbd_conf.h文件中将USBD_CUSTOM_HID_REPORT_DESC_SIZE的定义值修改 为163(默认值是2) - #defineUSBD_CUSTOM_HID_REPORT_DESC_SIZE 163 //2
复制代码 * ?2 N# r5 }* u
为什么这样修改呢? 简单说一下其中关键值的含义。 这个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 */8 p O9 |$ }$ G; l7 w- a6 I* L
- uint8_t i=0;
3 w; k1 @, }* ?! \ - sendbuffer[0]=0x07; //这个是report ID,每次发送报文都需要以这个为开始,这样主机才能正确//解析后边的数据含义
' m: E( d% \; v2 L* ` - sendbuffer[1]=0x01; //这个是实际发送的数据,可以自由定义,只要不超过报文描述符的限制
+ j8 N' W' T8 ~4 y3 V - /* USER CODE END 2 */
) [4 K7 t/ o) H9 K% ]; @ - /* Infinite loop */' r2 u# f% \; }7 i
- /* USER CODE BEGIN WHILE */
6 w, p; b4 b7 q" a: k* N) M - while (1): l2 S6 p$ ]* Z' v/ w( W
- {
6 d4 u0 D) b+ ~7 A' B& S- U# B - HAL_Delay(100); //延迟100ms
, k1 K$ m% n1 t0 P/ U% i! Y7 M5 D - sendbuffer[1]++; //每次发送都将变量自加1
$ {$ S8 ^% A7 R( \4 u - USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS,sendbuffer,2);//发送报文
/ P9 R2 J7 E. J& T& C% s7 X- N - /* USER CODE END WHILE */7 N K7 M( f0 H
- /* USER CODE BEGIN 3 */: Z7 n( M9 a$ o0 ^7 v# P) g
- }
8 u* B- h" c. @0 [3 W: Z9 X - /* USER CODE END 3 */
复制代码
4 g" f9 ?. m ]: w( X0 x3 d编译后下载到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)- R- D6 P& v, u6 V
- {
# j1 m% j0 _8 ], w# t - /* USER CODE BEGIN 6 */switch(event_idx)8 Y: u5 i I' c8 |* Y5 |. Z
- {3 r S% V0 D% W: E
- case 1: /* LED3 */. e- F) `6 X+ m, G' Y1 l; G" X- J
- (state == 1) ?HAL_GPIO_WritePin(LD3_GPIO_Port,LD3_Pin,GPIO_PIN_SET) :* S( }4 s/ J+ b' Z/ Y
- HAL_GPIO_WritePin(LD3_GPIO_Port,LD3_Pin,GPIO_PIN_RESET);4 G( M! j4 C+ V0 M; G0 J/ p9 B
- break;
8 o$ K. |. p2 d. K - case 2: /* LED4 */
- Z$ g& b$ Y, a$ C' g& s7 B - (state == 1) ?HAL_GPIO_WritePin(LD4_GPIO_Port,LD4_Pin,GPIO_PIN_SET) :. F$ h8 S v0 r8 M G
- HAL_GPIO_WritePin(LD4_GPIO_Port,LD4_Pin,GPIO_PIN_RESET);9 s+ N, A0 \- b9 r6 c
- break;
. c+ t$ [& _3 k* d! o - case 3: /* LED5 */' K- K, ]& ~4 T& H
- (state == 1) ?HAL_GPIO_WritePin(LD5_GPIO_Port,LD5_Pin,GPIO_PIN_SET) :& J k7 ]0 P" y( H9 F6 B/ A
- HAL_GPIO_WritePin(LD5_GPIO_Port,LD5_Pin,GPIO_PIN_RESET);5 E! b/ Z: I) N; n5 {7 _
- break;
M9 Y9 E: N8 M" l( G; X: J( ] - case 4: /* LED6 */" }" ~& P+ M% X4 ~
- (state == 1) ?HAL_GPIO_WritePin(LD6_GPIO_Port,LD6_Pin,GPIO_PIN_SET) :
* ^4 I0 v2 C; c4 P; P" X - HAL_GPIO_WritePin(LD6_GPIO_Port,LD6_Pin,GPIO_PIN_RESET);
8 p8 i! R& w1 r1 o. L& H: A - break;/ B4 S0 h. Y9 F" j. h( |" \
- default:9 m2 H( [+ A( b
- break;7 V: I! X) a; W; e, n# |1 ?
- }$ U1 h8 K6 v! ~: s# j4 W( m, D
- return (0);
3 h+ \* ?: M: C - /* USER CODE END 6 */+ T' u% t9 M/ T1 q8 R8 W6 y/ Y
- }
复制代码
% d# s/ X- C/ m, r1 [( C编译之后下载到MCU内,通过USBUSER连接到PC端,打开UsbHidDemonstrator,我们可以通过勾选右下角的图形界面来实现控制开发板上的LED电量或者关闭。 当然,这个是通过图像化的界面来进行控制,你也可以通过Input/outputtransfer中的写入对话框来完成这个操作。注意,写入的第一个字节是ID,表示你想控制的是哪个LED;第二个字节是0或者是1,表示你想让这个LDE的状态变成灭还是亮。 总结: 本范例程序是为了快速实现USB 从机设备与主句设备双向通讯目的,其初始化代码是用STM32CubeMX来生成的,大大降低了工程师开发USB设备的难度(尤其是是入门阶段的难度)。从这个工程的基础上,工程师可以比较方便的建立好框架工程并,对其中的代码进行研究,进而移植或增加自己的应用代码。
7 A+ N! ?8 l' H+ y, ^
文章出处: 茶话MCU + T9 V5 J% z& Y5 t# k( K5 z J
|