zoologist 发表于 2022-4-16 20:13:49

ESP32S2 实现触摸屏

本帖最后由 zoologist 于 2022-4-16 20:16 编辑

参照Teensy的触摸【参考1】,在 ESP32 S2 上实现了触摸屏。最关键的步骤有2个:1.   正确的 HID Descriptor,下面是一个10指触摸的触摸屏幕的描述符static const uint8_t report_descriptor[] = { // 8 TouchData
0x05, 0x0D,
0x09, 0x04,
0xA1, 0x01,
0x09, 0x22,
0xA1, 0x02,
0x09, 0x42,
0x15, 0x00,
0x25, 0x01,
0x75, 0x01,
0x95, 0x01,
0x81, 0x02,
0x09, 0x30,
0x25, 0x7F,
0x75, 0x07,
0x95, 0x01,
0x81, 0x02,
0x09, 0x51,
0x26, 0xFF, 0x00,
0x75, 0x08,
0x95, 0x01,
0x81, 0x02,
0x05, 0x01,
0x09, 0x30,
0x09, 0x31,
0x26, 0xFF, 0x7F,
0x65, 0x00,
0x75, 0x10,
0x95, 0x02,
0x81, 0x02,
0xC0,
0x05, 0x0D,
0x27, 0xFF, 0xFF, 0x00, 0x00,
0x75, 0x10,
0x95, 0x01,
0x09, 0x56,
0x81, 0x02,
0x09, 0x54,
0x25, 0x0A,
0x75, 0x08,
0x95, 0x01,
0x81, 0x02,
0x05, 0x0D,
0x09, 0x55,
0x25, 0x0A,
0x75, 0x08,
0x95, 0x01,
0xB1, 0x02,
0xC0,
};

对应发送的数据结构是:    // touch report
    //0: on/off + pressure
    //1: contact id
    //2: X lsb
    //3: X msb
    //4: Y lsb
    //5: Y msb
    //6: scan time lsb
    //7: scan time msb
//8: contact count
其中 Byte0 Bit0 是按下标志,一直为1,Bit1-7 是按键压力;Byte1是按键编号,从 0-255,可以理解为手指编号,比如:右手食指按下,编号为0;右手中指按下,编号为1;右手抬起后再次按下,会重新分配一个编号。Byte2-3按键的X坐标;Byte4-5按键的Y坐标;Byte6-7是按压发生的事件,是以 100us为单位;Byte8是当前正在发生的按压事件中触摸点的数量。在【参考2】有一个例子:

Table 7 Report Sequence for Two Contacts with Separated Lift(Two-Finger Hybrid)


      Report         1         2         3         4         5         6         7         8         9         10         11   
Contact Count22222211111
Contact 1 Tip Switch111110NRNRNRNRNR
Contact 1 X,YX₁,Y₁X₂,Y₂X₃,Y₃X₄,Y₄X₅,Y₅X₅,Y₅NRNRNRNRNR
Contact 2 Tip Switch11111111110
Contact 2 X,YX₁,Y₁X₂,Y₂X₃,Y₃X₄,Y₄X₅,Y₅X₆,Y₆X₇,Y₇X₈,Y₈X₉,Y₉X₁₀,Y₁₀X₁₀,Y₁₀


图中是2个手指图中是2个手指进行触摸的例子,R1 会分别报告手指1和2移动的信息,同时 Byte8 的 Contract Count 会等于2;R6的时候,因为手指1已经抬起,所以Contract Count会变成1..另外一个重要的,容易被忽视的要求:Get Report 的处理。即使上面的描述符正确报告,然后数据也正常发送到Windows中,你的触摸屏依然无法正常工作,原因就是缺少了对Get Report的处理。更糟糕的是:你无法使用 USBlyzer 这样的工具抓到 Teensy 中的数据。
Teensy 例子中上位机发送 GET_REPORT 收到返回值0x0a如果不在代码中特别处理,对于这个命令会 STALL 关于这个 COMMAND 的含义,目前没搞清楚【参考3】
对于我们来说,只要有一个返回值就能让它工作正常。最终一个可以工作的代码如下:#include "USB.h"
#include "USBHID.h"
USBHID HID;

