程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

Rockchip RK3399 - linux通过libusb读取usb数据包

----------------------------------------------------------------------------------------------------------------------------

开发板 :SOM-RK3399核心板+定制底板
eMMC16GB
LPDDR34GB
显示屏 :15.6英寸HDMI接口显示屏
u-boot2017.09
linux4.19
----------------------------------------------------------------------------------------------------------------------------

注意:本节介绍的内容基于《Rockchip RK3399 - 移植uboot 2017.09 & linux 4.19(友善之家脚本方式)》移植的运行环境:内核版本4.19.193以及debian 11根文件系统。

在《Rockchip RK3399 - linux通过usbmon抓取usb数据包》中我们分析了我们所使用的USB触摸屏的HID报告描述符,并通过usbmon工具进行USB触摸屏数据包的抓取,最终结合HID报告描述符分析抓取到的数据包中数据的含义。

在《Rockchip RK3399 - 移植uboot 2017.09 & linux 4.19(友善之家脚本方式)》中我们介绍了在SOM-RK3399核心板上,通过USB3.0 Type-C接口将开发板模拟成USB设备。

这一节我们将通过libusb来实现USB触摸屏数据包的捕获,并将捕获的数据通过模拟的USB触摸屏发送到PC

一、模拟USB设备回顾

我们首先回顾一下模拟USB设备是如何实现的,我们编写了一个hid_keyboard_mouse.sh脚本,由于USB3.0 Type-C接口被配置为了从设备,因此可以通过USB3.0 Type-C接口将开发板模拟成USB设备(同时模拟鼠标、键盘、触摸屏功能)。

1.1 hid_keyboard_mouse.sh

/opt/hid下新建hid_keyboard_mouse.sh脚本:

#!/bin/bash

gadget=g1

do_start(){
    has_mount=$(mount -l | grep /sys/kernel/config)
    if [[ -z  $has_mount ]];then
        mount -t configfs none /sys/kernel/config
    fi
    cd /sys/kernel/config/usb_gadget

    # 当我们创建完这个文件夹之后,系统自动的在这个文件夹中创建usb相关的内容 ,这些内容需要由创建者自己填写
    if [[ ! -d ${gadget} ]]; then
        mkdir ${gadget}
    else
    	exit 0
    fi
    cd ${gadget}

    #设置USB协议版本USB2.0
    echo 0x0200 > bcdUSB

    #定义产品的VendorID和ProductID
    echo "0x0525"  > idVendor
    echo "0xa4ac" > idProduct

    #实例化"英语"ID:
    mkdir strings/0x409

    #将开发商、产品和序列号字符串写入内核
    echo "76543210" > strings/0x409/serialnumber
    echo "mkelehk"  > strings/0x409/manufacturer
    echo "keyboard_mouse"  > strings/0x409/product

    #创建一个USB配置实例
    if [[ ! -d configs/c.1 ]]; then
        mkdir configs/c.1
    fi

    #定义配置描述符使用的字符串
    if [[ ! -d configs/c.1/strings/0x409 ]]; then
        mkdir configs/c.1/strings/0x409
    fi

    echo "hid" > configs/c.1/strings/0x409/configuration

    #创建接口
    mkdir functions/hid.0   #键盘
    mkdir functions/hid.1   #鼠标
    mkdir functions/hid.2   #触摸屏

    #接口0,模拟键盘  
    echo 1 > functions/hid.0/subclass   #启动设备符
    echo 1 > functions/hid.0/protocol   #标识键盘设备
    echo 8 > functions/hid.0/report_length  #标识该hid设备每次发送的报表长度为8字节
    echo -ne \\x05\\x01\\x09\\x06\\xa1\\x01\\x05\\x07\\x19\\xe0\\x29\\xe7\\x15\\x00\\x25\\x01\\x75\\x01\\x95\\x08\\x81\\x02\\x95\\x01\\x75\\x08\\x81\\x03\\x95\\x05\\x75\\x01\\x05\\x08\\x19\\x01\\x29\\x05\\x91\\x02\\x95\\x01\\x75\\x03\\x91\\x03\\x95\\x06\\x75\\x08\\x15\\x00\\x25\\x65\\x05\\x07\\x19\\x00\\x29\\x65\\x81\\x00\\xc0 > functions/hid.0/report_desc  #配置hid描述符

	#接口1,模拟键盘鼠标
    echo 1 > functions/hid.1/subclass  #启动设备符
    echo 2 > functions/hid.1/protocol  #鼠标协议
    echo 4 > functions/hid.1/report_length  # 相对值是4
    echo -ne \\x05\\x01\\x09\\x02\\xa1\\x01\\x09\\x01\\xa1\\x00\\x05\\x09\\x19\\x01\\x29\\x03\\x15\\x00\\x25\\x01\\x95\\x03\\x75\\x01\\x81\\x02\\x95\\x01\\x75\\x05\\x81\\x03\\x05\\x01\\x09\\x30\\x09\\x31\\x09\\x38\\x15\\x81\\x25\\x7f\\x75\\x08\\x95\\x03\\x81\\x06\\xc0\\xc0 > functions/hid.1/report_desc

    #接口2,模拟触摸屏
    echo 0 > functions/hid.2/subclass
    echo 0 > functions/hid.2/protocol
    echo 5 > functions/hid.2/report_length  #标识该hid设备每次发送的报表长度为5字节
    echo -ne \\x05\\x01\\x09\\x02\\xa1\\x01\\x09\\x01\\xa1\\x00\\x05\\x09\\x19\\x01\\x29\\x05\\x15\\x00\\x25\\x01\\x95\\x05\\x75\\x01\\x81\\x02\\x95\\x01\\x75\\x03\\x81\\x01\\x05\\x01\\x09\\x30\\x09\\x31\\x15\\x00\\x26\\xff\\x7f\\x35\\x00\\x46\\xff\\x7f\\x75\\x10\\x95\\x02\\x81\\x02\\xc0\\xc0 > functions/hid.2/report_desc

    #捆绑接口到配置config.1
    ln -s functions/hid.0 configs/c.1
    ln -s functions/hid.1 configs/c.1
    ln -s functions/hid.2 configs/c.1

    #配置USB3.0 OTG0的工作模式为Device(设备):
    #echo peripheral > /sys/devices/platform/ff770000.syscon/ff770000.syscon:usb2-phy@e460/otg_mode

    echo "sleep 3s"
    sleep 3s

    #将gadget驱动注册到UDC上,插上USB线到电脑上,电脑就会枚举USB设备。
    echo fe900000.dwc3 > UDC
}

do_stop() {
    cd /sys/kernel/config/usb_gadget/${gadaget}
    echo "" > UDC
}

case $1 in
    start)
        echo "Start hid gadget "
        do_start
        ;;
    stop)
        echo "Stop hid gadget"
        do_stop
        ;;
    *)
        echo "Usage: $0 (stop | start)"
        ;;
esac

/opt/profile.d下新建hid.sh脚本:

#!/bin/bash

/opt/hid/hid_keyboard_mouse.sh start

