How to Power down based on encoder movement?

I've incorporated a rotary encoder into a project using Timer-based rotary encoder logic by Peter Dannegger (the ClickEncoder library). This works very well however since the unit could sit doing its job for months once set up by the user I'd like to put the LCD to sleep and wake it up again based on the encoder movement.

The ClickEncoder library uses the avr interrupt handler and I'm wondering if I can call that, via ClickEncoder, to go to sleep and wake up as required.
Anyone familiar with ClickEncoder and/or the interrupt handler that can toss an idea my way?

Cheers.

Can you post a link the the library? Also, which Arduino are you using?

That calls for external electronics as displays, as far as I know, don't offer any sleep mode. Turning the backlight off is the closest as I see it.

1 Like

Looking at the library, it appears to poll the encoder pins every 1ms looking for changes.

Maybe one of these to control power to the LCD ("M" is the LCD power):

With some sort of State Change Detection based on one of the encoder variables:

I mean that you should do change detection on the encoder variables, and when they change, reset a timer and make sure the power is on. When the timer gets old, turn off the power to the LCD.

If you are talking about powering down the Arduino too, that would be more involved.

Its a Nokia 5110 which accepts a power down command like this

display.command(PCD8544_POWERDOWN); 

That works fine and it will wake up with simply

 display.begin();

Like Dave X said, the library polls encoder pins looking for movement which I was thinking of utilizing. I was hoping someone had already done this so I'm not reinventing the wheel

I think that the trick you are looking for is to do change detection on one or both of your encoder variables.

Take a look at these:

Maybe the time-delay in the last one would be helpful.

It's not an Arduino its an embedded Mega328P (32 pin TQFP) in a custom circuit and board I designed. The system works well however the Nokia LCD will, given enough time sitting idle, get slightly scrambled. I believe its losing the clock edge or something and getting out of sync. It takes a while but the system sits and looks for PIR signals for months and only needs the screen for system setup.

DaveX

You tweaked to the same thing I've been looking at, the polling.

// 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;
    }
  }

I'm thinking of the bool moved = false. Might be able to do something with that.

Maybe you should show your code as per: How to get the best out of this forum

I really wouldn't mess with the encoder library code, but instead with whatever code you are calling the library from.

The library's example has this bit:

You'd insert a bit of code in it to wake up your LCD if it was off, and also add in something to record the timestamp for when the encoder was tweaked. For example:

lastEncoderTweakMs = now;
if(LcdOn == false) {
   display.begin(); 
   LcdOn=true;
}

Then in a separate block, use the timestamp to check if it's been a while since the encoder was tweaked, and if so, turn off the LCD. Somehting like this completely untested snippet:

if( LcdOn && now - lastEncoderTweakMs > 3600000) {
   display.command(PCD8544_POWERDOWN);
   LcdOn = false;
}

The key concept I think you need is about paying attention to the time since the last (encoder movement).

No I wouldn't mess with the library but I was thinking of using the library to give me what I need and yes it would need a time related check.

My application is a variation on the ClickEncoder example since its scrolling through a list of menu and sub menu items. What's shown here comprises most of loop and the rest of loop is only 23 lines. (The sketch has nearly 800 lines of code, primarily functions). I've included the sections that apply here and the function called in readRotaryEncoder().

