这篇文章上次修改于 480 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
USB HID上位机简单实现
以CH549
为例,实现简单的HID
上位机功能。
修改HID报告描述符
在HID报告描述符中添加Vendor-Defined Usage Page
,然后并设置REPORT_ID
(每个子HID
设备都应该设置,且不重复),这样就能在不新增端点的情况下,实现自定义HID报文功能。
/* Vendor */
0x06, 0xB1, 0xFF, // Usage Page (Vendor-Defined 178)
0x09, 0x01, // Usage (Vendor-Defined 1)
0xA1, 0x01, // Collection (Application)
0x85, 0x04, // REPORT_ID (4)
0x09, 0x04, // Usage (Vendor-Defined 4)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x75, 0x08, // Report Size (8)
0x95, 0x3c, // Report Count (60)
0x91, 0x02, // Output(Data,Var,Abs,NWrp,Lin,Pref,NNul,NVol,Bit)
0x09, 0x01, // Usage (0x01)
0x25, 0x00, // Logical Maximum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x75, 0x08, // Report Size (8)
0x95, 0x3c, // Report Count (60)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection
/* 37 */
缺点是每个 HID
报文前都需要新增REPORT_ID
号,用来区分功能。
烧录至单片机,连接到电脑后会显示为符合HID标准的供应商定义设备
单片机的发送和接收
发送数据
UINT8 SendHID[61] = {0}; //Report Count (60),多出的一个位是REPORT_ID
SendHID[0] = 4; //REPORT_ID
SendHID[1] = 0; //data
SendHID[2] = 1; //data
Enp2BlukIn(SendHID, sizeof(SendHID)); //发送到上位机,这里使用WCH提供的函数,IN端口2
接收数据
UINT8 HID_OUT_report[61] = {0};
/*******************************************************************************
* Function Name : DeviceInterrupt()
* Description : CH559USB中断处理函数
*******************************************************************************/
......
case UIS_TOKEN_OUT | 2: // endpoint 2# 端点批量下传
if (U_TOG_OK) // 不同步的数据包将丢弃
{
len = USB_RX_LEN; //接收数据长度,数据从Ep2Buffer首地址开始存放
for (i = 0; i < len; i++)
{
if (Ep2Buffer[0] == 0x04) //当reportid==4
{
HID_OUT_report[i] = Ep2Buffer[i]; //接收到的数据保存到HID_OUT_report
}
}
}
break;
......
上位机部分
通常会使用HIDAPI
来实现,当然可以使用你喜欢的语言来写,这里就用Python
来实现。
这里用到的是cython-hidapi,它的核心还是HIDAPI
,你可以理解为套壳。
初始化
import hid
import time
vendor_id = 0x2b86
usage_page = 0xffb1 # 对应HID报告描述符的 0x06, 0xB1, 0xFF, // Usage Page (Vendor-Defined 178)
# 通过VID和usage_page查找USB上的单片机设备,当然PID也是可以的
def init_usb(vendor_id, usage_page):
h = hid.device()
hid_enumerate = hid.enumerate() # 导出所有的HID设备信息
for i in range(len(hid_enumerate)):
if (hid_enumerate[i]['usage_page'] == usage_page and hid_enumerate[i]['vendor_id'] == vendor_id):
device_path = hid_enumerate[i]['path']
if (device_path == 0): return "Device not found"
#遍历所有的HID设备,查找符合VID和usage_page的设备,获取路径
h.open_path(device_path) #通过路径打开设备
h.set_nonblocking(1) # enable non-blocking mode
发送和接收
buffer = [0] * 60
# 这里的列表元素个数必须为HID报告描述符所指定的个数,IN/OUT report都应为这个长度。
# 0x95, 0x3c, // Report Count (60)
# 不然Windows是不会处理这个report的,但是在Linux上就不会出现这个问题。
buffer[0] = 4 #report_id
buffer[1] = 0 #data
buffer[2] = 0 #data
def hid_report(vendor_id, usage_page, buffer): #调用函数发送数据,完成后等待单片机返回数据
try:
h.write(buffer) # 尝试向单片机发送数据
except (OSError, ValueError):
print("写入设备错误")
return 1
except NameError:
print("未初始化设备")
return 1
while 1:
try:
d = h.read(64) #尝试从单片机循环接收数据
except (OSError, ValueError):
print("读取数据错误")
return 2
if d:
print(">", d) #打印接收到的数据
break
if time.time() - time_start > 3: #接收超时
return 2
return d
没有评论