我们将这个脚本配置为开机自动执行,重新开机,开机会自动执行/etc/profile.d的所有shell脚本。查看hid设备;

root@SOM-RK3399v2:/home/pi# ls /dev/hidg* -nR
crw------- 1 0 0 236, 0 Sep 26 14:25 /dev/hidg0    # 键盘
crw------- 1 0 0 236, 1 Sep 26 14:25 /dev/hidg1    # 鼠标
crw------- 1 0 0 236, 2 Sep 26 14:25 /dev/hidg2    # 触摸屏 
root@SOM-RK3399v2:/home/pi# ls /sys/kernel/config/usb_gadget/g1/
UDC           bDeviceProtocol  bMaxPacketSize0  bcdUSB   functions  idVendor  strings
bDeviceClass  bDeviceSubClass  bcdDevice        configs  idProduct  os_desc

1.2 HID报告描述符

既然我们想通过模拟的USB触摸屏向PC发送数据,自然而然的我们要搞懂模拟的USB触摸屏的HID报告描述符:

echo -ne \\x05\\x01\\x09\\x02\\xa1\\x01\\x09\\x01\\xa1\\x00\\x05\\x09\\x19\\x01\\x29\\x05\\x15\\x00\\x25\\x01\\x95\\x05\\x75\\x01\\x81\\x02\\x95\\x01\\x75\\x03\\x81\\x01\\x05\\x01\\x09\\x30\\x09\\x31\\x15\\x00\\x26\\xff\\x7f\\x35\\x00\\x46\\xff\\x7f\\x75\\x10\\x95\\x02\\x81\\x02\\xc0\\xc0 > functions/hid.2/report_desc

根据《Rockchip RK3399 - linux通过usbmon抓取usb数据包》介绍的HID报告描述符的规则描述,我们得到;

0x05,0x01,   // USAGE_PAGE (Generic Desktop)
0x09,0x02,   // USAGE (Mouse)
0xa1,0x01,   // COLLECTION (Application)
	0x09,0x01,   //   USAGE (Pointer)
	0xa1,0x00,   //   COLLECTION (Physical)
		/* 下面定义的是用途Button.1~Button.5,加上3个无效位,一共占用1个字节 */
		0x05,0x09,  //     USAGE_PAGE (Button)
		0x19,0x01,  //     USAGE_MINIMUM (Button 1)
		0x29,0x05,  //     USAGE_MAXIMUM (Button 5)
		0x15,0x00,  //     LOGICAL_MINIMUM (0)
		0x25,0x01,  //     LOGICAL_MAXIMUM (1)
		0x95,0x05,  //     REPORT_COUNT (5)
		0x75,0x01,  //     REPORT_SIZE (1)
		0x81,0x02,  //     INPUT (Data,Var,Abs)
		0x95,0x01,  //     REPORT_COUNT (1)
		0x75,0x03,  //     REPORT_SIZE (3)
		0x81,0x01,  //     INPUT (Data,Var,Abs)
		
		/* 下面定义的是用途GenericDesktop.X、GenericDesktop.Y,各占用2个字节 */
		0x05,0x01,  //     USAGE_PAGE (Generic Desktop)
		0x09,0x30,  //     USAGE (X)
		0x09,0x31,  //     USAGE (Y)
		0x15,0x00,  //     LOGICAL_MINIMUM (0) 
		0x26,0xff,0x7f,  //     LOGICAL_MAXIMUM (0x7fff)
		0x35,0x00,  // PHYSICAL_MINIMUM (0)
		0x46,0xff,0x7f,  // PHYSICAL_MAXIMUM (0x7fff)
		0x75,0x10,  //     REPORT_SIZE (16)
		0x95,0x02,  //     REPORT_COUNT (2)
		0x81,0x02,  //     INPUT (Data,Var,Abs)
	0xc0,      // End COLLECTION
0xc0       // End COLLECTION

我们需要关注一下HID报告描述符中X、Y坐标的逻辑最小值和最大值,这个后面应用程序中有使用到;

 Logical Minimum(0)
 Logical Maximum(32767)

由于HID报告描述符只有1个报告,因此在USB数据包中就不用指定报告ID(需要注意的是:如果HID报告描述符有多个报告,那么存在一个USB数据包中有包含多个报告的场景),通过分析,我们可以知道一个USB数据包包含5个字节:

字节 描述
BYTE0 bit [7~5]:未使用
bit[4] 1:表示EXTRA键按下 0:表示松开
bit[3] 1:表示SIDE键按下 0:表示松开
bit[2] 1:表示中键按下 0:表示松开
bit[1] 1:表示右键按下 0:表示松开
bit[0] 1:表示左键按下 0:表示松开
BYTE1 X坐标低8位(绝对值)
BYTE2 X坐标高8位(绝对值)
BYTE3 Y坐标低8位(绝对值)
BYTE4 Y坐标高8位(绝对值)

我们只需要按照上面表格的描述构建USB数据包,并将数据写入/dev/hidg2设备中,即可通过USB OTG线将数据发送到PC

二、USB触摸屏回顾

在《Rockchip RK3399 - linux通过usbmon抓取usb数据包》中我们分析了我们所使用的USB触摸屏(PID=e5e3,VID=1a86)的HID报告描述符;

root@SOM-RK3399v2:/#  cat /sys/kernel/debug/hid/0003\:1A86\:E5E3.0007/rdesc
05 0d 09 04 a1 01 85 01 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 01 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 55 0e 65 11 09 30 35 00 46 79 08 81 02 26 00 10 46 4c 05 09 31 81 02 05 0d 09 48 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 01 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 55 0e 65 11 09 30 35 00 46 79 08 81 02 26 00 10 46 4c 05 09 31 81 02 05 0d 09 48 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 01 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 55 0e 65 11 09 30 35 00 46 79 08 81 02 26 00 10 46 4c 05 09 31 81 02 05 0d 09 48 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 01 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 55 0e 65 11 09 30 35 00 46 79 08 81 02 26 00 10 46 4c 05 09 31 81 02 05 0d 09 48 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 01 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 55 0e 65 11 09 30 35 00 46 79 08 81 02 26 00 10 46 4c 05 09 31 81 02 05 0d 09 48 81 02 c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 95 07 81 01 75 08 09 51 95 01 81 02 05 01 26 00 10 75 10 55 0e 65 11 09 30 35 00 46 79 08 81 02 26 00 10 46 4c 05 09 31 81 02 05 0d 09 48 81 02 c0 05 0d 27 ff ff 00 00 75 10 95 01 09 56 81 02 09 54 15 00 25 7f 95 01 75 08 81 02 85 02 09 55 95 01 25 0a b1 02 85 03 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0

同时对HID报告描述符进行了解析,得知第一个触摸点的描述数据位于捕获到的数据包的字节1~字节8中。

