Rotary Encoder LongPress

Dear all,

Currently i'm using the library 'LCDMenuLib' with an example to create a menu structure from this library.

This library also has some built in support for controlling of the menus with a rotary encoder, however i'm not getting it to work.

I've been looking around the whole example and its subfiles but can't seem to find a way to enable the rotary encoder to work.

I've tried several setups/wirings, but unfortunately haven't been able to get the rot. encoder to work yet, currently it doesn't react to the encoder at all.

I've checked the rotary encoder from errors by using some simple sketches to prove functionality of the rotary encoder, which is OK.

I've put the code in the attachment.

Can anyone tell me how to get the rotary encoder working? (And double-check me on how to wire the encoder?)

Thanks in advance.
A.

LCDML_CONTROL.ino (17.2 KB)

LCDML_DISP.ino (3.67 KB)

LCDML_FUNC_BACKEND.ino (1.12 KB)

LCDML_FUNC_DISP.ino (6.18 KB)

OLED096TEST905ROTARY.ino (7.69 KB)

I am reluctant to download and open a zip file. Please post the code in code tags. If the code is too large, attach the ino file. You can post images like this.

Please read the "how to use this forum-please read" stickies.

groundFungus:
I am reluctant to download and open a zip file. Please post the code in code tags. If the code is too large, attach the ino file. You can post images like this.

Please read the "how to use this forum-please read" stickies.

Thanks for your reply, I've now attached the .ino files separately instead of in a .zip file.

Hello,

to enable the rotary encoder open the "LCDML_CONTROL.ino and change _LCDML_CONTROL_cfg from 0 to 3

Then scroll down until you find a comment like this:

// *********************************************************************
// *************** (3) CONTROL WITH ENCODER ****************************
// *********************************************************************

Here you can define your "pins" where you can connect the encoder. I use the encoder with low active logic and internal pullups. (Common Pin on GND and Pin A and B directly to the arduino, one pin from the switch to gnd and the other directly to the arduino)

Set _LCDML_CONTROL_encoder_high_active to 0

Great! The setting of 0 to 3 did the job! That was the one i was looking for!

Now another question, i'd like to implement the function of the 'back-button' when the knob is pressed >= X ms.

Unfortunately, also this won't work yet..

This is the rotary encoder-part of the code in which i'd like to implement the 'go_back-on-hold' function.
Going back can be achieved by running this line:

LCDML_BUTTON_quit();

Only thing to be added to the code underneath is to notice when the button is pressed for more than X milliseconds, and then run the 'LCDML_BUTTON_quit();' line.

Any ideas on how to implement this? (Preferably without delays)

Thanks in advance!

// *********************************************************************
// *************** (3) CONTROL WITH ENCODER ****************************
// *********************************************************************
#elif(_LCDML_CONTROL_cfg == 3)
// settings
  #define _LCDML_CONTROL_encoder_pin_a           10 // pin encoder b
  #define _LCDML_CONTROL_encoder_pin_b           11 // pin encoder a
  #define _LCDML_CONTROL_encoder_pin_button      12 // pin taster
  #define _LCDML_CONTROL_encoder_high_active     0  // (0 = low active (pullup), 1 = high active (pulldown)) button
                                                      // // http://playground.arduino.cc/CommonTopics/PullUpDownResistor
// global defines
  uint8_t  g_LCDML_CONTROL_encoder_t_prev = 0;
  uint8_t  g_LCDML_CONTROL_encoder_a_prev = 0;


