一、USB简介

USB(Universal Serial BUS)通用串行总线,是一个外部总线标准,用于规范电脑与外部设备的连接和通讯。是应用在 PC 领域的接口技术。USB 接口支持设备的即插即用和热插拔功能。USB 是在 1994 年底由英特尔、康柏、IBM、Microsoft 等多家公司联合提出的。

USB 发展到现在已经有 USB1.0/1.1/2.0/3.0 等多个版本。目前用的最多的就是 USB1.1 和 USB2.0,USB3.0 目前已经开始普及。STM32F103 自带的 USB 符合 USB2.0 规范,不过 STM32F103 的 USB 都只能用来做设备,而不能用作主机。

标准 USB 共四根线组成,除 VCC/GND 外,另外为 D+,D-; 这两根数据线采用的是差分电压的方式进行数据传输的。在 USB 主机上,D-和 D+都是接了 15K 的电阻到低的,所以在没有设备接入的时候,D+、D-均是低电平。而在 USB 设备中,如果是高速设备,则会在 D+上接一个 1.5K 的电阻到 VCC,而如果是低速设备,则会在 D-上接一个 1.5K 的电阻到 VCC。这样当设备接入主机的时候,主机就可以判断是否有设备接入,并能判断设备是高速设备还是低速设备。

STM32F103 的 MCU 自带 USB 从控制器,符合 USB 规范的通信连接;PC 主机和微控制器之间的数据传输是通过共享一专用的数据缓冲区来完成的,该数据缓冲区能被 USB 外设直接访问。这块专用数据缓冲区的大小由所使用的端点数目和每个端点最大的数据分组大小所决定,每个端点最大可使用 512 字节缓冲区(专用的 512 字节,和 CAN 共用),最多可用于 16 个单向或 8 个双向端点。USB 模块同 PC 主机通信,根据 USB 规范实现令牌分组的检测,数据发送/接收的处理,和握手分组的处理。整个传输的格式由硬件完成,其中包括 CRC 的生成和校验。

1.1 USB HID简介

USB HID类是USB设备的一个标准设备类,包括的设备非常多。HID类设备定义它属于人机交互操作的设备,用于控制计算机操作的一些方面,如USB鼠标、USB键盘、USB游戏操纵杆等。但HID设备类不一定要有人机接口,只要符合HID类别规范的设备都是HID设备。

USB HID设备的一个好处就是操作系统自带了HID类的驱动程序,而用户无需去开发驱动程序,只要使用API系统调用即可完成通信。

官方资料:http://www.usb.org/developers/hidpage
其中包含最主要的两个说明:

  • 《Device Class Definition for human interface device (HID)》【描述了 HID 的基本组成和格式】
  • 《Universal Serial Bus HID Usage Tables》【对上面文档的补充,将各种不同的 HID 设备的基本组成列举出来】

二、新建工程

1. 打开 STM32CubeMX 软件,点击“新建工程”

2. 选择 MCU 和封装

3. 配置时钟
RCC 设置,选择 HSE(外部高速时钟) 为 Crystal/Ceramic Resonator(晶振/陶瓷谐振器)

选择 Clock Configuration,配置系统时钟 SYSCLK 为 72MHz
修改 HCLK 的值为 72 后,输入回车,软件会自动修改所有配置

4. 配置调试模式
非常重要的一步,否则会造成第一次烧录程序后续无法识别调试器
SYS 设置,选择 Debug 为 Serial Wire

三、USB

3.1 参数配置

Connectivity 中选择 USB 设置,并勾选 Device(FS) 激活 USB 设备。

Parameter Settings 进行具体参数配置。

  • Speed: Full Speed 12MBit/s(固定为全速)
  • Low Power: 默认 Disabled(在任何不需要使用usb模块的时候,通过写控制寄存器总可以使usb模块置于低功耗模式(low power mode ,suspend模式)。在这种模式下,不产生任何静态电流消耗,同时usb时钟也会减慢或停止。通过对usb线上数据传输的检测,可以在低功耗模式下唤醒usb模块。也可以将一特定的中断输入源直接连接到唤醒引脚上,以使系统能立即恢复正常的时钟系统,并支持直接启动或停止时钟系统。)

3.2 引脚配置

USB 的 DP 引脚必须上拉 1.5K 欧的电阻,电脑才能检测到 USB,否则检测不到。