位7 位6 位5 位4 位3 位2 位1 位0
字节0 报告编号
字节1 第一个触摸点状态
字节2 第一个触摸点标识符
字节3 X坐标低8位(绝对值)
字节4 X坐标高8位(绝对值)
字节5 Y坐标低8位(绝对值)
字节6 Y坐标高8位(绝对值)
字节7 触摸笔宽度低8位
字节8 触摸笔宽度高8位

我们需要关注一下报告描述符中X、Y坐标的逻辑最小值和最大值;

 Logical Minimum(0)
 Logical Maximum(4096)  // 0x1000

实际上这个就是移动触摸点时捕获到的USB数据包中X、Y坐标的区间范围;

  • 当触摸点移动到屏幕的最左侧,X->0;
  • 当触摸点移动到屏幕的最右侧,X->4096;
  • 当触摸点移动到屏幕的最上边,Y->0;
  • 当触摸点移动到屏幕的最下边,Y->4096;

三、应用程序

这里我们编写一个应用程序通过libusb来实现USB触摸屏数据包的捕获,并将捕获的数据通过模拟的USB触摸屏设备发送到PC。具体思路如下:

  • 通过libusb库读取USB触摸屏的数据包;
  • 结合USB触摸屏的HID报告描述符,获取到第一个触摸点的信息(按键状态、X、Y坐标);
  • 结合模拟的USB触摸屏的HID报告描述符,将按键状态、X、Y坐标转换成模拟的USB触摸屏的所需的报告格式;
  • 向模拟的USB触摸屏写入报告数据;

3.1 应用程序

3.1.1 usb_test.c

/opt/libusb目录下编写测试应用程序usb_test.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <getopt.h>
#include <ctype.h>
#include <signal.h>
#include <linux/input.h>
#include <linux/uinput.h>
#include <stdbool.h>
#include "libusb.h"

/* Developers will wish to consult the API documentation: http://api.libusb.info  */
/* https://github.com/libusb/libusb */

static int verbose = 1;

static volatile sig_atomic_t rcv_exit;

/* 触摸点ID */
static int touch_id = 100;

/* 模拟的usb触摸屏设备 x、y坐标逻辑最大值 */
static int gadget_x_logical_maximum = 0x7fff;
static int gadget_y_logical_maximum = 0x7fff;

/* 存放hid报告描述符解析后的信息 */
struct hid_descriptor_t
{
    /* usb触摸屏描述信息 */
    uint16_t vid;               /* 生产厂家ID */
    uint16_t pid;                /* 产品ID */

    /* */
    uint8_t endpoint_address;   /* 端点地址 */
    uint8_t report_length;      /* 报告长度,标识该hid设备每次发送的报表长度 */

    /* 第一个触摸点描述信息 */
    uint16_t btn_offset;        /* 按键状态偏移位,单位为位 */
    uint16_t index_x_low;       /* x坐标低8位索引,单位为字节 */
    uint16_t index_x_high;      /* x坐标高8位索引,单位为字节 */
    uint16_t index_y_low ;      /* y坐标低8位索引,单位为字节 */
    uint16_t index_y_high;      /* y坐标高8位索引,单位为字节 */
    int x_logical_minimum;      /* x坐标逻辑最小值 */
    int x_logical_maximum;      /* x坐标逻辑最大值 */
    int y_logical_minimum;      /* y坐标逻辑最小值 */
    int y_logical_maximum;      /* y坐标逻辑最大值 */
    int interface_number;       /* bInterfaceNumber */
};

/* usb触摸屏报告 */
struct touch_screen_report_t
{
    uint8_t buttons;        /* 按键按下状态 */
    uint16_t x;             /* x坐标 */
    uint16_t y;             /* y坐标 */
};

/* 存放不同usb触摸屏hid报告描述符解析后的信息 */
static const struct hid_descriptor_t hids[] = {
    [0] = { 0x1a86, 0xe5e3, 0x82, 52, 8, 3, 4, 5, 6, 0, 0x1000, 0, 0x1000, 0 },
    [1] = { 0x222a, 0x0001, 0x81, 52, 14, 2, 3, 4, 5, 0, 0x0780, 0, 0x0438, 0 },
    [2] = { 0x03eb, 0x8a6e, 0x83, 59, 8, 2, 3, 6, 7, 0, 0x0fff, 0, 0x0fff,1 },
};

/**
 * 上报事件
 * @param fd:文件描述符
 * @param type:事件类型
 * @param code:事件code
 * @param value:事件值
 */
static int report_key(int fd, int type, int code, int32_t value)
{
    struct input_event event;
    event.type = type;
    event.code = code;
    event.value = value;
    gettimeofday(&event.time, 0);

    if (write(fd, &event, sizeof(struct input_event)) < 0)
    {
        perror("report key error!\n");
        return -1;
    }

    return 0;
}

/**
 * 上报slot,对于协议B,内核驱动应该把每一个识别出的触摸点和一个slot相关联
 * @param fd:文件描述符
 * @param slot: 当前发送的是哪个slot的坐标信息,也就是哪个触摸点  
 *              比如10点触摸,最多可以同时支持10个触摸点,slot可以理解为是第几个触摸点
 */
static void input_mt_slot(int fd, int slot)
{
    report_key(fd, EV_ABS, ABS_MT_SLOT, slot);
}

/**
 * 使用当前slot来传播触摸状态的改变,通过修改关联slot的ABS_MT_TRACKING_ID来达到对触摸点的创建,替换和销毁。
 * 1. ABS_MT_TRACKING_ID用来跟踪触摸点属于哪一条线,如果触摸点的ID值与上一次事件中ID值相等,那么他们就属于同一条线,
 *    ID值并不是随便赋值的,而是硬件上跟踪了触摸点的轨迹,比如按下一个点硬件会跟踪这个点的ID,只要不抬起上报的点都会和这个ID相关
 * 2. 上报ABS_MT_TRACKING_ID -1系统会清除对应的ID和slot
 * @param fd:文件描述符
 * @param active: 1连续触摸(表示触摸点一直按下),0抬起(表示触摸点无效了)
 */
static void input_mt_report_slot_state(int fd, bool active)
{
    // 上报ABS_MT_TRACKING_ID -1系统会清除对应的slot和ID,表示触摸点抬起
    if (!active) {
        report_key(fd, EV_ABS, ABS_MT_TRACKING_ID, -1);
        return;
    }

    // ABS_MT_TRACKING_ID大于0,表示触摸点按下   如果ABS_MT_TRACKING_ID本次并没有改变表示触摸点还在一个轨迹上
    report_key(fd, EV_ABS, ABS_MT_TRACKING_ID, touch_id);
}

/**
 * 上报触摸点坐标
 * @param fd:文件描述符
 * code: 要上报的是什么数据
 * value: 要上报的数据值
 * return: 无
 */
static void input_report_abs(int fd, unsigned int code,int value)
{
    report_key(fd, EV_ABS, code, value);
}

/**
 * 触摸点按下或者抬起
 * @param fd:文件描述符
 * @param x:x坐标
 * @param y:y坐标
 * @param down:true 按下状态,false抬起状态
 */