static const uint8_t report_descriptor[] = { // 8 TouchData
0x05, 0x0D,
0x09, 0x04,
0xA1, 0x01,
0x09, 0x22,
0xA1, 0x02,
0x09, 0x42,
0x15, 0x00,
0x25, 0x01,
0x75, 0x01,
0x95, 0x01,
0x81, 0x02,
0x09, 0x30,
0x25, 0x7F,
0x75, 0x07,
0x95, 0x01,
0x81, 0x02,
0x09, 0x51,
0x26, 0xFF, 0x00,
0x75, 0x08,
0x95, 0x01,
0x81, 0x02,
0x05, 0x01,
0x09, 0x30,
0x09, 0x31,
0x26, 0xFF, 0x7F,
0x65, 0x00,
0x75, 0x10,
0x95, 0x02,
0x81, 0x02,
0xC0,
0x05, 0x0D,
0x27, 0xFF, 0xFF, 0x00, 0x00,
0x75, 0x10,
0x95, 0x01,
0x09, 0x56,
0x81, 0x02,
0x09, 0x54,
0x25, 0x0A,
0x75, 0x08,
0x95, 0x01,
0x81, 0x02,
0x05, 0x0D,
0x09, 0x55,
0x25, 0x0A,
0x75, 0x08,
0x95, 0x01,
0xB1, 0x02,
0xC0,
};

class CustomHIDDevice: public USBHIDDevice {
public:
    CustomHIDDevice(void) {
      static bool initialized = false;
      if (!initialized) {
      initialized = true;
      HID.addDevice(this, sizeof(report_descriptor));
      }
    }
    uint16_t _onGetFeature(uint8_t report_id, uint8_t* buffer, uint16_t len)
      {
      buffer=0x0A;
      return 0x01;
      }
    void begin(void) {
      HID.begin();
    }

    uint16_t _onGetDescriptor(uint8_t* buffer) {
      memcpy(buffer, report_descriptor, sizeof(report_descriptor));
      return sizeof(report_descriptor);
    }

    bool send(uint8_t * value) {
      return HID.SendReport(0, value, 9);
    }
};

CustomHIDDevice Device;

const int buttonPin = 0;
int previousButtonState = HIGH;
uint8_t TouchData;

void setup() {
Serial.begin(115200);
Serial.setDebugOutput(true);
Device.begin();
USB.begin();
}

void loop() {
if (HID.ready()) {
    Serial.println("Finger");
    // touch report
    //0: on/off + pressure
    //1: contact id
    //2: X lsb
    //3: X msb
    //4: Y lsb
    //5: Y msb
    //6: scan time lsb
    //7: scan time msb
    //8: contact count
    for (int i=0;i<200;i+=100) {
    TouchData = 0x81; TouchData = 0x08;
    TouchData = (16000)&0xFF; TouchData = ((16000)>>8)&0xFF;
    TouchData = (4000+i)&0xFF; TouchData = ((4000+i)>>8)&0xFF;
    TouchData = (millis()*10)&0xFF; TouchData = (millis()*10>>8)&0xFF;
    TouchData = 0x01;
    Device.send(TouchData);
    delay(10);
    TouchData = 0x81; TouchData = 0x08;
    TouchData = (16000)&0xFF; TouchData = ((16000)>>8)&0xFF;
    TouchData = (4000+i)&0xFF; TouchData = ((4000+i)>>8)&0xFF;
    TouchData = (millis()*10)&0xFF; TouchData = (millis()*10>>8)&0xFF;
    TouchData = 0x01;
    delay(10);
    }
    //每隔10秒
    delay(5000);
}
}


再复杂一点,做一个画圆的:
#include "USB.h"
#include "USBHID.h"
USBHID HID;

int STARTX=16000;
int STARTY=12000;
int STARTR=2000;
static const uint8_t report_descriptor[] = { // 8 TouchData
0x05, 0x0D,
0x09, 0x04,
0xA1, 0x01,
0x09, 0x22,
0xA1, 0x02,
0x09, 0x42,
0x15, 0x00,
0x25, 0x01,
0x75, 0x01,
0x95, 0x01,
0x81, 0x02,
0x09, 0x30,
0x25, 0x7F,
0x75, 0x07,
0x95, 0x01,
0x81, 0x02,
0x09, 0x51,
0x26, 0xFF, 0x00,
0x75, 0x08,
0x95, 0x01,
0x81, 0x02,
0x05, 0x01,
0x09, 0x30,
0x09, 0x31,
0x26, 0xFF, 0x7F,
0x65, 0x00,
0x75, 0x10,
0x95, 0x02,
0x81, 0x02,
0xC0,
0x05, 0x0D,
0x27, 0xFF, 0xFF, 0x00, 0x00,
0x75, 0x10,
0x95, 0x01,
0x09, 0x56,
0x81, 0x02,
0x09, 0x54,
0x25, 0x0A,
0x75, 0x08,
0x95, 0x01,
0x81, 0x02,
0x05, 0x0D,
0x09, 0x55,
0x25, 0x0A,
0x75, 0x08,
0x95, 0x01,
0xB1, 0x02,
0xC0,
};

