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

Implemented optional on screen usage of Surface Dial, reworked some o… #169

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
102 changes: 74 additions & 28 deletions examples/SurfaceDial/SurfaceDial.ino
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@

/*
Copyright (c) 2017 wind-rider
See the readme for credit to other people.

Surface dial example

Use an encoder and a button to create a Surface Dial-compatible device.
See the connection diagram how to wire it up.

Please note that:
- I tested it using an Arduino Pro Micro; TinkerCad didn't have that in its component library
- you obviously don't need a motor, but TinkerCad didn't have a separate encoder

The encoder processing code is coming from https://www.allwinedesigns.com/blog/pocketnc-jog-wheel

Edited by OmegaRogue
*/

#include "HID-Project.h"
Expand All @@ -23,37 +23,43 @@ int pinB = 3;
// input pin for pushbutton
int pinButton = 4;

volatile bool previousButtonValue = false;
unsigned long keyPrevMillis = 0;
const unsigned long keySampleIntervalMs = 25;
byte longKeyPressCountMax = 840; // 80 * 25 = 2000 ms
byte longKeyPressCount = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

why does this work with a count rather than with millis directly?

Copy link
Author

Choose a reason for hiding this comment

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

idk i found the code online and adapted it for this purpose


byte prevKeyState = HIGH; // button is active low

volatile int previous = 0;
volatile int counter = 0;

void setup() {
pinMode(pinA, INPUT_PULLUP);
pinMode(pinA, INPUT_PULLUP);
pinMode(pinB, INPUT_PULLUP);

pinMode(pinButton, INPUT_PULLUP);

attachInterrupt(digitalPinToInterrupt(pinA), changed, CHANGE);
attachInterrupt(digitalPinToInterrupt(pinA), changed, CHANGE);
attachInterrupt(digitalPinToInterrupt(pinB), changed, CHANGE);

SurfaceDial.begin();
SurfaceDial.putOnScreen(true);
}

