瀏覽代碼

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

cuu 2 年之前
父節點
當前提交
b1370d2124

+ 53 - 0
Code/devterm_keyboard/debouncer.h

@@ -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

+ 23 - 0
Code/devterm_keyboard/debouncer.ino

@@ -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;
+}

+ 3 - 0
Code/devterm_keyboard/devterm.h

@@ -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

+ 9 - 2
Code/devterm_keyboard/devterm_keyboard.ino

@@ -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);
   

+ 29 - 0
Code/devterm_keyboard/glider.h

@@ -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

+ 60 - 0
Code/devterm_keyboard/glider.ino

@@ -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 };
+}

+ 14 - 5
Code/devterm_keyboard/keymaps.ino

@@ -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;
     
   }

+ 1 - 1
Code/devterm_keyboard/keys.h

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

+ 8 - 8
Code/devterm_keyboard/keys.ino

@@ -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;
 

+ 56 - 0
Code/devterm_keyboard/math.h

@@ -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

+ 23 - 0
Code/devterm_keyboard/math.ino

@@ -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;
+}

+ 38 - 0
Code/devterm_keyboard/ratemeter.h

@@ -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

+ 40 - 0
Code/devterm_keyboard/ratemeter.ino

@@ -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;
+  }
+}

+ 34 - 0
Code/devterm_keyboard/state.h

@@ -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

+ 36 - 0
Code/devterm_keyboard/state.ino

@@ -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;
+  }
+}

+ 30 - 0
Code/devterm_keyboard/tickwaiter.h

@@ -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

+ 0 - 53
Code/devterm_keyboard/trackball.h

@@ -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*);

+ 81 - 49
Code/devterm_keyboard/trackball.ino

@@ -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;
 
-  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();
+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());
+  }
+}
+ 
+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);
   
 }