static void touch_screen_pressed(int fd, int x, int y, bool down)
{
    // 上报触摸点序号
    input_mt_slot(fd, 0);

    if(down){
        // 为触摸点分配ID
        input_mt_report_slot_state(fd, 1);
         // 上报触摸点X轴坐标信息
        input_report_abs(fd, ABS_MT_POSITION_X, x);
        // 上报触摸点Y轴坐标信息
        input_report_abs(fd, ABS_MT_POSITION_Y, y);
    }else{
        input_mt_report_slot_state(fd, 0);
    }

    // 同步事件
    report_key(fd, EV_SYN, SYN_REPORT, 0);
}

/**
 * 创建一个类似于触摸屏的虚拟设备,返回文件描述符
 */
static int create_touch_screen()
{
     int fd,err;
     struct uinput_user_dev dev;

     fd = open("/dev/uinput", O_WRONLY|O_NONBLOCK);
     if (fd < 0) {
         printf("Error could not open /dev/uinput device: %s\n", strerror(errno));
         return -1;
     }

     // configure touch device event properties
     memset(&dev, 0, sizeof(dev));

     // 设备的别名
     strncpy(dev.name, "TouchScreen", UINPUT_MAX_NAME_SIZE);
     dev.id.version = 1;
     dev.id.bustype = BUS_USB;

    // 支持10点触摸
     dev.absmin[ABS_MT_SLOT] = 0;
     dev.absmax[ABS_MT_SLOT] = 9;


     dev.absmin[ABS_MT_TRACKING_ID] = 0;
     dev.absmax[ABS_MT_TRACKING_ID] = 65535;

     dev.absmin[ABS_MT_TOUCH_MAJOR] = 0;
     dev.absmax[ABS_MT_TOUCH_MAJOR] = 0xff;

	 // 屏幕最小/大的X尺寸
     dev.absmin[ABS_MT_POSITION_X] = 0;      
     dev.absmax[ABS_MT_POSITION_X] = gadget_x_logical_maximum;  

	 // 屏幕最小/大的Y尺寸
     dev.absmin[ABS_MT_POSITION_Y] = 0;     
     dev.absmax[ABS_MT_POSITION_Y] = gadget_y_logical_maximum;  

	 //屏幕按下的压力值
     dev.absmin[ABS_MT_PRESSURE] = 0;
     dev.absmax[ABS_MT_PRESSURE] = 0xff;     

     // Setup the uinput device
     err = ioctl(fd, UI_SET_EVBIT, EV_KEY);
     if (err < 0){
        goto err;
     }
     err = ioctl(fd, UI_SET_EVBIT, EV_REL);
     if (err < 0){
        goto err;
     }
     err = ioctl (fd, UI_SET_EVBIT,  EV_ABS);
     if (err < 0){
        goto err;
     }
     err = ioctl (fd, UI_SET_ABSBIT, ABS_MT_SLOT);
     if (err < 0){
        goto err;
     }
     err = ioctl (fd, UI_SET_ABSBIT, ABS_MT_TOUCH_MAJOR);
     if (err < 0){
        goto err;
     }
     err = ioctl (fd, UI_SET_ABSBIT, ABS_MT_POSITION_X);
     if (err < 0){
        goto err;
     }
     err = ioctl (fd, UI_SET_ABSBIT, ABS_MT_POSITION_Y);
     if (err < 0){
        goto err;
     }
     err = ioctl (fd, UI_SET_ABSBIT, ABS_MT_TRACKING_ID);
     if (err < 0){
        goto err;
     }
     err = ioctl (fd, UI_SET_ABSBIT, ABS_MT_PRESSURE);
     if (err < 0){
        goto err;
     }
     err = ioctl (fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT);
     if (err < 0){
        goto err;
     }
     err = ioctl (fd, UI_SET_KEYBIT, BTN_TOUCH);
     if (err < 0){
        goto err;
     }

     /* Create input device into input sub-system */
     err = write(fd, &dev, sizeof(dev));
     if (err < 0){
        goto err;
     }
     err = ioctl(fd, UI_DEV_CREATE);
     if (err < 0){
        goto err;
     }

     return fd;
err:
     printf("(%s) Failed to initialise\n",__func__);
     close(fd);
}

/**
 * 输出usb端点额外信息
 * @param config:usb端点伴随描述符
 */
static void print_endpoint_comp(const struct libusb_ss_endpoint_companion_descriptor *ep_comp)
{
    printf("      USB 3.0 Endpoint Companion:\n");
    printf("        bMaxBurst:           %u\n", ep_comp->bMaxBurst);
    printf("        bmAttributes:        %02xh\n", ep_comp->bmAttributes);
    printf("        wBytesPerInterval:   %u\n", ep_comp->wBytesPerInterval);
}

/**
 * 输出usb端点信息
 * @param config:usb端点描述符
 */
static void print_endpoint(const struct libusb_endpoint_descriptor *endpoint)
{
    int i, ret;

    printf("      Endpoint:\n");
    // 端点地址:位[3:0]表示端点号,第7位是方向:0位输出、1为输入
    printf("        bEndpointAddress:    %02xh\n", endpoint->bEndpointAddress);
    // 端点属性:位[1:0]值00表示控制、01表示等时、10表示批量、11表示中断
    printf("        bmAttributes:        %02xh\n", endpoint->bmAttributes);
    // 本端点接受或发送的最大信息包的大小
    printf("        wMaxPacketSize:      %u\n", endpoint->wMaxPacketSize);
    // 轮询数据传输端点的时间间隔,用在中断传输上,比如间隔时间查询鼠标的数据;
    printf("        bInterval:           %u\n", endpoint->bInterval);
    printf("        bRefresh:            %u\n", endpoint->bRefresh);
    printf("        bSynchAddress:       %u\n", endpoint->bSynchAddress);

    // 遍历usb端点额外信息
    for (i = 0; i < endpoint->extra_length;) {
        if (LIBUSB_DT_SS_ENDPOINT_COMPANION == endpoint->extra[i + 1]) {
            struct libusb_ss_endpoint_companion_descriptor *ep_comp;

            // 获取端点伴随描述符的详细信息
            ret = libusb_get_ss_endpoint_companion_descriptor(NULL, endpoint, &ep_comp);
            if (LIBUSB_SUCCESS != ret)
            continue;

            // 输出usb端点信息
            print_endpoint_comp(ep_comp);

            // 释放usb端点伴随描述符
            libusb_free_ss_endpoint_companion_descriptor(ep_comp);
        }

        i += endpoint->extra[i];
    }
}

/**
 * 输出usb接口信息
 * @param config:usb接口描述符
 */