// *********************************************************************
// setup
void LCDML_CONTROL_setup()
{
  // set encoder update intervall time 
  LCDML_BACK_dynamic_setLoopTime(LCDML_BACKEND_control, 5UL);  // 5ms 

  // init pins  
  pinMode(_LCDML_CONTROL_encoder_pin_a      , INPUT_PULLUP);
  pinMode(_LCDML_CONTROL_encoder_pin_b      , INPUT_PULLUP);
  pinMode(_LCDML_CONTROL_encoder_pin_button , INPUT_PULLUP); 
}
// *********************************************************************
// loop
void LCDML_CONTROL_loop()
{    
  // read encoder status
  unsigned char a = digitalRead(_LCDML_CONTROL_encoder_pin_a);
  unsigned char b = digitalRead(_LCDML_CONTROL_encoder_pin_b);
  unsigned char t = digitalRead(_LCDML_CONTROL_encoder_pin_button);
  
  // change button status if high and low active are switched
  if (_LCDML_CONTROL_encoder_high_active == 1) {
    t != t;

  }
  
  // check encoder status and set control menu
  if (!a && g_LCDML_CONTROL_encoder_a_prev) {
    g_LCDML_CONTROL_encoder_t_prev = 1;
    
    if (!b) { LCDML_BUTTON_up();   }
    else    { LCDML_BUTTON_down(); }            
  } 
  else {
    // check button press time for enter
    if((millis() - g_LCDML_DISP_press_time) >= _LCDML_DISP_cfg_button_press_time) {
      g_LCDML_DISP_press_time = millis(); // reset button press time
      
      // press button once
      if (!t && g_LCDML_CONTROL_encoder_t_prev == 0) {          
          LCDML_BUTTON_enter();          
      } 
      else {
        g_LCDML_CONTROL_encoder_t_prev = 0;
          }
        }
      }      
  
  g_LCDML_CONTROL_encoder_a_prev = a;  // set new encoder status 
  
}
// *********************************************************************
// ******************************* END *********************************
// *********************************************************************

I have not tested the code, but it can be work :slight_smile:

Add two global variables:

...
...

#elif(_LCDML_CONTROL_cfg == 3)
// settings
  #define _LCDML_CONTROL_encoder_pin_a           A0 // pin encoder b
  #define _LCDML_CONTROL_encoder_pin_b           A1 // pin encoder a
  #define _LCDML_CONTROL_encoder_pin_button      8 // pin taster
  #define _LCDML_CONTROL_encoder_high_active     1  // (0 = low active (pullup), 1 = high active (pulldown)) button
                                                      // // http://playground.arduino.cc/CommonTopics/PullUpDownResistor
// global defines
  uint8_t  g_LCDML_CONTROL_encoder_t_prev = 0;
  uint8_t  g_LCDML_CONTROL_encoder_a_prev = 0; 
  uint8_t  g_LCDML_CONTROL_reminder_button_press = 0; // <--  NEW  
  uint8_t  g_LCDML_CONTROL_reminder_wait_time = 0;    // <--  NEW

Change something in the loop function for rotary encoder

void LCDML_CONTROL_loop()
{    
    // read encoder status
    unsigned char a = digitalRead(_LCDML_CONTROL_encoder_pin_a);
    unsigned char b = digitalRead(_LCDML_CONTROL_encoder_pin_b);
    unsigned char t = digitalRead(_LCDML_CONTROL_encoder_pin_button);
    
    // change button status if high and low active are switched
    if (_LCDML_CONTROL_encoder_high_active == 1) {
        t != t;
    }
    
    // check encoder status and set control menu
    if (!a && g_LCDML_CONTROL_encoder_a_prev) {
        g_LCDML_CONTROL_encoder_t_prev = 1;
    
        if (!b) { LCDML_BUTTON_up();   }
        else    { LCDML_BUTTON_down(); }            
    } 
    else {
        // check if button is pressed
        if(!t)
        {
        // button is pressed
            if(g_LCDML_CONTROL_reminder_wait_time < 255) { // check overrun (uint8_t)
                g_LCDML_CONTROL_reminder_wait_time++;  // currently increments every 5 ms with the loop time
            }
        }
        else
        {
        // button is not pressed
            if(g_LCDML_CONTROL_reminder_button_press == 1)
            {               
                // check waittime for quit >= 300 ms
                if((g_LCDML_CONTROL_reminder_wait_time * LCDBL_BACK_dynamic_getLoopTime(LCDML_BACKEND_control)) >= 300)
                {
                    // call quit
                    Serial.println("call quit");
                    LCDML_BUTTON_quit(); 
                }
                else
                {
                    // call enter
                    Serial.println("call enter");
                    LCDML_BUTTON_enter(); 
                }
                
                g_LCDML_CONTROL_reminder_wait_time = 0;
                g_LCDML_CONTROL_reminder_button_press = 0;                
            }
        }   
        
    
        // check button press time for enter
        if((millis() - g_LCDML_DISP_press_time) >= _LCDML_DISP_cfg_button_press_time) {
            g_LCDML_DISP_press_time = millis(); // reset button press time
            
            // press button once
            if (!t && g_LCDML_CONTROL_encoder_t_prev == 0) 
            {
                // button was pressed, but how long ?
                g_LCDML_CONTROL_reminder_button_press = 1;
            } 
            else {
                g_LCDML_CONTROL_encoder_t_prev = 0;
            }
        }      
    }
    g_LCDML_CONTROL_encoder_a_prev = a;  // set new encoder status 
  
}

