C++USB通信。使用hidapi库与控制板通信。C++与hid设备通信。hidapi库的配置与使用。
- 前言
- 一、HID是什么?
- 二、使用步骤
-
- 1.引入库
- 2.获取USB-HID设备
- 3.打开和关闭USB-HID设备
- 4.发送和接收报告
- 5.发送和接收数据
- 总结
前言
这篇文章主要是记录C++与USB-hid设备(即免驱动的usb设备,鼠标、键盘就是hid设备)进行通信,它也是一种USB设备,只不过不需要安装驱动,如果要与非USB-HID设备通信请参考libusb。
libusb首页
github地址
一、HID是什么?
USB-HID是Universal Serial Bus-Human Interface Device的缩写,由其名称可以了解HID设备是直接与人交互的设备,例如键盘、鼠标与游戏杆等。
二、使用步骤
1.引入库
代码如下(示例):
从github上下载hidapi
地址:https://github.com/signal11/hidapi
这个项目是C语言编写的跨平台的项目,实现源码分别在各个平台文件夹下,各平台均使用同一头文件,该头文件是hidapi文件夹下的hidapi.h文件。源码文件以windows为例,其文件夹下可以找到hid.c的文件。
将头文件和源文件添加到C++项目中
然后include使用就可以了
#include "hidapi.h"//USB-HID通信所需头文件,该头文件来自第三方,参考链接:http://github.com/signal11/hidapi
#pragma comment (lib,"setupapi.lib")//hidapi所需的lib环境,没有此文件会导致链接错误
- 1
- 2
我这里由于动态库编译时会出现链接错误,因此加上下面那一行。
2.获取USB-HID设备
代码如下(示例):
if (hid_init())//初始化函数,实际上不调用它hid_enumerate和下面的hid_open也会自动调用
return -1;
hid_device_info* Hids, * HidsCopy;//一个用于接收设备信息的单链表,另一个用来遍历,该结构体使用unicode编码,所以下面都要用unicode处理方式
Hids = hid_enumerate(0x154F, 0x4304);//获取vid为0x154F,pid为0x4304的HID设备链表,这里如果都是0就是获取所有的HID设备
HidsCopy = Hids;
LPCWSTR wpSerialNumber = L"\0";//用来去重,一个hid设备一般有多个端点,因此会读到多个
while (HidsCopy)
{
if (CompareStringW(LOCALE_INVARIANT, NORM_LINGUISTIC_CASING, wpSerialNumber, -1, HidsCopy->serial_number, -1) != CSTR_EQUAL)//去重
{
cout << HidsCopy->serial_number;
wpSerialNumber = HidsCopy->serial_number;//我这里只需要序列号,实际上这个结构体还有很多数据,可以参考官方的实例
}
HidsCopy = HidsCopy->next;
}
hid_free_enumeration(Hids);//释放设备链表
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
3.打开和关闭USB-HID设备
/*********************************************************
函数名称: OpenUSB(LPCTSTR PscSerialNumber)
函数功能: 打开USB-HID设备
输入参数:
参数1: LPCTSTR PscSerialNumber - HID设备的序列号
返回值:
成功 - 0
失败 - -1
********************************************************/
int OpenUSB(LPCTSTR PscSerialNumber)
{
hid_device *handle = hid_open(0x154F, 0x4304, PscSerialNumber);//打开指定vid、pid、序列号的设备
if (!handle)
{
LOG("无法打开USB设备");
hid_exit();
return -1;
}
// 将hid_read()函数设置为非阻塞。
if (hid_set_nonblocking(handle, 1) != 0)// 1启用非阻塞 0禁用非阻塞。
{
hid_close(handle);
hid_exit();
LOG("设置非阻塞失败,错误原因:%s", hid_error(handle));
return -1;
}
return 0;
}
- 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
//关闭USB-HID设备
/*********************************************************
函数名称: CloseUSB(hid_device*& handle)
函数功能: 关闭USB-HID设备
输入参数:
参数1: hid_device*& handle - 操作句柄
********************************************************/
bool CloseUSB(hid_device*& handle)
{
hid_close(handle);
handle = nullptr;//这里将指针置空,增强安全性
hid_exit();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
4.发送和接收报告
/*********************************************************
函数名称: SendMsg(hid_device* Usb, char* Send)
函数功能: 向USB-HID设备发送报告
输入参数:
参数1: hid_device* Usb - USB-HID设备操作句柄
参数2: char* Send - 要发送的数据,第一位不要放数据,要留给报告编号
返回值:
成功 - true
失败 - false
********************************************************/
bool SendMsg(hid_device* Usb, char* Send)
{
Send[0] = 0x01;//报告编号,上位机向HID写数据时,每个包传输的第一个byte为写数据report ID,上、下位机必须一致。
//USB通信报告长度最短为32,加上一字节开头报告编号,最短为33。
int nActualLength = hid_send_feature_report(Usb, Send, 33);
if (nActualLength != 33)
{
LOG("发送指令失败,错误代码:%hx", hid_error(Usb));
return false;
}
return true;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
/*********************************************************
函数名称: ReceiveMsg(hid_device* Usb, byte* pReceiveBuf, size_t nLength)
函数功能: 接收来自USB-HID设备应答的报告
输入参数:
参数1: hid_device* Usb - USB-HID设备操作句柄
参数2: size_t nLength - 应答缓冲区长度
输出参数:
参数1: byte* pReceiveBuf - 应答数据缓冲区
返回值:
实际接收到数据的长度
********************************************************/
int ReceiveMsg(hid_device* Usb, byte* pReceiveBuf, size_t nLength)
{
//接收报告必须将接收缓冲区首字节改为与设备一致才能通信成功
pReceiveBuf[0] = 0x01;
int nRecvLen = hid_get_feature_report(Usb, pReceiveBuf, 64);//这里必须设置接收长度,并且不管有没有数据,,执行成功了返回的就是这里设置的值加上一字节报告ID,也就是65,实际上缓冲区里面全是0x00
if (nRecvLen != 0)
{
//HID通信会自动补齐,如果没有数据就全部都是0x00
if (pReceiveBuf[0] == 0x00)
nRecvLen = 0;//由于第一个字节设置为了0x01,所以第一个字节是0x00就认为没有收到数据。
else if (pReceiveBuf[1] == 0x1b)//判断实际接收到的数据长度,否则接收报告长度固定返回65
{
//由于公司的协议里帧头是0x1b,帧头后面两位就是帧长度,因此可以通过解析帧长度获得实际接收的数据长度
nRecvLen = (pReceiveBuf[2] << 8) | pReceiveBuf[3];
}
}
return nRecvLen;
}
- 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
另外:
参考资料了解到
HID一般使用的是中断传输方式。中断传输的最大包长根据USB设备的速率决定。低速设备(LS):最大包长8字节;全速设备(FS):最大包长64字节;高速设备(HS):最大包长1024字节。如果使用的是全速设备,却要传输256字节,可以采用拆包传输的方式处理,也就是拆成4个64字节的包来传输。这在HID通信中也是一种常用的方式。
作者:大雨哈哈
链接:https://www.zhihu.com/question/282658997/answer/1545044186
因此还是要根据自己的hid设备类型来判断发送和接收的报告长度,如果长度过大就需要分包处理。
5.发送和接收数据
发送数据可以通过hid_write(hid_device *device, const unsigned char *data, size_t length)
接收数据通过hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds)
或hid_read(hid_device *device, unsigned char *data, size_t length)
由于公司的下位机只需要report来通信,所以这里作者并没有实现。不过用法与上述大致是相同的。
总结
以上就是hidapi库的配置方法和使用,仅作为学习记录参考。
如果本文章有误,请指出,感激不尽!