borrwoed a lot code from https://github.com/foriequal0/devterm_keyboard in order to optimize the trackball

This commit is contained in:
cuu 2021-12-16 20:41:37 +08:00
parent 1a1be01dbb
commit b1370d2124
18 changed files with 538 additions and 118 deletions

View File

@ -0,0 +1,53 @@
#ifndef DEBOUNCER_H
#define DEBOUNCER_H
#include <cstdint>
typedef uint8_t millis_t;
const millis_t DEBOUNCE_MS = 5;
/**
@brief Asymmetric debouncer
*/
class Debouncer {
public:
Debouncer();
void updateTime(millis_t delta);
bool sample(bool value);
private:
millis_t timeout;
};
template<typename T, T millis>
class Timeout {
public:
Timeout() {
timeout = 0;
}
void updateTime(millis_t delta) {
if (timeout > delta) {
timeout -= delta;
} else {
timeout = 0;
}
}
void expire() {
timeout = 0;
}
bool get() const {
return timeout == 0;
}
void reset() {
timeout = millis;
}
private:
uint16_t timeout;
};
#endif

View File

@ -0,0 +1,23 @@
#include "debouncer.h"
Debouncer::Debouncer()
: timeout(0)
{
}
void Debouncer::updateTime(millis_t delta) {
if (timeout > delta) {
timeout -= delta;
} else {
timeout = 0;
}
}
bool Debouncer::sample(bool value) {
if (value || timeout == 0) {
timeout = DEBOUNCE_MS;
return true;
}
return false;
}

View File