To add back buttons it is easy to create a menuitem with "Back" and use only the "enter" function on this.

You can add in the main setup this line to enable menu rollover to jump fast from the last element to the first element.

LCDML.enRollover(); // add this after the group initialisation

Meine Änderung aus LCDML_001liquidCrystal / LCDML_control:

// *********************************************************************
// *************** (3) CONTROL WITH ENCODER ****************************
// *********************************************************************
#elif(_LCDML_CONTROL_cfg == 3)
// settings
#define _LCDML_CONTROL_encoder_pin_a           10 // pin encoder SW
#define _LCDML_CONTROL_encoder_pin_b           11 // pin encoder DT
#define _LCDML_CONTROL_encoder_pin_button       9 // pin encoder CLK
#define _LCDML_CONTROL_encoder_high_active     0  // (0 = low active (pullup), 1 = high active (pulldown)) button
// // http://playground.arduino.cc/CommonTopics/PullUpDownResistor
// global defines
unsigned long g_LCDML_DISP_press_time = 0;
unsigned long g_LCDML_DISP_releas_time = 0;
uint8_t  g_LCDML_CONTROL_encoder_t_prev = 0;
uint8_t  g_LCDML_CONTROL_encoder_t_dn_prev = 1;
unsigned char  g_LCDML_CONTROL_encoder_a_prev = 0;
uint8_t  g_LCDML_CONTROL_encoder_ab_prev = 0;
uint16_t  g_LCDML_CONTROL_reminder_button_press = 400; // <--  NEW
uint8_t  g_LCDML_CONTROL_reminder_wait_time = 80;    // <--  NEW
// *********************************************************************
void lcdml_menu_control(void)
{
  // If something must init, put in in the setup condition
  if (LCDML.BT_setup()) {
    // runs only once
    // set encoder update intervall time
    //LCDML_BACK_dynamic_setLoopTime(LCDML_BACKEND_control, 1000UL);  // 1000us

    // init pins
    pinMode(_LCDML_CONTROL_encoder_pin_a      , INPUT_PULLUP);
    pinMode(_LCDML_CONTROL_encoder_pin_b      , INPUT_PULLUP);
    pinMode(_LCDML_CONTROL_encoder_pin_button , INPUT_PULLUP);
  }


  // read encoder status
  unsigned char a = digitalRead(_LCDML_CONTROL_encoder_pin_a);
  unsigned char b = digitalRead(_LCDML_CONTROL_encoder_pin_b);
  unsigned char t = digitalRead(_LCDML_CONTROL_encoder_pin_button);

  // change button status if high and low active are switched
  if (_LCDML_CONTROL_encoder_high_active == 1) {
    t != t;
  }

// check button press time for enter / quit
  if (g_LCDML_CONTROL_encoder_t_prev != t) {
    if (t == 0) {
      g_LCDML_DISP_press_time = millis();
      g_LCDML_CONTROL_encoder_t_dn_prev = 0;
      
    } else {
      g_LCDML_DISP_releas_time = millis();
    }
  } else {
    if (g_LCDML_CONTROL_encoder_t_dn_prev == 0){
      if (t == 0) {
        if ((millis()-g_LCDML_DISP_press_time)>g_LCDML_CONTROL_reminder_button_press) {
          LCDML.BT_quit();  
          g_LCDML_CONTROL_encoder_t_dn_prev = 1;
        }
      } else {
        if ((millis()-g_LCDML_DISP_press_time)>g_LCDML_CONTROL_reminder_wait_time) {
          LCDML.BT_enter();  
        }
        g_LCDML_CONTROL_encoder_t_dn_prev = 1;
      }  
    }
  }
  g_LCDML_CONTROL_encoder_t_prev = t;
  
 // check encoder status and set control menu
  if ((a == b) != (g_LCDML_CONTROL_encoder_ab_prev)) {
    if (a != b){  
      g_LCDML_CONTROL_encoder_a_prev = a;
    } else {
      if ( g_LCDML_CONTROL_encoder_a_prev != a) {
        LCDML.BT_up();      
      } else {
        LCDML.BT_down();
      }
    }
  }
  g_LCDML_CONTROL_encoder_ab_prev = (a == b);
}