static void print_altsetting(const struct libusb_interface_descriptor *interface)
{
    uint8_t i;

    printf("    Interface:\n");
    // 接口的编号
    printf("      bInterfaceNumber:      %u\n", interface->bInterfaceNumber);
    // 接口的设置的编号
    printf("      bAlternateSetting:     %u\n", interface->bAlternateSetting);
    // 使用的端点个数(不包括端点0), 表示有多少个端点描述符
    printf("      bNumEndpoints:         %u\n", interface->bNumEndpoints);
    // 接口类型
    printf("      bInterfaceClass:       %u\n", interface->bInterfaceClass);
    // 接口子类型
    printf("      bInterfaceSubClass:    %u\n", interface->bInterfaceSubClass);
    // 接口所遵循的协议
    printf("      bInterfaceProtocol:    %u\n", interface->bInterfaceProtocol);
    // 描述该接口的字符串索引值
    printf("      iInterface:            %u\n", interface->iInterface);

    // 遍历usb端点描述符,输出usb端点信息
    for (i = 0; i < interface->bNumEndpoints; i++)
        print_endpoint(&interface->endpoint[i]);
}

static void print_2_0_ext_cap(struct libusb_usb_2_0_extension_descriptor *usb_2_0_ext_cap)
{
    printf("    USB 2.0 Extension Capabilities:\n");
    printf("      bDevCapabilityType:    %u\n", usb_2_0_ext_cap->bDevCapabilityType);
    printf("      bmAttributes:          %08xh\n", usb_2_0_ext_cap->bmAttributes);
}

static void print_ss_usb_cap(struct libusb_ss_usb_device_capability_descriptor *ss_usb_cap)
{
    printf("    USB 3.0 Capabilities:\n");
    printf("      bDevCapabilityType:    %u\n", ss_usb_cap->bDevCapabilityType);
    printf("      bmAttributes:          %02xh\n", ss_usb_cap->bmAttributes);
    printf("      wSpeedSupported:       %u\n", ss_usb_cap->wSpeedSupported);
    printf("      bFunctionalitySupport: %u\n", ss_usb_cap->bFunctionalitySupport);
    printf("      bU1devExitLat:         %u\n", ss_usb_cap->bU1DevExitLat);
    printf("      bU2devExitLat:         %u\n", ss_usb_cap->bU2DevExitLat);
}

static void print_bos(libusb_device_handle *handle)
{
    struct libusb_bos_descriptor *bos;
    uint8_t i;
    int ret;

    ret = libusb_get_bos_descriptor(handle, &bos);
    if (ret < 0)
    return;

    printf("  Binary Object Store (BOS):\n");
    printf("    wTotalLength:            %u\n", bos->wTotalLength);
    printf("    bNumDeviceCaps:          %u\n", bos->bNumDeviceCaps);

    for (i = 0; i < bos->bNumDeviceCaps; i++) {
        struct libusb_bos_dev_capability_descriptor *dev_cap = bos->dev_capability[i];

        if (dev_cap->bDevCapabilityType == LIBUSB_BT_USB_2_0_EXTENSION) {
            struct libusb_usb_2_0_extension_descriptor *usb_2_0_extension;

            ret = libusb_get_usb_2_0_extension_descriptor(NULL, dev_cap, &usb_2_0_extension);
            if (ret < 0)
            return;

            print_2_0_ext_cap(usb_2_0_extension);
            libusb_free_usb_2_0_extension_descriptor(usb_2_0_extension);
        } else if (dev_cap->bDevCapabilityType == LIBUSB_BT_SS_USB_DEVICE_CAPABILITY) {
            struct libusb_ss_usb_device_capability_descriptor *ss_dev_cap;

            ret = libusb_get_ss_usb_device_capability_descriptor(NULL, dev_cap, &ss_dev_cap);
            if (ret < 0)
            return;

            print_ss_usb_cap(ss_dev_cap);
            libusb_free_ss_usb_device_capability_descriptor(ss_dev_cap);
        }
    }

    libusb_free_bos_descriptor(bos);
}

/**
 * 输出usb接口信息
 * @param config:usb接口
 */
static void print_interface(const struct libusb_interface *interface)
{
    int i;

    // 遍历接口所支持的可选设置
    for (i = 0; i < interface->num_altsetting; i++)
        print_altsetting(&interface->altsetting[i]);
}

/**
 * 输出usb配置信息
 * @param config:usb配置描述符
 */
static void print_configuration(struct libusb_config_descriptor *config)
{
    uint8_t i;

    printf("  Configuration:\n");
    // 配置描述符的总长度,以字节为单位
    printf("    wTotalLength:            %u\n", config->wTotalLength);
    // 配置所支持的接口数量
    printf("    bNumInterfaces:          %u\n", config->bNumInterfaces);
    // 配置值,用于在设置或选择配置时标识该配置
    printf("    bConfigurationValue:     %u\n", config->bConfigurationValue);
    // 配置描述符字符串的索引。可以使用该索引在设备的字符串描述符中找到配置描述符的字符串表示
    printf("    iConfiguration:          %u\n", config->iConfiguration);
    // 供电模式的选择
    printf("    bmAttributes:            %02xh\n", config->bmAttributes);
    // 设备从总线提取的最大电流
    printf("    MaxPower:                %u\n", config->MaxPower);

    // 遍历usb接口描述符,输出usb接口信息
    for (i = 0; i < config->bNumInterfaces; i++)
        print_interface(&config->interface[i]);
}

/**
 * 判断当前设备的vid和pid是否和指定的匹配
 * @param vid:生产厂商ID
 * @param pid:产品ID
 */
bool device_match_vid_pid(libusb_device *dev,uint16_t vid, uint16_t pid)
{
    struct libusb_device_descriptor desc;
    int ret;
    // 获取usb设备描述符
    ret = libusb_get_device_descriptor(dev, &desc);
    if (ret < 0) {
        fprintf(stderr, "failed to get device descriptor");
        return false;
    }
    return desc.idVendor == vid && desc.idProduct == pid;
}

/**
 * 输出usb设备信息
 *
 * @param dev:usb设备
 * @param handle:该变量是libusb库中的设备句柄,用于操作被封装的USB设备
 */