查看野火指南者开发板原理图可知,需要将 PD6 配置为低电平使能 USB。

在右边图中找到 PD6 引脚,选择 GPIO_Output

GPIO output level 中选择 Low 输出低电平。

3.3 配置时钟

选择 Clock Configuration,USB 时钟配置为 48MHz,且来源最好是外部晶振分频得到。

3.4 USB Device

USB有主机(Host)和设备(Device)之分。一般电脑的USB接口为主机接口,而键盘、鼠标、U盘等则为设备。

部分型号的STM32芯片有1~2个USB接口。像STM32F103系列的有一个USB Device接口,STM32F407系列的有2个USB接口,既可以作为HOST,又可以作为Device,还可以作为OTG接口。

Middleware 中选择 USB_DEVICE 设置,在 Class For FS IP 设备类别选择 Custom Human Interface Device Class(HID) 自定义人机接口设备。

参数配置保持默认。

  • CUSTOM_HID_FS_BINTERVAL(主机读取设备数据时间间隔): 0x5(STM32将数据发送到一个缓存区,而不是直接发送到上位机,而上位机每隔一端时间会来访问缓冲区读取数据。读取时间间隔过快会导致多次数据发送,过慢会导致数据丢失)
  • USBD_CUSTOM_HID_REPORT (Total length for Report descriptor(IN ENDPOINT))(报告描述符大小): 2(默认为2,可根据自定义设备描述符的具体大小修改)
  • USBD_CUSTOMHID_OUTREPORT_BUF_SIZE (Maximum report buffer size)(发送与接收数据大小): 2(默认为2,HID一次最多可以发送64个字节)

设备描述符保持默认。

四、生成代码

输入项目名和项目路径

选择应用的 IDE 开发环境 MDK-ARM V5

每个外设生成独立的 ’.c/.h’ 文件
不勾:所有初始化代码都生成在 main.c
勾选:初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。

点击 GENERATE CODE 生成代码

五、修改报告描述符

默认报告描述表为空的,此时烧录运行默认代码程序,电脑识别驱动程序错误,需要修改自定义设备的报告描述符。

打开工程文件夹Middlewares/USB_DEVICE/Appusbd_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 =
{
  /* USER CODE BEGIN 0 */
  	0x06,0xFF,0x00,     // USAGE_PAGE (Vendor Page: 0xFF00) 表示一个报文标签之类的用途类页
	0x09,0x01,			// USAGE (Vendor Usage 1) 表示一个报告ID标志
	0xA1,0x01,			// COLLECTION (Application) 表示应用集合,要以下面最后的0xc0结束它
	0x15,0x00,			// LOGICAL_MINIMUM (0) 表示每个传输数据限定为0
	0x26,0xFF,0x00,		// LOGICAL_MAXIMUM (255) 表示每个传输数据的最大值限定为255
	0x75,0x08,			// REPORT_SIZE (8) 传输字段的宽度为8bit,表示每个传输的数据范围为0~ffff ffff
	0x95,0x40,			// REPORT_COUNT (64)每次发送的数据长度,这里是64位
	0x09,0x02,			// USAGE (Vendor Usage 2) 表示一个报告ID标志
	0x81,0x02,			// INPUT (Data,Var,Abs) 表示USB要输入数据到PC的功能
	0x09,0x03,			// USAGE (Vendor Usage 3) 表示一个报告ID标志
	0x91,0x02,			// OUTPUT (Data,Var,Abs) 表示USB设备要接收PC的数据的功能
	0x0A,0x00,0xFF,	    // UsageS(0xFF00)
	0x0B1,0x02, 		// feature (data, variable, absolute)
  /* USER CODE END 0 */
  0xC0                  // END_COLLECTION 结束标志
};

上面描述表分别定义了Input、Ouput、Feature三个报告,Input用于MCU上传数据,Output下传数据,Feature可用于上下传数据。所有报告大小定义为64byte,每次最大上下传数据量也就为64byte,另外数组USBD_CUSTOM_HID_REPORT_DESC_SIZE也要对应修改为30。

