spejsiot/mopidy-control/app/ClickEncoder.cpp

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