在官方的例程中,也提供了使用tinyusb进行开发的多个例程:
在上述例程中:
- HPM6750作为主机(host)模式时,能够读取U盘,或者接受键盘鼠标的输入
- HPM6750作为设备(device)模式时,可以变身U盘,可以提供虚拟串口,还可以模拟输入设备。
这篇分享中,就基于 hid_generic_inout 这个例程,最终实现通过USB来控制开发板板载的RGB LED。
一、hid_generic_inout例程的基本使用
HID是Human interface device的缩写,从网上的文章 USB 协议分析之 HID 设备 可以了解到如下的信息:
USB HID类是USB设备的一个标准设备类,包括的设备非常多。HID类设备定义它属于人机交互操作的设备,用于控制计算机操作的一些方面,如USB鼠标、USB键盘、USB游戏操纵杆等。但HID设备类不一定要有人机接口,只要符合HID类别规范的设备都是HID设备。 USB HID设备的一个好处就是操作系统自带了HID类的驱动程序,而用户无需去开发驱动程序,只要使用API系统调用即可完成通信。
然后在电脑上,通过USB HID接口,给开发板发送信息,开发板收到后,将信息回显回来。
如上面所说,这样一个设备,不管是Windows,还是Linux,抑或是macOS,都能直接识别并进行数据通讯,而不需要专门的驱动。当然,要实现一些特定的功能,那还是需要专用驱动的。
将 hid_generic_inout 例程编译后,下载到开发板,就能进行测试了。
需要注意的是,下载后,需要将开发板的USB0接口连接到电脑,如下图所示:
可以将该接口,直接连接到电脑,也可以连接到USB扩展坞。
如果是在Windows电脑上,就可以在设备管理器中,看到如下的设备课:
如果是在Linux或者macOS系统,可以通过lsusb命令来查看设备:
上述信息中,TinyUSB Device设备的信息,是在 ./src/usb_descriptors.c 中定义的:
系统识别到设备以后,就能进行发送数据和回显测试了。
在hid_generic_inout的源码中,提供了一个 hid_echo.py 测试python脚本,用于写入数据和接受数据的测试。
不过,该测试脚本中,使用的是python模块 pywinusb ,这个 pywinusb 只能在windows下面使用。
经过简单的研究,在linux或者macOS下,可以直接使用 https://pypi.org/project/hid/ 这个模块来进行操作。使用pip install hid即可安装。
实际使用的hid_echo_hid.py测试脚本如下:
# Copyright (c) 2021 HPMicro
# SPDX-License-Identifier: BSD-3-Clause
# import pywinusb.hid as hid
import hid
import os
import time
import sys
import operator
# VID and PID customization changes here...
VID = 0xcafe
PID = 0x4004
# Send buffer
buffer = [0xff]*1024
# Const
TIMEOUT = -1
PASS = 0
FAIL = 1
# Result
result = TIMEOUT
def search_dev():
# filter = hid.HidDeviceFilter(vendor_id = VID, product_id = PID)
# hid_device = filter.get_devices()
# return hid_device
for device_dict in hid.enumerate():
if device_dict['vendor_id'] == VID and device_dict['product_id'] == PID:
keys = list(device_dict.keys())
# keys.sort()
for key in keys:
if key in ['vendor_id', 'product_id']:
print("%s : 0x%x" % (key, device_dict[key]))
else:
print("%s : %s" % (key, device_dict[key]))
return [hid.device()]
return [False]
def recv_data(data):
print("<=================== USB HID Read ========================>")
for i in range(0, len(data)):
print("0x{0:02x}" .format(data), end=" ")
print("\n")
global result
result = (PASS if (operator.eq(data[1:-1], buffer[1:-1]) == True) else FAIL)
return None
def send_data(device, report_id):
print("<=================== USB HID Write ========================>")
# buffer[0] = report[0].report_id
buffer[0] = report_id
print("0x{0:02x}" .format(buffer[0]), end=" ")
for i in range(1,1024):
buffer = i % 256
print("0x{0:02x}" .format(buffer), end=" ")
print("\n")
# report[0].set_raw_data(buffer)
# report[0].send()
device.write(buffer)
return None
if __name__ == '__main__':
device = search_dev()[0]
device.open(VID, PID)
print("Manufacturer: %s" % device.get_manufacturer_string())
print("Product: %s" % device.get_product_string())
print("Serial No: %s" % device.get_serial_number_string())
# device.set_raw_data_handler(recv_data)
device.set_nonblocking(1)
print("Write the data:")
# send_data(device.find_output_reports())
send_data(device, 1)
time.sleep(1)
# read back the answer
print("Read the data:")
recv_buffer = device.read(1024)
recv_data(recv_buffer)
print("Closing the device")
device.close()
if result == PASS:
print("USB hid echo passed!")
elif result == FAIL:
print("USB HID echo failed!")
else:
print("USB HID echo timed out!")
该脚本在官方脚本的基础上进行修改,改用了hid模块。
将调试接口也连接到电脑,然后用串口工具监听串口,再重启开发板,可以看到下面的信息:
因为系统识别后,就会向开发板发送查询信息,开发板也会返回自身设备信息的数据,使得系统能够识别出来。
然后,执行脚本 python hid_echo_hid.py,就能发送和接受数据了:
最后显示的passed,表示回显的数据和发送的数据,对上了。
二、通过USB控制RGB LED
在hid_generic_inout中,默认使用了RGB LED的绿色LED,并且在main.c的循环中,会交替闪烁。
现在要通过USB控制RGB LED,那么需要进行一些预先的规划:
在默认的程序中,将收到的数据,直接回显回来。
那么我们可以在收到数据后,对收到的数据进行分析,符合一定的规则,就执行一定的动作。
控制RGB LED相对比较简单,所以设计如下的数据规则,实际上就类似一个简单的数据帧协议。
数据帧:索引号 状态
其中:
- 索引号:取值为0、1、2,对听R、G、B三种颜色的LED
- 状态:取值为0、1,0表示熄灭,1表示点亮
设定好了以上的规则,我们就可以动手修改程序了。
首先,默认定义的接收缓冲区为1024字节,有点长,可以修改为64字节。更短也可以,符合实际需要就成。
然后,要修改main.c,添加控制RGB LED的代码,以及根据收到的数据进行控制的代码。
下面是控制RGB LED的代码:
void board_led_write_rgb(uint8_t c, uint8_t state)
{
if(c==1) {
gpio_write_pin(BOARD_G_GPIO_CTRL, BOARD_G_GPIO_INDEX, BOARD_G_GPIO_PIN, state);
} else if(c==2) {
gpio_write_pin(BOARD_B_GPIO_CTRL, BOARD_B_GPIO_INDEX, BOARD_B_GPIO_PIN, state);
} else {
gpio_write_pin(BOARD_R_GPIO_CTRL, BOARD_R_GPIO_INDEX, BOARD_R_GPIO_PIN, state);
}
}
根据收到的数据进行控制的代码,可以放在 tud_hid_set_report_cb() 回调中进行处理,修改后的代码如下:
void tud_hid_set_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize)
{
uint8_t echo_buf[bufsize];
bool led_state = false;
uint8_t c = 0;
printf("tud_hid_set_report_cb:[%d\n", bufsize);
/* echo back anything we received from host */
memcpy(echo_buf, buffer, bufsize);
for(int i=0;i<bufsize;i++) {
printf("%02x ", echo_buf);
}
if(bufsize>=2) {
led_state = buffer[1] ? true : false;
if(buffer[0]==1) {
c = 1;
} else if(buffer[0]==2) {
c = 2;
} else {
c = 0;
}
board_led_write_rgb(c, led_state);
}
#if (CFG_TUSB_REPORT_ID_COUNT > 0)
report_id = REPORT_ID_IN;
tud_hid_report(report_id, echo_buf, bufsize);
#else
tud_hid_report(0, echo_buf, bufsize);
#endif
printf("\nend...\n");
}
在上述代码中,根据收到的控制数据中的最开始两个字节,进行控制,并调用board_led_write_rgb来控制对应的RGB LED。
然后还有把默认循环中led交替亮灭的部分先注释掉:
int main(void)
{
board_init();
board_init_led_pins();
board_init_usb_pins();
board_timer_create(USB_APP_DELAY_INTERVAL, board_timer_callback);
printf("USB%d Device - HID Generic Inout Demo\r\n", BOARD_DEVICE_RHPORT_NUM);
tusb_init();
while (1) {
tud_task(); /* tinyusb device task */
// led_blinking_task();
}
return 0;
}
然后编译代码,并下载到开发板。
现在,我们可以在python交互式命令行中,进行测试了:
执行到最后一步,开发板上的RGB LED的红灯,将会点亮。
如果设置buffer[2]=0,并再次发送数据:
执行后,开发板上的RGB LED的红灯,将会熄灭。
如果设置buffer[1] =1 或者 2,就能够控制绿色LED或者蓝色LED。
再次基础上,我们可以编写下面的脚本 hid_rgb_led.py:
import hid
import time
# VID and PID customization changes here...
VID = 0xcafe
PID = 0x4004
# Send buffer
buffer = [0x00]*64
def set_rgb_led(device, report_id, c, state):
print("<=================== USB HID Write ========================>")
buffer[0] = report_id
buffer[1] = c
buffer[2] = state
for i in range(0, len(buffer)):
print("0x{0:02x}" .format(buffer), end=" ")
print("\n")
device.write(buffer)
return None
if __name__ == '__main__':
device = hid.device()
device.open(VID, PID)
print("Manufacturer: %s" % device.get_manufacturer_string())
print("Product: %s" % device.get_product_string())
print("Serial No: %s" % device.get_serial_number_string())
# device.set_raw_data_handler(recv_data)
device.set_nonblocking(1)
while True:
for c in range(0,3):
for s in range(1,3):
print("Write the data: c=%d s=%d" % (c,s%2))
set_rgb_led(device, 1, c, s%2)
time.sleep(0.1)
# read back the answer
print("Read the data:")
recv_buffer = device.read(64)
print("<=================== USB HID Read ========================>")
for i in range(0, len(recv_buffer)):
print("0x{0:02x}" .format(recv_buffer), end=" ")
print("\n")
time.sleep(0.1)
print("Closing the device")
device.close()
然后执行python hid_rgb_led.py,会有如下的输出:
如果串口监听还打开者,也会看到开发板收到了对应的信息:
而开发板上的RGB LED,就会欢快的闪烁了: