debouncing rotary encoder - almost there...

working on my first arduino project a spot welder to make battery packs.

ive put in functionality to change timings for "heat", "delay" and "weld" via an LCD menu.

i had quite some problems with a bouncy rotary encoder, then found some handy code on the web to debounce the encoder. all works well now, but as soon as i add the "write to lcd" code (currently commented out), the rotary encoder output starts bouncing again.

i think the write to lcd (or any other code) takes too long and this causes the problem.

im thinking of using an interupt to "write to lcd" with a delay of ~100ms after the encoder has stopped turning or something like that. (the normal if statements with delays dont seem to work)

full code below, any tips on this issue (or any other comments on my first cpp code) would be great!

thanks
S

//#define PinCLK 6
//#define PinDT 7
//#define PinSW 8   //<<< what do these do???!! xxx

#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27,20,4);  // set the LCD address to 0x27 for a 16 chars and 2 line display

// Rotary Encoder Module connections
const int PinSW=8;   // Rotary Encoder Switch PIN number
const int PinDT=7;    // DATA signal
const int PinCLK=6;    // CLOCK signal
const int TriggerSwitch = 2;
const int RelayOutput = 12;

int EncoderButtonState = 0; 
int TriggerSwitchState = 0 ;
int RelayState = 0 ; 

int SelectedTimeIncrement = 1;  //
int TimeIncrementOptions[] = {1,10,100,1000} ;
int Index1 = 0 ; 
int Index2 = 0 ; 
   
// Variables to debounce Rotary Encoder
unsigned long TimeOfLastDebounce = 0;
int DelayofDebounce = 1 ;//0.01;

//Variable to debounce LCD
unsigned long TimeOfLastLCDwrite;
int LCDdelay = 300;

// Store previous Pins state
int PreviousCLK;   
int PreviousDATA;

int TimeVariableStartValues[] = {50, 20, 150} ;  
//int x ; //dummy variable
int temp ;
unsigned long lastButtonPress = 0; //used to debounce rotary encoder BUTTON

void setup() {
  // Put current pins state in variables
  PreviousCLK=digitalRead(PinCLK);
  PreviousDATA=digitalRead(PinDT);

  Serial.begin (9600);

  // Set the Switch pin to use Arduino PULLUP resistors
  pinMode(PinSW, INPUT_PULLUP);
  pinMode(PinDT, INPUT) ; //_PULLUP);
  pinMode(PinCLK, INPUT) ; //   _PULLUP); 

   //SET UP LCD
   lcd.init();
   lcd.backlight();
   // WriteResultsToLCD (SelectedTimeIncrement , TimeVariableStartValues[0], TimeVariableStartValues[1], TimeVariableStartValues[2]) ;
}

void loop() {
  
        

          // If enough time has passed check the rotary encoder
          if ((millis() - TimeOfLastDebounce) > DelayofDebounce) {

              check_rotary();  // Rotary Encoder check routine below
                 
              PreviousCLK=digitalRead(PinCLK);
              PreviousDATA=digitalRead(PinDT);
              
              TimeOfLastDebounce=millis();  
            }  
}

// Check if Rotary Encoder was moved
void check_rotary() {

             if ((PreviousCLK == 0) && (PreviousDATA == 1)) {
                if ((digitalRead(PinCLK) == 1) && (digitalRead(PinDT) == 0)) {
                  Toggle_UP_function() ;
                }
                if ((digitalRead(PinCLK) == 1) && (digitalRead(PinDT) == 1)) {
                  Toggle_DOWN_function();
                }
              }
            
            if ((PreviousCLK == 1) && (PreviousDATA == 0)) {
                if ((digitalRead(PinCLK) == 0) && (digitalRead(PinDT) == 1)) {
                  Toggle_UP_function();
                }
                if ((digitalRead(PinCLK) == 0) && (digitalRead(PinDT) == 0)) {
                  Toggle_DOWN_function();
                }
              }
            
            if ((PreviousCLK == 1) && (PreviousDATA == 1)) {
                if ((digitalRead(PinCLK) == 0) && (digitalRead(PinDT) == 1)) {
                  Toggle_UP_function();
                }
                if ((digitalRead(PinCLK) == 0) && (digitalRead(PinDT) == 0)) {
                  Toggle_DOWN_function();
                }
              }  
            
            if ((PreviousCLK == 0) && (PreviousDATA == 0)) {
                if ((digitalRead(PinCLK) == 1) && (digitalRead(PinDT) == 0)) {
                  Toggle_UP_function();
                }
                if ((digitalRead(PinCLK) == 1) && (digitalRead(PinDT) == 1)) {
                  Toggle_DOWN_function();
                }
              }             
 }