void print_device(libusb_device *dev, libusb_device_handle *handle)
{
    struct libusb_device_descriptor desc;
    unsigned char string[256];
    const char *speed;
    int ret;
    uint8_t i;

    // 获取usb设备的速度
    switch (libusb_get_device_speed(dev)) {
        case LIBUSB_SPEED_LOW: speed = "1.5M"; break;
        case LIBUSB_SPEED_FULL: speed = "12M"; break;
        case LIBUSB_SPEED_HIGH: speed = "480M"; break;
        case LIBUSB_SPEED_SUPER: speed = "5G"; break;
        case LIBUSB_SPEED_SUPER_PLUS: speed = "10G"; break;
        default: speed = "Unknown";
    }

    // 获取usb设备描述符
    ret = libusb_get_device_descriptor(dev, &desc);
    if (ret < 0) {
        fprintf(stderr, "failed to get device descriptor");
        return;
    }

    if (!handle)
        libusb_open(dev, &handle);

    // 输出usb总线编号、设备地址、生产厂商ID、产品ID、速度信息
    printf("Dev (bus %u, device %u): %04X - %04X speed: %s\n",
           libusb_get_bus_number(dev), libusb_get_device_address(dev),
           desc.idVendor, desc.idProduct, speed);

    if (handle){
        // 输出生产厂商信息
        if (desc.iManufacturer) {
            // 获取string描述符
            ret = libusb_get_string_descriptor_ascii(handle, desc.iManufacturer, string, sizeof(string));
            if (ret > 0)
                printf("  Manufacturer:              %s\n", (char *)string);
        }

        // 输出产品信息
        if (desc.iProduct) {
            // 获取string描述符
            ret = libusb_get_string_descriptor_ascii(handle, desc.iProduct, string, sizeof(string));
            if (ret > 0)
                printf("  Product:                   %s\n", (char *)string);
        }

        // 输出序列号信息
        if (desc.iSerialNumber && verbose) {
            ret = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, string, sizeof(string));
            if (ret > 0)
                printf("  Serial Number:             %s\n", (char *)string);
        }
    }

    if (verbose) {
        // 遍历配置描述符
        for (i = 0; i < desc.bNumConfigurations; i++) {
            struct libusb_config_descriptor *config;

            // 获取配置描述符
            ret = libusb_get_config_descriptor(dev, i, &config);
            if (LIBUSB_SUCCESS != ret) {
                printf("  Couldn't retrieve descriptors\n");
                continue;
            }

            // 输出配置描述符信息
            print_configuration(config);

            // 释放通过libusb_get_config_descriptor函数获取到的配置描述符
            libusb_free_config_descriptor(config);
        }

        // USB2.0以上协议  USB 2.0 的版本号是 0x0200
        if (handle && desc.bcdUSB >= 0x0201)
            print_bos(handle);
    }

    if(handle){
        libusb_close(handle);
    }
}

/**
 * 用于测试指定名称的usb设备,设备存在返回0,设备不存在返回1
 * @param device_name:usb设备名称(必须指定)
 */
int test_wrapped_device(const char *device_name)
{
    libusb_device_handle *handle;
    int r, fd;

    // 打开设备文件
    fd = open(device_name, O_RDWR);
    if (fd < 0) {
        printf("Error could not open %s: %s\n", device_name, strerror(errno));
        return 1;
    }

    // 使用libusb提供的函数对设备进行封装、第三个参数是用于保存设备句柄的指针handle
    r = libusb_wrap_sys_device(NULL, fd, &handle);
    if (r) {
        printf("Error wrapping device: %s: %s\n", device_name, libusb_strerror(r));
        close(fd);
        return 1;
    }

    // 输出设备信息
    print_device(libusb_get_device(handle), handle);
    close(fd);
    return 0;
}

/**
 * 信号处理函数
 * @param signum:信号值
 */
static void sig_handler(int signum)
{
    switch (signum) {
        case SIGTERM:
            rcv_exit = 1;
            break;
        case SIGINT:
            rcv_exit = 1;
            break;
        case SIGUSR1:
            break;
    }
}

static void usage(char *program)
{
    printf("%s - test usb data transfers to/from usb device\n",program);
    printf("Usage:\n");
    printf("  %s [options]\n", program);
    printf("options are:\n");
    printf("Common:\n");
    printf("  --help (or -h)\n");
    printf("  -v vendor_id\n");
    printf("  -p product_id\n");
    printf("  -d usb device name\n");
    printf("  -o usb gadget device name\n");
}


/**
 * 向模拟的usb触摸屏设备发送数据,每次发送5字节报告
 * [0]的D0就是左键,D1就是右键,D2就是中键
 * [1]为X轴低字节
 * [2]为X轴高字节
 * [3]为Y轴低字节
 * [4]为Y轴高字节

 * @param fd:usb gadget device的文件描述符,文件所指向的hid设备的报告描述符需要按照发送的数据格式配置
 * @param touch_screen_report_t: 报告内容
 *
 * 写入成功返回0,否则返回-1
 */
static int send_usb_packet(int fd, struct touch_screen_report_t *report)
{
    char data[5] = {0,0,0,0,0};
    data[0] = report->buttons;
    data[1] = report->x & 0xFF;
    data[2] = (report->x >> 8) & 0xFF;
    data[3] = report->y & 0xFF;
    data[4] = (report->y >> 8) & 0xFF;

//    printf("send data:\n");
//    for(int i=0;i<5;i++){
//       printf("0x%x ",data[i]);
//    }
//    printf("\n");

    // 写入失败
    if (write(fd, data, 5) != 5) {
        printf("Error write usb gadget device\n");
        return -1;
    }

    /* 伪造双击效果,鼠标点击间隔小于系统设置的鼠标双击的间隔时间 */
//    data[0] = 0;
//    if (write(fd, data, 5) != 5) {
//        printf("Error write usb gadget device\n");
//        return -1;
//    }

    return 0;
}

/**
 * 解析读取到的usb数据包,获取按键状态、x、y坐标,并转换为模拟的usb触摸屏的数据格式;
 * @param hid:hid报告描述符信息,用于解析读取到的usb数据包
 * @param data:读取的usb数据包
 * @param report:写入解析后的按键状态、x、y坐标
 *
 * 解析成功返回0,否则返回-1
 */
static int parse_usb_package(const struct hid_descriptor_t *hid,const char *data, struct touch_screen_report_t *report)
{
    if(!hid){
        printf("Error parse usb package, invalid hid descriptor.\n");
        return -1;
    }

    // logical_minimum-logical_maximum 映射到 0~0x7fff
    double x_scale =  1.0 * (gadget_x_logical_maximum-0) / (hid->x_logical_maximum - hid->x_logical_minimum);
    double y_scale =  1.0 * (gadget_y_logical_maximum-0) / (hid->y_logical_maximum - hid->y_logical_minimum);
    report->buttons = (data[hid->btn_offset/8] &  (1<< hid->btn_offset%8)) >> (hid->btn_offset%8);
    report->x = (data[hid->index_x_low] | data[hid->index_x_high] << 8) * x_scale ;
    report->y = (data[hid->index_y_low] | data[hid->index_y_high] << 8) * y_scale;
    return 0;
}

/**
 * 读取usb数据包
 * @param vendor_id:生产厂商ID
 * @param product_id:产品ID
 * @param gadget_device_name:模拟的usb触摸屏设备
 */