class CustomHIDDevice: public USBHIDDevice {
public:
    CustomHIDDevice(void) {
      static bool initialized = false;
      if (!initialized) {
      initialized = true;
      HID.addDevice(this, sizeof(report_descriptor));
      }
    }
    uint16_t _onGetFeature(uint8_t report_id, uint8_t* buffer, uint16_t len)
      {
      buffer=0x0a;
      return 1;
      }
    void begin(void) {
      HID.begin();
    }

    uint16_t _onGetDescriptor(uint8_t* buffer) {
      memcpy(buffer, report_descriptor, sizeof(report_descriptor));
      return sizeof(report_descriptor);
    }

    bool send(uint8_t * value) {
      return HID.SendReport(0, value, 9);
    }
};

CustomHIDDevice Device;

const int buttonPin = 0;
int previousButtonState = HIGH;
uint8_t TouchData;

void setup() {
Serial.begin(115200);
Serial.setDebugOutput(true);
Device.begin();
USB.begin();
}

void loop() {
if (HID.ready()) {
    Serial.println("Finger");
    // touch report
    //0: on/off + pressure
    //1: contact id
    //2: X lsb
    //3: X msb
    //4: Y lsb
    //5: Y msb
    //6: scan time lsb
    //7: scan time msb
    //8: contact count
    for (int i=0;i<101;i++) {
    TouchData = 0x81; TouchData = 0x08;
    TouchData = ((int)(STARTX+STARTR*sin(2*PI*i/100)))&0xFF;
    TouchData = ((int)(STARTX+STARTR*sin(2*PI*i/100)))>>8&0xFF;
    TouchData = ((int)(STARTY+STARTR*cos(2*PI*i/100)))&0xFF;
    TouchData = ((int)(STARTY+STARTR*cos(2*PI*i/100)))>>8&0xFF;
    TouchData = (millis()*10)&0xFF; TouchData = (millis()*10>>8)&0xFF;
    TouchData = 0x01;
    Device.send(TouchData);
    delay(10);
    }
    //每隔10秒
    delay(5000);
    STARTX=STARTX+300;
    STARTY=STARTY+300;
   
}
}
测试结果:
视频:https://www.bilibili.com/video/BV1e3411n7hm?share_source=copy_web就是这样,不要问这个能干啥, 还没想好。
参考:1.   https://www.arduino.cn/thread-107925-1-1.html2.   https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-precision-touchpad-required-hid-top-level-collections3.   https://download.microsoft.com/d ... ocol-spec-v1-0.docx

天意岸 发表于 2022-8-14 10:59:56

我想问下哈,如果是多点触控,比如两点TouchData数组相当于加长一倍么?那关于最后一位表示按压事件中触摸点的数量,应该全都设置成2么(相当于数组中第8和第17个数就是2)?那这样我看最开始的十点数据也不太对啊

zoologist 发表于 2022-8-15 09:09:58

天意岸 发表于 2022-8-14 10:59
我想问下哈,如果是多点触控,比如两点TouchData数组相当于加长一倍么?那关于最后一位表示按压事件中触摸 ...

最前面的描述符就是10点触控的,然后后面的报告中每次发送的长度都是相同的。

两个手指同时操作,数据不会在一起,会分开发送。

比如,物理上我们两个手指从一角滑动到另外一个角落,

数据上是分开发送的,数据据不会变长。

天意岸 发表于 2022-8-15 15:00:30

zoologist 发表于 2022-8-15 09:09
最前面的描述符就是10点触控的,然后后面的报告中每次发送的长度都是相同的。

两个手指同时操作,数据不 ...

哦哦,我明白了,那最后一个问题就是前面两个include文件怎么搞得,我觉得你这个还是很有用的,目前我想用视觉做虚拟触屏,现在坐标可以返回了就差触屏实现了

zoologist 发表于 2022-8-15 16:15:15

天意岸 发表于 2022-8-15 15:00
哦哦,我明白了,那最后一个问题就是前面两个include文件怎么搞得,我觉得你这个还是很有用的,目前我想 ...

这个应该是 ESP32 S2自带的,不是第三方的
页: [1]
查看完整版本: ESP32S2 实现触摸屏