void Toggle_UP_function(){

        if (Index1 == 0) {
                  if (Index2==3) (Index2=0);
                  else if (Index2 ++ );               
        }

        else if (Index1 != 0) {
        temp = Index1 -1 ;
        TimeVariableStartValues[temp] ++ ;

        }

        if ((millis()- TimeOfLastLCDwrite) > LCDdelay){   
           // WriteResultsToLCD (SelectedTimeIncrement , TimeVariableStartValues[0], TimeVariableStartValues[1], TimeVariableStartValues[2]) ;
          }
}

void Toggle_DOWN_function(){

        if (Index1 == 0) {
                  if (Index2==0) (Index2=3);
                  else if (Index2 --
        }

        else if (Index1 != 0) {
        temp = Index1 -1 ;
        TimeVariableStartValues[temp] -- ;      
        }

        if ((millis()- TimeOfLastLCDwrite) > LCDdelay){   
           // WriteResultsToLCD (SelectedTimeIncrement , TimeVariableStartValues[0], TimeVariableStartValues[1], TimeVariableStartValues[2]) ;
        }
}

void WriteResultsToLCD (int a, int b, int c, int d) {
            
       lcd.setCursor(0,0);
       lcd.print("SET INCR,MSec       ");
       lcd.setCursor(0,1);
       lcd.print("HEAT                ");
       lcd.setCursor(0,2);
       lcd.print("DELAY               ");
       lcd.setCursor(0,3);
       lcd.print("WELD                ");

       lcd.setCursor(14,0);
       lcd.print(a);
             
       lcd.setCursor(8,1);
       lcd.print(b);
   
       lcd.setCursor(8,2);
       lcd.print(c);
       lcd.setCursor(8,3);
       lcd.print(d);

          TimeOfLastLCDwrite = millis();
          

}

2020_06_04_SpotWelder_2.0.ino (7.76 KB)

Hello sapre,
Welcome to the forum.

++Karma; // For posting your code correctly on your first post.

I can't give you a complete answer to your problem but:

delay(300); // Put in a slight delay to help debounce the reading

That is not a 'slight delay', it is a lifetime in processor terms. To put your 'slight delay' in perspective, when I wrote code to read a rotary encoder I discovered I had to read it every millisecond or more often to read it reliably. You have 300 milliseconds where nothing happens. Also, for debouncing 20 milliseconds is typically OK. Don't use delay for anything, delay stops your program dead.

These will help you:
Using millis for timing
Demonstration for several things at the same time
de-bounce tutorial

There's probably other stuff you need, I am sure someone else will provide additional help.

I expect to get flamed for saying this, but I use hardware debounce on my encoders (like the KT-040). Set the pinMode to INPUT_PULLUP and connect 0.1uF caps from A, B and switch from inputs to ground (CLK, DT and SW to ground). Very simple.

I use a Bourns PEC12R rotary encoder in one of my designs, with this datasheet suggested filter. It is used to indicate the file number on an SD card to be accessed, which have a limit of 0 to 99, or 0 to FF, the range is selectable with the HEX_FILE_NAMES switch (limited by a 2 character display).
You could do similar, with the rotary encoder pulses being interpreted in the Interrupt Service Routine to count stuff up and down, where loop() then does something with the data.

Nack Gammon wrote this PCINT code to read the rotary encoder turns with the 328P processor.

volatile boolean fired = false;
const byte Encoder_A_Pin = 8;  // PB0, pin 12 (TQFP) on board
const byte Encoder_B_Pin = 9;  // PB1, pin 13 (TQFP) on board
const unsigned long ROTARY_DEBOUNCE_TIME = 100; // milliseconds

  volatile int fileNumber = 0;

  #if HEX_FILE_NAMES
    const int MAX_FILE_NUMBER = 0xFF;
  #else
    const int MAX_FILE_NUMBER = 99;
  #endif // HEX_FILE_NAMES

In setup:  
// pin change interrupt (example for D9)
  PCMSK0 = bit (PCINT0) | bit (PCINT1);  // want pin 8 and 9
  PCIFR  = bit (PCIF0);   // clear any outstanding interrupts
  PCICR  = bit (PCIE0);   // enable pin change interrupts for D0 to D7

In loop:
    if (fired)
      {
      // debugging display perhaps?
          
      fired = false;
      Serial.println ("tick"); // used to tell if the rotary encoder was clicked or not.

      }  // end if fired

Interrupt Service Routine to handle the PCINTs from the encoder turning:
 // handle pin change interrupt for D8 to D13 here
ISR (PCINT0_vect)
{
static byte pinA, pinB;  
static boolean ready;
static unsigned long lastFiredTime;

  byte newPinA = digitalRead (Encoder_A_Pin);
  byte newPinB = digitalRead (Encoder_B_Pin);
  
  if (pinA == newPinA && 
      pinB == newPinB)
      return;    // spurious interrupt

  // so we only record a turn on both the same (HH or LL)
  
  // Forward is: LH/HH or HL/LL
  // Reverse is: HL/HH or LH/LL

  if (newPinA == newPinB)
    {
    if (ready)
      {        
      if (millis () - lastFiredTime >= ROTARY_DEBOUNCE_TIME)
        {
        if (newPinA == HIGH)  // must be HH now
          {
          if (pinA == LOW)
            fileNumber ++;
          else
            fileNumber --;
          }
        else
          {                  // must be LL now
          if (pinA == LOW)  
            fileNumber --;
          else
            fileNumber ++;        
          }
        if (fileNumber > MAX_FILE_NUMBER)
          fileNumber = 0;
        else if (fileNumber < 0)
          fileNumber = MAX_FILE_NUMBER;
        lastFiredTime = millis ();
        fired = true;
        } 
      ready = false;
      }  // end of being ready
    }  // end of completed click
  else
    ready = true;
    
  pinA = newPinA;
  pinB = newPinB;
    
 }  // end of PCINT2_vect

This demo is a little dark, it shows the filenumber count wrapping around 0 (0 back to FF and down, then back to 0 and up). Note the use of 'volatile' for variables that are used in the ISR.
https://www.youtube.com/watch?v=f51eSlcZt-g
Bourns PEC12R rotary encoder filter.JPG

Bourns PEC12R rotary encoder filter.JPG

Flames of approval! Get the job done, hardware software whatever.

There is no such thing as an all-software solution. Software gotta run on hardware.

Besides, caps are cheap.

a7

thanks all for the comments! ive been lurking on the forum last few weeks and already picked up quite a lot from old threads, great to get this feedback too.

@Perry - point taken. i was meaning to change the delay as i read its not recommended. but that part of the code (the button press) was behaving reasonably. will check out the links, thanks for those...
@groundFungus - i will try capacitors if i dont get any further. what confuses me is that the debouncing code ("check rotary") worksperfectly, until i add the "write to lcd" part.
@crossroads - thanks for the Interrupt code. i understand the concept, but I have to spend some time on it to properly understand.

i will also edit my code and leave only the critical parts in to make easier to read!

cheers all..

By far the best way I've found to deal with rotary encoders is the State Table approach that I first read about here: Buxtronix: Rotary encoders, done properly.

It handles debounce as part of the algorithm. Here's my implementation of the technique:

It requires 2 pins / encoder supporting external interrupts. So, on an Uno, that's Pins 2 & 3. But, it 100% suppresses contact bounce problems without resorting to blocking delays or dodgy capacitor hacks. You're welcome to use it or implement the technique in your own code.

thanks for the tip gfvalvo, i'll have a look through and might try to implement if i have time.

in the meantime, i managed to sort this out yesterday.

as mentioned, the "rotary check" routine was perfectly debouncing the output, however, adding the "lcd write" re-introduced bouncy behaviour.

i separated the lcd write from the debouncing code, and implemented it with a short delay ie. trigger LCD write after waiting X ms after last encoder event seemed to fix it. i must admit i still dont fully understand why, but i need to move on!

see attached serial plot. as you can see the variable increase and decrease smoothly with only the occasional "bounce". its not perfect, but practically, its infrequent and doesnt affect the operation noticeably. i'll play about with the delay times and may even be able to improve it further..