美文网首页程序员
蓝牙HID无线触摸屏

蓝牙HID无线触摸屏

作者: 施瓦辛格777 | 来源:发表于2018-07-23 16:44 被阅读0次

    写在前面

    主机:Android 5.0+ 内核3.4
    从机:SensorTile
    先上一个效果图


    000405qi5vew2cf2abcjko.gif

    原理解析

    HID事件到Android屏幕上经历了如下过程:

    HID => linux kernel input子系统 => Android input子系统
    

    HID是标准的输入协议,对于不同的操作系统而言,也有自己的input子系统。

    Android层要求

    以Android为例,如果要想让Android系统认为一个输入设备是一个触摸屏,需要该内核设备上报:

    // 对于单点触控而言
    ABS_X ABS_Y 和 BTN_TOUCH
    // 对于多点触控而言
    ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y 绝对轴 以及 BTN_TOUCH
    

    且需要一个配置文件放到/system/usr/idc/中,如/system/usr/idc/HID.idc,内容如下指定其为触摸屏设备类型:

    touch.deviceType = touchScreen
    

    注:标明输入设备为触摸屏还有一个方法输入设备报告存在 INPUT_PROP_DIRECT 输入属性,但是对于蓝牙HID而言,在Android中的实现是基于uhid实现的,要这样做比较难,所以一般会使用放配置文件的方式。输入设备上报的键值和输入属性可以使用getevent -i查看。
    理论依据来自:https://source.android.com/devices/input/touch-devices

    内核层要求

    Android层要求中提到的ABS_X ABS_Y 和 BTN_TOUCH等等都是需要内核去上报的键值,内核中就需要要求HID设备按照一定要求上传HID键值,因为内核键值是由HID键值转换过来的。

    // drivers/hid/hid-input.c
        case HID_UP_DIGITIZER:
            switch (usage->hid & 0xff) {
            case 0x00: /* Undefined */
                goto ignore;
    
            case HID_DG_TIPPRESSURE: /* TipPressure */
                if (!test_bit(BTN_TOUCH, input->keybit)) {
                    device->quirks |= HID_QUIRK_NOTOUCH;
                    set_bit(EV_KEY, input->evbit);
                    set_bit(BTN_TOUCH, input->keybit);
                }
                map_abs_clear(ABS_PRESSURE);
                break;
    
            case HID_DG_INRANGE: /* InRange */
                switch (field->physical & 0xff) {
                case 0x21: map_key(BTN_TOOL_MOUSE); break;
                case 0x22: map_key(BTN_TOOL_FINGER); break;
                default: map_key(BTN_TOOL_PEN); break;
                }
                break;
    
            case HID_DG_INVERT: /* Invert */
                map_key_clear(BTN_TOOL_RUBBER);
                break;
    
            case HID_DG_TOUCH: /* Touch */
            case HID_DG_TIPSWITCH: /* TipSwitch */
            case HID_DG_TIPSWITCH2: /* TipSwitch2 */
                device->quirks &= ~HID_QUIRK_NOTOUCH;
                map_key_clear(BTN_TOUCH);
                break;
    
            case HID_DG_BARRELSWITCH: /* BarrelSwitch */
                map_key_clear(BTN_STYLUS);
                break;
    
            case HID_DG_TABLETPICK: /* TabletPick */
                map_key_clear(BTN_STYLUS2);
                break;
    
            case HID_DG_CONTACTID: /* ContactID */
                device->quirks |= HID_QUIRK_MULTITOUCH;
                goto unknown;
    
            default:  goto unknown;
            }
    

    由此可以看到HID描述符中需要按照以下要求:

    1. Usage PageDIGITIZER,对应内核代码中的HID_UP_DIGITIZER
    2. 需要包含HID_DG_TOUCH、HID_DG_TIPSWITCH、HID_DG_TIPSWITCH2三者其中之一,才能上报BTN_TOUCH
    3. 如果包含了HID_DG_INRANGE则会上传BTN_TOOL_PEN有可能会导致画蛇添足;(我在实际测试中确实遇到这个问题,一个上报BTN_TOOL_PEN事件,而不上报BTN_TOUCH导致无法实现真正的点击事件);
      根据这些分析,将微软的Sample Report Descriptor for a Touch Digitizer Device (Windows 7)提到的HID描述符简单的修整,得到了想要的的描述符:
            0x05, 0x0d,                         // USAGE_PAGE (Digitizers)
                0x09, 0x04,                         // USAGE (Touch Screen)
                0xa1, 0x01,                         // COLLECTION (Application)
                0x85, 0x01,                         //   REPORT_ID (Touch)
                0x09, 0x20,                         //   USAGE (Stylus)
                0xa1, 0x00,                         //   COLLECTION (Physical)
                0x09, 0x42,                         //     USAGE (Tip Switch)
                0x15, 0x00,                         //     LOGICAL_MINIMUM (0)
                0x25, 0x01,                         //     LOGICAL_MAXIMUM (1)
                0x75, 0x01,                         //     REPORT_SIZE (1)
                0x95, 0x01,                         //     REPORT_COUNT (1)
                0x81, 0x02,                         //     INPUT (Data,Var,Abs)
                0x95, 0x03,                         //     REPORT_COUNT (3)
                0x81, 0x03,                         //     INPUT (Cnst,Ary,Abs)
    //          0x09, 0x32,                         //     USAGE (In Range)
    //          0x09, 0x47,                         //     USAGE (Confidence)
    //          0x95, 0x02,                         //     REPORT_COUNT (1)
    //          0x81, 0x02,                         //     INPUT (Data,Var,Abs)
    //          0x95, 0x0a,                         //     REPORT_COUNT (10)
    //          0x81, 0x03,                         //     INPUT (Cnst,Ary,Abs)
                0x05, 0x01,                         //     USAGE_PAGE (Generic Desktop)
                0x26, 0xff, 0x7f,                   //     LOGICAL_MAXIMUM (32767)
                0x75, 0x10,                         //     REPORT_SIZE (16)
                0x95, 0x01,                         //     REPORT_COUNT (1)
                0xa4,                               //     PUSH
                0x55, 0x0d,                         //     UNIT_EXPONENT (-3)
                0x65, 0x00,                         //     UNIT (None)
                0x09, 0x30,                         //     USAGE (X)
                0x35, 0x00,                         //     PHYSICAL_MINIMUM (0)
                0x46, 0x00, 0x00,                   //     PHYSICAL_MAXIMUM (0)
                0x81, 0x02,                         //     INPUT (Data,Var,Abs)
                0x09, 0x31,                         //     USAGE (Y)
                0x46, 0x00, 0x00,                   //     PHYSICAL_MAXIMUM (0)
                0x81, 0x02,                         //     INPUT (Data,Var,Abs)
                0xb4,                               //     POP
    //          0x05, 0x0d,                         //     USAGE PAGE (Digitizers)
    //          0x09, 0x48,                         //     USAGE (Width)
    //          0x09, 0x49,                         //     USAGE (Height)
    //          0x95, 0x02,                         //     REPORT_COUNT (2)
    //          0x81, 0x02,                         //     INPUT (Data,Var,Abs)
    //          0x95, 0x01,                         //     REPORT_COUNT (1)
    //          0x81, 0x03,                         //     INPUT (Cnst,Ary,Abs)
                0xc0,                               //   END_COLLECTION
                0xc0,                               // END_COLLECTION
    

    使用uhid来测试该描述符,测试代码,确实可以实现触摸屏的效果了。
    注:对于多点触摸设备,那么描述符中需要包含HID_DG_CONTACTID即可,这里先不展开分析。

    蓝牙HID从机

    这里使用的是ST的SensorTile,根据其提供的HID的Demo,修改对应的成对应的描述符,并在while循环里实现一个从左上角滑动到右下角的模拟操作,这样就可以实现通过蓝牙HID触摸屏了。
    具体的改动见:https://gitee.com/kangear/STM32CubeExpansion_BLE1_V2.8.0/commit/04e5e0af893c1099d1ce459307223735d3658e6d

    展望

    目前实现了单点触摸屏,稍候会另起一个文章,讲述如何实现多点触摸屏,到那时才会是真正的蓝牙HID触摸屏。能实现之后,可以基于这个实现物理外挂,把手游的一些控制转换成体感游戏等等。

    <完>

    相关文章

      网友评论

        本文标题:蓝牙HID无线触摸屏

        本文链接:https://www.haomeiwen.com/subject/wdhcmftx.html