@ -2,6 +2,7 @@
#define DEVTERM_H
#define KEY_LATENCY 1400
#include "state.h"
#include <USBComposite.h>
typedef struct key_debouncing{
@ -32,6 +33,8 @@ class DEVTERM {
//**Serial and USBCompositeSerial can not use together, otherwise the keyboard firmware uploading will be dead**
//and you will need to find a way out to flash the stm32duino bootloader once again
//USBSerial *_Serial;//_Serial = &Serial;
State *state;
uint32_t delta;
};
#define KEYBOARD_PULL 1 // 1 for PULLUP, 0 FOR PULLDOWN

View File

@ -2,6 +2,7 @@
#include "keys.h"
#include "trackball.h"
#include "devterm.h"
#include "tickwaiter.h"
#include <USBComposite.h>
@ -17,6 +18,8 @@ const uint8_t reportDescription[] = {
HID_MOUSE_REPORT_DESCRIPTOR()
};
static const uint32_t LOOP_INTERVAL_MS = 0;
static TickWaiter<LOOP_INTERVAL_MS> waiter;
void setup() {
USBComposite.setManufacturerString("ClockworkPI");
@ -29,7 +32,8 @@ void setup() {
dev_term.Consumer = new HIDConsumer(HID);
dev_term.Keyboard->setAdjustForHostCapsLock(false);
dev_term.state = new State();
dev_term.Keyboard_state.layer = 0;
dev_term.Keyboard_state.prev_layer = 0;
@ -55,8 +59,11 @@ void setup() {
}
void loop() {
dev_term.delta = waiter.waitForNextTick();
dev_term.state->tick(dev_term.delta);
trackball_task(&dev_term);
keys_task(&dev_term); //keys above keyboard
keyboard_task(&dev_term);

View File

@ -0,0 +1,29 @@
#ifndef GLIDER_H
#define GLIDER_H
#include <cstdint>
class Glider {
public:
Glider();
void setDirection(int8_t);
void update(float velocity, uint16_t sustain);
void updateSpeed(float velocity);
void stop();
struct GlideResult {
int8_t value;
bool stopped;
};
GlideResult glide(uint8_t delta);
public:
int8_t direction;
float speed;
uint16_t sustain;
uint16_t release;
float error;
};
#endif

View File

@ -0,0 +1,60 @@
#include <cmath>
#include "glider.h"
#include "math.h"
Glider::Glider()
: speed(0),
sustain(0),
release(0),
error(0)
{}
void Glider::setDirection(int8_t direction) {
if (this->direction != direction) {
stop();
}
this->direction = direction;
}
void Glider::update(float speed, uint16_t sustain) {
this->speed = speed;
this->sustain = sustain;
this->release = sustain;
}
void Glider::updateSpeed(float speed) {
this->speed = speed;
}
void Glider::stop() {
this->speed = 0;
this->sustain = 0;
this->release = 0;
this->error = 0;
}
Glider::GlideResult Glider::glide(millis_t delta) {
const auto alreadyStopped = speed == 0;
error += speed * delta;
int8_t distance = 0;
if (error > 0) {
distance = clamp<int8_t>(std::ceil(error));
}
error -= distance;
if (sustain > 0) {
const auto sustained = min(sustain, (uint16_t)delta);
sustain -= sustained;
} else if (release > 0) {
const auto released = min(release, (uint16_t)delta);
speed = speed * (release - released) / release;
release -= released;
} else {
speed = 0;
}
const int8_t result = direction * distance;
return GlideResult { result, !alreadyStopped && speed == 0 };
}

View File

@ -58,7 +58,7 @@ enum SKEYS {
_VOLUME_M,
_VOLUME_P,
_TRACKBALL_BTN,
};
#define DEF_LAYER 0x00
@ -105,7 +105,7 @@ const uint16_t keys_maps[KEYS_NUM] = {_JOYSTICK_UP,_JOYSTICK_DOWN, _JOYSTICK_LEF
_JOYSTICK_RIGHT,_JOYSTICK_A,_JOYSTICK_B, \
_JOYSTICK_X,_JOYSTICK_Y,_LEFT_SHIFT_KEY,_FN_KEY,\
_LEFT_CTRL_KEY,_CMD_KEY , _LEFT_ALT, \
_MOUSE_LEFT,_MOUSE_MID,_MOUSE_RIGHT};
_MOUSE_LEFT,_MOUSE_MID,_MOUSE_RIGHT,_TRACKBALL_BTN};
uint8_t check_pd2(){ // if swtich 2 in back is set to on(HIGH)
@ -241,13 +241,14 @@ void keyboard_action(DEVTERM*dv,uint8_t row,uint8_t col,uint8_t mode) {
void keypad_action(DEVTERM*dv,uint8_t col,uint8_t mode) {
uint16_t k;
k = keys_maps[col];
k = keys_maps[col];
if(k == EMP){
return;
}
switch(k) {
case _LEFT_SHIFT_KEY:
if(mode == KEY_PRESSED) {
@ -419,7 +420,15 @@ void keypad_action(DEVTERM*dv,uint8_t col,uint8_t mode) {
dv->Keyboard->release(k);
}
break;
case _TRACKBALL_BTN:
if(mode == KEY_PRESSED) {
dv->state->pressMiddleClick();
}else {
dv->state->releaseMiddleClick();
dv->Mouse->click(MOUSE_MIDDLE);
}
break;
default:break;
}

View File

@ -16,7 +16,7 @@
# define KEY_DEBOUNCE 5
#endif
#define KEYS_NUM 16
#define KEYS_NUM 17
void keys_task(DEVTERM*);

View File

@ -2,12 +2,12 @@
KEY_DEB keypad_debouncing;
uint8_t keys_io[ KEYS_NUM ]= {KEY1,KEY2,KEY3,KEY4,KEY5,KEY6,KEY7,KEY8,KEY9,KEY10,KEY11,KEY12,KEY13,KEY14,KEY15,KEY16};
uint8_t keys_io[ KEYS_NUM ]= {KEY1,KEY2,KEY3,KEY4,KEY5,KEY6,KEY7,KEY8,KEY9,KEY10,KEY11,KEY12,KEY13,KEY14,KEY15,KEY16,KEY0};
/* keys state(1:on, 0:off) */
static uint16_t keys;
static uint16_t keys_debouncing;
static uint16_t keys_prev;
static uint32_t keys;
static uint32_t keys_debouncing;
static uint32_t keys_prev;
void init_keys(){
int i;
@ -19,7 +19,7 @@ void init_keys(){
}
uint8_t scan_keys(){
uint16_t data;
uint32_t data;
uint8_t s;
data = 0;
@ -69,9 +69,9 @@ void keys_task(DEVTERM*dv){
scan_keys();
uint16_t _mask =1;
uint16_t _change = 0;
uint16_t _pressed = 0;
uint32_t _mask =1;
uint32_t _change = 0;
uint32_t _pressed = 0;
_change = keys ^ keys_prev;

View File

@ -0,0 +1,56 @@
#ifndef MATH_H
#define MATH_H
#include <cstdint>
#include <limits>
#include <cmath>
uint32_t getDelta(uint32_t prev, uint32_t now);
uint32_t getDelta(uint32_t prev, uint32_t now, uint32_t max);
template<typename T>
T sign(T value) {
if (value > 0) {
return 1;
}
if (value < 0) {
return -1;
}
return 0;
}
template<typename T, typename U>
T clamp(U value) {
if (value >= std::numeric_limits<T>().max()) {
return std::numeric_limits<T>().max();
}
if (value <= std::numeric_limits<T>().min()) {
return std::numeric_limits<T>().min();
}
return value;
}
template<typename T>
T min(T x, T y) {
if (x < y) {
return x;
}
return y;
}
template<typename T>
T max(T x, T y) {
if (x > y) {
return x;
}
return y;
}
template<typename T>
T hypot(T x, T y) {
return std::sqrt(x * x + y * y);
}
#endif

View File

@ -0,0 +1,23 @@
#include <limits>
#include "math.h"
uint32_t getDelta(uint32_t prev, uint32_t now) {
uint32_t delta;
if (now >= prev) {
delta = now - prev;
} else {
delta = std::numeric_limits<uint32_t>().max() - prev - now + 1;
}
return delta;
}
uint32_t getDelta(uint32_t prev, uint32_t now, uint32_t max) {
const auto delta = getDelta(prev, now);
if (delta < max) {
return delta;
}
return max;
}

View File

@ -0,0 +1,38 @@
#ifndef RATEMETER_H
#define RATEMETER_H
#include <cstdint>
#include "debouncer.h"
class RateMeter {
public:
RateMeter();
void onInterrupt();
void tick(millis_t delta);
void expire();
uint16_t delta() const;
// Hall sensor edges per seconds.
// stopped: 0
// really slow => ~3
// medium => ~30
// fast => < 300
// max => 1000
float rate() const;
private:
uint32_t lastTime;
// really Range, emperically:
// fast => < 5_000 us,
// medium => 20_000 - 40_000 us
// really slow => 250_000 us
uint32_t averageDelta;
static const uint16_t CUTOFF_MS = 1000;
// Cut off after some seconds to prevent multiple timestamp overflow (~70 mins)
Timeout<uint16_t, CUTOFF_MS> cutoff;
};
#endif

View File

@ -0,0 +1,40 @@
#include <Arduino.h>
#include <cstdint>
#include "ratemeter.h"
#include "math.h"
RateMeter::RateMeter()
: lastTime(0)
{}
void RateMeter::onInterrupt() {
const auto now = millis();
if (cutoff.get()) {
averageDelta = CUTOFF_MS;
} else {
const auto delta = getDelta(lastTime, now, CUTOFF_MS);
averageDelta = (averageDelta + delta) / 2;
}
lastTime = now;
cutoff.reset();
}
void RateMeter::tick(millis_t delta) {
cutoff.updateTime(delta);
}
uint16_t RateMeter::delta() const {
return averageDelta;
}
float RateMeter::rate() const {
if (cutoff.get()) {
return 0.0f;
} else if (averageDelta == 0) {
// to ensure range 0 ~ 1000.0
return 1000.0f;
} else {
return 1000.0f / (float)averageDelta;
}
}

View File

@ -0,0 +1,34 @@
#ifndef STATE_H
#define STATE_H
#include <bitset>
#include <array>
#include <USBComposite.h>
#include "debouncer.h"
enum class TrackballMode : uint8_t {
Wheel,
Mouse,
};
class State
{
public:
static const uint16_t MIDDLE_CLICK_TIMEOUT_MS = 0;
State();
void tick(uint8_t delta);
bool fn;
void pressMiddleClick();
bool releaseMiddleClick();
TrackballMode moveTrackball();
private:
bool middleClick;
Timeout<uint16_t, MIDDLE_CLICK_TIMEOUT_MS> middleClickTimeout;
};
#endif

View File

@ -0,0 +1,36 @@
#include <cassert>
#include <algorithm>
#include <limits>
#include "state.h"
State::State()
: fn(false),
middleClick(false)
{
}
void State::tick(millis_t delta)
{
middleClickTimeout.updateTime(delta);
}
void State::pressMiddleClick() {
middleClick = true;
middleClickTimeout.reset();
}
bool State::releaseMiddleClick() {
middleClick = false;
const auto timeout = middleClickTimeout.get();
return !timeout;
}
TrackballMode State::moveTrackball() {
middleClickTimeout.expire();
if (middleClick) {
return TrackballMode::Wheel;
} else {
return TrackballMode::Mouse;
}
}

View File

@ -0,0 +1,30 @@
#ifndef TICKWAITER_H
#define TICKWAITER_H
#include <cstdint>
#include "math.h"
template<uint32_t TargetInterval>
class TickWaiter {
public:
uint8_t waitForNextTick() {
const auto last = this->last;
const auto now = millis();
this->last = now;
const auto delta = getDelta(last, now, 255);
if (delta >= TargetInterval) {
return delta;
}
delay(TargetInterval - delta);
const auto now2 = millis();
return getDelta(last, now2, 255);
}
private:
uint32_t last = 0;
};
#endif

View File

@ -12,64 +12,11 @@
#define EXPONENTIAL_BASE 1.2
*/
#define BTN_PIN KEY0
#define RIGHT_PIN HO3
#define LEFT_PIN HO1
#define DOWN_PIN HO4
#define UP_PIN HO2
typedef struct _track_speed {
uint8_t bounce_interval;
uint8_t base_move_pixels;
uint8_t exponential_bound;
double exponential_base;
}TrackSpeed;
class Direction {
public:
Direction(int pin1, int pin2) {
this->pins[0] = pin1;
this->pins[1] = pin2;
pinMode(this->pins[0], INPUT);
pinMode(this->pins[1], INPUT);
};
int read_action() {
for(int i = 0; i < 2; ++i) {
this->current_actions[i] = digitalRead(this->pins[i]);
this->current_action_times[i] = millis();
if(this->current_actions[i] != this->last_actions[i]) {
this->last_actions[i] = this->current_actions[i];
exponential = ( ts->exponential_bound - (this->current_action_times[i] - this->last_action_times[i]));
exponential = (exponential > 0) ? exponential : 1;
move_multiply = ts->exponential_base;
for(int i = 0; i < exponential; ++i) {
move_multiply *= ts->exponential_base;
}
this->last_action_times[i] = this->current_action_times[i];
if(i == 0) {
return (-1) * ts->base_move_pixels * move_multiply;
} else {
return ts->base_move_pixels * move_multiply;
}
}
}
return 0;
};
TrackSpeed *ts;
private:
int pins[2];
int current_actions[2];
int last_actions[2];
int exponential;
double move_multiply;
unsigned long current_action_times[2];
unsigned long last_action_times[2];
};
void trackball_init(DEVTERM*);
void trackball_task(DEVTERM*);

View File

@ -9,71 +9,103 @@
#include <USBComposite.h>
#include "trackball.h"
#include "ratemeter.h"
#include "glider.h"
#include "math.h"
int btn_state;
int btn_read_state;
unsigned long btn_current_action_time;
unsigned long btn_last_action_time;
// mouse move
int x_move, y_move;
Direction x_direction(LEFT_PIN, RIGHT_PIN);
Direction y_direction(UP_PIN, DOWN_PIN);
enum Axis: uint8_t {
AXIS_X,
AXIS_Y,
AXIS_NUM,
};
TrackSpeed Normal_ts;
TrackSpeed Detail_ts;
TrackSpeed *ts_ptr;
static int8_t distances[AXIS_NUM];
static RateMeter rateMeter[AXIS_NUM];
static Glider glider[AXIS_NUM];
void trackball_task(DEVTERM*dv) {
if(dv-> Keyboard_state.fn_on > 0) {
ts_ptr = &Detail_ts;
}else {
ts_ptr = &Normal_ts;
static const int8_t WHEEL_DENOM = 2;
static int8_t wheelBuffer;
static float rateToVelocityCurve(float input) {
return std::pow(std::abs(input) / 50, 1.5);
}
template<Axis AXIS, int8_t Direction>
static void interrupt() {
distances[AXIS] += Direction;
rateMeter[AXIS].onInterrupt();
glider[AXIS].setDirection(Direction);
const auto rx = rateMeter[AXIS_X].rate();
const auto ry = rateMeter[AXIS_Y].rate();
const auto rate = std::sqrt(rx * rx + ry * ry);
const auto ratio = rateToVelocityCurve(rate) / rate;
const auto vx = rx * ratio;
const auto vy = ry * ratio;
if (AXIS == AXIS_X) {
glider[AXIS_X].update(vx, rateMeter[AXIS_X].delta());
glider[AXIS_Y].updateSpeed(vy);
} else {
glider[AXIS_X].updateSpeed(vx);
glider[AXIS_Y].update(vy, rateMeter[AXIS_Y].delta());
}
x_direction.ts = ts_ptr;
y_direction.ts = ts_ptr;
btn_read_state = digitalRead(BTN_PIN);
if(btn_read_state != btn_state) {
btn_current_action_time = millis();
if(btn_current_action_time - btn_last_action_time > ts_ptr->bounce_interval ) {
btn_state = btn_read_state;
btn_last_action_time = btn_current_action_time;
if(btn_state == HIGH) {
dv->Mouse->release();
} else {
dv->Mouse->press();
}
void trackball_task(DEVTERM*dv) {
int8_t x = 0, y = 0, w = 0;
noInterrupts();
rateMeter[AXIS_X].tick(dv->delta);
rateMeter[AXIS_Y].tick(dv->delta);
const auto mode = dv->state->moveTrackball();
switch(mode){
case TrackballMode::Mouse: {
const auto rX = glider[AXIS_X].glide(dv->delta);
const auto rY = glider[AXIS_Y].glide(dv->delta);
x = rX.value;
y = rY.value;
if (rX.stopped) {
glider[AXIS_Y].stop();
}
if (rY.stopped) {
glider[AXIS_Y].stop();
}
break;
}
case TrackballMode::Wheel: {
wheelBuffer += distances[AXIS_Y];
w = wheelBuffer / WHEEL_DENOM;
wheelBuffer -= w * WHEEL_DENOM;
break;
}
}
distances[AXIS_X] = 0;
distances[AXIS_Y] = 0;
interrupts();
x_move = x_direction.read_action();
y_move = y_direction.read_action();
if(x_move != 0 || y_move != 0) {
dv->Mouse->move(x_move, y_move, 0);
}
dv->Mouse->move(x, y, -w);
}
void trackball_init(DEVTERM*){
pinMode(BTN_PIN,INPUT);
Normal_ts.bounce_interval = 30;
Normal_ts.base_move_pixels = 5;
Normal_ts.exponential_bound = 14;
Normal_ts.exponential_base = 1.2;
Detail_ts.bounce_interval = 100;
Detail_ts.base_move_pixels = 3;
Detail_ts.exponential_bound = 10;
Detail_ts.exponential_base = 1.2;
pinMode(LEFT_PIN, INPUT);
pinMode(UP_PIN, INPUT);
pinMode(RIGHT_PIN, INPUT);
pinMode(DOWN_PIN, INPUT);
attachInterrupt(LEFT_PIN, &interrupt<AXIS_X, -1>, ExtIntTriggerMode::CHANGE);
attachInterrupt(RIGHT_PIN, &interrupt<AXIS_X, 1>, ExtIntTriggerMode::CHANGE);
attachInterrupt(UP_PIN, &interrupt<AXIS_Y, -1>, ExtIntTriggerMode::CHANGE);
attachInterrupt(DOWN_PIN, &interrupt<AXIS_Y, 1>, ExtIntTriggerMode::CHANGE);
}