// *********************************************************************
// ******************************* END *********************************
// *********************************************************************

Mit diesem Encoder:

Nach dem ich so viel abgeschaut hab, kann ich mal was beisteuern.

Jomelo:
I have not tested the code, but it can be work :slight_smile:

Add two global variables:

...

...

#elif(_LCDML_CONTROL_cfg == 3)
// settings
 #define _LCDML_CONTROL_encoder_pin_a           A0 // pin encoder b
 #define _LCDML_CONTROL_encoder_pin_b           A1 // pin encoder a
 #define _LCDML_CONTROL_encoder_pin_button      8 // pin taster
 #define _LCDML_CONTROL_encoder_high_active     1  // (0 = low active (pullup), 1 = high active (pulldown)) button
                                                     // // Arduino Playground - HomePage
// global defines
 uint8_t  g_LCDML_CONTROL_encoder_t_prev = 0;
 uint8_t  g_LCDML_CONTROL_encoder_a_prev = 0;
 uint8_t  g_LCDML_CONTROL_reminder_button_press = 0; // <--  NEW  
 uint8_t  g_LCDML_CONTROL_reminder_wait_time = 0;    // <--  NEW




Change something in the loop function for rotary encoder


void LCDML_CONTROL_loop()
{    
   // read encoder status
   unsigned char a = digitalRead(_LCDML_CONTROL_encoder_pin_a);
   unsigned char b = digitalRead(_LCDML_CONTROL_encoder_pin_b);
   unsigned char t = digitalRead(_LCDML_CONTROL_encoder_pin_button);
   
   // change button status if high and low active are switched
   if (_LCDML_CONTROL_encoder_high_active == 1) {
       t != t;
   }
   
   // check encoder status and set control menu
   if (!a && g_LCDML_CONTROL_encoder_a_prev) {
       g_LCDML_CONTROL_encoder_t_prev = 1;
   
       if (!b) { LCDML_BUTTON_up();   }
       else    { LCDML_BUTTON_down(); }            
   }
   else {
       // check if button is pressed
       if(!t)
       {
       // button is pressed
           if(g_LCDML_CONTROL_reminder_wait_time < 255) { // check overrun (uint8_t)
               g_LCDML_CONTROL_reminder_wait_time++;  // currently increments every 5 ms with the loop time
           }
       }
       else
       {
       // button is not pressed
           if(g_LCDML_CONTROL_reminder_button_press == 1)
           {              
               // check waittime for quit >= 300 ms
               if((g_LCDML_CONTROL_reminder_wait_time * LCDBL_BACK_dynamic_getLoopTime(LCDML_BACKEND_control)) >= 300)
               {
                   // call quit
                   Serial.println("call quit");
                   LCDML_BUTTON_quit();
               }
               else
               {
                   // call enter
                   Serial.println("call enter");
                   LCDML_BUTTON_enter();
               }
               
               g_LCDML_CONTROL_reminder_wait_time = 0;
               g_LCDML_CONTROL_reminder_button_press = 0;                
           }
       }  
       
   
       // check button press time for enter
       if((millis() - g_LCDML_DISP_press_time) >= _LCDML_DISP_cfg_button_press_time) {
           g_LCDML_DISP_press_time = millis(); // reset button press time
           
           // press button once
           if (!t && g_LCDML_CONTROL_encoder_t_prev == 0)
           {
               // button was pressed, but how long ?
               g_LCDML_CONTROL_reminder_button_press = 1;
           }
           else {
               g_LCDML_CONTROL_encoder_t_prev = 0;
           }
       }      
   }
   g_LCDML_CONTROL_encoder_a_prev = a;  // set new encoder status
 
}





To add back buttons it is easy to create a menuitem with "Back" and use only the "enter" function on this.



You can add in the main setup this line to enable menu rollover to jump fast from the last element to the first element. 



LCDML.enRollover(); // add this after the group initialisation

Thanks for your reply!
Using this code gives an error:

LCDML_BACKEND_control' was not declared in this scope

The error is on this line:

if((g_LCDML_CONTROL_reminder_wait_time * LCDBL_BACK_dynamic_getLoopTime(LCDML_BACKEND_control)) >= 300)

I've already checked the spelling of 'LCDML_BACKEND_control' and it's the same as used in other places in the (already existing and working) code. To make sure the spelling is exactly the same i've also copied the text, but unfortunately it's still 'not declared in this scope'.

Any ideas on how to fix this error? I can't seem to find/understand the cause of it.

Please test the code from johonline, it seems to work and it is configurable.

Well, with the code from johononline i've received so many errors, solved many of them but they keep coming, that's why i'd prefer using the code supplied by you (Jomelo).

Tested on breadboard:

Functions:

  1. Short switch pressed => enter
  2. Long switch pressed => quit
  3. rot left => up
  4. rot right => down
  5. switch pressed and rot left => left
  6. switch pressed and rot right => right
// *********************************************************************
// *************** (3) CONTROL WITH ENCODER ****************************
// *********************************************************************
#elif(_LCDML_CONTROL_cfg == 3)
// settings
  #define _LCDML_CONTROL_encoder_pin_a                  4 // pin encoder b
  #define _LCDML_CONTROL_encoder_pin_b                  5 // pin encoder a
  #define _LCDML_CONTROL_encoder_pin_button             7 // pin taster
  #define _LCDML_CONTROL_encoder_high_active            0 // (0 = low active (pullup), 1 = high active (pulldown)) button
                                                          // http://playground.arduino.cc/CommonTopics/PullUpDownResistor

  #define _LCDML_CONTROL_encoder_refresh_time           5UL  // 5ms
  #define _LCDML_CONTROL_encoder_switch_time            75UL // 75 ms 

// macros which define the functionality
  #define _LCDML_CONTROL_encoder_switch_press_short()   LCDML_BUTTON_enter()
  #define _LCDML_CONTROL_encoder_rotary_a()             LCDML_BUTTON_up()  
  #define _LCDML_CONTROL_encoder_rotary_b()             LCDML_BUTTON_down()

  #define _LCDML_CONTROL_encoder_advanced_switch        1
  #define _LCDML_CONTROL_encoder_switch_press_long()    LCDML_BUTTON_quit()

  #define _LCDML_CONTROL_encoder_advanced_rotary        1
  #define _LCDML_CONTROL_encoder_rotary_a_and_press()   LCDML_BUTTON_left()
  #define _LCDML_CONTROL_encoder_rotary_b_and_press()   LCDML_BUTTON_right()
                                                      
  #define _LCDML_CONTROL_encoder_t_long_press    1000   // maximum is 1275 (5*255)
  
// global defines
  uint8_t  g_LCDML_CONTROL_encoder_t_prev = 0;
  uint8_t  g_LCDML_CONTROL_encoder_a_prev = 0;
  uint8_t  g_LCDML_CONTROL_t_pressed = 0;
  uint8_t  g_LCDML_CONTROL_t_press_time = 0;

// *********************************************************************
// setup
void LCDML_CONTROL_setup()
{
  // set encoder update intervall time 
  LCDML_BACK_dynamic_setLoopTime(LCDML_BACKEND_control, _LCDML_CONTROL_encoder_refresh_time);  // 5ms 

  // init pins
  if(_LCDML_CONTROL_encoder_high_active == 0) 
  {  
    pinMode(_LCDML_CONTROL_encoder_pin_a      , INPUT_PULLUP);
    pinMode(_LCDML_CONTROL_encoder_pin_b      , INPUT_PULLUP);
    pinMode(_LCDML_CONTROL_encoder_pin_button , INPUT_PULLUP);
  } 
}

// *********************************************************************
// loop
void LCDML_CONTROL_loop()
{    
  // read encoder status
  unsigned char a = digitalRead(_LCDML_CONTROL_encoder_pin_a);
  unsigned char b = digitalRead(_LCDML_CONTROL_encoder_pin_b);
  unsigned char t = digitalRead(_LCDML_CONTROL_encoder_pin_button);
  
  // change button status if high and low active are switched
  if (_LCDML_CONTROL_encoder_high_active == 1) {
    t = !t;
  }

  // check if the button was pressed and save this state
  if((millis() - g_LCDML_DISP_press_time) >= _LCDML_CONTROL_encoder_switch_time) {
    g_LCDML_DISP_press_time = millis(); // reset button press time
    
    // press button once
    if (t == false && g_LCDML_CONTROL_encoder_t_prev == 0) 
    {
      g_LCDML_CONTROL_t_pressed = 1;
    } 
    else {
      g_LCDML_CONTROL_encoder_t_prev = 0;
    }
  } 

  // check if button is currently pressed
  if(t == false) 
  {
    // check if the advanced rotary function is enabled
    if(_LCDML_CONTROL_encoder_advanced_rotary == 1)
    {
      // check if the rotary encoder was moved
      if (a == false && g_LCDML_CONTROL_encoder_a_prev ) {
        g_LCDML_CONTROL_encoder_t_prev = 1;
        
        if (b == false) 
        { 
          // switch active and rotary b moved
          _LCDML_CONTROL_encoder_rotary_b_and_press();       
        }
        else    
        { 
          // switch active and rotary a moved
          _LCDML_CONTROL_encoder_rotary_a_and_press(); 
        }
  
        g_LCDML_CONTROL_t_pressed = 0;
        g_LCDML_CONTROL_t_press_time = 0;            
      }
    } 

    // check advanced mode "long press switch"
    if(_LCDML_CONTROL_encoder_advanced_switch == 1)
    {    
      // button was pressed
      if(g_LCDML_CONTROL_t_pressed == 1) 
      {
        // check overrun and stop
        if(g_LCDML_CONTROL_t_press_time < 255) 
        {
          g_LCDML_CONTROL_t_press_time++;
        }
      }
    }
  }
  else
  {
    // switch is not active

    // check encoder
    if (a == false && g_LCDML_CONTROL_encoder_a_prev) {
      g_LCDML_CONTROL_encoder_t_prev = 1;
      
      if (b == false) 
      { 
        _LCDML_CONTROL_encoder_rotary_a();        
      }
      else    
      { 
        _LCDML_CONTROL_encoder_rotary_b(); 
      }

      g_LCDML_CONTROL_t_pressed = 0;
      g_LCDML_CONTROL_t_press_time = 0;
      g_LCDML_DISP_press_time = millis();            
    } 

    // check if an button was pressed
    if(g_LCDML_CONTROL_t_pressed == 1) 
    {
      if(g_LCDML_CONTROL_t_press_time * _LCDML_CONTROL_encoder_refresh_time >= _LCDML_CONTROL_encoder_t_long_press && _LCDML_CONTROL_encoder_advanced_switch == 1)
      {        
        _LCDML_CONTROL_encoder_switch_press_long();
      } 
      else 
      {
        _LCDML_CONTROL_encoder_switch_press_short();
      }
      
      g_LCDML_CONTROL_t_pressed = 0;
      g_LCDML_CONTROL_t_press_time = 0;
      g_LCDML_DISP_press_time = millis();
    }
  }
  
  g_LCDML_CONTROL_encoder_a_prev = a;  // set new encoder status 
  
}
// *********************************************************************
// ******************************* END *********************************
// *********************************************************************

Thanks Jomelo, that one works!

In addition i've got another question on how to change a variable when in a submenu?
For instance when i click/open the Brightness menu, i've now made a menu which shows the current Brightness value, but i'd also like to be able to adjust it by rotating the rotary encoder when this submenu is active.

The information is currently displayed by:

void LCDML_DISP_loop(BrightnessSet)
   { 
     // loop
     // is called when it is triggert
     // - with LCDML_DISP_triggerMenu( millisecounds ) 
     // - with every button status change



u8g.firstPage(); 
do {
u8g.setFont(_LCDML_DISP_font);
u8g.drawStr(15, 20, "Brightness: ");
 
u8g.setFont(_LCDML_DISP_fontLarge1);
//u8g.drawStr(8, 30, "Eeee");

char buf[9];
sprintf (buf, "%d", BRIGHTNESS);
u8g.drawStr(25, 55, buf);

 } while( u8g.nextPage() );
 delay(200); 




     // check if any button is presed (enter, up, down, left, right)
     if(LCDML_BUTTON_checkAny()) {         
       LCDML_DISP_funcend();
     } 
   }

Thanks for your great help everyone!