通过STM32CubeMX生成HID双向通讯工程之示例 , [' {7 o- `0 z: C4 G) \% Z
[下面内容转载于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 =
; _8 A; J4 N- M3 g - {
! u$ Q% @8 t4 {6 J8 Y9 z3 V% S - 0x06, 0xFF, 0x00, /* USAGE_PAGE(Vendor Page: 0xFF00) */
- m4 `: V/ W% Y; o- r" |4 W - 0x09, 0x01, /* USAGE (Demo Kit) */
. r9 \/ X( ~# B - 0xa1, 0x01, /* COLLECTION(Application) */
2 A- Q+ Q- N$ l" N8 y2 K8 \ - /* 6 */7 K* P9 C2 [6 R; ?/ k, s% M
- /* LED1 */
; U5 v' o/ m$ R" f. A$ v" i6 l - 0x85, LED1_REPORT_ID, /* REPORT_ID(1) */
% l8 Y) \( H3 G. M6 y2 R - 0x09, 0x01, /* USAGE (LED 1) */
; E: D2 I7 l# r* j - 0x15, 0x00, /* LOGICAL_MINIMUM (0)*/' d) h V3 P+ ?) W! R6 {1 Z
- 0x25, 0x01, /* LOGICAL_MAXIMUM (1)*/0 W6 N9 A/ N* s9 t' E
- 0x75, 0x08, /* REPORT_SIZE (8) *// R3 N8 i' d# ?; ~
- 0x95, LED1_REPORT_COUNT, /*REPORT_COUNT (1) */7 j, c( Q& r d
- 0xB1, 0x82, /* FEATURE(Data,Var,Abs,Vol) */
- S5 s5 Y) j$ s( d2 {! J) Z, t - 0x85, LED1_REPORT_ID, /* REPORT_ID(1) */
* C' b8 k; E: U. f( ~7 e - 0x09, 0x01, /* USAGE (LED 1) */
0 r) O) ~6 f) s9 K. [% ~ - 0x91, 0x82, /* OUTPUT(Data,Var,Abs,Vol) */" t7 X) o6 D' a% a$ a8 N4 r) c
- /* 26 */# C8 A$ F* z( ?- X( g& q
- /* LED2 */5 I4 f# T! Q/ Z- e% b
- 0x85, LED2_REPORT_ID, /* REPORT_ID 2*/
! L! i8 x Y% U! {# ]$ C - 0x09, 0x02, /* USAGE (LED 2) */& e+ G `& n2 L( _/ x
- 0x15, 0x00, /* LOGICAL_MINIMUM (0)*/+ L$ s0 P3 ]% [2 T
- 0x25, 0x01, /* LOGICAL_MAXIMUM (1)*/
8 O6 L; P) X8 H) V1 p- e - 0x75, 0x08, /* REPORT_SIZE (8) */
% {9 i& ~1 u# A- X- j$ v. S - 0x95, LED2_REPORT_COUNT, /*REPORT_COUNT (1) */7 `3 [5 l6 d5 l
- 0xB1, 0x82, /* FEATURE(Data,Var,Abs,Vol) */
2 R, K8 P. t1 n& `$ b, { - 0x85, LED2_REPORT_ID, /* REPORT_ID(2) */6 [9 Y& J. H2 T$ N& N0 y
- 0x09, 0x02, /* USAGE (LED 2) */5 s1 }. Z4 A: O
- 0x91, 0x82, /* OUTPUT(Data,Var,Abs,Vol) */
2 \4 b6 x$ V) C4 g5 |9 L8 k, u) ~# w% O - /* 46 */
/ |7 U ]' H/ w. @- ]' P& n9 j - /* LED3 */
7 h i9 u9 u6 s( `* Z o - 0x85, LED3_REPORT_ID, /* REPORT_ID(3) */
0 t5 @# A) F$ d9 |( k/ C" P9 T - 0x09, 0x03, /* USAGE (LED 3) */
" g( g* a- r. @* S - 0x15, 0x00, /* LOGICAL_MINIMUM (0)*/% v$ Y. N q1 A) z0 S2 g( G
- 0x25, 0x01, /* LOGICAL_MAXIMUM (1)*/
6 r/ g* P( b6 @- k- c( w - 0x75, 0x08, /* REPORT_SIZE (8) */ |. r% Z7 u+ X" H" h& O
- 0x95, LED3_REPORT_COUNT, /*REPORT_COUNT (1) */( _7 V- z# r1 m
- 0xB1, 0x82, /* FEATURE(Data,Var,Abs,Vol) */ a" g! D4 {( z F( U' `. n
- 0x85, LED3_REPORT_ID, /* REPORT_ID(3) */4 n% q2 d" b: e' R' `4 D$ x) D/ Y
- 0x09, 0x03, /* USAGE (LED 3) */ D/ k, w6 W( r
- 0x91, 0x82, /* OUTPUT (Data,Var,Abs,Vol) */
( |. o8 j9 m) X) f9 A/ e8 y0 Y - /* 66 */2 X& p# l0 I Q4 u0 P8 J2 n
- /* LED4 */
! q. ]5 d1 u2 U5 s" T7 r$ n' `2 z( J, Q - 0x85, LED4_REPORT_ID, /* REPORT_ID4) */4 h8 {) d" a9 f M9 Y4 R
- 0x09, 0x04, /* USAGE (LED 4) */4 }9 r9 i* B4 a1 ]: C$ G' Y9 Y
- 0x15, 0x00, /* LOGICAL_MINIMUM (0)*/
: D+ n" E) d& b6 C6 y8 B( k - 0x25, 0x01, /* LOGICAL_MAXIMUM (1)*/. `8 S+ c7 B/ L; n: g$ q
- 0x75, 0x08, /* REPORT_SIZE (8) */) p* D7 t5 ^3 `; f2 S
- 0x95, LED4_REPORT_COUNT, /*REPORT_COUNT (1) */' i7 V/ k" Q1 x0 J) C7 r) t9 O; v0 \
- 0xB1, 0x82, /* FEATURE(Data,Var,Abs,Vol) */4 Q0 i7 t c h4 ?2 h7 z1 J8 s3 N
- 0x85, LED4_REPORT_ID, /* REPORT_ID(4) *// l. ^/ w! d+ _( d% v* a
- 0x09, 0x04, /* USAGE (LED 4) */2 [$ S! F. [: L: v) Y# R" o$ v
- 0x91, 0x82, /* OUTPUT(Data,Var,Abs,Vol) *// X7 c' @/ L" A* A5 ]
- /* 86 */6 K9 d! t5 o: p; f5 {
- /* key Push Button */
1 x8 k) {) R3 x8 @% Z. @! n - 0x85, KEY_REPORT_ID, /* REPORT_ID(5) */" S# V( H* R! |1 q( a: h' i
- 0x09, 0x05, /* USAGE (Push Button)*/" {2 b; c; ^& V/ n; H
- 0x15, 0x00, /* LOGICAL_MINIMUM (0)*/% b1 [- T) Y' A! o* s7 ? m: e9 L
- 0x25, 0x01, /* LOGICAL_MAXIMUM (1)*/5 L5 e3 A1 `0 H! [% ~7 z
- 0x75, 0x01, /* REPORT_SIZE (1) */
$ N* N" r1 e& a" C, R - 0x81, 0x82, /* INPUT(Data,Var,Abs,Vol) */1 @- U2 P4 U# N! ?% c' \
- 0x09, 0x05, /* USAGE (Push Button)*/
: v7 i5 B/ d# M1 K$ w/ i - 0x75, 0x01, /* REPORT_SIZE (1) */( I" m) A; \' X( Y* g* c
- 0xb1, 0x82, /* FEATURE(Data,Var,Abs,Vol) */2 v& v* p$ y- E [ K( W) [
- 0x75, 0x07, /* REPORT_SIZE (7) */; [' n3 g* f' y4 i1 K2 ]7 r( k+ I; c
- 0x81, 0x83, /* INPUT(Cnst,Var,Abs,Vol) */
! c7 V3 N+ W5 o4 {9 X% a) z - 0x85, KEY_REPORT_ID, /* REPORT_ID(2) */
" p; j2 s( A! k7 {3 I1 T+ Z4 K - 0x75, 0x07, /* REPORT_SIZE (7) */4 |4 D3 \' S7 M5 |6 H! C1 I
- 0xb1, 0x83, /* FEATURE (Cnst,Var,Abs,Vol)*/
* x) |& K# m9 _ - /* 114 */$ A( |. O6 \5 A/ }7 m5 `. C6 Q1 Q y
- /* Tamper Push Button */
$ c2 X9 D2 S: I& w - 0x85, TAMPER_REPORT_ID,/* REPORT_ID(6) */, Q+ _4 f' _- L' T
- 0x09, 0x06, /* USAGE (Tamper PushButton) */& ~7 {& y) R H h
- 0x15, 0x00, /* LOGICAL_MINIMUM (0)*/
+ J) S/ y4 P- F1 W% b - 0x25, 0x01, /* LOGICAL_MAXIMUM (1)*/
. j- L# Y: C z5 J8 t - 0x75, 0x01, /* REPORT_SIZE (1) */
) l( q* a1 W! ~% a9 D6 C% k2 I4 a - 0x81, 0x82, /* INPUT(Data,Var,Abs,Vol) */" _6 N. P( t3 x% w$ J' k+ I6 o
- 0x09, 0x06, /* USAGE (Tamper PushButton) */
8 c2 V1 A8 {8 I1 h2 ]! K* m - 0x75, 0x01, /* REPORT_SIZE (1) */" Z n3 Z0 W8 N1 F( V! a. S
- 0xb1, 0x82, /* FEATURE(Data,Var,Abs,Vol) */5 y- q* u6 }. q0 h% e' v' l
- 0x75, 0x07, /* REPORT_SIZE (7) */
' s' d8 \4 h, ~/ g+ ?/ f, } - 0x81, 0x83, /* INPUT (Cnst,Var,Abs,Vol)*/) h& P) u+ X: f- t
- 0x85, TAMPER_REPORT_ID,/* REPORT_ID(6) */
+ W, }2 u' U" I+ ` - 0x75, 0x07, /* REPORT_SIZE (7) */! Z, J2 F H% u* E6 \. q* \
- 0xb1, 0x83, /* FEATURE(Cnst,Var,Abs,Vol) */
& @* E! Z, a0 H* r/ v4 e2 q - /* 142 */
7 A9 H; }' c( J - /* ADC IN *// h$ {: k& ]; l6 V- N5 V0 b
- 0x85, ADC_REPORT_ID, /* REPORT_ID */
0 }/ `) g- V" w+ M - 0x09, 0x07, /* USAGE (ADC IN) */
9 i( I9 n9 W. u9 s- L - 0x15, 0x00, /* LOGICAL_MINIMUM (0)*/
) X& H% d- J9 m - 0x26, 0xff, 0x00, /* LOGICAL_MAXIMUM(255) */5 }: z% p: c9 t# D# T
- 0x75, 0x08, /* REPORT_SIZE (8) */
+ {' B0 h: V* k) _ - 0x81, 0x82, /* INPUT(Data,Var,Abs,Vol) */8 R _0 U" D- h
- 0x85, ADC_REPORT_ID, /* REPORT_ID(7) */8 T- _: d) j0 F+ H' r) L6 a7 p8 {& {
- 0x09, 0x07, /* USAGE (ADC in) */
: M7 J( ^1 D1 o2 ^7 ]- T; t - 0xb1, 0x82, /* FEATURE (Data,Var,Abs,Vol)*/
, I! L3 m/ ?- Z& {1 L - /* 161 */5 h- ~6 W, K7 W+ Y. t4 j
- 0xc0 /* END_COLLECTION */6 V2 a2 N9 [; B" K
- };
复制代码 ; v7 \9 D. R! k2 i! q" R8 V l
注意:这里一定要覆盖“同名”数组,千万不要覆盖错了。 之后将如下代码复制到usbd_custom_hid_if_if.h中。 - #define LED1_REPORT_ID 0x01- \5 F6 f3 F N
- #define LED1_REPORT_COUNT 0x01; Q7 V+ v0 }8 U; ]: N) M% q' g
- #define LED2_REPORT_ID 0x02
; Q3 h! z3 f+ p1 w$ K: _ - #define LED2_REPORT_COUNT 0x01
( @0 K/ d: Y2 k* R2 Y8 Q# |: K - #define LED3_REPORT_ID 0x03
k0 B0 @& A5 f& S' D1 S6 h2 v - #define LED3_REPORT_COUNT 0x01
2 a# n: C, y. E) m# c1 d2 c! R" e' J! F - #define LED4_REPORT_ID 0x04
( i4 c$ t2 @6 |' Y O - #define LED4_REPORT_COUNT 0x01
& ^( V& h/ h7 U& Z - #define KEY_REPORT_ID 0x05' \' r, N" @8 b/ Q: F& q
- #define TAMPER_REPORT_ID 0x06
; ~8 L% C! G* M$ d - #define ADC_REPORT_ID 0x07
复制代码
" _8 c- {/ s% H {* n w 最后在usbd_conf.h文件中将USBD_CUSTOM_HID_REPORT_DESC_SIZE的定义值修改 为163(默认值是2) - #defineUSBD_CUSTOM_HID_REPORT_DESC_SIZE 163 //2
复制代码
: R( @$ p& c2 l5 D9 E& 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 */
9 I- o( `1 c% i; ~ - uint8_t i=0;
6 N; M# v# l' }$ H1 X - sendbuffer[0]=0x07; //这个是report ID,每次发送报文都需要以这个为开始,这样主机才能正确//解析后边的数据含义" r+ T- p0 @. a' m4 t4 F. B
- sendbuffer[1]=0x01; //这个是实际发送的数据,可以自由定义,只要不超过报文描述符的限制
& Q6 w. C0 ~6 b - /* USER CODE END 2 */
% u% ^" W1 F; H: v4 `9 E6 Q8 t - /* Infinite loop */5 e- i6 a8 Z. K, F7 @" I2 }
- /* USER CODE BEGIN WHILE */
' A2 e3 y8 s0 d. f! e - while (1)0 W. [1 B- m$ A% a/ ^- @
- {
1 W9 R) r- {, g; Z - HAL_Delay(100); //延迟100ms
1 J( X4 G9 d: O0 f; i - sendbuffer[1]++; //每次发送都将变量自加11 r! z2 d; v* q+ Y! o) [" E( ]
- USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS,sendbuffer,2);//发送报文7 O5 c C% c# ?4 }. w7 B7 W' h7 Y
- /* USER CODE END WHILE */( S) C! s6 K0 S' U
- /* USER CODE BEGIN 3 */
9 T+ p6 t L. I) L9 r - }+ l( ^4 w5 D: | X9 I
- /* USER CODE END 3 */
复制代码
: C! y* w8 @/ Y7 x7 ]$ V) y编译后下载到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)
+ ^6 s, t4 `/ m - {
/ M! L, e# D0 E: ^! W - /* USER CODE BEGIN 6 */switch(event_idx)
' z0 a. ^0 B) ]! Y4 K - {2 X2 O `8 l1 m0 z
- case 1: /* LED3 */
2 F$ q2 T O& o4 a - (state == 1) ?HAL_GPIO_WritePin(LD3_GPIO_Port,LD3_Pin,GPIO_PIN_SET) :
* q8 h% S7 S, \, y7 g4 ? - HAL_GPIO_WritePin(LD3_GPIO_Port,LD3_Pin,GPIO_PIN_RESET);9 ]: n( [' e# `: J. {& K+ O6 t% q6 s
- break;
6 a7 u2 g& {8 U, q0 r - case 2: /* LED4 */
3 l# z" R3 |( H* U- F - (state == 1) ?HAL_GPIO_WritePin(LD4_GPIO_Port,LD4_Pin,GPIO_PIN_SET) :
1 {3 E3 l% O' V9 h8 h - HAL_GPIO_WritePin(LD4_GPIO_Port,LD4_Pin,GPIO_PIN_RESET);4 V! X1 P, U, b9 ]% y
- break;' ^5 @. W9 a" l
- case 3: /* LED5 */
% C+ V, ?1 |( F* L R3 _ - (state == 1) ?HAL_GPIO_WritePin(LD5_GPIO_Port,LD5_Pin,GPIO_PIN_SET) :
3 u" g% F# p. X7 H+ m' T/ b7 P* ] - HAL_GPIO_WritePin(LD5_GPIO_Port,LD5_Pin,GPIO_PIN_RESET);+ A* n' p L: s
- break;& {6 u( \9 {5 x9 i- D
- case 4: /* LED6 */
; q z7 N' v. z" N - (state == 1) ?HAL_GPIO_WritePin(LD6_GPIO_Port,LD6_Pin,GPIO_PIN_SET) :! X1 r i! l+ s" G3 U5 [( e
- HAL_GPIO_WritePin(LD6_GPIO_Port,LD6_Pin,GPIO_PIN_RESET);% g* X7 W5 f2 A9 e
- break;. {$ ^6 M/ f% p
- default: w0 q2 j4 ]1 D/ t
- break;7 ~5 u8 A% L8 R
- }* v5 G p h* x1 g
- return (0);
+ D0 B2 o2 W/ i2 e - /* USER CODE END 6 */
3 p( z- ?$ m' }- R3 K! b. Z - }
复制代码 ) o6 D7 I9 i, ?( e/ i( p
编译之后下载到MCU内,通过USBUSER连接到PC端,打开UsbHidDemonstrator,我们可以通过勾选右下角的图形界面来实现控制开发板上的LED电量或者关闭。 当然,这个是通过图像化的界面来进行控制,你也可以通过Input/outputtransfer中的写入对话框来完成这个操作。注意,写入的第一个字节是ID,表示你想控制的是哪个LED;第二个字节是0或者是1,表示你想让这个LDE的状态变成灭还是亮。 总结: 本范例程序是为了快速实现USB 从机设备与主句设备双向通讯目的,其初始化代码是用STM32CubeMX来生成的,大大降低了工程师开发USB设备的难度(尤其是是入门阶段的难度)。从这个工程的基础上,工程师可以比较方便的建立好框架工程并,对其中的代码进行研究,进而移植或增加自己的应用代码。
- ]$ A5 B- U/ x& V( ~
文章出处: 茶话MCU
; p3 i; V5 R) V) r+ g |