这里我主要说一下如何做一个USB下位机,这里主要分3部分:1、建立工程;2、添加报文描述符;3、数据的传输。这里就不讲USB的理论知识了,有想要了解的自行百度一下就可以了。

建立工程:工程建立参考:https://www.cnblogs.com/libra13179/p/7193375.html

  1、首先打开USB

  

  

  2、接着把USB设置为下图HID模式

  

  3、选择外部时钟

  

  4、配置时钟树

  

  5、配置USB设置

  

  

  下面的USB设置就有点讲究了,

  

6、确认后,生成代码

  

添加报文描述符:

  这里只是建立了一个工程,你编译不会有错,不过PC还是无法识别你这个是USB设备的,因为缺少了关键的报文描述符,这里我就说一下报文描述符怎么写,这个你可以直接用HID descriptor Tool软件生成,如下图,下图就是一个报文的基本要素了,该描述符主要作用是告诉PC机一下USB的信息,简单来说就是告诉PC我有什么用。

  

  这里我说一下这个报文的要素,第一部分你可以理解为报文头;第二部分你可以理解为USB告诉PC机问我要做什么,这里主要告诉PC机我要做一个接收与发送的设备;第三部分是结束标志。

============================================================

下面我插入一个对报文描述符进行一个简单的描述:

  

标签解析举例(Usage :0x50, 0x01):

  (Usage Page标签0x0?)0x05=0000 0101 :

  0x05表示前缀,0x01为数据部分,0x05转换成二进制,就是0000 01 01,按照HID类协议5.3 generic item format的定义,这个字节被分成3个部分:

  bit0~bit1代表的是这个前缀后面跟的数据长度,这里就是后面0x01的长度,两位可以表示最大4字节的数据,即bsize

  bit2~bit3代表的是这个前缀的类型,总共可以有三种类型:0=main,1=global,2=local,3=reserved;

  bit4~bit7代表前tag,一般分为input(二进制的1000 00 nn,即bit4~bit7=1000,代表一个tag,bit2~bit3=00,代表main,bit0~bit1=nn,代表这个前缀后面还有nn所代表的数据),output(二进制的 1001 00 nn),feature(1011 00 nn),collection(1010 00 nn),end collection(1100 00 nn)

即:

  0000:Usage Page

  01: bType,全局(bType=0:主项目;bType=1:全局项目;bType=2:区域项目)

  01:bSzie,1字节(bSzie为项目所需数据字节数目,bSzie可为1、2、4,注意bSzie不可为3)

 (Page ID)0x01: 表示该Page为Generalic Desktop Controls(Usage ID 0为保留。ID 1到0x1F为”top level” collection保留,这些ID虽然对于Application不是必须,但可以用于识别通用设备类型)

  

  Usage = (usage page:usage ID):其将数据的操控与它的用途作一对一的对应,所以解读报告后就可以知道每个数据作何种操作。所以“传输的数据”和“操作”只是一事件的两种描述方式。用途是以一个32位卷标(称作usage tag)来表示,高16位称作usage page(用途类页),低16位称为usage ID(用途识别名),文件universal serial Bus HID Usage Table完整列出所有的usage pages(用途类页)和usage ID(用途识别名),使用者必须遵照文件的规范来声明操作的用途。用途卷标只是报告描述符诸多标签的一个,利用这些卷标取可以清楚完整的描述符操作的用途。

  这里其实说的并不清楚,因为作者有点懒就不细说了,想了解更多的可以百度《圈圈教你玩USB》,这里说的比我好。

==============================================================================

言归正传,我们生成了报文描述符后,保存为.h文件,我对这些报文做了一些很浅显的解析,可能表达不够准确,打开可以看到如下代码所示:

char ReportDescriptor[] = 
{                     //这里34就是前面建立工程第五点说的,报文描述符大小
0x06, 0x00, 0xff, // USAGE_PAGE (Vendor Defined Page 1) 表示一个报文标签之类的用途类页
0x09, 0x01, // USAGE (Vendor Usage 1) 表示一个报告ID标志
0xa1, 0x01, // COLLECTION (Application) 表示应用集合,要以下面最后的0xc0结束它 0x09, 0x01, // USAGE (Vendor Usage 1)同下同名解析
0x15, 0x00, // LOGICAL_MINIMUM (0) 同下同名解析
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255) 同下同名解析
0x95, 0x40, // REPORT_COUNT (64)  同下REPORT_COUNT
0x75, 0x08, // REPORT_SIZE (8)   同下REPORT_SIZE
0x81, 0x02, // INPUT (Data,Var,Abs) 表示USB要输入数据到PC的功能 0x09, 0x01, // USAGE (Vendor Usage 1) 每个功能的一个卷标志
0x15, 0x00, // LOGICAL_MINIMUM (0) 表示每个传输数据限定为0
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255) 表示每个传输数据的最大值限定为255
0x95, 0x40, // REPORT_COUNT (64) 每次接收的数据长度,这里是64位
0x75, 0x08, // REPORT_SIZE (8) 传输字段的宽度为8bit,表示每个传输的数据范围为0~ffff ffff
0x91, 0x02, // OUTPUT (Data,Var,Abs) 表示USB设备要接收PC的数据的功能
0xc0 // END_COLLECTION  结束标志
};

  到这里我们就可以下一步了,打开刚才建立的工程,在 usbd_custom_hid_if.c 文件里,找到 CUSTOM_HID_ReportDesc_FS 这个函数,把刚才生成的报文文件覆盖掉函数里面的文件,代码如下:

/** Usb HID report descriptor. */
__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
{
0x06, 0x00, 0xff, // USAGE_PAGE (Vendor Defined Page 1)
0x09, 0x01, // USAGE (Vendor Usage 1)
0xa1, 0x01, // COLLECTION (Application)
0x09, 0x01, // USAGE (Vendor Usage 1)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
0x95, 0x40, // REPORT_COUNT (64)
0x75, 0x08, // REPORT_SIZE (8)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x09, 0x01, // USAGE (Vendor Usage 1)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
0x95, 0x40, // REPORT_COUNT (64)
0x75, 0x08, // REPORT_SIZE (8)
0x91, 0x02, // OUTPUT (Data,Var,Abs)
0xc0 // END_COLLECTION
};

然后再修改将usbd_conf.h做对应修改:

  #define USBD_CUSTOMHID_OUTREPORT_BUF_SIZE      64

  #define USBD_CUSTOM_HID_REPORT_DESC_SIZE       34

同时修改usbd_customhid.h文件中的发送与接收长度为64

  #define CUSTOM_HID_EPIN_SIZE                  0x40

  #define CUSTOM_HID_EPOUT_SIZE              0x40

  

  到这里基本就算做出一个USB设备了,我们编译下载程序看看。

  如果电脑显示了这个USB设备,但是有黄色感叹号,说明USB枚举成功,可是驱动安装失败,这时我们可以libusb自带的inf-wizard工具生成USB驱动程序,要怎么安装驱动解决这个问题可以参考驱动安装:https://blog.csdn.net/niepangu/article/details/44984325,驱动安装成功的话,到这里我们就可以看到USB正常列举出来啦,下面我们就可以写USB数据收发代码了。

USB数据传输:参考http://www.stm32cube.com/article/138

  关于数据传输,HID设备是采用轮询方式传输的,ST默认20ms速度实在不敢恭维,还得要改一下枚举时的声明,同样是usbd_customhid.c文件,

__ALIGN_BEGIN static uint8_t USBD_CUSTOM_HID_CfgDesc[USB_CUSTOM_HID_CONFIG_DESC_SIZ] __ALIGN_END =
{
....
....
0x20, /*bInterval: Polling Interval (20 ms)*/ /* 34 */ ....
.... 0x20,/* bInterval: Polling Interval (20 ms) */ /* 41 */
}

这两个地方随心来改,最小可以改到0x01。这就快很多啦。到这里数据传输准备工作就做好了,下面我们先来说一下USB发送:

  先定义个发送BUFF:

uint8_t send_buf[] = {//定义一个USB的发送BUFF
,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,,};

  再包括发送函数头文件以及声明一个外部定义:

#include "usbd_customhid.h" //包括发送函数头文件
extern USBD_HandleTypeDef hUsbDeviceFS; //外部声明USB发送函数

  现在可以在main函数里添加发送代码了,我这里设置按一下按键就发送一次,同时led亮1s:

  while ()
{
  /* USER CODE BEGIN 3 */
if(HAL_GPIO_ReadPin(GPIOE,k1_Pin)==)
{
//按键消抖
while(HAL_GPIO_ReadPin(GPIOE,k1_Pin)==);
//点亮指示灯
HAL_GPIO_WritePin(GPIOE,led1_Pin,GPIO_PIN_RESET);
//USB发送数据
USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, send_buf, sizeof(send_buf));
       //实际发送到上位机的数据有65个,一个报文ID:0 + 64个send_buf 数据,一共65个数据到上位机
HAL_Delay();
//关闭指示灯
HAL_GPIO_WritePin(GPIOE,led1_Pin,GPIO_PIN_SET);
}
}

发送函数就是这么简单,接下来说接收函数了,接收函数是中断接收的,建立工程时已经默认开启了。下面就说一下USB接收:

  先在main.c定义个接收BUFF:

unsigned char USB_Recive_Buffer[]; //USB接收缓存
unsigned char USB_Received_Count = ;//USB接收数据计数

  打开usbd_custom_hid_if.c文件,添加外部声明:

extern unsigned char USB_Recive_Buffer[];
extern unsigned char USB_Received_Count;

  接着在usbd_custom_hid_if.c文件中找到 static int8_t CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state) 函数,当USB接收完数据后,就会进入到这个函数,我们可以在这里添加接收函数,把函数修改如下:

/**
* @brief Manage the CUSTOM HID class events
* @param event_idx: Event index
* @param state: Event state
* @retval USBD_OK if all operations are OK else USBD_FAIL
*/
//当芯片完成一组数据接收的时候,中断会调用CUSTOM_HID_OutEvent_FS这个回调函数
static int8_t CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state)
{
/* USER CODE BEGIN 6 */
char i;
HAL_GPIO_TogglePin(GPIOE,led1_Pin); /*查看接收数据长度
USB_Received_Count = USBD_GetRxCount( &hUsbDeviceFS,CUSTOM_HID_EPOUT_ADDR );
printf("USB_Received_Count = %d \r\n",USB_Received_Count); USB_Received_Count = USBD_GetRxCount( &hUsbDeviceFS,CUSTOM_HID_EPIN_ADDR );
printf("USB_Received_Count_in = %d \r\n",USB_Received_Count);
*/ USBD_CUSTOM_HID_HandleTypeDef *hhid; //定义一个指向USBD_CUSTOM_HID_HandleTypeDef结构体的指针
hhid = (USBD_CUSTOM_HID_HandleTypeDef*)hUsbDeviceFS.pClassData;//得到USB接收数据的储存地址 for(i=;i<;i++)
{
USB_Recive_Buffer[i]=hhid->Report_buf[i]; //把接收到的数据送到自定义的缓存区保存(Report_buf[i]为USB的接收缓存区)
//printf("USB_Recive_Buffer[%d] = 0x%x \r\n",i,USB_Recive_Buffer[i]); //打印接收到的信息,确认是否正确,调试用
}
return USBD_OK;
/* USER CODE END 6 */
}

  这样就 USB_Recive_Buffer[i] 里面就保存到接收到的数据了,用Bus Hound可以看到已经成功啦。

  

补充:上位机USB通信部分:https://www.cnblogs.com/xingboy/p/9816234.html 这里的上位机是用C#编写的窗体程序

STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发的更多相关文章

  1. 转载 STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发

    STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发  本文转载自 https://www.cnblogs.com/xingboy/p/9913963.html 这里我主要说一 ...

  2. C# 访问USB(HID)设备

    原文:C# 访问USB(HID)设备 二话不说,直接给代码,如果您真想做这方面的东西,还是稍微研究下,没有现成的好类用,就需要自己了解其原理 //引用空间 using System; using Sy ...

  3. android usb Host模式下与usb Hid 设备的通信

    做android 与USB HID设备的通信有段时间了,总结一下遇到的问题和解决方法: 1,第一次遇到的问题:android 版本低不支持usb hid, 被要求做相关项目的时候,就从mUsbMana ...

  4. Windows与自定义USB HID设备通信说明.

    1 .   所使用的典型 Windows API CreateFile ReadFile WriteFile 以下函数是 DDK 的内容: HidD_SetFeature HidD_GetFeatur ...

  5. USB HID设备报告描述符详解(转)

    转自:http://group.ednchina.com/93/198.aspx. 参考:USB HID usage table 概述:   报告在这里意思是数据传输(data transfer),而 ...

  6. (二)POI-创建一个sheet页,并添加行列数据

    原文:https://blog.csdn.net/class157/article/details/92800439 1.只创建sheet页 package com.java.poi; import ...

  7. USB HID 协议入门

    转载请注明来源:cuixiaolei的技术博客 USB HID设备类的应用场合 USB HID类是USB设备的一个标准设备类,包括的设备非常多.HID类设备定义它属于人机交互操作的设备,用于控制计算机 ...

  8. USB HID复合设备实例—键盘+鼠标

    实现这种USB HID复合设备有两种方法,在<USB HID协议入门>一节已经讲到其中一种方法,说一个USB HID设备可以包含多种功能的报告描述符合集,这样可以实现复合设备,如带鼠标功能 ...

  9. Android USB Host 与 Hid 设备通信bulkTransfer()返回-1问题的原因

    近期一直在做Android USB Host 与USB Hid设备(STM32FXXX)的通信,遇到了很多问题.项目源码以及所遇到的其他问题可以见本博客其他相关文章,这里重点讲一下bulkTransf ...

随机推荐

  1. Python爬虫基础--爬取车模照片

    import urllib from urllib import request, parse from lxml import etree class CarModel: def __init__( ...

  2. 提高生产力:SpringMVC中,使用扩展数据类型TypedMap接收Web请求参数

    在Web项目中,如果前端MVC框架使用的是SpringMVC,可以使用Map接收前端请求参数,比bean要方便很多. 尤其是SpringMVC和Mybatis一起用的时候,用Map大大减少了需要的be ...

  3. Jenkins学习总结(6)——了解DevOps的前世今生

    DevOps是什么?从哪里来? DevOps的概念 DevOps一词的来自于Development和Operations的组合,突出重视软件开发人员和运维人员的沟通合作,通过自动化流程来使得软件构建. ...

  4. spring data JPA使用quartz定时器的具体实现

    第一步.在pom.xml中的配置 <!--quartz--> <dependency> <groupId>org.quartz-scheduler</grou ...

  5. spring 整合freemarker 实现模板继承

    <!--freemarker 配置--> <bean id="freemarkerConfig" class="org.springframework. ...

  6. 洛谷—— P1120 小木棍 [数据加强版]

    https://www.luogu.org/problem/show?pid=1120 题目描述 乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过50. 现在,他想把小木棍拼接 ...

  7. 洛谷——P1115 最大子段和

    https://www.luogu.org/problem/show?pid=1115#sub 题目描述 给出一段序列,选出其中连续且非空的一段使得这段和最大. 输入输出格式 输入格式: 输入文件ma ...

  8. spring容器启动过程理解

    一.一切从手动启动IoC容器开始 ClassPathResource resource = new ClassPathResource("bean.xml"); DefaultLi ...

  9. mysql开发之---每日一得01

    2015年7月7日------------------------- 1.truncate表会清空建表语句auto_increment的值:某个表的id即是主键也是自增,你能够选择插入随意id值,假设 ...

  10. 从WAVE头文件里获取压缩方式

    在解析WAV文件时.会从头文件里读取TAG值,表示当前文件的压缩格式,如该值为1,表示没有压缩,其他表示不同的压缩方式. 如当中为6时,表示压缩方式是alaw 列表例如以下: TAG值 音频描写叙述 ...