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

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

 本文转载自 https://www.cnblogs.com/xingboy/p/9913963.html

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

 

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

 

  1、首先打开USB

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

  

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

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

  3、选择外部时钟

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

  4、配置时钟树

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

  5、配置USB设置

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

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

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

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

6、确认后,生成代码

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

 

添加报文描述符:

 

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

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

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

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

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

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

标签解析举例(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不是必须,但可以用于识别通用设备类型)

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

  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文件,我对这些报文做了一些很浅显的解析,可能表达不够准确,打开可以看到如下代码所示:

转载  STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发
char ReportDescriptor[34] = 
{                     //这里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  结束标志 };
转载  STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发

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

转载  STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发
/** 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
};
转载  STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发

 

然后再修改将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文件,

 

转载  STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发
__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 */ }
转载  STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发

 

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

  先定义个发送BUFF:

uint8_t send_buf[64] = {//定义一个USB的发送BUFF
                         1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
                         17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,
                         40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64}; 

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

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

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

转载  STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发
  while (1)
  {
    /* USER CODE BEGIN 3 */
        if(HAL_GPIO_ReadPin(GPIOE,k1_Pin)==0)
        {    
            //按键消抖
            while(HAL_GPIO_ReadPin(GPIOE,k1_Pin)==0);
            //点亮指示灯
            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(1000); //关闭指示灯 HAL_GPIO_WritePin(GPIOE,led1_Pin,GPIO_PIN_SET); } }
转载  STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发

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

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

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

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

extern unsigned char USB_Recive_Buffer[64];
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接收完数据后,就会进入到这个函数,我们可以在这里添加接收函数,把函数修改如下:

转载  STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发
/**
  * @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=0;i<64;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 */
}
转载  STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发

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

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

 

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

上一篇:Modbus通信CRC16校验程序


下一篇:云家居项目