Forgive the formatting. It was done after beta testing was completed to allow less scrolling while working later with the sketch.

 readRotaryEncoder();

  /*** BUTTON ACTIONS and MENU NAVIGATION ***/
  /*
    Lots going on here however its simply scrolling through the main and sub menus. The cursor (in reverse video) moves up or down the
    screen with the next menu line appearing as the page changes.
  */
  ClickEncoder::Button b = encoder->getButton();
  if (b != ClickEncoder::Open) {
    switch (b) {
      case ClickEncoder::DoubleClicked:              //look for a double click 
        if (backLightState) switchBacklight(false);  // if backlight is off turn it on and vice versa
        else switchBacklight(true);
        break;
      case ClickEncoder::Clicked:  //single click
        middle = true;
        break;
    }
  }  //endif
  //remainder of the loop has been formatted for spacing
  if (up && page == 1) {
    up = false;
    if (menuitem == 2 && frame == 2) frame--;
    if (menuitem == 4 && frame == 4) frame--;
    if (menuitem == 3 && frame == 3) frame--;
    lastMenuItem = menuitem;
    menuitem--;
    if (menuitem == 0) menuitem = 1;
  } else if (up && page == 2 && menuitem == 1) {
    up = false;
    limitArea1--;
    if (limitArea1 < 5) limitArea1++;  //see notes above for more info on area timing
  } else if (up && page == 2 && menuitem == 2) {
    up = false;
    limitArea2--;
    if (limitArea2 < 5) limitArea2++;
  } else if (up && page == 2 && menuitem == 3) {
    up = false;
    selectedZone--;
    if (selectedZone == -1) selectedZone = 3;
  } else if (up && page == 2 && menuitem == 4) {
    up = false;
    selectedAlarm--;
    if (selectedAlarm == -1) selectedAlarm = 1;
  }
  if (down && page == 1) {
    down = false;
    if (menuitem == 3 && lastMenuItem == 2) frame++;
    else if (menuitem == 4 && lastMenuItem == 3) frame++;
    else if (menuitem == 5 && lastMenuItem == 4 && frame != 4) frame++;
    lastMenuItem = menuitem;
    menuitem++;
    if (menuitem == 7) menuitem--;
  }  //endif
  else if (down && page == 2 && menuitem == 1) {
    down = false;
    limitArea1++;
    if (limitArea1 > 999) limitArea1--;  //only have space for 3 digits
  } else if (down && page == 2 && menuitem == 2) {
    down = false;
    limitArea2++;
    if (limitArea2 > 999) limitArea2--;
  } else if (down && page == 2 && menuitem == 3) {
    down = false;
    selectedZone++;
    if (selectedZone == 4) selectedZone = 0;
  }

  /*** MIDDLE BUTTON PRESS ***/
  //the action taken on a button press depends on which page and menu item are in focus (reverse video)
  if (middle) {                         //button pressed
    middle = false;                     //reset for the next press
    if (page == 2 && menuitem == 1) {   //Area1 Timer highlighted on screen
      if (limitArea1 != sysArray[0]) {  //test for a value change made by the user
        sysArray[0] = limitArea1;       //update the value
        eeprmJob('W', 1);               //store the value
        sendMessage('S');               //tell the user
      }
    }
    if (page == 2 && menuitem == 2) {   //Area2 Timer
      if (limitArea2 != sysArray[1]) {  //test for a value change
        sysArray[1] = limitArea2;       //update the value
        eeprmJob('W', 2);               //store the value
        sendMessage('S');               //tell the user
      }
    }
    if (page == 2 && menuitem == 3) triggerZone(selectedZone);  //manual triggering with an encoder button press
    if (page == 1 && menuitem == 5) {                           // backlight control
      if (backLightState) switchBacklight(false);               // if its off turn it on and vice versa
      else switchBacklight(true);
    }
    if (page == 1 && menuitem == 6) resetDefaults();  // reset area times to default values, also turns off the backlight
    else if (page == 1 && menuitem <= 4) page = 2;
    else if (page == 2) page = 1;
  }  //end if

The function

void readRotaryEncoder() {
  value += encoder->getValue(); //** THIS LINE IS OF INTEREST, SEE BELOW
  //up and down were reversed here to detect proper direction of rotation
  if (value / 2 > last) {
    last = value / 2;
    up = true;
    delay(50);
  } else if (value / 2 < last) {
    last = value / 2;
    down = true;
    delay(50);
  }
}  //end readRotaryEncoder

I was thinking of simply using the value 'r' returned from the library function getValue() in a timer.

int16_t ClickEncoder::getValue(void)
{
  int16_t val;
  
  cli();
  val = delta;

  if (steps == 2) delta = val & 1;
  else if (steps == 4) delta = val & 3;
  else delta = 0; // default to 1 step per notch

  sei();
  
  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;
}

I decided to use the data passed back to getValue() to do the job.

Added this before setup()

//*NEW statements 
boolean lcdState;               //current LCD power state
unsigned long lastEncoderMove;  //last time the encoder was used
#define ENCODER_IDLE 180000      //time since the encoder was used

modified the LCD initialization in setup()

 //initialize the LCD and show the splash screen
  display.begin();
  lcdState = true;  //*NEW the LCD is powered up
  resetDisplay(1);
  display.setCursor(0, 5);
  display.print(__SPLASH__);
  display.display();
  delay(4000);  //allow for read
  display.clearDisplay();
  setContrast();
  Timer1.initialize(500);            //used by the encoder
  Timer1.attachInterrupt(timerIsr);  //used by the encoder
  last = encoder->getValue();
}  //end setup

Added an if statement to the drawMenu() function in loop()

//*NEW if statement
 if (lcdState == true) drawMenu();  //draw the LCD if not powered down

and then did everything in readRotaryEncoder()

void readRotaryEncoder() {
  value += encoder->getValue();
  //up and down were reversed here to detect proper direction of rotation
  if (value / 2 > last) {
    last = value / 2;
    up = true;
    delay(50);
    lastEncoderMove = ms;  //*NEW record the movment 
    lcdState = true;       //*NEW update the LCD power state 
  } else if (value / 2 < last) {
    last = value / 2;
    down = true;
    delay(50);
    lastEncoderMove = ms;  //*NEW record the movment 
    lcdState = true;       //*NEW update 
  }//*NEW - the actual power down is done here
  if (ms - lastEncoderMove > ENCODER_IDLE && lcdState == true) {  //its powered up and been idle for the designated time
    display.command(PCD8544_POWERDOWN);                           //put the screen to sleep
    lcdState = !lcdState;   //update
  }
}  //end readRotaryEncoder

The power down and prep for power up is done in the one function by monitoring the encoder acceleration. The actual power up is done on the first redraw after lcdState is set back to true. Something could probably be done using the interrupt attached to Timer1 prior to setup but it wasn't worth the trouble and I would still need the new global definitions so no space savings.

Thanks for the input. Always helps to bounce an idea off someone.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.