220 lines
5.8 KiB
C++
220 lines
5.8 KiB
C++
|
// ----------------------------------------------------------------------------
|
||
|
// Rotary Encoder Driver with Acceleration
|
||
|
// Supports Click, DoubleClick, Long Click
|
||
|
//
|
||
|
// (c) 2010 karl@pitrich.com
|
||
|
// (c) 2014 karl@pitrich.com
|
||
|
//
|
||
|
// Timer-based rotary encoder logic by Peter Dannegger
|
||
|
// http://www.mikrocontroller.net/articles/Drehgeber
|
||
|
// ----------------------------------------------------------------------------
|
||
|
|
||
|
#include "ClickEncoder.h"
|
||
|
#include <SmingCore/SmingCore.h>
|
||
|
|
||
|
// ----------------------------------------------------------------------------
|
||
|
// Button configuration (values for 1ms timer service calls)
|
||
|
//
|
||
|
#define ENC_BUTTONINTERVAL 10 // check button every x milliseconds, also debouce time
|
||
|
#define ENC_DOUBLECLICKTIME 400 // second click within 600ms
|
||
|
#define ENC_HOLDTIME 1000 // report held button after 1.2s
|
||
|
|
||
|
// ----------------------------------------------------------------------------
|
||
|
// Acceleration configuration (for 1000Hz calls to ::service())
|
||
|
//
|
||
|
#define ENC_ACCEL_TOP 3072 // max. acceleration: *12 (val >> 8)
|
||
|
#define ENC_ACCEL_INC 25
|
||
|
#define ENC_ACCEL_DEC 2
|
||
|
|
||
|
// ----------------------------------------------------------------------------
|
||
|
|
||
|
#if ENC_DECODER != ENC_NORMAL
|
||
|
# ifdef ENC_HALFSTEP
|
||
|
// decoding table for hardware with flaky notch (half resolution)
|
||
|
const int8_t ClickEncoder::table[16] __attribute__((__progmem__)) = {
|
||
|
0, 0, -1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, -1, 0, 0
|
||
|
};
|
||
|
# else
|
||
|
// decoding table for normal hardware
|
||
|
const int8_t ClickEncoder::table[16] __attribute__((__progmem__)) = {
|
||
|
0, 1, -1, 0, -1, 0, 0, 1, 1, 0, 0, -1, 0, -1, 1, 0
|
||
|
};
|
||
|
# endif
|
||
|
#endif
|
||
|
|
||
|
// ----------------------------------------------------------------------------
|
||
|
|
||
|
ClickEncoder::ClickEncoder(uint8_t A, uint8_t B, uint8_t BTN, uint8_t stepsPerNotch, bool active)
|
||
|
: doubleClickEnabled(true), accelerationEnabled(true),
|
||
|
delta(0), last(0), acceleration(0),
|
||
|
button(Open), steps(stepsPerNotch),
|
||
|
pinA(A), pinB(B), pinBTN(BTN), pinsActive(active)
|
||
|
{
|
||
|
uint8_t configType = (pinsActive == false) ? INPUT_PULLUP : INPUT;
|
||
|
pinMode(pinA, configType);
|
||
|
pinMode(pinB, configType);
|
||
|
pinMode(pinBTN, configType);
|
||
|
|
||
|
if (digitalRead(pinA) == pinsActive) {
|
||
|
last = 3;
|
||
|
}
|
||
|
|
||
|
if (digitalRead(pinB) == pinsActive) {
|
||
|
last ^=1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ----------------------------------------------------------------------------
|
||
|
// call this every 1 millisecond via timer ISR
|
||
|
//
|
||
|
void ClickEncoder::service(void)
|
||
|
{
|
||
|
bool moved = false;
|
||
|
unsigned long now = millis();
|
||
|
|
||
|
if (accelerationEnabled) { // decelerate every tick
|
||
|
acceleration -= ENC_ACCEL_DEC;
|
||
|
if (acceleration & 0x8000) { // handle overflow of MSB is set
|
||
|
acceleration = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if ENC_DECODER == ENC_FLAKY
|
||
|
last = (last << 2) & 0x0F;
|
||
|
|
||
|
if (digitalRead(pinA) == pinsActive) {
|
||
|
last |= 2;
|
||
|
}
|
||
|
|
||
|
if (digitalRead(pinB) == pinsActive) {
|
||
|
last |= 1;
|
||
|
}
|
||
|
|
||
|
uint8_t tbl = pgm_read_byte(&table[last]);
|
||
|
if (tbl) {
|
||
|
delta += tbl;
|
||
|
moved = true;
|
||
|
}
|
||
|
#elif ENC_DECODER == ENC_NORMAL
|
||
|
int8_t curr = 0;
|
||
|
|
||
|
if (digitalRead(pinA) == pinsActive) {
|
||
|
curr = 3;
|
||
|
}
|
||
|
|
||
|
if (digitalRead(pinB) == pinsActive) {
|
||
|
curr ^= 1;
|
||
|
}
|
||
|
|
||
|
int8_t diff = last - curr;
|
||
|
|
||
|
if (diff & 1) { // bit 0 = step
|
||
|
last = curr;
|
||
|
delta += (diff & 2) - 1; // bit 1 = direction (+/-)
|
||
|
moved = true;
|
||
|
}
|
||
|
#else
|
||
|
# error "Error: define ENC_DECODER to ENC_NORMAL or ENC_FLAKY"
|
||
|
#endif
|
||
|
|
||
|
if (accelerationEnabled && moved) {
|
||
|
// increment accelerator if encoder has been moved
|
||
|
if (acceleration <= (ENC_ACCEL_TOP - ENC_ACCEL_INC)) {
|
||
|
acceleration += ENC_ACCEL_INC;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// handle button
|
||
|
//
|
||
|
#ifndef WITHOUT_BUTTON
|
||
|
static uint16_t keyDownTicks = 0;
|
||
|
static uint8_t doubleClickTicks = 0;
|
||
|
static unsigned long lastButtonCheck = 0;
|
||
|
|
||
|
if (pinBTN > 0 // check button only, if a pin has been provided
|
||
|
&& (now - lastButtonCheck) >= ENC_BUTTONINTERVAL) // checking button is sufficient every 10-30ms
|
||
|
{
|
||
|
lastButtonCheck = now;
|
||
|
|
||
|
if (digitalRead(pinBTN) == pinsActive) { // key is down
|
||
|
keyDownTicks++;
|
||
|
if (keyDownTicks > (ENC_HOLDTIME / ENC_BUTTONINTERVAL)) {
|
||
|
button = Held;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (digitalRead(pinBTN) == !pinsActive) { // key is now up
|
||
|
if (keyDownTicks /*> ENC_BUTTONINTERVAL*/) {
|
||
|
if (button == Held) {
|
||
|
button = Released;
|
||
|
doubleClickTicks = 0;
|
||
|
}
|
||
|
else {
|
||
|
#define ENC_SINGLECLICKONLY 1
|
||
|
if (doubleClickTicks > ENC_SINGLECLICKONLY) { // prevent trigger in single click mode
|
||
|
if (doubleClickTicks < (ENC_DOUBLECLICKTIME / ENC_BUTTONINTERVAL)) {
|
||
|
button = DoubleClicked;
|
||
|
doubleClickTicks = 0;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
doubleClickTicks = (doubleClickEnabled) ? (ENC_DOUBLECLICKTIME / ENC_BUTTONINTERVAL) : ENC_SINGLECLICKONLY;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
keyDownTicks = 0;
|
||
|
}
|
||
|
|
||
|
if (doubleClickTicks > 0) {
|
||
|
doubleClickTicks--;
|
||
|
if (--doubleClickTicks == 0) {
|
||
|
button = Clicked;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#endif // WITHOUT_BUTTON
|
||
|
|
||
|
}
|
||
|
|
||
|
// ----------------------------------------------------------------------------
|
||
|
|
||
|
int16_t ClickEncoder::getValue(void)
|
||
|
{
|
||
|
int16_t val;
|
||
|
|
||
|
val = delta;
|
||
|
|
||
|
if (steps == 2) delta = val & 1;
|
||
|
else if (steps == 4) delta = val & 3;
|
||
|
else delta = 0; // default to 1 step per notch
|
||
|
|
||
|
if (steps == 4) val >>= 2;
|
||
|
if (steps == 2) val >>= 1;
|
||
|
|
||
|
int16_t r = 0;
|
||
|
int16_t accel = ((accelerationEnabled) ? (acceleration >> 8) : 0);
|
||
|
|
||
|
if (val < 0) {
|
||
|
r -= 1 + accel;
|
||
|
}
|
||
|
else if (val > 0) {
|
||
|
r += 1 + accel;
|
||
|
}
|
||
|
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
// ----------------------------------------------------------------------------
|
||
|
|
||
|
#ifndef WITHOUT_BUTTON
|
||
|
ClickEncoder::Button ClickEncoder::getButton(void)
|
||
|
{
|
||
|
ClickEncoder::Button ret = button;
|
||
|
if (button != ClickEncoder::Held) {
|
||
|
button = ClickEncoder::Open; // reset
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
#endif
|