在HPM_SDK中,提供了对tinyusb的支持,可以方便我们进行usb设备相关的开发。
在官方的例程中,也提供了使用tinyusb进行开发的多个例程:
image.png

在上述例程中:

  • 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系统调用即可完成通信。
hid_generic_inout提供的功能,就是将开发板作为 USB HID 设备,通过USB接入到电脑。
然后在电脑上,通过USB HID接口,给开发板发送信息,开发板收到后,将信息回显回来。
如上面所说,这样一个设备,不管是Windows,还是Linux,抑或是macOS,都能直接识别并进行数据通讯,而不需要专门的驱动。当然,要实现一些特定的功能,那还是需要专用驱动的。

将 hid_generic_inout 例程编译后,下载到开发板,就能进行测试了。
需要注意的是,下载后,需要将开发板的USB0接口连接到电脑,如下图所示:
image.png
可以将该接口,直接连接到电脑,也可以连接到USB扩展坞。

如果是在Windows电脑上,就可以在设备管理器中,看到如下的设备课:
image.png
如果是在Linux或者macOS系统,可以通过lsusb命令来查看设备:
image.png

上述信息中,TinyUSB Device设备的信息,是在 ./src/usb_descriptors.c 中定义的:
image.png

image.png

系统识别到设备以后,就能进行发送数据和回显测试了。

在hid_generic_inout的源码中,提供了一个  hid_echo.py 测试python脚本,用于写入数据和接受数据的测试。
image.png

不过,该测试脚本中,使用的是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模块。

将调试接口也连接到电脑,然后用串口工具监听串口,再重启开发板,可以看到下面的信息:
image.png

因为系统识别后,就会向开发板发送查询信息,开发板也会返回自身设备信息的数据,使得系统能够识别出来。

然后,执行脚本 python hid_echo_hid.py,就能发送和接受数据了:
image.png

最后显示的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字节。更短也可以,符合实际需要就成。
image.png

然后,要修改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交互式命令行中,进行测试了:
image.png
执行到最后一步,开发板上的RGB LED的红灯,将会点亮。

如果设置buffer[2]=0,并再次发送数据:
image.png
执行后,开发板上的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,会有如下的输出:
image.png

如果串口监听还打开者,也会看到开发板收到了对应的信息:
image.png

而开发板上的RGB LED,就会欢快的闪烁了: