Rotary encoder, with single-, double-click, hold and rotation.

For the discussion of Arduino related topics.

Moderator: phalanx

Post Reply
smathev
Posts: 3
Joined: Tue Oct 30, 2018 1:18 pm

Rotary encoder, with single-, double-click, hold and rotation.

Post by smathev » Tue Oct 30, 2018 1:26 pm

Hi all.

I've been trying to learn to program my Pro Micro board to use as a music-controller/volume button, IE.:
single click for play/pause, double-click for next, hold for previous and rotation for volume. I've found some various code snippets, which I've tried mashing together. Unfortunately, I can't get the rotation to register:

I'm using serial.print and keyboard.press to troubleshoot and figure out what's happening - but so far I can only get it to register clicks on the button, and once I start rotating the encoder it stops serial.printing, but still sends the keycode on button-clicks.

Any help in figuring out what I'm missing would be appreciated!

Thanks in advance.

Code: Select all

/* 4-Way Button:  Click, Double-Click, Press+Hold, and Press+Long-Hold Test Sketch

  By Jeff Saltzman
  Oct. 13, 2009

  To keep a physical interface as simple as possible, this sketch demonstrates generating four output events from a single push-button.
  1) Click:  rapid press and release
  2) Double-Click:  two clicks in quick succession
  3) Press and Hold:  holding the button down
  4) Long Press and Hold:  holding the button for a long time
*/

/*
  Connect button between pin 9 and ground
  Connect the encoder to pin 6, 7 and ground on the common pin
*/


#define ledPin1 17          // digital output pin for LED 1
#define ledPin2 16          // digital output pin for LED 2
#define ledPin3 15          // digital output pin for LED 3
#define ledPin4 14          // digital output pin for LED 4

// LED variables
boolean ledVal1 = false;    // state of LED 1
boolean ledVal2 = false;    // state of LED 2
boolean ledVal3 = false;    // state of LED 3
boolean ledVal4 = false;    // state of LED 4

// constants won't change. They're used here to
// set pin numbers:
const int buttonPin = 19;    // the number of the pushbutton pin
const int encoder0PinA = 6; 
const int encoder0PinB = 7;

// Variables will change:
int ledState = HIGH;         // the current state of the output pin
int buttonState;             // the current reading from the input pin
int lastButtonState = LOW;   // the previous reading from the input pin
long lastDebounceTime = 0;  // the last time the output pin was toggled
long debounceDelay = 50;    // the debounce time; increase if the output flickers
long longpressDelay = 500;    // If we hold it longer than 500ms then it is a long press.
int val;
int encoder0Pos = 0;
int encoder0PinALast = LOW;
long encoderLastValue = 0;
//int lastCommand; //0=volup 1=voldown 2=next, 3=prev
int lastDirection; //0=--, 1=++
int n = LOW;
int reading;


//=================================================

/*
  Include Keyboard to enable sending keycodes
*/

#include <Keyboard.h>

void setup() {
   Serial.begin(9600);      // open the serial port at 9600 bps:
  // Set button input pin
  pinMode(buttonPin, INPUT);
  digitalWrite(buttonPin, HIGH );
  // Rotary
  pinMode (encoder0PinA, INPUT);
  pinMode (encoder0PinB, INPUT);
  // Set LED output pins
  pinMode(ledPin1, OUTPUT);
  digitalWrite(ledPin1, ledVal1);
  pinMode(ledPin2, OUTPUT);
  digitalWrite(ledPin2, ledVal2);
  pinMode(ledPin3, OUTPUT);
  digitalWrite(ledPin3, ledVal3);
  pinMode(ledPin4, OUTPUT);
  digitalWrite(ledPin4, ledVal4);
  Keyboard.begin();
}

void loop() {
  // Get button event and act accordingly
  int b = checkButton();
  if (b == 1) clickEvent();
  if (b == 2) doubleClickEvent();
  if (b == 3) holdEvent();
  if (b == 4) longHoldEvent();
  if (b == 5) volumeUp();
  if (b == 6) volumeDown();
}

//=================================================
// Events to trigger

void clickEvent() {
  ledVal1 = !ledVal1;
  digitalWrite(ledPin1, ledVal1);
  //Keyboard.press(KEY_LEFT_CTRL);
  //Keyboard.press(KEY_LEFT_ALT);
  Keyboard.press('S');
  delay(100);
  Keyboard.releaseAll();
  Serial.println ("ClickEvent");
}

void doubleClickEvent() {
  ledVal2 = !ledVal2;
  digitalWrite(ledPin2, ledVal2);
  //Keyboard.press(KEY_LEFT_CTRL);
  //Keyboard.press(KEY_LEFT_ALT);
  Keyboard.press('A');
  delay(100);
  Keyboard.releaseAll();
  Serial.println ("DoubleClickEvent");
}

void holdEvent() {
  ledVal3 = !ledVal3;
  digitalWrite(ledPin3, ledVal3);
  //Keyboard.press(KEY_LEFT_CTRL);
  //Keyboard.press(KEY_LEFT_ALT);
  Keyboard.press('D');
  delay(100);
  Keyboard.releaseAll();
  Serial.println ("HoldEvent");
}

void longHoldEvent() {
  ledVal4 = !ledVal4;
  digitalWrite(ledPin4, ledVal4);
}

void volumeUp() {
    Keyboard.press('Q');
  delay(100);
  Keyboard.releaseAll();
  Serial.println ("Rotate R2 Event");
}

void volumeDown() {
    Keyboard.press('Z');
  delay(100);
  Keyboard.releaseAll();
  Serial.println ("Rotate R1 Event");
}

//=================================================
//  MULTI-CLICK:  One Button, Multiple Events

// Button timing variables
int debounce = 20;          // ms debounce p<eriod to prevent flickering when pressing or releasing the button
int DCgap = 250;            // max ms between clicks for a double click event
int holdTime = 1000;        // ms hold period: how long to wait for press+hold event
int longHoldTime = 3000;    // ms long hold period: how long to wait for press+hold event

// Button variables
boolean buttonVal = HIGH;   // value read from button
boolean buttonLast = HIGH;  // buffered value of the button's previous state
boolean DCwaiting = false;  // whether we're waiting for a double click (down)
boolean DConUp = false;     // whether to register a double click on next release, or whether to wait and click
boolean singleOK = true;    // whether it's OK to do a single click
long downTime = -1;         // time the button was pressed down
long upTime = -1;           // time the button was released
boolean ignoreUp = false;   // whether to ignore the button release because the click+hold was triggered
boolean waitForUp = false;        // when held, whether to wait for the up event
boolean holdEventPast = false;    // whether or not the hold event happened already
boolean longHoldEventPast = false;// whether or not the long hold event happened already

int checkButton() {
  int event = 0;
  buttonVal = digitalRead(buttonPin);
      n = digitalRead(encoder0PinA);
if ((encoder0PinALast == LOW) && (n == HIGH)) {
    if (digitalRead(encoder0PinB) == LOW) { 
      //to make a double check we are going the right direction.
      if (lastDirection == 0) {
        encoder0Pos--;
      }
      lastDirection = 0;
        DCwaiting = false;
        singleOK = false;
        DConUp = false;
    } else {
      if (lastDirection == 1) {
        encoder0Pos++;
        DCwaiting = false;
        singleOK = false;
        DConUp = false;
      }
      lastDirection = 1;
    }
  }
  encoder0PinALast = n;
  
  // Button pressed down
  if (buttonVal == LOW && buttonLast == HIGH && (millis() - upTime) > debounce)
  {
    downTime = millis();
    ignoreUp = false;
    waitForUp = false;
    singleOK = true;
    holdEventPast = false;
    longHoldEventPast = false;
    if ((millis() - upTime) < DCgap && DConUp == false && DCwaiting == true)  DConUp = true;
    else  DConUp = false;
    DCwaiting = false;
  }
  // Button released
  else if (buttonVal == HIGH && buttonLast == LOW && (millis() - downTime) > debounce)
  {
    if (not ignoreUp)
    {
      upTime = millis();
      if (DConUp == false) DCwaiting = true;
      else
      {
        event = 2;
        DConUp = false;
        DCwaiting = false;
        singleOK = false;
      }
    }
  }
  // Test for normal click event: DCgap expired
  if ( buttonVal == HIGH && (millis() - upTime) >= DCgap && DCwaiting == true && DConUp == false && singleOK == true && event != 2)
  {
    event = 1;
    DCwaiting = false;
  }
  // Test for hold
  if (buttonVal == LOW && (millis() - downTime) >= holdTime) {
    // Trigger "normal" hold
    if (not holdEventPast)
    {
      event = 3;
      waitForUp = true;
      ignoreUp = true;
      DConUp = false;
      DCwaiting = false;
      //downTime = millis();
      holdEventPast = true;
    }
    // Trigger "long" hold
    if ((millis() - downTime) >= longHoldTime)
    {
      if (not longHoldEventPast)
      {
        event = 4;
        longHoldEventPast = true;
      }
    }
  }
  if ( buttonVal == HIGH && encoderLastValue != encoder0Pos) {
    //nothing happens with the button so if the rotary encoder moves now, it is volume. 
    //Well actually this code runs also if the button isnt released, so we need to make sure it isnt a longpress aswell... which we do two rows up.
    if (encoderLastValue > encoder0Pos) {
      //volume down
        event = 5;
        DCwaiting = false;
        singleOK = false;
        DConUp = false;
      //Serial.println ("Volume down");
    }
    else if (encoderLastValue < encoder0Pos) {
      //volume up
        event = 6;
        DCwaiting = false;
        singleOK = false;
        DConUp = false;
      //Serial.println ("Volume up");
    }
  }

  buttonLast = buttonVal;
  encoderLastValue=encoder0Pos;
  return event;
}

paulvha
Posts: 297
Joined: Sat Nov 11, 2017 2:39 am

Re: Rotary encoder, with single-, double-click, hold and rotation.

Post by paulvha » Wed Oct 31, 2018 2:45 am

Couple of thoughts :
if your Serial.printing() stops completely, but your keycode are still sent, it feels like memory corruption is happening. You have declared encoderLastValue as long, but your encoder0Pos as int. You compare those and store values. Maybe try to define them both the same.

your encoder pins do not seem to have a pull-up or pull-down internal resistor set. Is that what you wanted ?

Not sure why it the check-button, when handling the encoder change on different places, it is also sets values that are only relevant to the button ( DCwaiting = false;, singleOK = false; DConUp = false;)

Looks to me that the encoder pins input have nothing to do with the button pin. There might be a better ways to handle. If encoder change is detected, handle that in check-button() completely in the beginning and return. If no encoder change, skip that code and handle the button. Otherwise I would make it 2 separate calls, one to check for the button and one to handle the encoder.

smathev
Posts: 3
Joined: Tue Oct 30, 2018 1:18 pm

Re: Rotary encoder, with single-, double-click, hold and rotation.

Post by smathev » Wed Oct 31, 2018 3:20 am

Hi. Thanks for responding and helping out.

I've corrected the issues you found:
encoderLastValue and encoder0Pos are now both int.
I've made the rotation-check into a seperate function, see code below. The values set that were only relevant to the button (DCwaiting = false;, singleOK = false; DConUp = false;) is because I'm a complete noob and was trying some things out :)

Concerning the Serial.Printing() stopping - I figured out that it only stops printing to the Serial Monitor in ArduinoIDE. If I restart (close and reopen) the serial monitor it starts resending the printing-messages for the button. However, still nothing on rotation.

My components are:
1. Rotary encoder: https://i.ebayimg.com/images/g/0~QAAOSw ... s-l640.jpg
2. A Pro Micro 5V.

My wiring is:
Rotary encoder button pins to ground and pin 19/A1 (that's working)
Rotary encoder rotation pins (the two outward pins) to pin 6 and pin 7 and also wired to VCC (both of them)
Rotary encoder middle pin to ground.
No resistors are used so far.

You question concerning pull_up or pull_down resistors I don't quite understand?

Once again, thanks for your time!

New code:

Code: Select all

/* 4-Way Button:  Click, Double-Click, Press+Hold, and Press+Long-Hold Test Sketch

  By Jeff Saltzman
  Oct. 13, 2009

  To keep a physical interface as simple as possible, this sketch demonstrates generating four output events from a single push-button.
  1) Click:  rapid press and release
  2) Double-Click:  two clicks in quick succession
  3) Press and Hold:  holding the button down
  4) Long Press and Hold:  holding the button for a long time
*/

/*
  Connect button between pin 9 and ground
  Connect the encoder to pin 6, 7 and ground on the common pin
*/


#define ledPin1 17          // digital output pin for LED 1
#define ledPin2 16          // digital output pin for LED 2
#define ledPin3 15          // digital output pin for LED 3
#define ledPin4 14          // digital output pin for LED 4

// LED variables
boolean ledVal1 = false;    // state of LED 1
boolean ledVal2 = false;    // state of LED 2
boolean ledVal3 = false;    // state of LED 3
boolean ledVal4 = false;    // state of LED 4

// constants won't change. They're used here to
// set pin numbers:
const int buttonPin = 19;    // the number of the pushbutton pin
const int encoder0PinA = 6;
const int encoder0PinB = 7;

// Variables will change:
int ledState = HIGH;         // the current state of the output pin
int buttonState;             // the current reading from the input pin
int lastButtonState = LOW;   // the previous reading from the input pin
long lastDebounceTime = 0;  // the last time the output pin was toggled
long debounceDelay = 50;    // the debounce time; increase if the output flickers
long longpressDelay = 500;    // If we hold it longer than 500ms then it is a long press.
int val;
int encoder0Pos = 0;
int encoder0PinALast = LOW;
int encoderLastValue = 0;
//int lastCommand; //0=volup 1=voldown 2=next, 3=prev
int lastDirection; //0=--, 1=++
int n = LOW;
int reading;


//=================================================

/*
  Include Keyboard to enable sending keycodes
*/

#include <Keyboard.h>

void setup() {
   Serial.begin(9600);      // open the serial port at 9600 bps:
  // Set button input pin
  pinMode(buttonPin, INPUT);
  digitalWrite(buttonPin, HIGH );
  // Rotary
  pinMode (encoder0PinA, INPUT);
  pinMode (encoder0PinB, INPUT);
  // Set LED output pins
  pinMode(ledPin1, OUTPUT);
  digitalWrite(ledPin1, ledVal1);
  pinMode(ledPin2, OUTPUT);
  digitalWrite(ledPin2, ledVal2);
  pinMode(ledPin3, OUTPUT);
  digitalWrite(ledPin3, ledVal3);
  pinMode(ledPin4, OUTPUT);
  digitalWrite(ledPin4, ledVal4);
  Keyboard.begin();
}

void loop() {
  // Get button event and act accordingly
  int b = checkButton();
  int r = rotationFunction();
  if (r == 2) volumeUp();
  if (r == 1) volumeDown();
  if (b == 1) clickEvent();
  if (b == 2) doubleClickEvent();
  if (b == 3) holdEvent();
  if (b == 4) longHoldEvent();
}

//=================================================
// Events to trigger

void clickEvent() {
  ledVal1 = !ledVal1;
  digitalWrite(ledPin1, ledVal1);
  Keyboard.press('S');
  delay(100);
  Keyboard.releaseAll();
  Serial.println ("ClickEvent");
}

void doubleClickEvent() {
  ledVal2 = !ledVal2;
  digitalWrite(ledPin2, ledVal2);
  Keyboard.press('A');
  delay(100);
  Keyboard.releaseAll();
  Serial.println ("DoubleClickEvent");
}

void holdEvent() {
  ledVal3 = !ledVal3;
  digitalWrite(ledPin3, ledVal3);
  Keyboard.press('D');
  delay(100);
  Keyboard.releaseAll();
  Serial.println ("HoldEvent");
}

void longHoldEvent() {
  ledVal4 = !ledVal4;
  digitalWrite(ledPin4, ledVal4);
}

void volumeUp() {
  Keyboard.press('Q');
  delay(100);
  Keyboard.releaseAll();
  Serial.println ("Rotate R2 Event");
}

void volumeDown() {
  Keyboard.press('Z');
  delay(100);
  Keyboard.releaseAll();
  Serial.println ("Rotate R1 Event");
}

//=================================================
//  MULTI-CLICK:  One Button, Multiple Events

// Button timing variables
int debounce = 20;          // ms debounce p<eriod to prevent flickering when pressing or releasing the button
int DCgap = 250;            // max ms between clicks for a double click event
int holdTime = 1000;        // ms hold period: how long to wait for press+hold event
int longHoldTime = 3000;    // ms long hold period: how long to wait for press+hold event

// Button variables
boolean buttonVal = HIGH;   // value read from button
boolean buttonLast = HIGH;  // buffered value of the button's previous state
boolean DCwaiting = false;  // whether we're waiting for a double click (down)
boolean DConUp = false;     // whether to register a double click on next release, or whether to wait and click
boolean singleOK = true;    // whether it's OK to do a single click
long downTime = -1;         // time the button was pressed down
long upTime = -1;           // time the button was released
boolean ignoreUp = false;   // whether to ignore the button release because the click+hold was triggered
boolean waitForUp = false;        // when held, whether to wait for the up event
boolean holdEventPast = false;    // whether or not the hold event happened already
boolean longHoldEventPast = false;// whether or not the long hold event happened already

int checkButton() {
  int event = 0;
  buttonVal = digitalRead(buttonPin);
  // Button pressed down
  if (buttonVal == LOW && buttonLast == HIGH && (millis() - upTime) > debounce)
  {
    downTime = millis();
    ignoreUp = false;
    waitForUp = false;
    singleOK = true;
    holdEventPast = false;
    longHoldEventPast = false;
    if ((millis() - upTime) < DCgap && DConUp == false && DCwaiting == true)  DConUp = true;
    else  DConUp = false;
    DCwaiting = false;
  }
  // Button released
  else if (buttonVal == HIGH && buttonLast == LOW && (millis() - downTime) > debounce)
  {
    if (not ignoreUp)
    {
      upTime = millis();
      if (DConUp == false) DCwaiting = true;
      else
      {
        event = 2;
        DConUp = false;
        DCwaiting = false;
        singleOK = false;
      }
    }
  }
  // Test for normal click event: DCgap expired
  if ( buttonVal == HIGH && (millis() - upTime) >= DCgap && DCwaiting == true && DConUp == false && singleOK == true && event != 2)
  {
    event = 1;
    DCwaiting = false;
  }
  // Test for hold
  if (buttonVal == LOW && (millis() - downTime) >= holdTime) {
    // Trigger "normal" hold
    if (not holdEventPast)
    {
      event = 3;
      waitForUp = true;
      ignoreUp = true;
      DConUp = false;
      DCwaiting = false;
      //downTime = millis();
      holdEventPast = true;
    }
    // Trigger "long" hold
    if ((millis() - downTime) >= longHoldTime)
    {
      if (not longHoldEventPast)
      {
        event = 4;
        longHoldEventPast = true;
      }
    }
  }

  buttonLast = buttonVal;
  return event;
}

int rotationFunction()  {
  int direction = 0;
    n = digitalRead(encoder0PinA);
  if ((encoder0PinALast == LOW) && (n == HIGH)) {
    if (digitalRead(encoder0PinB) == LOW) { 
      //to make a double check we are going the right direction.
      if (lastDirection == 0) {
        encoder0Pos--;
      }
      lastDirection = 0;
    } else {
      if (lastDirection == 1) {
        encoder0Pos++;
      }
      lastDirection = 1;
    }
  }
  encoder0PinALast = n;
  
  //We make the vol up/down descision with the lastButttonState.
  if (buttonVal == HIGH && checkButton == 0) {
    //nothing happens with the button so if the rotary encoder moves now, it is volume. 
    //Well actually this code runs also if the button isnt released, so we need to make sure it isnt a longpress aswell... which we do two rows up.
    if (encoderLastValue > encoder0Pos) {
      //volume down
  direction = 1;
    }
    else if (encoderLastValue < encoder0Pos) {
      //volume up
  direction = 2;
    }
  }
  return direction;
  encoderLastValue=encoder0Pos;
}

smathev
Posts: 3
Joined: Tue Oct 30, 2018 1:18 pm

Re: Rotary encoder, with single-, double-click, hold and rotation.

Post by smathev » Wed Oct 31, 2018 4:35 am

I've been working on it since your last input, and I managed to make it work as intended:

Code: Select all

/* 4-Way Button:  Click, Double-Click, Press+Hold, and Press+Long-Hold Test Sketch

  By Jeff Saltzman
  Oct. 13, 2009

  To keep a physical interface as simple as possible, this sketch demonstrates generating four output events from a single push-button.
  1) Click:  rapid press and release
  2) Double-Click:  two clicks in quick succession
  3) Press and Hold:  holding the button down
  4) Long Press and Hold:  holding the button for a long time
*/

/*
  Connect button between pin 9 and ground
  Connect the encoder to pin 6, 7 and ground on the common pin
*/


#define ledPin1 17          // digital output pin for LED 1
#define ledPin2 16          // digital output pin for LED 2
#define ledPin3 15          // digital output pin for LED 3
#define ledPin4 14          // digital output pin for LED 4

// LED variables
boolean ledVal1 = false;    // state of LED 1
boolean ledVal2 = false;    // state of LED 2
boolean ledVal3 = false;    // state of LED 3
boolean ledVal4 = false;    // state of LED 4

// constants won't change. They're used here to
// set pin numbers:
const int buttonPin = 19;    // the number of the pushbutton pin
const int encoder0PinA = 6;
const int encoder0PinB = 7;

// Variables will change:
int ledState = HIGH;         // the current state of the output pin
int buttonState;             // the current reading from the input pin
int lastButtonState = LOW;   // the previous reading from the input pin
long lastDebounceTime = 0;  // the last time the output pin was toggled
long debounceDelay = 50;    // the debounce time; increase if the output flickers
long longpressDelay = 500;    // If we hold it longer than 500ms then it is a long press.
int val;
int encoder0Pos = 0;
int encoder0PinALast = LOW;
int encoderLastValue = 0;
//int lastCommand; //0=volup 1=voldown 2=next, 3=prev
int lastDirection; //0=--, 1=++
int n = LOW;
int reading;


//=================================================

/*
  Include Keyboard to enable sending keycodes
*/

#include <Keyboard.h>

void setup() {
   Serial.begin(9600);      // open the serial port at 9600 bps:
  // Set button input pin
  pinMode(buttonPin, INPUT);
  digitalWrite(buttonPin, HIGH );
  // Rotary
  pinMode (encoder0PinA, INPUT_PULLUP);
  pinMode (encoder0PinB, INPUT_PULLUP);
  // Set LED output pins
  pinMode(ledPin1, OUTPUT);
  digitalWrite(ledPin1, ledVal1);
  pinMode(ledPin2, OUTPUT);
  digitalWrite(ledPin2, ledVal2);
  pinMode(ledPin3, OUTPUT);
  digitalWrite(ledPin3, ledVal3);
  pinMode(ledPin4, OUTPUT);
  digitalWrite(ledPin4, ledVal4);
  Keyboard.begin();
}

void loop() {
  // Get button event and act accordingly
  int r = rotationFunction();
  if (r == 1) volumeDown();
  if (r == 2) volumeUp();
  int b = checkButton();
  if (b == 1) clickEvent();
  if (b == 2) doubleClickEvent();
  if (b == 3) tripleClickEvent();
  if (b == 3) holdEvent();
  if (b == 4) longHoldEvent();
}

//=================================================
// Events to trigger

void clickEvent() {
  ledVal1 = !ledVal1;
  digitalWrite(ledPin1, ledVal1);
  Keyboard.press('S');
  delay(100);
  Keyboard.releaseAll();
  Serial.println ("ClickEvent");
}

void doubleClickEvent() {
  ledVal2 = !ledVal2;
  digitalWrite(ledPin2, ledVal2);
  Keyboard.press('D');
  delay(100);
  Keyboard.releaseAll();
  Serial.println ("DoubleClickEvent");
}

void doubleClickEvent() {
  ledVal2 = !ledVal2;
  digitalWrite(ledPin2, ledVal2);
  Keyboard.press('T');
  delay(100);
  Keyboard.releaseAll();
  Serial.println ("tripleClickEvent");
}

void holdEvent() {
  ledVal3 = !ledVal3;
  digitalWrite(ledPin3, ledVal3);
  Keyboard.press('H');
  delay(100);
  Keyboard.releaseAll();
  Serial.println ("HoldEvent");
}

void longHoldEvent() {
  ledVal4 = !ledVal4;
  digitalWrite(ledPin4, ledVal4);
    Keyboard.press('L');
  delay(100);
  Keyboard.releaseAll();
  Serial.println ("HoldEvent");
}

void volumeUp() {
  Keyboard.press('U');
  delay(100);
  Keyboard.releaseAll();
  Serial.println ("Rotate R2 Event");
}

void volumeDown() {
  Keyboard.press('D');
  delay(100);
  Keyboard.releaseAll();
  Serial.println ("Rotate R1 Event");
}

//=================================================
//  MULTI-CLICK:  One Button, Multiple Events

// Button timing variables
int debounce = 20;          // ms debounce p<eriod to prevent flickering when pressing or releasing the button
int DCgap = 250;            // max ms between clicks for a double click event
int holdTime = 1000;        // ms hold period: how long to wait for press+hold event
int longHoldTime = 3000;    // ms long hold period: how long to wait for press+hold event

// Button variables
boolean buttonVal = HIGH;   // value read from button
boolean buttonLast = HIGH;  // buffered value of the button's previous state
boolean DCwaiting = false;  // whether we're waiting for a double click (down)
boolean DConUp = false;     // whether to register a double click on next release, or whether to wait and click
boolean singleOK = true;    // whether it's OK to do a single click
long downTime = -1;         // time the button was pressed down
long upTime = -1;           // time the button was released
boolean ignoreUp = false;   // whether to ignore the button release because the click+hold was triggered
boolean waitForUp = false;        // when held, whether to wait for the up event
boolean holdEventPast = false;    // whether or not the hold event happened already
boolean longHoldEventPast = false;// whether or not the long hold event happened already

int checkButton() {
  int event = 0;
  buttonVal = digitalRead(buttonPin);
  // Button pressed down
  if (buttonVal == LOW && buttonLast == HIGH && (millis() - upTime) > debounce)
  {
    downTime = millis();
    ignoreUp = false;
    waitForUp = false;
    singleOK = true;
    holdEventPast = false;
    longHoldEventPast = false;
    if ((millis() - upTime) < DCgap && DConUp == false && DCwaiting == true)  DConUp = true;
    else  DConUp = false;
    DCwaiting = false;
  }
  // Button released
  else if (buttonVal == HIGH && buttonLast == LOW && (millis() - downTime) > debounce)
  {
    if (not ignoreUp)
    {
      upTime = millis();
      if (DConUp == false) DCwaiting = true;
      else
      {
        event = 2;
        DConUp = false;
        DCwaiting = false;
        singleOK = false;
      }
    }
  }
  // Test for normal click event: DCgap expired
  if ( buttonVal == HIGH && (millis() - upTime) >= DCgap && DCwaiting == true && DConUp == false && singleOK == true && event != 2)
  {
    event = 1;
    DCwaiting = false;
  }
  // Test for hold
  if (buttonVal == LOW && (millis() - downTime) >= holdTime) {
    // Trigger "normal" hold
    if (not holdEventPast)
    {
      event = 3;
      waitForUp = true;
      ignoreUp = true;
      DConUp = false;
      DCwaiting = false;
      //downTime = millis();
      holdEventPast = true;
    }
    // Trigger "long" hold
    if ((millis() - downTime) >= longHoldTime)
    {
      if (not longHoldEventPast)
      {
        event = 4;
        longHoldEventPast = true;
      }
    }
  }

  buttonLast = buttonVal;
  return event;
}

int rotationFunction()  {
  int direction = 0;
    n = digitalRead(encoder0PinA);
  if ((encoder0PinALast == LOW) && (n == HIGH)) {
    if (digitalRead(encoder0PinB) == LOW) { 
      //to make a double check we are going the right direction.
        encoder0Pos--;
    } else {
        encoder0Pos++;
    }
  }
  encoder0PinALast = n;
 
  if (buttonLast == HIGH) {
    if (encoderLastValue > encoder0Pos) {
      //volume down
  direction = 1;
    }
    else if (encoderLastValue < encoder0Pos) {
      //volume up
  direction = 2;
    }
  }
  encoderLastValue=encoder0Pos;
  return direction;
}
This is probably not the best or fastest working code, but it is now working as it should - IE. both sending keypresses and serial.Printin when I turn the rotary encoder.

Some of the issues that I looked at are the if-statements around the encoder0pos-- and encoder0pos++, which forced a loop, because of the LastDirection statement. And, furthermore I added a resistor (10k, I believe) between each of the VCC<>Rotary Encoder pins.

So, above is the working code (which functions for me) with a wiring scheme like the following:
Rotary encoder button pins to ground and pin 19/A1 (that's working)
Rotary encoder rotation pins (the two outward pins) to pin 6 and pin 7
Rotary encoder rotation pins (the two outward pins) are also each wired to VCC, both with a 10k resistor in between.
Rotary encoder middle pin to ground.

Any and all input regarding cleaner, faster or simpler code is appreciated!

paulvha
Posts: 297
Joined: Sat Nov 11, 2017 2:39 am

Re: Rotary encoder, with single-, double-click, hold and rotation.

Post by paulvha » Wed Oct 31, 2018 6:28 am

Good.. !!!

To your earlier question about pull-up: I see you have to add internal pull-up resistors with the commands: pinMode (encoder0PinA, INPUT_PULLUP); and pinMode (encoder0PinB, INPUT_PULLUP); Otherwise it can not detect a "high"

Actually the commands on the buttonPin are the "old" way of doing the same :
pinMode(buttonPin, INPUT);
digitalWrite(buttonPin, HIGH );

and could be replaced with: pinMode (buttonPin, INPUT_PULLUP);

Not sure why you kept the check on the button in int rotationFunction()
if (buttonLast == HIGH) {
Is that necessary?

Post Reply