static int interrupt_data_rw(uint16_t vendor_id, uint16_t product_id,const char * gadget_device_name)
{
    int kernelDriverDetached = 0;
    unsigned char data[64]={0};
    int length = 0;
    int r,j,fd_gadget = 0,fd_event = 0;
    libusb_device_handle *handle;
    struct touch_screen_report_t report;
    const struct hid_descriptor_t *hid = NULL;

    // 查找匹配的hid报告描述符信息
    for(int i=0; i< sizeof(hids) / sizeof(hids[0]); i++){
        if((hids[i].vid == vendor_id) && (hids[i].pid == product_id)){
            hid = &hids[i];
        }
    }

    if(!hid){
        printf("Error please config hid descriptor info for usb device vid:0x%x pid:0x%x \n", vendor_id, product_id);
        return -1;;
    }

    // 打开指定Vendor ID和Product ID的USB设备,并返回设备句柄
    handle = libusb_open_device_with_vid_pid(NULL, vendor_id, product_id);
    if (handle == NULL) {
        printf("libusb_open() failed\n");
        return -1;;
    }

    /* 驱动必须解绑定,否则数据由驱动程序处理 */
    if(libusb_kernel_driver_active(handle, hid->interface_number)){
        printf("Kernel Driver Active\n");
        r = libusb_detach_kernel_driver(handle, hid->interface_number);
        if (r == 0) {
            printf("Detach Kernel Driver\n");
            kernelDriverDetached = 1;
        }else{
            fprintf(stderr, "Error detaching kernel driver.\n");
            return -1;;
        }
    }

    /* 指定当前接口 */
    r = libusb_claim_interface(handle, hid->interface_number);
    if (r != 0){
        fprintf(stderr, "Error claiming interface.\n");
        goto exit;
    }

    // 打开模拟的usb触摸屏设备
    if(gadget_device_name){
        // 打开设备文件
        fd_gadget = open(gadget_device_name, O_RDWR, 0666);
        if (fd_gadget < 0) {
            printf("Error could not open usb gadget device %s: %s\n", gadget_device_name, strerror(errno));
            return 1;
        }
    }

    // 创建一个类似于触摸屏的虚拟设备
    fd_event = create_touch_screen();

    // 退出标志位
    while(!rcv_exit){
        memset(data, 0, sizeof(data));

        /* 中断方式读取断点数据,指定端点地址、报告长度,超时时间设置为0无线等待,直至有值 */
        r = libusb_interrupt_transfer(handle, hid->endpoint_address, data, hid->report_length, &length, 0);
        if ((r < 0) || (length == 0)){
            printf("bulk recive error,r:%s length:%d\n", libusb_strerror(r),length);
            usleep(500000);  // 单位微秒
        }else{
            printf("receive data:\n");
            for(j=0; j<length; j++) {
                printf("0x%x ",data[j]);
            }
            printf("\n");

            // 解析usb device数据包,获取按键按下状态、以及x、y坐标
            r = parse_usb_package(hid,data,&report);

            // 向模拟的usb触摸屏设备发送数据
            if(fd_gadget > 0 && r == 0){
               send_usb_packet(fd_gadget,&report);
            }

            // 向触摸屏输入设备发送数据
            if(fd_event > 0 && r == 0){
               touch_screen_pressed(fd_event, report.x, report.y, report.buttons);
            }
        }
    }

    // 关闭usb gadget设备
    if(fd_gadget > 0){
        close(fd_gadget);
    }

    // 关闭触摸屏输入设备
    if(fd_event > 0){
        close(fd_event);
    }

    /* 释放指定的接口 */
    r = libusb_release_interface(handle, hid->interface_number);
    if (0 != r){
        fprintf(stderr, "Error releasing interface.\n");
    }

    exit:
        if(kernelDriverDetached){
            //恢复驱动绑定,否则鼠标不可用
            libusb_attach_kernel_driver(handle, hid->interface_number);
        }

        libusb_close(handle);

    return r;
}

int main(int argc, char *argv[])
{
    char *program = argv[0];
    int option;
    const char *usb_device_name = NULL;  // usb设备名称 /dev/bus/usb/001/005
    const char *gadget_device_name = NULL;  // 模拟usb设备
    libusb_device **devs;
    ssize_t cnt;
    int r, i,fd = 0;
    uint16_t vid=0, pid=0;
    libusb_device_handle *handle = NULL;

    static const struct option options[] = {
        { "vendid", required_argument, NULL, 'v' },
        { "productid", required_argument, NULL, 'p' },
        { "gadgetdevicename", required_argument, NULL, 'o' },
        { "help",  no_argument, NULL, 'h' },
    };

    /* Parse command line options, if any */
    while ((option = getopt_long_only(argc, argv,"hv:p:d:o:",options, NULL))){
        if (option == -1)
            break;
        switch (option) {
        case 'v':
             vid = strtoul(optarg, NULL, 0);
             break;
        case 'p':
            pid = strtoul(optarg, NULL, 0);
            break;
        case 'o':
            gadget_device_name = optarg;
            break;
        case 'h':
            usage(program);
            exit(EXIT_SUCCESS);
            break;
        default:
            printf("ERROR: Invalid command line option\n");
            usage(program);
            exit(EXIT_FAILURE);
        }
    }

    printf("vid:0x%x pid:0x%x gadgetdevicename:%s\n",vid,pid,gadget_device_name);

    // 测试模拟的usb触摸屏设备
    if(gadget_device_name){
        // 打开设备文件
        fd = open(gadget_device_name, O_RDWR, 0666);
        if (fd < 0) {
            printf("Error could not open usb gadget device %s: %s\n", gadget_device_name, strerror(errno));
            return 1;
        }
         close(fd);
    }

    // 初始化libusb库
    r = libusb_init(NULL);
    if (r < 0)
        return r;


	// 获取usb设备列表,保存到devs
	cnt = libusb_get_device_list(NULL, &devs);
	if (cnt < 0) {
		libusb_exit(NULL);
		return 1;
	}

	// 遍历每一个usb设备,如果和指定的vid,pid匹配,输出usb设备信息
	for (i = 0; devs[i]; i++){
        if(device_match_vid_pid(devs[i], vid, pid)){
            print_device(devs[i], handle);
            break;
        }
    }

	// 释放由libusb_get_device_list函数分配的usb设备列表资源
	libusb_free_device_list(devs, 1);

    // 将程序中的SIGINT信号(通常由用户按下 Ctrl+C 产生)交给名为sig_handler的函数处理
    signal(SIGINT, sig_handler);
    // 将程序中的SIGTERM信号(通常由操作系统或其他进程发送给目标进程来请求其正常终止)交给名为sig_handler的函数处理
    signal(SIGTERM, sig_handler);

    // read usb data
    interrupt_data_rw(vid,pid, gadget_device_name);

    libusb_exit(NULL);
    return r;
}
3.1.2 配置解析

该应用程序目前仅支持三款USB触摸屏,其中有我们实验中一直使用的USB触摸屏(PID=e5e3,VID=1a86),这里我将USB触摸屏的HID报告描述符的描述信息保存到了hids数组中;由于我仅仅关注HID报告描述符中第一个触摸点的状态以及坐标等信息,因此上面记录了这些信息在介绍的USB数据包中的索引。

/* 存放不同usb触摸屏hid报告描述符解析后的信息 */
static const struct hid_descriptor_t hids[] = {
    [0] = { 0x1a86, 0xe5e3, 0x82, 52, 8, 3, 4, 5, 6, 0, 0x1000, 0, 0x1000, 0 },
    [1] = { 0x222a, 0x0001, 0x81, 52, 14, 2, 3, 4, 5, 0, 0x0780, 0, 0x0438, 0 },
    [2] = { 0x03eb, 0x8a6e, 0x83, 59, 8, 2, 3, 6, 7, 0, 0x0fff, 0, 0x0fff,1 },
};

其中:

(1) 0x1a86USB设备的idVendor

