Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi-touch touchscreen support (#123) #297

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions examples/Touchscreen/Touchscreen.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
Copyright (c) 2021 ilufang
See the readme for credit to other people.

Multi-touch touchscreen example
Draw 7 parallel lines across the screen.
Open Microsoft Whiteboard, select a brush and press the button.

See HID Project documentation for more infos
https://github.com/NicoHood/HID/wiki
*/

#include <HID-Project.h>

const int pinButton = 2;

void setup() {
pinMode(pinButton, INPUT_PULLUP);
Touchscreen.begin();
}

void loop() {
while(digitalRead(pinButton));

int16_t x = 2000, y = 2000;

for (; x <= 8000; x+=10) {
for (int i = 0; i < 7; i++) {
Touchscreen.setFinger(i, x, y+i*1000, (i+1)*15);
}
Touchscreen.send();
delay(10);
}

for (int i = 0; i < 7; i++) {
Touchscreen.releaseFinger(i);
}
Touchscreen.send();
}
115 changes: 115 additions & 0 deletions src/HID-APIs/TouchscreenAPI.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
Copyright (c) 2021 ilufang
See the readme for credit to other people.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

// Include guard
#pragma once

/// Maximum amount of fingers supported
#define HID_TOUCHSCREEN_MAXFINGERS 10
/// Number of fingers in a single report
#define HID_TOUCHSCREEN_REPORTFINGERS 2

// A report will always be the same size, even if you report fewer fingers than
// REPORTFINGERS. The unused finger entries will simply be zero. If more fingers
// are present than REPORTFINGERS, multiple reports will be sent to report all
// fingers. This is know as "Hybrid Mode" on MSDN. The number of supported
// fingers identified by Windows will still be MAXFINGERS. More than MAXFINGERS
// contacts may be ignored by Windows even with hybrid mode.


// Bit-mask flags for 'status' in the HID report
#define HID_TOUCHSCREEN_TOUCH_CONTACT 0x01
#define HID_TOUCHSCREEN_TOUCH_IN_RANGE 0x02

typedef struct ATTRIBUTE_PACKED {
uint8_t status;
uint8_t pressure;
uint16_t x;
uint16_t y;
} HID_Touchscreen_Finger_t;

typedef union ATTRIBUTE_PACKED {
uint8_t whole8[0];
uint16_t whole16[0];
uint32_t whole32[0];
struct ATTRIBUTE_PACKED {
uint8_t count;
struct ATTRIBUTE_PACKED {
NicoHood marked this conversation as resolved.
Show resolved Hide resolved
uint8_t identifier;
HID_Touchscreen_Finger_t touch;
} contacts[HID_TOUCHSCREEN_REPORTFINGERS];
};
} HID_TouchscreenReport_Data_t;

class TouchscreenAPI
{
public:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've not written c++ for a while, but why dont you have a constructor? I can see, that I am clearing the data in the other constructors, why dont we do that here as well?
https://github.com/NicoHood/HID/blob/master/src/HID-APIs/MouseAPI.hpp#L27

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implicit default constructor is specified to zero everything in C++. But for consistency I added one anyway.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember that this enlarges the sketches flash, is that still correct? I am wondering why I've added it in the first place then...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I shouldn't as it's the exact same behavior as the default constructor. I believe the compiler can figure out that static linking is all required for the global instances and no extra assign-to-zero code would be generated.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you test that real quick? It it adds additional overhead we should remove it from all other classes as well, I'd say.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The explicit initializer list in the constructor (not the constructor itself) made the example program text 32 bytes larger. In disassembly gcc inserted runtime code to zero memory. If the commonsense in x86 that global data are zeroed during exe image loading also applies to AVR (in my test instance it does), then it is preferable to not include an explicit initializer list.

I'm afraid I do not have the free time to perform project-wide polishing for you. I've removed the initializer list from this case. If there are no other touchscreen-specific issues, could you merge and publish this and then perform any other optimizations you'd like? Thanks.


inline void begin();

NicoHood marked this conversation as resolved.
Show resolved Hide resolved
/**
* Set contact status for a finger in the internal data structure. You must
* call send manually after setting all fingers to flush them through USB.
*
* @param id Finger id. Must be in the range of 0-MAXFINGERS. Same finger
* must have same id throughout contact. Allocations does not need
* to be continuous.
* @param x, y Coordinates. Range 0-10000. (0,0) is top-left on Windows.
* @param pressure Contact pressure. Range 0-127. When set to 0, the touch
* is reported as hovering (in-range)
* @return 1 if success. 0 if id is out-of-bounds
*/
inline int setFinger(uint8_t id, uint16_t x, uint16_t y, uint8_t pressure=100);

/**
* Release finger in the internal data structure. You must call send
* manually after setting all fingers to flush them through USB.
*
* @param id Finger id. Must be in the range of 0-MAXFINGERS. Same finger
* must have same id throughout contact. Allocations does not need
* to be continuous.
* @return 1 if success. 0 if id is out-of-bounds
*/
inline int releaseFinger(uint8_t id);

/**
* Generates an HID report reflecting the currently recorded touch status
* and send through USB.
*/
inline int send();

/// Send generated report. Needs to be implemented in a lower level
virtual int sendReport(void *report, int length) = 0;
virtual int sendReport(HID_TouchscreenReport_Data_t &report) = 0;
NicoHood marked this conversation as resolved.
Show resolved Hide resolved

protected:

/// Internal records of the current touch statuses. Status in this struct
/// is used only internally and differs from the one in the report
HID_Touchscreen_Finger_t _fingers[HID_TOUCHSCREEN_MAXFINGERS];
/// Number of active contacts, including just release contacts
uint8_t _fingers_count;
};

// Implementation is inline
#include "TouchscreenAPI.hpp"
109 changes: 109 additions & 0 deletions src/HID-APIs/TouchscreenAPI.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
Copyright (c) 2021 ilufang
See the readme for credit to other people.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

// Include guard
#pragma once

enum _finger_status_t {
_MT_STATE_INACTIVE = 0,
_MT_STATE_CONTACT,
_MT_STATE_RELEASED
};

void TouchscreenAPI::begin() {
send();
}

int TouchscreenAPI::setFinger(uint8_t id, uint16_t x, uint16_t y, uint8_t pressure) {
if (id >= HID_TOUCHSCREEN_MAXFINGERS) {
return 0;
}
if (_fingers[id].status == _MT_STATE_INACTIVE) {
_fingers_count++;
}
_fingers[id].status = _MT_STATE_CONTACT;
_fingers[id].pressure = pressure;
_fingers[id].x = x;
_fingers[id].y = y;
return 1;
}

int TouchscreenAPI::releaseFinger(uint8_t id) {
if (id >= HID_TOUCHSCREEN_MAXFINGERS) {
return 0;
}
_fingers[id].status = _MT_STATE_RELEASED;
return 1;
}

int TouchscreenAPI::send() {
int ret = 0;
HID_TouchscreenReport_Data_t report;

// Craft report(s)
report.count = _fingers_count;

int rptentry=0;
for (int i = 0; i < HID_TOUCHSCREEN_MAXFINGERS; i++) {
if (_fingers[i].status == _MT_STATE_INACTIVE)
continue;
NicoHood marked this conversation as resolved.
Show resolved Hide resolved

report.contacts[rptentry].identifier = i; // valid for first report only

if (_fingers[i].status == _MT_STATE_RELEASED) {
// Released contacts need to be reported once with TipSW=0
report.contacts[rptentry].touch = {};
_fingers_count--;
_fingers[i].status = _MT_STATE_INACTIVE;
} else {
// Active contacts must be reported even when not moved
report.contacts[rptentry].touch.status = HID_TOUCHSCREEN_TOUCH_IN_RANGE;
if (_fingers[i].pressure > 0)
report.contacts[rptentry].touch.status |= HID_TOUCHSCREEN_TOUCH_CONTACT;
NicoHood marked this conversation as resolved.
Show resolved Hide resolved
report.contacts[rptentry].touch.x = _fingers[i].x;
report.contacts[rptentry].touch.y = _fingers[i].y;
report.contacts[rptentry].touch.pressure = _fingers[i].pressure;
}

rptentry++;
if (rptentry == HID_TOUCHSCREEN_REPORTFINGERS) {
// Report full. Send now.
// If there are more contacts, they will be sent in subsequent
// reports with contact count set to 0
// See "Hybrid Mode" on MSDN docs
ret += sendReport(report);
rptentry = 0;
report.count = 0;
}
}

if (rptentry != 0) {
// Send remaining touches
for (; rptentry != HID_TOUCHSCREEN_REPORTFINGERS; rptentry++) {
report.contacts[rptentry] = {};
}
ret += sendReport(report);
}

return ret;
}
1 change: 1 addition & 0 deletions src/HID-Project.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,6 @@ THE SOFTWARE.
#include "SingleReport/SingleNKROKeyboard.h"
#include "MultiReport/NKROKeyboard.h"
#include "MultiReport/SurfaceDial.h"
#include "SingleReport/Touchscreen.h"

// Include Teensy HID afterwards to overwrite key definitions if used
1 change: 1 addition & 0 deletions src/HID-Settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ THE SOFTWARE.
#define HID_REPORTID_SURFACEDIAL 10
#endif


NicoHood marked this conversation as resolved.
Show resolved Hide resolved
#if defined(ARDUINO_ARCH_AVR)

// Use default alignment for AVR
Expand Down
Loading