客户在做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中选择开启class for 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 =
{
0x06, 0xff, 0x00, /* usage_page(vendor page: 0xff00) */
0x09, 0x01, /* usage (demo kit) */
0xa1, 0x01, /* collection(application) */
/* 6 */
/* led1 */
0x85, led1_report_id, /* report_id(1) */
0x09, 0x01, /* usage (led 1) */
0x15, 0x00, /* logical_minimum (0)*/
0x25, 0x01, /* logical_maximum (1)*/
0x75, 0x08, /* report_size (8) */
0x95, led1_report_count, /*report_count (1) */
0xb1, 0x82, /* feature(data,var,abs,vol) */
0x85, led1_report_id, /* report_id(1) */
0x09, 0x01, /* usage (led 1) */
0x91, 0x82, /* output(data,var,abs,vol) */
/* 26 */
/* led2 */
0x85, led2_report_id, /* report_id 2*/
0x09, 0x02, /* usage (led 2) */
0x15, 0x00, /* logical_minimum (0)*/
0x25, 0x01, /* logical_maximum (1)*/
0x75, 0x08, /* report_size (8) */
0x95, led2_report_count, /*report_count (1) */
0xb1, 0x82, /* feature(data,var,abs,vol) */
0x85, led2_report_id, /* report_id(2) */
0x09, 0x02, /* usage (led 2) */
0x91, 0x82, /* output(data,var,abs,vol) */
/* 46 */
/* led3 */
0x85, led3_report_id, /* report_id(3) */
0x09, 0x03, /* usage (led 3) */
0x15, 0x00, /* logical_minimum (0)*/
0x25, 0x01, /* logical_maximum (1)*/
0x75, 0x08, /* report_size (8) */
0x95, led3_report_count, /*report_count (1) */
0xb1, 0x82, /* feature(data,var,abs,vol) */
0x85, led3_report_id, /* report_id(3) */
0x09, 0x03, /* usage (led 3) */
0x91, 0x82, /* output (data,var,abs,vol) */
/* 66 */
/* led4 */
0x85, led4_report_id, /* report_id4) */
0x09, 0x04, /* usage (led 4) */
0x15, 0x00, /* logical_minimum (0)*/
0x25, 0x01, /* logical_maximum (1)*/
0x75, 0x08, /* report_size (8) */
0x95, led4_report_count, /*report_count (1) */
0xb1, 0x82, /* feature(data,var,abs,vol) */
0x85, led4_report_id, /* report_id(4) */
0x09, 0x04, /* usage (led 4) */
0x91, 0x82, /* output(data,var,abs,vol) */
/* 86 */
/* key push button */
0x85, key_report_id, /* report_id(5) */
0x09, 0x05, /* usage (push button)*/
0x15, 0x00, /* logical_minimum (0)*/
0x25, 0x01, /* logical_maximum (1)*/
0x75, 0x01, /* report_size (1) */
0x81, 0x82, /* input(data,var,abs,vol) */
0x09, 0x05, /* usage (push button)*/
0x75, 0x01, /* report_size (1) */
0xb1, 0x82, /* feature(data,var,abs,vol) */
0x75, 0x07, /* report_size (7) */
0x81, 0x83, /* input(cnst,var,abs,vol) */
0x85, key_report_id, /* report_id(2) */
0x75, 0x07, /* report_size (7) */
0xb1, 0x83, /* feature (cnst,var,abs,vol)*/
/* 114 */
/* tamper push button */
0x85, tamper_report_id,/* report_id(6) */
0x09, 0x06, /* usage (tamper pushbutton) */
0x15, 0x00, /* logical_minimum (0)*/
0x25, 0x01, /* logical_maximum (1)*/
0x75, 0x01, /* report_size (1) */
0x81, 0x82, /* input(data,var,abs,vol) */
0x09, 0x06, /* usage (tamper pushbutton) */
0x75, 0x01, /* report_size (1) */
0xb1, 0x82, /* feature(data,var,abs,vol) */
0x75, 0x07, /* report_size (7) */
0x81, 0x83, /* input (cnst,var,abs,vol)*/
0x85, tamper_report_id,/* report_id(6) */
0x75, 0x07, /* report_size (7) */
0xb1, 0x83, /* feature(cnst,var,abs,vol) */
/* 142 */
/* adc in */
0x85, adc_report_id, /* report_id */
0x09, 0x07, /* usage (adc in) */
0x15, 0x00, /* logical_minimum (0)*/
0x26, 0xff, 0x00, /* logical_maximum(255) */
0x75, 0x08, /* report_size (8) */
0x81, 0x82, /* input(data,var,abs,vol) */
0x85, adc_report_id, /* report_id(7) */
0x09, 0x07, /* usage (adc in) */
0xb1, 0x82, /* feature (data,var,abs,vol)*/
/* 161 */
0xc0 /* end_collection */
};
注意:这里一定要覆盖“同名”数组,千万不要覆盖错了。
之后将如下代码复制到usbd_custom_hid_if_if.h中。
#define led1_report_id 0x01
#define led1_report_count 0x01
#define led2_report_id 0x02
#define led2_report_count 0x01
#define led3_report_id 0x03
#define led3_report_count 0x01
#define led4_report_id 0x04
#define led4_report_count 0x01
#define key_report_id 0x05
#define tamper_report_id 0x06
#define adc_report_id 0x07
最后在usbd_conf.h文件中将usbd_custom_hid_report_desc_size的定义值修改
为163(默认值是2)
#defineusbd_custom_hid_report_desc_size 163 //2
为什么这样修改呢? 简单说一下其中关键值的含义。
这个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》的文档来参考。
这样基本的程序框架就已经成功了。此时我们可以先编译一下,看看是否有任何遗漏的或者笔误。如果编译是正确的,那么我们就可以先下载到硬件开发板上,连接到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 */
uint8_t i=0;
sendbuffer[0]=0x07; //这个是report id,每次发送报文都需要以这个为开始,这样主机才能正确//解析后边的数据含义
sendbuffer[1]=0x01; //这个是实际发送的数据,可以自由定义,只要不超过报文描述符的限制
/* user code end 2 */
/* infinite loop */
/* user code begin while */
while (1)
{
hal_delay(100); //延迟100ms
sendbuffer[1]++; //每次发送都将变量自加1
usbd_custom_hid_sendreport(&husbdevicefs,sendbuffer,2);//发送报文
/* user code end while */
/* user code begin 3 */
}
/* user code end 3 */
编译后下载到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)
{
/* user code begin 6 */switch(event_idx)
{
case 1: /* led3 */
(state == 1) ?hal_gpio_writepin(ld3_gpio_port,ld3_pin,gpio_pin_set) :
hal_gpio_writepin(ld3_gpio_port,ld3_pin,gpio_pin_reset);
break;
case 2: /* led4 */
(state == 1) ?hal_gpio_writepin(ld4_gpio_port,ld4_pin,gpio_pin_set) :
hal_gpio_writepin(ld4_gpio_port,ld4_pin,gpio_pin_reset);
break;
case 3: /* led5 */
(state == 1) ?hal_gpio_writepin(ld5_gpio_port,ld5_pin,gpio_pin_set) :
hal_gpio_writepin(ld5_gpio_port,ld5_pin,gpio_pin_reset);
break;
case 4: /* led6 */
(state == 1) ?hal_gpio_writepin(ld6_gpio_port,ld6_pin,gpio_pin_set) :
hal_gpio_writepin(ld6_gpio_port,ld6_pin,gpio_pin_reset);
break;
default:
break;
}
return (0);
/* user code end 6 */
}
编译之后下载到mcu内,通过usb user连接到pc端,打开usb hiddemonstrator,我们可以通过勾选右下角的图形界面来实现控制开发板上的led电量或者关闭。
当然,这个是通过图像化的界面来进行控制,你也可以通过input/outputtransfer中的写入对话框来完成这个操作。注意,写入的第一个字节是id,表示你想控制的是哪个led;第二个字节是0或者是1,表示你想让这个lde的状态变成灭还是亮。
总结:
本范例程序是为了快速实现usb 从机设备与主句设备双向通讯目的,其初始化代码是用stm32cubemx来生成的,大大降低了工程师开发usb设备的难度(尤其是是入门阶段的难度)。从这个工程的基础上,工程师可以比较方便的建立好框架工程并,对其中的代码进行研究,进而移植或增加自己的应用代码。
stm32单片机中文官网
意法半导体/st/stm
Eidoo应用项目的开展将成为区块链中的瑞士军刀
联发科技手持四张王牌 掀开智能驾驶的未来
交流接触器的基本技术参数
闪存门后时代,华为手机增速减缓,OPPO或可超越成为国内第一!
C语言固件中指针的使用方法
基于STM32CubeMX生成HID双向通讯工程的说明
如何构建芯片可靠性模型?
如何将LED显示屏与WiFi网络、微信、手机APP结合 将成为LED显示屏企业和媒体研究的重点
解读基于矢量网络分析仪ZVB的放大器测试分析
光合作用测量系统的产品介绍说明
51单片机对串联锂离子电池组监测系统的设计
电子芯闻早报:大脑芯片植入助瘫痪者恢复知觉
基于3G通信网的移动机器人远程监控的设计与实现
新能源汽车充电桩插头如何实现过热保护?
华为海洋许剑涛:海底光缆有三大发展趋势
预计五年内全球智慧城市市场价值将达2.46万亿美元
变频器的基本参数有哪些?变频器参数该如何设置?
南昌市教育局与科大讯飞成功签署战略合作协议
激光电视市场发展前景如何?
您需要实时了解对WindowsFile Server进行的重要更改