(2) 0xe5e3USB设备的idProduct

(3) 0x82USB通信的端点地址;

获取USB通信端点地址有两种方法:

方法一:可以通过cat /sys/bus/usb/devices/1-1.2/1-1.2:1.0/查看;

root@SOM-RK3399v2:/# cat /sys/bus/usb/devices/1-1.2/
1-1.2:1.0/           bDeviceProtocol      bNumInterfaces       descriptors          driver/              manufacturer         ep_00/               idProduct            busnum               devpath              idVendor             ......         
root@SOM-RK3399v2:/etc/profile.d# cat /sys/bus/usb/devices/1-1.2/1-1.2\:1.0/  
0003:1A86:E5E3.0007/  bInterfaceNumber      bInterfaceSubClass    driver/               modalias             
authorized            bInterfaceClass       bInterfaceProtocol    bNumEndpoints         ep_82/    
......

这里1-1.2表示的使用的USB触摸屏设备(开发板插入USB设备时,dmesg可以看到1-1.2),该目录下文件:

  • 1-1.2:1.0目录下为USB接口信息,最后一个数字0就是当前的接口编号(bInterfaceNumber);
  • ep_00表示枚举设备时使用的通信端点地址;
  • descriptors存放的USB设备描述符。

如果USB设备下有多个USB接口,则需要挨个查看确定为触摸功能的那个接口;那如何确认哪个接口是触摸功能呢?可以通过查看接口的HID报告描述符信息来确定;

root@SOM-RK3399v2:/# hexdump /sys/bus/usb/devices/1-1.2/1-1.2:1.0/0003:1A86:E5E3.0007/report_descriptor
0000000 0d05 0409 01a1 0185 2209 02a1 4209 0015
0000010 0125 0175 0195 0281 0795 0181 0875 5109
0000020 0195 0281 0105 0026 7510 5510 650e 0911
0000030 3530 4600 0879 0281 0026 4610 054c 3109
0000040 0281 0d05 4809 0281 09c0 a122 0902 1542
0000050 2500 7501 9501 8101 9502 8107 7501 0908
0000060 9551 8101 0502 2601 1000 1075 0e55 1165
0000070 3009 0035 7946 8108 2602 1000 4c46 0905
0000080 8131 0502 090d 8148 c002 2209 02a1 4209
0000090 0015 0125 0175 0195 0281 0795 0181 0875
....

通过分析HID报告描述符,来判断该描述符是不是描述了触摸功能;通过cat /sys/kernel/debug/hid/0003\:1A86\:E5E3.0007/rdesc命令可以获取到解析后的HID报告描述符,具体可以参考https://www.cnblogs.com/zyly/p/17747761.html#_label4_1

0x05, 0x0d,    // USAGE_PAGE (Digitizer)
0x09, 0x04,    // USAGE (Touch Screen)
0xa1, 0x01,    // COLLECTION (Application)  

如果该接口是USB触摸屏功能,那么找到USB接口目录下的ep_xx目录,那么xx就是端口号,比如这里就是82,16进制。

root@SOM-RK3399v2:/# ls /sys/bus/usb/devices/1-1.2/1-1.2:1.0/ | grep "ep"
ep_82

方法二:usb_test应用程序启动时会输出接口所使用的的bEndpointAddress

需要注意的是如果USB通信端点地址错误,可能出现如下错误:

bulk recive error,r:Input/Output Error length:0

(4) 52:USB触摸屏发送的数据包的长度;通过usbmon抓取到的数据包中有USB数据包的长度;

ffffffc0c88dab00 1925775309 C Ii:1:004:2 0:1 52 = 01010075 09080930 00000000 00000000 00000000 00000000 00000000 00000000
                               |__ 输入       |_______ 实际读取的长度 

需要注意的是如果接收缓存区大小设置的小于实际读取的数据包的长度,可能出现如下错误:

bulk recive error,r:Overflow length:0

(5) 8:按键状态在接收到的USB数据包中的索引为1的字节的位[0];

(6) 3:x坐标低8位在接收到的USB数据包中的索引为3;

(7) 4:x坐标高8位在接收到的USB数据包中的索引为4;

(8) 5:y坐标低8位在接收到的USB数据包中的索引为5;

(9) 6:y坐标高8位在接收到的USB数据包中的索引为6;

(10) 0:x坐标逻辑最小值,即接收到的USB数据包中x坐标的最小值;

(11) 0x1000:x逻辑最大值,即接收到的USB数据包中x坐标的最大值;

(12) 0:y坐标逻辑最小值,即接收到的USB数据包中y坐标的最小值;

(13) 0x1000:y逻辑最大值,即接收到的USB数据包中y坐标的最大值;

(14) 0:当前选中的接口编号,对于有些USB设备,可能存在多个接口,该值设置为所使用接口的bInterfaceNumber的值;

如果需要支持其他的USB触摸屏,需要自行获取自己使用的USB触摸屏的HID报告描述符,并按照上面结构体填入相应信息。

编译应用程序:

root@SOM-RK3399v2:/opt/libusb# gcc  -o usb_test usb_test.c  -I/usr/include/libusb-1.0   -lusb-1.0
root@SOM-RK3399v2:/opt/libusb# ls -l usb_test
-rwxr-xr-x 1 root root 25192 Oct 15 10:46 usb_test

3.2 测试

亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。

日期姓名金额
2023-09-06*源19
2023-09-11*朝科88
2023-09-21*号5
2023-09-16*真60
2023-10-26*通9.9
2023-11-04*慎0.66
2023-11-24*恩0.01
2023-12-30I*B1
2024-01-28*兴20
2024-02-01QYing20
2024-02-11*督6
2024-02-18一*x1
2024-02-20c*l18.88
2024-01-01*I5
2024-04-08*程150
2024-04-18*超20
2024-04-26.*V30
2024-05-08D*W5
2024-05-29*辉20
2024-05-30*雄10
2024-06-08*:10
2024-06-23小狮子666
2024-06-28*s6.66
2024-06-29*炼1
2024-06-30*!1
2024-07-08*方20
2024-07-18A*16.66
2024-07-31*北12
2024-08-13*基1
2024-08-23n*s2
2024-09-02*源50
2024-09-04*J2
2024-09-06*强8.8
2024-09-09*波1
2024-09-10*口1
2024-09-10*波1
2024-09-12*波10
2024-09-18*明1.68
2024-09-26B*h10
2024-09-3010
2024-10-02M*i1
2024-10-14*朋10
2024-10-22*海10
2024-10-23*南10
2024-10-26*节6.66
2024-10-27*o5
2024-10-28W*F6.66
2024-10-29R*n6.66
2024-11-02*球6
2024-11-021*鑫6.66
2024-11-25*沙5
2024-11-29C*n2.88
posted @   大奥特曼打小怪兽  阅读(1417)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
如果有任何技术小问题,欢迎大家交流沟通,共同进步

公告 & 打赏

>>

欢迎打赏支持我 ^_^

最新公告

程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)。

了解更多

点击右上角即可分享
微信分享提示