也可借助 HID Descriptor Tool (DT) HID描述符工具根据自己需求生成,想了解更多的可以百度《圈圈教你玩USB》
官网下载:https://usb.org/sites/default/files/documents/dt2_4.zip
百度网盘:https://pan.baidu.com/s/1ayjdQtc7e9NWwYJqdp0pXA?pwd=4ghb 提取码:4ghb

  • 第一部分为报文头
  • 第二部分为USB告诉PC机问我要做什么,这里主要告诉PC机我要做一个接收与发送的设备
    • **INPUT:**是描述这个USB设备作为输入的时候的数据格式
    • **OUTPUT:**是描述该USB设备输出的数据格式
    • **LOGICAL_MINIMUM:**是指每个字节数据的最小值
    • **LOGICAL_MAXIMUM:**是指每个字节数据的最大值
    • **REPORT_COUNT:**是指定每次传输数据最多多少个字节
    • **REPORT_SIZE:**是指定每次传输数据每个字节的位数
  • 第三部分为结束标志

六、修改端点大小

打开工程文件夹Middlewares/USB_Device_Libraryusbd_customhid.h文件

要能最大上下传 64byte(0x40) 数据,还需修改对应上下传端点大小为 64

#define CUSTOM_HID_EPIN_ADDR                 0x81U
#define CUSTOM_HID_EPIN_SIZE                 0x40 	//0x02U Modify

#define CUSTOM_HID_EPOUT_ADDR                0x02U	//0x01U Modify
#define CUSTOM_HID_EPOUT_SIZE                0x40 	//0x02U Modify

ADDR中最高位代表端点的方向,1对应IN,0对应OUT,剩下7位为端点号,0x81代表端点1为IN Endpoint 1(EP1),这里修改为OUT的端点为Endpoint 2(EP2),两个端点的SIZE也由2改为64。

七、修改发送缓冲区大小和报告描述符大小

打开工程文件夹Application/User/USB_DEVICE/Targetusbd_conf.h文件

#define USBD_CUSTOMHID_OUTREPORT_BUF_SIZE     64
/*---------- -----------*/
#define USBD_CUSTOM_HID_REPORT_DESC_SIZE     30

发送缓冲区大小USBD_CUSTOMHID_OUTREPORT_BUF_SIZE改为最大的64byte,另外报告描述符大小USBD_CUSTOM_HID_REPORT_DESC_SIZE改为上面CUSTOM_HID_ReportDesc_FS数组的大小30

八、添加串口打印

串口打印功能查看 STM32CubeMX学习笔记(6)——USART串口使用

九、增加上下传数据

9.1 EP1上传数据

添加头文件和USB设备句柄。

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include "usbd_customhid.h"
/* USER CODE END Includes */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
extern USBD_HandleTypeDef hUsbDeviceFS;
/* USER CODE END PV */

添加上传数据功能,USB 库里已经有写好上传函数USBD_CUSTOM_HID_SendReport(),会通过Endpoint 1上传数据,调用就可以上传数据,在主循环中添加上传数据功能。

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
  uint8_t bEP1_SendBuf[64] = { 0 };
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USB_DEVICE_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  printf("\r\n****** USB-HID Custom Example ******\r\n\r\n");
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    if(USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, bEP1_SendBuf, 64) == USBD_OK)
	{
		bEP1_SendBuf[0]++;
	}
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

9.2 EP2下传数据

打开工程文件夹Application/User/USB_DEVICE/Appusbd_custom_hid_if.c文件,找到CUSTOM_HID_OutEvent_FS()函数,添加以下代码,实现对接收数据的串口打印。

/**
  * @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
  */
static int8_t CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state)
{
  /* USER CODE BEGIN 6 */
  char log[250];
  uint8_t i;
  uint8_t len = USBD_GetRxCount(&hUsbDeviceFS, CUSTOM_HID_EPOUT_ADDR);  // 第一参数是USB句柄,第二个参数的是接收的末端地址;要获取发送的数据长度的话就把第二个参数改为发送末端地址即可
  USBD_CUSTOM_HID_HandleTypeDef *hhid;                                  // 定义一个指向USBD_CUSTOM_HID_HandleTypeDef结构体的指针
  hhid = (USBD_CUSTOM_HID_HandleTypeDef *)hUsbDeviceFS.pClassData;      // 得到USB接收数据的储存地址
  for(i = 0; i < len; i++)
  {
    sprintf(log + (i*2), "%02x", hhid->Report_buf[i]);
  }
  printf("%s\n", log);
    
  return (USBD_OK);
  /* USER CODE END 6 */
}

