----------------------------------------------------------------------------------------------------------------------------
开发板 :SOM-RK3399
核心板+定制底板eMMC
:16GB
LPDDR3
:4GB
显示屏 :15.6
英寸HDMI
接口显示屏u-boot
:2017.09
linux
:4.19
----------------------------------------------------------------------------------------------------------------------------
注意:本节介绍的内容基于《Rockchip RK3399 - linux-headers
制作》中移植的运行环境:内核版本4.19.193
以及debian 11
根文件系统。
在《Rockchip RK3399 - linux
通过usbmon
抓取usb
数据包》中我们分析了我们所使用的USB
触摸屏的HID
报告描述符,并通过usbmon
工具进行USB
触摸屏数据包的抓取,最终结合HID
报告描述符分析抓取到的数据包中数据的含义。
在《Rockchip RK3399 - linux-headers
制作》中我们介绍了在SOM-RK3399
核心板上,通过USB3.0 Type-C PHY1
接口将开发板模拟成USB
设备。
这一节我们将通过libusb
来实现USB
触摸屏数据包的捕获,并将捕获的数据通过模拟的USB
触摸屏发送到PC
。
一、模拟USB
设备回顾
我们首先回顾一下模拟USB
设备是如何实现的,我们编写了一个hid_keyboard_mouse.sh
脚本,由于USB3.0 Type-C PHY1
接口被配置为了从设备,因此可以通过USB3.0 Type-C PHY1
接口将开发板模拟成USB
设备(同时模拟鼠标、键盘、触摸屏功能)。
1.1 hid_keyboard_mouse.sh
在/etc/profile.d
下新建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 #触摸屏
-
- #配置hid描述符
- 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
-
- echo 1 > functions/hid.1/subclass
- echo 2 > functions/hid.1/protocol
- echo 4 > functions/hid.1/report_length
- 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
-
- #配置hid描述符
- 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/2.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
我们将这个脚本配置为开机自动执行,重新开机,开机会自动执行/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 应用程序
在/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坐标逻辑最大值 */
- };
-
- /* 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 },
- [1] = { 0x222a, 0x0001, 0x81, 52, 14, 2, 3, 4, 5, 0, 0x0780, 0, 0x0438 },
- };
-
- /**
- * 上报事件
- * @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]);
- }
-
- /**
- * 输出usb设备信息
- * @param dev:usb设备
- * @param handle:该变量是libusb库中的设备句柄,用于操作被封装的 USB 设备
- * @param vendor_id:生产厂商ID
- * @param product_id:产品ID
- */
- static void print_device(libusb_device *dev, libusb_device_handle *handle, uint16_t vid, uint16_t pid)
- {
- 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);
-
- // 生产厂商ID、产品ID和指定的匹配
- if((desc.idVendor == vid) && (desc.idProduct == pid)){
- // 输出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设备名称
- * @param vendor_id:生产厂商ID
- * @param product_id:产品ID
- */
- static int test_wrapped_device(const char *device_name, uint16_t vendor_id, uint16_t product_id)
- {
- 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, vendor_id, product_id);
- 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, 0)){
- printf("Kernel Driver Active\n");
- r = libusb_detach_kernel_driver(handle, 0);
- 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, 0);
- 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, 0);
- if (0 != r){
- fprintf(stderr, "Error releasing interface.\n");
- }
-
- exit:
- if(kernelDriverDetached){
- //恢复驱动绑定,否则鼠标不可用
- libusb_attach_kernel_driver(handle, 0);
- }
-
- 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' },
- { "usbdevicename", required_argument, NULL, 'd' },
- { "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 'd':
- usb_device_name = optarg;
- 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 usbdevicename:%s gadgetdevicename:%s\n",vid,pid,usb_device_name,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;
-
- if (usb_device_name) {
- printf("test_wrapped_device\n");
- r = test_wrapped_device(usb_device_name,vid,pid);
- } else {
- // 获取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++)
- print_device(devs[i], handle, vid, pid);
-
- // 释放由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;
- }
该应用程序目前仅支持两款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 },
- [1] = { 0x222a, 0x0001, 0x81, 52, 14, 2, 3, 4, 5, 0, 0x0780, 0, 0x0438 },
- };
其中:
(1) 0x1a86
:USB
设备的idVendor
;
(2) 0xe5e3
:USB
设备的idProduct
;
(3) 0x82
:USB
通信的端点地址;
获取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
接口信息;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
报告描述符,来判断该描述符描述的是不是用于描述触摸屏;
- 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坐标的最大值;
如果需要支持其他的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 测试
3.2.1 查看USB
数据包
首先需要将USB
触摸屏(PID=e5e3
,VID=1a86
)连接到开发板,输入如下命令:
- root@SOM-RK3399v2:/opt/libusb# ./usb_test -v 0x1a86 -p 0xe5e3
- vid:0x1a86 pid:0xe5e3 usbdevicename:(null) gadgetdevicename:(null)
- Dev (bus 1, device 4): 1A86 - E5E3 speed: 12M
- Manufacturer: wch.cn
- Product: USB2IIC_CTP_CONTROL
- Configuration:
- wTotalLength: 34
- bNumInterfaces: 1
- bConfigurationValue: 1
- iConfiguration: 0
- bmAttributes: 80h
- MaxPower: 32
- Interface: // USB触摸屏设备接口可能有多个,如果有多个,只关注bInterfaceSubClass=0/1、bInterfaceProtocol=0/2、bInterfaceClass=3的接口
- bInterfaceNumber: 0
- bAlternateSetting: 0
- bNumEndpoints: 1
- bInterfaceClass: 3 // hid
- bInterfaceSubClass: 0 // 子类为0
- bInterfaceProtocol: 0 // 协议为0
- iInterface: 0
- Endpoint:
- bEndpointAddress: 82h // 端点地址
- bmAttributes: 03h
- wMaxPacketSize: 64
- bInterval: 1
- bRefresh: 0
- bSynchAddress: 0
- Kernel Driver Active
- Detach Kernel Driver
其中:
- v:后面是
USB
设备的idVendor
; - p:后面是
USB
设备的idProduct
;
此时如果单个手指点击触摸屏,将会输出接收到的数据包:
- receive data:
- 0x1 0x1 0x0 0x99 0x5 0xcc 0x6 0x30 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x1 0x1 0x1
- receive data:
- 0x1 0x1 0x0 0x99 0x5 0xcc 0x6 0x30 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x1 0x1 0x1
- receive data:
- 0x1 0x1 0x0 0x99 0x5 0xcc 0x6 0x30 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x1 0x1 0x1
- receive data:
- 0x1 0x1 0x0 0x99 0x5 0xcc 0x6 0x30 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x1 0x1 0x1
可以看到每次接收到的数据包含52个字节,在上一篇博客中我们已经说过USB
触摸屏(PID=e5e3
,VID=1a86
)ID
为1的报告实际读取的数据长度为52
个字节:
字节索引 | 描述 |
---|---|
字节0 | 报告ID(1) |
字节1~8 | 描述第一个触摸点 |
字节9~16 | 描述第二个触摸点 |
字节17~24 | 描述第三个触摸点 |
字节25~32 | 描述第四个触摸点 |
字节33~40 | 描述第五个触摸点 |
字节41~48 | 描述第六个触摸点 |
字节49~50 | 描述检测和更新触摸点状态的时间间隔 |
字节51 | 描述触摸点数量 |
可以看到最后一个字节表示的是同时触摸点的数量,这里为1,因为我是单指按下的。
我们尝试同时6个手指点击触摸屏:
- receive data:
- 0x1 0x1 0x0 0xf 0xd 0x99 0x0 0x30 0x0 0x1 0x1 0xe1 0x8 0xe6 0x3 0x30 0x0 0x1 0x2 0x85 0x2 0x22 0x3 0x30 0x0 0x1 0x3 0x75 0x5 0x77 0x2 0x30 0x0 0x1 0x4 0xc7 0xb 0xb3 0xd 0x30 0x0 0x1 0x5 0x75 0x6 0x55 0x5 0x30 0x0 0x1 0x1 0x6
- receive data:
- 0x1 0x1 0x0 0x33 0xd 0xd5 0x0 0x30 0x0 0x1 0x1 0x0 0x9 0xee 0x3 0x30 0x0 0x1 0x2 0x8a 0x2 0x4c 0x3 0x30 0x0 0x1 0x3 0x61 0x5 0x22 0x3 0x30 0x0 0x0 0x4 0xc7 0xb 0xb3 0xd 0x30 0x0 0x1 0x5 0x70 0x6 0xee 0x5 0x30 0x0 0x1 0x1 0x6
可以看到最后一个字节为6,需要注意的是我使用的USB
触摸屏最多支持同时有6个触摸点。
如果需要终止应用程序,输入CTRL + C
,同时点击触摸屏即可。
3.2.2 模拟USB
触摸
将USB
触摸屏(PID=e5e3
,VID=1a86
)连接到开发板,同时通过USB OTG
线将USB3.0 Type-C PHY1
接口连接到PC
;
此时PC
识别到一个USB
设备插入,在设备管理器查看该USB
设备信息可以看到VID
、PID
就是我们在hid_keyboard_mouse.sh
中设置的。
输入如下命令:
- root@SOM-RK3399v2:/opt/libusb# ./usb_test -v 0x1a86 -p 0xe5e3 -o /dev/hidg2
- vid:0x1a86 pid:0xe5e3 usbdevicename:(null) gadgetdevicename:/dev/hidg2
- Dev (bus 1, device 4): 1A86 - E5E3 speed: 12M
- Manufacturer: wch.cn
- Product: USB2IIC_CTP_CONTROL
- Configuration:
- wTotalLength: 34
- bNumInterfaces: 1
- bConfigurationValue: 1
- iConfiguration: 0
- bmAttributes: 80h
- MaxPower: 32
- Interface:
- bInterfaceNumber: 0
- bAlternateSetting: 0
- bNumEndpoints: 1
- bInterfaceClass: 3
- bInterfaceSubClass: 0
- bInterfaceProtocol: 0
- iInterface: 0
- Endpoint:
- bEndpointAddress: 82h
- bmAttributes: 03h
- wMaxPacketSize: 64
- bInterval: 1
- bRefresh: 0
- bSynchAddress: 0
- Kernel Driver Active
- Detach Kernel Driver
其中:
- v:后面是
USB
设备的idVendor
; - p:后面是
USB
设备的idProduct
; - o:后面是模拟的
USB
触摸屏设备;
此时我们点击触摸屏,就会实现控制PC
的效果。等价于将USB
触摸屏(PID=e5e3
,VID=1a86
)连接到PC
。
补充:由于我们使用的触摸屏比较小,而PC
的屏幕比较大,因此在触摸屏上移动一小段位置,PC
屏幕上鼠标箭头就会移动很多,这里存在一个缩放比例的问题,因此想选中PC
上某一图标也是比较麻烦的。为此可以考虑将触摸屏的HDMI
接口连接到PC
,这样缩放比例就为1,操作起来就会很方便。
测试发现一个问题,双击经常会失灵。因为应用程序程序读取USB
触摸屏PID=e5e3
,VID=1a86
)的数据包,解析后再通过模拟的USB
触摸屏设备发送到PC
。由于每一个数据包从接收到发送处理的时间间隔较长,在windows
系统中当接收到的两次连续点击的数据包时间间隔超过一定时就被当做点击处理了,目前有三种优化思路:
- 减少应用程序的处理时长,比如屏蔽掉
printf
函数; - 记录上一个触摸点以及发生的时间,与当前触摸点发生时间进行比较,如果小于某个时间间隔,先模拟上一个触摸点的按下和松开(具体实现需要自己调试测试);
windows
系统:控制面板 --> 硬件和声音 --> 设备和打印机 --> 鼠标 --> 双击速度(设置为最慢);
如果需要终止应用程序,输入CTRL + C
,同时点击触摸屏即可。
参考文章
[1] https://github.com/libusb/libusb
[2] Developers will wish to consult the API documentation
[3] Linux
模拟触摸滑动以及按下
[4] 【i.MX6ULL
】驱动开发13——电容触摸驱动实践(下)