void changed() {
int A = digitalRead(pinA);
int A = digitalRead(pinA);
int B = digitalRead(pinB);

int current = (A << 1) | B;
int combined = (previous << 2) | current;
if(combined == 0b0010 ||

if(combined == 0b0010 ||
combined == 0b1011 ||
combined == 0b1101 ||
combined == 0b1101 ||
combined == 0b0100) {
counter++;
}

if(combined == 0b0001 ||
combined == 0b0111 ||
combined == 0b1110 ||
Expand All @@ -64,22 +70,62 @@ void changed() {
previous = current;
}

void loop(){
bool buttonValue = digitalRead(pinButton);
if(buttonValue != previousButtonValue){
if(buttonValue) {
SurfaceDial.press();
} else {
SurfaceDial.release();
}
previousButtonValue = buttonValue;
void loop(){
if (millis() - keyPrevMillis >= keySampleIntervalMs) {
keyPrevMillis = millis();

byte currKeyState = digitalRead(pinButton);

if ((prevKeyState == HIGH) && (currKeyState == LOW)) {
keyPress();
}
else if ((prevKeyState == LOW) && (currKeyState == HIGH)) {
keyRelease();
}
else if (currKeyState == LOW) {
longKeyPressCount++;
}

prevKeyState = currKeyState;
}

if(counter >= 4) {
SurfaceDial.rotate(10);
counter -= 4;
} else if(counter <= -4) {
SurfaceDial.rotate(-10);
counter += 4;
}
if(counter >= 1) {
SurfaceDial.reportData(10,0,0);
counter -= 1;
} else if(counter <= -1) {
SurfaceDial.reportData(-10,0,0);
counter += 1;
}
}

// called when button is kept pressed for less than 2 seconds
void shortKeyPress() {
SurfaceDial.click();
}


// called when button is kept pressed for more than 2 seconds
void longKeyPress() {

SurfaceDial.release();
}



// called when key goes from not pressed to pressed
void keyPress() {
SurfaceDial.press();
longKeyPressCount = 0;
}

// called when key goes from pressed to not pressed
void keyRelease() {


if (longKeyPressCount >= longKeyPressCountMax) {
longKeyPress();
}
else {
shortKeyPress();
}
}
24 changes: 20 additions & 4 deletions src/HID-APIs/SurfaceDialAPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,15 @@ typedef union{
uint8_t whole8[0];
uint16_t whole16[0];
uint32_t whole32[0];
uint64_t whole64[0];
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe these wholeX variables are actually not needed, see #147
If you want you can remove them after testing.

Copy link
Owner

Choose a reason for hiding this comment

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

@hansmbakker Would you mind checking the project where those variables are used, and remove them entirely from the project? Maybe the uin8_t version could be kept with the name "raw", but a proper length value.

Copy link
Contributor

Choose a reason for hiding this comment

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

@NicoHood unfortunately I'm a bit busy on other things to pick this up quickly; also it is not immediately clear to me what you mean with

but a proper length value

@NicoHood or @OmegaRogue is that something you could pick up?

Copy link
Author

Choose a reason for hiding this comment

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

@NicoHood what would the length be?

struct{
uint16_t button: 1;
uint16_t rotation: 15;
//int8_t xAxis;
//int8_t yAxis;

int16_t xAxis: 16;
int16_t yAxis: 16;
uint16_t width: 16;


};
} HID_SurfaceDialReport_Data_t;

Expand All @@ -49,17 +52,30 @@ class SurfaceDialAPI
inline void end(void);
inline void click(void);
inline void rotate(int16_t rotation);
inline void position(int16_t x, int16_t y);
Copy link
Contributor

Choose a reason for hiding this comment

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

W.r.t. the data type - shouldn't the X and Y coordinates be unsigned?

Copy link
Author

Choose a reason for hiding this comment

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

but isnt 0, 0 the screen center?

inline void reportData(int16_t rotation, int16_t x, int16_t y);
inline void press(void);
inline void release(void);
inline void releaseAll(void);
inline void releaseAll(void);
inline void putOnScreen(bool s);
inline bool isPressed();
inline int16_t getX();
inline int16_t getY();
inline bool getOnScreen();
inline void update();

// Sending is public in the base class for advanced users.
virtual void SendReport(void* data, int length) = 0;

protected:
bool _button;
bool _onScreen;
int16_t _xAxis;
int16_t _yAxis;
inline void button(bool b);
inline void xAxis(int16_t x);
inline void yAxis(int16_t y);
inline void onScreen(bool s);
};

// Implementation is inline
Expand Down
86 changes: 76 additions & 10 deletions src/HID-APIs/SurfaceDialAPI.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ THE SOFTWARE.
// Include guard
#pragma once

SurfaceDialAPI::SurfaceDialAPI(void) : _button(false)
SurfaceDialAPI::SurfaceDialAPI(void) : _button(false), _xAxis(0), _yAxis(0), _onScreen(false)
{
// Empty
}
Expand All @@ -37,25 +37,40 @@ void SurfaceDialAPI::begin(void)
void SurfaceDialAPI::end(void)
{
_button = false;
rotate(0);
update();
}

void SurfaceDialAPI::click(void)
{
_button = true;
rotate(0);
update();
_button = false;
rotate(0);
update();
}

void SurfaceDialAPI::rotate(int16_t rotation)
{
reportData(rotation, _xAxis, _yAxis);
}

void SurfaceDialAPI::position(int16_t x, int16_t y)
{
reportData(0, x, y);
}

void SurfaceDialAPI::reportData(int16_t rotation, int16_t x, int16_t y)
{
HID_SurfaceDialReport_Data_t report;
_xAxis = x;
_yAxis = y;
report.button = _button;
report.rotation = rotation;
//report.xAxis = x;
//report.yAxis = y;

report.width = 3000;
if(_onScreen)
{
report.xAxis = _xAxis;
report.yAxis = _yAxis;
}
SendReport(&report, sizeof(report));
}

Expand All @@ -64,10 +79,21 @@ void SurfaceDialAPI::button(bool b)
if (b != _button)
{
_button = b;
rotate(0);
update();
}
}

void SurfaceDialAPI::xAxis(int16_t x)
{
_xAxis = x;
reportData(0, _xAxis, _yAxis);
}
void SurfaceDialAPI::yAxis(int16_t y)
{
_yAxis = y;
reportData(0, _xAxis, _yAxis);
}

void SurfaceDialAPI::press(void)
{
button(true);
Expand All @@ -81,10 +107,50 @@ void SurfaceDialAPI::release(void)
void SurfaceDialAPI::releaseAll(void)
{
_button = false;
rotate(0);
update();
}

void SurfaceDialAPI::onScreen(bool s)
{
_onScreen = s;
update();
}

void SurfaceDialAPI::putOnScreen(bool s)
{
onScreen(s);
}

void SurfaceDialAPI::update()
Copy link
Contributor

Choose a reason for hiding this comment

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

Not very intuitive what the difference between posrot and update is. It turns out the difference is in the rotation variable.

{
HID_SurfaceDialReport_Data_t report;
report.button = _button;
report.rotation = 0;
report.width = 3000;
if(_onScreen)
{
report.xAxis = _xAxis;
Copy link
Contributor

Choose a reason for hiding this comment

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

validation of a correct value is not done yet

Copy link
Author

Choose a reason for hiding this comment

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

how should the values be validated?

Copy link
Contributor

Choose a reason for hiding this comment

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

ah, I see in the docs that you can specify values out of range to remove the device from the screen

Copy link
Author

Choose a reason for hiding this comment

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

Which Docs say that you can specify values out of the range to remove the device from the screen?

report.yAxis = _yAxis;
}
SendReport(&report, sizeof(report));
}

bool SurfaceDialAPI::isPressed()
{
return _button;
return _button;
}

bool SurfaceDialAPI::getOnScreen()
{
return _onScreen;
}

int16_t SurfaceDialAPI::getX()
{
return _xAxis;
}

int16_t SurfaceDialAPI::getY()
{
return _yAxis;
}
38 changes: 19 additions & 19 deletions src/MultiReport/SurfaceDial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,25 +50,25 @@ static const uint8_t _hidMultiReportDescriptorSurfaceDial[] PROGMEM = {
0x16, 0xf0, 0xf1, // LOGICAL_MINIMUM (-3600)
0x26, 0x10, 0x0e, // LOGICAL_MAXIMUM (3600)
0x81, 0x06, // INPUT (Data,Var,Rel)
// 0x09, 0x30, // USAGE (X)
// 0x75, 0x10, // REPORT_SIZE (16)
// 0x55, 0x0d, // UNIT_EXPONENT (-3)
// 0x65, 0x13, // UNIT (Inch,EngLinear)
// 0x35, 0x00, // PHYSICAL_MINIMUM (0)
// 0x46, 0xc0, 0x5d, // PHYSICAL_MAXIMUM (24000)
// 0x15, 0x00, // LOGICAL_MINIMUM (0)
// 0x26, 0xff, 0x7f, // LOGICAL_MAXIMUM (32767)
// 0x81, 0x02, // INPUT (Data,Var,Abs)
// 0x09, 0x31, // USAGE (Y)
// 0x46, 0xb0, 0x36, // PHYSICAL_MAXIMUM (14000)
// 0x81, 0x02, // INPUT (Data,Var,Abs)
// 0x05, 0x0d, // USAGE_PAGE (Digitizers)
// 0x09, 0x48, // USAGE (Width)
// 0x36, 0xb8, 0x0b, // PHYSICAL_MINIMUM (3000)
// 0x46, 0xb8, 0x0b, // PHYSICAL_MAXIMUM (3000)
// 0x16, 0xb8, 0x0b, // LOGICAL_MINIMUM (3000)
// 0x26, 0xb8, 0x0b, // LOGICAL_MAXIMUM (3000)
// 0x81, 0x03 // INPUT (Cnst,Var,Abs)
0x09, 0x30, // USAGE (X)
0x75, 0x10, // REPORT_SIZE (16)
0x55, 0x0d, // UNIT_EXPONENT (-3)
0x65, 0x13, // UNIT (Inch,EngLinear)
0x35, 0x00, // PHYSICAL_MINIMUM (0)
0x46, 0xc0, 0x5d, // PHYSICAL_MAXIMUM (24000)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xff, 0x7f, // LOGICAL_MAXIMUM (32767)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x09, 0x31, // USAGE (Y)
0x46, 0xb0, 0x36, // PHYSICAL_MAXIMUM (14000)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x05, 0x0d, // USAGE_PAGE (Digitizers)
0x09, 0x48, // USAGE (Width)
Copy link
Contributor

Choose a reason for hiding this comment

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

Windows expects a value for the width if you want to use the position reporting.

Copy link
Author

Choose a reason for hiding this comment

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

but the width is constant, minimum value is 3000, maximum value is 3000

Copy link
Contributor

Choose a reason for hiding this comment

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

ok

0x36, 0xb8, 0x0b, // PHYSICAL_MINIMUM (3000)
0x46, 0xb8, 0x0b, // PHYSICAL_MAXIMUM (3000)
0x16, 0xb8, 0x0b, // LOGICAL_MINIMUM (3000)
0x26, 0xb8, 0x0b, // LOGICAL_MAXIMUM (3000)
0x81, 0x03, // INPUT (Cnst,Var,Abs)
0xc0, // END_COLLECTION
0xc0 // END_COLLECTION
};
Expand Down