stm32的USB自定义HID与上位机通信

简介

1.由来

通常我们使用stm32与pc通信的方式分为无线和有线,无线方式用wifi或蓝牙模块,我使用过程中一直无法接受这样的连接因为这样很不稳定,常常需要重启下位机或者上位机重新连接。
而有线方式我们会用到URAT,或是USB的虚拟串口,这两种方式中UART需要再接CH340类似的模块,并且两种方式都需要pc安装驱动。
于是我打算做一个不需要转接模块,也不需要上位机额外安装驱动的基于USB-HID的连接通信。

2.工具

1.硬件采用stm32F407ZGT6的usb外设,做从机,使用自定义HID类。
2.软件使用stm32cubeide生成代码编写业务代码,上位机配置java环境,使用java的JNA技术调用系统HID接口驱动HID设备。

3.注意

1.stm32的usb外设可选高速的USB2.0,和全速的USB1.0,由于stm32F4不自带usb的高速PHY,使用高速模式需要外接USB3300之类的模块,这里我们不要求通信速度,于是使用全速模式。
usb集线器向下兼容,全速模式可接几乎所有的usb扩展口。
2.USB主机在D+,D-线都会接15k的下拉电阻,而USB全速模式的从机需要在D+线上接1.5k的上拉电阻,USB集线器正是通过差分线的上下拉状态来选择与设备通信方式的,也可以解决热拔插的问题。
使用时需要检查开发板原理图是否有上拉,一般购买的开发板都会做好这些,用一个GPIO打开开关管即可实现上拉。

下位机实现

1.cubeide代码生成

首先创建项目,在内置的cubemx配置中,SYS下配置debug方式,可以是swd或jtag,这取决于硬件连接。
使能内外部晶振,配置时钟树,主频在168Mhz,usb外设48MHz。
在project manager中勾选以.c/.h为外设生成文件。
开启usb外设,作为从机,选择device_only,配置参数默认即可。

开启中间件USB_DEVICE,选择usb类为自定义HID(custom HID),BINTERVAL为响应主机发送数据的延时时间,尽量越小越好。
另外两个参数是后面描述符所需要的大小,现在就修改即可。

设备描述符中的内容均可修改,VID和PID是上位机识别HID设备的识别码,需要记住。

以上步骤确认无误后,ctrl+s保存并生成代码即可。

2.代码修改实现收发

生成代码后,进入目录USB_DEVICE/App/下打开usbd_custom_hid_if.c文件,添加端点描述符,

代码如下

    0x05,0x8c, /* USAGE_PAGE (ST Page) */
    0x09,0x01, /* USAGE (Demo Kit) */
    0xa1,0x01, /* COLLECTION (Application) */

    // The Input report
    0x09,0x03, // USAGE ID - Vendor defined
    0x15,0x00, // LOGICAL_MINIMUM (0)
    0x26,0x00, 0xFF, // LOGICAL_MAXIMUM (255)
    0x75,0x08, // REPORT_SIZE (8bit)
    0x95,0x40, // REPORT_COUNT (64Byte)
    0x81,0x02, // INPUT (Data,Var,Abs)

    // The Output report
    0x09,0x04, // USAGE ID - Vendor defined
    0x15,0x00, // LOGICAL_MINIMUM (0)
    0x26,0x00,0xFF, // LOGICAL_MAXIMUM (255)
    0x75,0x08, // REPORT_SIZE (8bit)
    0x95,0x40, // REPORT_COUNT (64Byte)
    0x91,0x02, // OUTPUT (Data,Var,Abs)

添加之后,下载运行,在pc的设备管理器中就可以看到此外设。

我们有hal库提供的接收数据和发送数据的接口:

uint8_t USBD_CUSTOM_HID_SendReport(USBD_HandleTypeDef *pdev, uint8_t *report, uint16_t len); //发送


static int8_t CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state); //接收