9.3 Feature Report上下传数据

前面EP1 InEP2 Out为中断端点上下传,因报告描述符中有定义Feature报告,现在可通过控制端点EP0来进行Get/Set Feature Report数据传输。

Cube Library中没有提供相应的接口,这部分的代码实现,必须去修改原始的类命令处理。

打开工程文件夹Middlewares/USB_Device_Libraryusbd_customhid.c文件,找到USBD_CUSTOM_HID_Setup()函数,添加以下代码,实现对类命令SET_REPORTGET_REPORT进行处理。

        case CUSTOM_HID_REQ_SET_REPORT:
          //类命令的wValue高字节,1-Input Report; 2-Output Report; 3-Feature Report
          if(((req->wValue>>8) & 0xFF) == 0x03)			//add  Set Feature report
          {
              printf("Set Feature report\n");
        	  hhid->IsReportAvailable = 1U;
        	  USBD_CtlPrepareRx(pdev, hhid->Report_buf, req->wLength);
              char log[250];
              uint8_t i;
              for(i = 0; i < req->wLength; i++)
              {
                  sprintf(log + (i*2), "%02x", hhid->Report_buf[i]);
              }
              printf("%s\n", log);
          }
          else if(((req->wValue>>8) & 0xFF) == 0x02)		//add  Set out report
          {
              printf("Set out report\n");
//              hhid->IsReportAvailable = 1U;
//              USBD_CtlPrepareRx(pdev, hhid->Report_buf, req->wLength);
          }
          break;
        //add
        case CUSTOM_HID_REQ_GET_REPORT:
          if(((req->wValue>>8) & 0xFF) == 0x03)		//add  Get Feature
          {
              printf("Get Feature\n");
        	  hhid->Report_buf[0] = 0xAA;
        	  USBD_CtlSendData(pdev, hhid->Report_buf, req->wLength);
              char log[250];
              uint8_t i;
              for(i = 0; i < req->wLength; i++)
              {
                  sprintf(log + (i*2), "%02x", hhid->Report_buf[i]);
              }
              printf("%s\n", log);
          }
          break;

十、查看效果

编译工程,下载到板子上,插上USB线连接到电脑上,识别出为未指定设备。

注意: 如果设备带有感叹号,则参考下面十二、注意事项

10.1 下载测试工具

10.2 测试上传数据

  • Bus Hound
    打开Bus Hound,在Devices页面找到我们的HID设备,注意VID和PID与我们设置的一致,勾选该设备:

    然后切换到Capture页面,点击Run,检测收发数据,可以看到开发板向计算机发送的数据,长度为64字节:

  • PortHelper
    查找USB,然后根据USB设备的名称找到STM32的USB接口:

    然后点击打开USB,勾选Hex显示,检测收发数据,可以看到开发板向计算机发送的数据,长度为64字节:

10.3 测试下传数据

  • Bus Hound
    Devices页面,选中我们自定义的HID设备后,点击右下方的Send Commands,打开发送窗口:

    选中USB选项卡,然后选中Interrupt Out,在Data Length处填入64,即需要发送64个字节;修改下方的数据为想要发送的数据,这里为便于观察,设置成全0x11,然后点击Run发送。

打开串口工具查看:

  • PortHelper
    点击打开USB,勾选Hex发送,修改下方的数据为想要发送的数据:

打开串口工具查看:

10.4 测试Feature Report上下传数据

  • Bus Hound
    • 接收Set Feature Report数据

查看串口打印:

  • 接收Get Feature Report数据

查看串口打印:

十一、工程代码

链接:https://pan.baidu.com/s/16yaJxfE5GxKhF-wkOwiFQg?pwd=02fu
提取码:02fu

十二、注意事项

用户代码要加在 USER CODE BEGIN NUSER CODE END N 之间,否则下次使用 STM32CubeMX 重新生成代码后,会被删除。

如果USB端口出现感叹号设备无法启动的问题,可适当将堆改大,如0x400


• 由 Leung 写于 2022 年 11 月 4 日

• 参考:STM32CubeMX生成STM32F072 USB 自定义HID Device
    【STM32+cubemx】0018 HAL库开发:自定义usb HID设备实现
    YIE002开发探索09-USB(HID双向通信)
    STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发
    STM32 基础系列教程 27 - USB_HID

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