#ifndef MARET_RotaryEncoder_h
#define MARET_RotaryEncoder_h
extern "C" {
typedef void (*callbackFunction)(void);
typedef void (*parameterizedCallbackFunction)(void *);
}
class MARET_RotaryEncoder{
public:
enum class Direction{
NOROTATION = 0,
CLOCKWISE = 1,
COUNTERCLOCKWISE = -1
};
MARET_RotaryEncoder();
MARET_RotaryEncoder(const int pinSW, const int pinCLK, const int pinDT, const boolean activeLow = true, const bool pullupActive = true);
MARET_RotaryEncoder(const int pinSW, const boolean activeLow = true, const bool pullupActive = true);
MARET_RotaryEncoder(const int pinCLK, const int pinDT);
long getPosition();
Direction getDirection();
void setDebounceTicks(const int ticks);
void setClickTicks(const int ticks);
void setPressTicks(const int ticks);
void click(callbackFunction newFunction);
void click(parameterizedCallbackFunction newFunction, void *parameter);
void doubleClick(callbackFunction newFunction);
void doubleClick(parameterizedCallbackFunction newFunction, void *parameter);
void multiClick(callbackFunction newFunction);
void multiClick(parameterizedCallbackFunction newFunction, void *parameter);
void longPressStart(callbackFunction newFunction);
void longPressStart(parameterizedCallbackFunction newFunction, void *parameter);
void longPressStop(callbackFunction newFunction);
void longPressStop(parameterizedCallbackFunction newFunction, void *parameter);
void duringLongPress(callbackFunction newFunction);
void duringLongPress(parameterizedCallbackFunction newFunction, void *parameter);
void setPosition(long newPosition);
void tick(void);
void tick(bool level);
void reset(void);
unsigned long getMillisBetweenRotations() const;
unsigned long getRPM();
int getNumberClicks(void);
bool right();
bool left();
bool isIdle() const { return _state == OCS_INIT; }
bool isLongPressed() const { return _state == OCS_PRESS; };
private:
int _pinSW;
int _pinCLK;
int _pinDT;
int _rotation;
volatile int8_t _lastPosition;
volatile long _position;
volatile long _positionExt;
volatile long _positionExtPrev;
unsigned long _positionExtTime;
unsigned long _positionExtTimePrev;
unsigned int _debounceTicks = 50;
unsigned int _clickTicks = 400;
unsigned int _pressTicks = 800;
int _buttonPressed;
callbackFunction _clickFunc = NULL;
parameterizedCallbackFunction _paramClickFunc = NULL;
void *_clickFuncParam = NULL;
callbackFunction _doubleClickFunc = NULL;
parameterizedCallbackFunction _paramDoubleClickFunc = NULL;
void *_doubleClickFuncParam = NULL;
callbackFunction _multiClickFunc = NULL;
parameterizedCallbackFunction _paramMultiClickFunc = NULL;
void *_multiClickFuncParam = NULL;
callbackFunction _longPressStartFunc = NULL;
parameterizedCallbackFunction _paramLongPressStartFunc = NULL;
void *_longPressStartFuncParam = NULL;
callbackFunction _longPressStopFunc = NULL;
parameterizedCallbackFunction _paramLongPressStopFunc = NULL;
void *_longPressStopFuncParam;
callbackFunction _duringLongPressFunc = NULL;
parameterizedCallbackFunction _paramDuringLongPressFunc = NULL;
void *_duringLongPressFuncParam = NULL;
enum stateMachine_t : int {
OCS_INIT = 0,
OCS_DOWN = 1,
OCS_UP = 2,
OCS_COUNT = 3,
OCS_PRESS = 6,
OCS_PRESSEND = 7,
UNKNOWN = 99
};
void _newState(stateMachine_t nextState);
stateMachine_t _state = OCS_INIT;
stateMachine_t _lastState = OCS_INIT;
unsigned long _startTime;
int _nClicks;
int _maxClicks = 1;
};
#endif
#define LATCH0 0
#define LATCH3 3
const int8_t KNOBDIR[] = {
0, -1, 1, 0,
1, 0, 0, -1,
-1, 0, 0, 1,
0, 1, -1, 0
};
MARET_RotaryEncoder::MARET_RotaryEncoder(){
_pinSW = -1;
}
MARET_RotaryEncoder::MARET_RotaryEncoder(const int pinSW, const int pinCLK, const int pinDT, const boolean activeLow, const bool pullupActive){
_pinSW = pinSW;
_pinCLK = pinCLK;
_pinDT = pinDT;
if (activeLow) {
_buttonPressed = LOW;
}
else {
_buttonPressed = HIGH;
}
if (pullupActive) {
pinMode(pinSW, INPUT_PULLUP);
}
else {
pinMode(pinSW, INPUT);
}
pinMode(pinCLK, INPUT_PULLUP);
pinMode(pinDT, INPUT_PULLUP);
int sig1 = digitalRead(_pinCLK);
int sig2 = digitalRead(_pinDT);
_lastPosition = sig1 | (sig2 << 1);
_position = 0;
_positionExt = 0;
_positionExtPrev = 0;
_rotation = 0;
}
MARET_RotaryEncoder::MARET_RotaryEncoder(const int pinSW, const boolean activeLow, const bool pullupActive){
_pinSW = pinSW;
if (activeLow) {
_buttonPressed = LOW;
}
else {
_buttonPressed = HIGH;
}
if (pullupActive) {
pinMode(pinSW, INPUT_PULLUP);
}
else {
pinMode(pinSW, INPUT);
}
}
MARET_RotaryEncoder::MARET_RotaryEncoder(const int pinCLK, const int pinDT){
_pinCLK = pinCLK;
_pinDT = pinDT;
pinMode(pinCLK, INPUT_PULLUP);
pinMode(pinDT, INPUT_PULLUP);
int sig1 = digitalRead(_pinCLK);
int sig2 = digitalRead(_pinDT);
_lastPosition = sig1 | (sig2 << 1);
_position = 0;
_positionExt = 0;
_positionExtPrev = 0;
_rotation = 0;
}
long MARET_RotaryEncoder::getPosition(){
return _positionExt;
}
MARET_RotaryEncoder::Direction MARET_RotaryEncoder::getDirection(){
MARET_RotaryEncoder::Direction ret = Direction::NOROTATION;
if(_positionExtPrev > _positionExt){
ret = Direction::COUNTERCLOCKWISE;
_positionExtPrev = _positionExt;
_rotation = -1;
}
else if(_positionExtPrev < _positionExt){
ret = Direction::CLOCKWISE;
_positionExtPrev = _positionExt;
_rotation = 1;
}
else{
ret = Direction::NOROTATION;
_positionExtPrev = _positionExt;
_rotation = 0;
}
return ret;
}
void MARET_RotaryEncoder::setPosition(long newPosition){
_position = ((newPosition << 2) | (_position & 0x03L));
_positionExt = newPosition;
_positionExtPrev = newPosition;
}
void MARET_RotaryEncoder::setDebounceTicks(const int ticks){
_debounceTicks = ticks;
}
void MARET_RotaryEncoder::setClickTicks(const int ticks){
_clickTicks = ticks;
}
void MARET_RotaryEncoder::setPressTicks(const int ticks){
_pressTicks = ticks;
}
void MARET_RotaryEncoder::click(callbackFunction newFunction){
_clickFunc = newFunction;
}
void MARET_RotaryEncoder::click(parameterizedCallbackFunction newFunction, void *parameter){
_paramClickFunc = newFunction;
_clickFuncParam = parameter;
}
void MARET_RotaryEncoder::doubleClick(callbackFunction newFunction){
_doubleClickFunc = newFunction;
_maxClicks = max(_maxClicks, 2);
}
void MARET_RotaryEncoder::doubleClick(parameterizedCallbackFunction newFunction, void *parameter){
_paramDoubleClickFunc = newFunction;
_doubleClickFuncParam = parameter;
_maxClicks = max(_maxClicks, 2);
}
void MARET_RotaryEncoder::multiClick(callbackFunction newFunction){
_multiClickFunc = newFunction;
_maxClicks = max(_maxClicks, 100);
}
void MARET_RotaryEncoder::multiClick(parameterizedCallbackFunction newFunction, void *parameter){
_paramMultiClickFunc = newFunction;
_multiClickFuncParam = parameter;
_maxClicks = max(_maxClicks, 100);
}
void MARET_RotaryEncoder::longPressStart(callbackFunction newFunction){
_longPressStartFunc = newFunction;
}
void MARET_RotaryEncoder::longPressStart(parameterizedCallbackFunction newFunction, void *parameter){
_paramLongPressStartFunc = newFunction;
_longPressStartFuncParam = parameter;
}
void MARET_RotaryEncoder::longPressStop(callbackFunction newFunction){
_longPressStopFunc = newFunction;
}
void MARET_RotaryEncoder::longPressStop(parameterizedCallbackFunction newFunction, void *parameter){
_paramLongPressStopFunc = newFunction;
_longPressStopFuncParam = parameter;
}
void MARET_RotaryEncoder::duringLongPress(callbackFunction newFunction){
_duringLongPressFunc = newFunction;
}
void MARET_RotaryEncoder::duringLongPress(parameterizedCallbackFunction newFunction, void *parameter){
_paramDuringLongPressFunc = newFunction;
_duringLongPressFuncParam = parameter;
}
void MARET_RotaryEncoder::reset(void){
_state = MARET_RotaryEncoder::OCS_INIT;
_lastState = MARET_RotaryEncoder::OCS_INIT;
_nClicks = 0;
_startTime = 0;
}
int MARET_RotaryEncoder::getNumberClicks(void){
return _nClicks;
}
void MARET_RotaryEncoder::tick(void){
int sig1 = digitalRead(_pinCLK);
int sig2 = digitalRead(_pinDT);
int8_t thisPosition = sig1 | (sig2 << 1);
if (_pinSW >= 0) {
tick(digitalRead(_pinSW) == _buttonPressed);
}
if(_lastPosition != thisPosition){
_position += KNOBDIR[thisPosition | (_lastPosition << 2)];
_lastPosition = thisPosition;
if((thisPosition == LATCH0) || (thisPosition == LATCH3)){
_positionExt = _position >> 2;
_positionExtTimePrev = _positionExtTime;
_positionExtTime = millis();
}
}
}
void MARET_RotaryEncoder::_newState(stateMachine_t nextState){
_lastState = _state;
_state = nextState;
}
void MARET_RotaryEncoder::tick(bool activeLevel){
unsigned long now = millis();
unsigned long waitTime = (now - _startTime);
switch (_state) {
case MARET_RotaryEncoder::OCS_INIT:
if (activeLevel) {
_newState(MARET_RotaryEncoder::OCS_DOWN);
_startTime = now;
_nClicks = 0;
}
break;
case MARET_RotaryEncoder::OCS_DOWN:
if ((!activeLevel) && (waitTime < _debounceTicks)) {
_newState(_lastState);
}
else if (!activeLevel) {
_newState(MARET_RotaryEncoder::OCS_UP);
_startTime = now;
}
else if ((activeLevel) && (waitTime > _pressTicks)) {
if (_longPressStartFunc) _longPressStartFunc();
if (_paramLongPressStartFunc) _paramLongPressStartFunc(_longPressStartFuncParam);
_newState(MARET_RotaryEncoder::OCS_PRESS);
}
break;
case MARET_RotaryEncoder::OCS_UP:
if ((activeLevel) && (waitTime < _debounceTicks)) {
_newState(_lastState);
}
else if (waitTime >= _debounceTicks) {
_nClicks++;
_newState(MARET_RotaryEncoder::OCS_COUNT);
}
break;
case MARET_RotaryEncoder::OCS_COUNT:
if (activeLevel) {
_newState(MARET_RotaryEncoder::OCS_DOWN);
_startTime = now;
}
else if ((waitTime > _clickTicks) || (_nClicks == _maxClicks)) {
if (_nClicks == 1) {
if (_clickFunc) _clickFunc();
if (_paramClickFunc) _paramClickFunc(_clickFuncParam);
}
else if (_nClicks == 2) {
if (_doubleClickFunc) _doubleClickFunc();
if (_paramDoubleClickFunc) _paramDoubleClickFunc(_doubleClickFuncParam);
}
else {
if (_multiClickFunc) _multiClickFunc();
if (_paramMultiClickFunc) _paramMultiClickFunc(_multiClickFuncParam);
}
reset();
}
break;
case MARET_RotaryEncoder::OCS_PRESS:
if (!activeLevel) {
_newState(MARET_RotaryEncoder::OCS_PRESSEND);
_startTime = now;
}
else {
if (_duringLongPressFunc) _duringLongPressFunc();
if (_paramDuringLongPressFunc) _paramDuringLongPressFunc(_duringLongPressFuncParam);
}
break;
case MARET_RotaryEncoder::OCS_PRESSEND:
if ((activeLevel) && (waitTime < _debounceTicks)) {
_newState(_lastState);
}
else if (waitTime >= _debounceTicks) {
if (_longPressStopFunc) _longPressStopFunc();
if (_paramLongPressStopFunc) _paramLongPressStopFunc(_longPressStopFuncParam);
reset();
}
break;
default:
_newState(MARET_RotaryEncoder::OCS_INIT);
break;
}
}
unsigned long MARET_RotaryEncoder::getMillisBetweenRotations() const{
return (_positionExtTime - _positionExtTimePrev);
}
unsigned long MARET_RotaryEncoder::getRPM(){
unsigned long timeBetweenLastPosition = _positionExtTime - _positionExtTimePrev;
unsigned long timeToLastPosition = millis() - _positionExtTime;
unsigned long t = max(timeBetweenLastPosition, timeToLastPosition);
return 60000.0 / ((float)(t * 20));
}
bool MARET_RotaryEncoder::right(){
if(_rotation == 1){
_rotation = 0;
return true;
}
else{
return false;
}
}
bool MARET_RotaryEncoder::left(){
if(_rotation == -1){
_rotation = 0;
return true;
}
else{
return false;
}
}
#define led 2
#define SW 3
#define CLK 4
#define DT 5
MARET_RotaryEncoder rotary(3, true);
int ledState = LOW;
unsigned long pressStartTime;
void checkTicks(){
rotary.tick();
}
void singleClick(){
Serial.println("click() is detected!");
}
void doubleClick(){
Serial.println("doubleClick() is detected!");
ledState = !ledState;
digitalWrite(led, ledState);
}
void multiClick(){
int n = rotary.getNumberClicks();
if(n == 3){
Serial.println("triple click is detected!");
}
else if(n == 4){
Serial.println("quadruple click is detected!");
}
else{
Serial.print("multiClick( ");
Serial.print(n);
Serial.println(" ) is detected!");
}
ledState = !ledState;
digitalWrite(led, ledState);
}
void pressStart(){
Serial.println("pressStart()");
pressStartTime = millis() - 1000;
}
void pressStop(){
Serial.print("pressStop( ");
Serial.print(millis() - pressStartTime);
Serial.println(" ) is detected!");
}
void setup(){
Serial.begin(9600);
Serial.println("MARET_RotaryEncoder example with interrupts is starting!");
pinMode(led, OUTPUT);
digitalWrite(led, ledState);
attachInterrupt(digitalPinToInterrupt(SW), checkTicks, CHANGE);
rotary.click(singleClick);
rotary.doubleClick(doubleClick);
rotary.multiClick(multiClick);
rotary.setPressTicks(1000);
rotary.longPressStart(pressStart);
rotary.longPressStop(pressStop);
}
void loop(){
rotary.tick();
delay(10);
}