我希望能在main函数中发送,发送函数的第一个参数需要一个设备指针,我没有找到能引用到设备变量的头文件,于是自己添加在USB_DEVICE/App/下usbd_custom_hid_if.h中,

/* USER CODE BEGIN INCLUDE */
 extern USBD_HandleTypeDef hUsbDeviceFS;

同时在main函数中包含这个头文件,接收函数也有被这个头文件包含的头文件声明,一举两得。
看接收函数的接口,发现每次只能接收2Byte,而发送也每次只能发送6byte,这是HID报告长度固定导致的,于是我在这俩函数之上再封装一层。
对于发送我希望发送任意长度字符串,而上位机的接收每次缓冲到64byte才会完成接收,于是每次发送超过64byte,多余的补0即可。
代码:

/* USER CODE BEGIN PFP */
void USB_SentStr(uint8_t *str);
/* USER CODE END PFP */

/* USER CODE BEGIN 4 */
void USB_SentStr(uint8_t *str) {
	int strSize = strlen(str); //get length
	if(strSize > 64) return; //if too long
	uint8_t temp[66] = {0}; //buffer string
	for(int i = 0; i < strSize; i++) {
		temp[i] = str[i];  //move to buffer
	}
	for(int j = 0; j < 11; j++) {
		while(((USBD_CUSTOM_HID_HandleTypeDef*)(hUsbDeviceFS.pClassData))->state == CUSTOM_HID_BUSY); //if dma trans busy
		USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, &temp[j * 6], 6); // sent data
	}
}
/* USER CODE END 4 */

调用此函数即可发送任意长度的串。
而对于接收,每次只能接收2byte,于是封装一层,接收到\0表示一行结束,将flag拉高,同时也在usbd_custom_hid_if.h中extern出去给main函数处理数据,
在usbd_custom_hid_if.c中添加如下

/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/

uint8_t USB_DataBuffer[50] = {0};
uint8_t USB_GetDataFlag = 0;
uint8_t USB_DataBufferIndex = 0;
/* USER CODE END PV */

/* USER CODE BEGIN 6 */
	if(USB_GetDataFlag == 0) {
		if(event_idx == 0) {
			USB_GetDataFlag = 1;
			USB_DataBuffer[USB_DataBufferIndex] = 0;
			USB_DataBufferIndex = 0;
		} else if(state == 0) {
			USB_GetDataFlag = 1;
			USB_DataBuffer[USB_DataBufferIndex] = event_idx;
			USB_DataBuffer[USB_DataBufferIndex + 1] = 0;
			USB_DataBufferIndex = 0;
		} else {
			USB_DataBuffer[USB_DataBufferIndex] = event_idx;
			USB_DataBufferIndex++;
			USB_DataBuffer[USB_DataBufferIndex] = state;
			USB_DataBufferIndex++;
		}
	}

在usbd_custom_hid_if.c中添加

 extern uint8_t USB_DataBuffer[50];
 extern uint8_t USB_GetDataFlag;
/* USER CODE END INCLUDE */

在main函数中根据USB_GetDataFlag被拉高知道有数据到了USB_DataBuffer,处理完后拉低即可。

上位机实现

上位机采用java的JNA调用hidapi.dll,也可选linux下的libhidapi.so(我没试过),win转linux跨平台应该只需要改少量代码就可以实现,并且不需要额外驱动支持,因为hid驱动系统会本身自带的有。
数据收发调用动态链接库提供的api即可,发送时需要将待发送的串拆分每次发送2byte,这样下位机才能完整接收到。
同时收发的实现就是开多线程即可,我的方式是开两个线程一个收一个发,而主线程进while死循环。

程序代码

上位机java程序代码:https://github.com/untitledx6/java-stm32HID
下位机stm32项目代码:https://github.com/untitledx6/CubeIDE_Work/tree/master/F4_USBdata

posted @   胡萝卜怪  阅读(13878)  评论(1编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示