How to Store a Momentary Button Press As a Menu Selection

by dmjlambert in Circuits > Arduino

5686 Views, 36 Favorites, 0 Comments

How to Store a Momentary Button Press As a Menu Selection

FPJI5XMIEY3A3XQ.jpg

I want to press a button to change the mode in which my sketch runs. So, the button acts like a menu selection, choosing mode 0, mode 1, mode 2, and so on. Depending on what mode I select, the sketch does different things.

I also want my choice to be remembered even if I reset the Arduino or power it off and back on.

This is done on an Arduino by using EEPROM storage. Since the EEPROM storage on the Arduino is rated to work for 100,000 writes per memory cell, I also want to use it somewhat evenly, not storing to the same cells over and over. Avoiding writing to the same cells over and over, and spreading out writes to different memory locations for the same variable, is called wear leveling.

Video:


The Wiring

remember menu selection eeprom_bb.png
remember menu selection eeprom_schem.png

The wiring for the demo sketch: Pin 12 is set to input with internal pullup, and a button press connects it to ground. Pin 13 blinks the UNO's onboard LED. Pins 2 through 5 are outputs that are tied through 390 ohm resistors to LEDs and VCC.

The Code

The code:

#include <EEPROMex.h>

#define NUMSELECTIONS 4
// global variables
unsigned int addressButton1StateBits;
int currentSelection;
boolean button1Pressed = 0;
boolean previousButton1Pressed = 0;
unsigned long debounceCount;
unsigned long blinkMillis;
unsigned int blinkInterval;

void setup() {
  //Serial.begin(57600);
  // initialize EEPROM settings
  EEPROM.setMaxAllowedWrites(400);
  EEPROM.setMemPool(256, EEPROMSizeATmega328);
  addressButton1StateBits = EEPROM.getAddress(4);
  currentSelection = readEESelection();
  // Serial.print("\nRestored Selection ");
  // Serial.println(currentSelection);
  pinMode(12, INPUT_PULLUP);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(13, OUTPUT);
  digitalWrite(2, 1);
  digitalWrite(3, 1);
  digitalWrite(4, 1);
  digitalWrite(5, 1);
  digitalWrite(13, 1);
  // turn on the LED for the restored selection
  digitalWrite(currentSelection + 2, 0);
}  // end of setup

void loop() {
  // read the button no more often than once every 20 ms, debouncing
  if (millis() > 20 && debounceCount < millis() - 20) {
    debounceCount = millis();
    button1Pressed = !digitalRead(12);
  }
  // if the button has been pressed, increment and store selection
  if (previousButton1Pressed != button1Pressed) {
    previousButton1Pressed = button1Pressed;
    // only take action when button is pressed, not when released
    if (button1Pressed) {
      // turn off the LED for the old selection
      digitalWrite(currentSelection + 2, 1);
      currentSelection++;
      currentSelection %= NUMSELECTIONS;
      // turn on the LED for the new selection
      digitalWrite(currentSelection + 2, 0);
      // Serial.print("Current Selection ");
      // Serial.println(currentSelection);
      updateEESelection();
    }
  }

  // Here in the loop is where we can do different things depending on
  // what selection was made with the button.
  // In this case we blink the Arduino on-board LED at different intervals
  // depending on the selection.
  // This also demonstrates main loop processing without delay blocking.
  if (currentSelection == 0) {
    // Dim the LED by blinking it rapidly, bit-banging PWM
    if (digitalRead(13)) {
      blinkInterval = 0;
    }
    else {
      blinkInterval = 10;
    }
  }
  else if (currentSelection == 1) {
    // Blink once per second
    blinkInterval = 500;
  }
  else if (currentSelection == 2) {
    // Blink every 2 seconds
    blinkInterval = 1000;
  }
  else if (currentSelection == 3) {
    // Quick flash every 1.5 seconds, this is also bit-banging PWM, very slow
    if (digitalRead(13)) {
      blinkInterval = 50;
    }
    else {
      blinkInterval = 1450;
    }
  }
  if (millis() > blinkInterval && blinkMillis < millis() - blinkInterval) {
    blinkMillis = millis();
    digitalWrite(13, !digitalRead(13));
  }

}  // end of loop

byte readEESelection() {
  // count through the bits of storage until we find a bit set to 1
  byte i;
  for (i = 0; i < 32; i++) {
    byte byteNum, bitNum;
    byteNum = i / 8;
    bitNum = i % 8;
    boolean bitValue = EEPROM.readBit(addressButton1StateBits + byteNum, bitNum);
    if (bitValue) {
      break;
    }
  }
  // return the modulus of the bit number, that is the selection number
  return i % NUMSELECTIONS;
}

void updateEESelection() {
  while (readEESelection() != currentSelection) {
    // count through the bits of storage until we find a bit set to 1
    // and set that bit to 0 
    byte i, byteNum, bitNum;
    for (i = 0; i < 32; i++) {
      byteNum = i / 8;
      bitNum = i % 8;
      if (EEPROM.readBit(addressButton1StateBits + byteNum, bitNum) == 1) {
        EEPROM.updateBit(addressButton1StateBits + byteNum, bitNum, 0);
        break;
      }
      // if we don't find a bit set to 1, set all to 1 to start search over
      if (i == 31) { // a bit to clear was not found before the last bit
        for (i = 0; i < 4; i++) {
          EEPROM.updateByte(addressButton1StateBits + i, 0xFF);
        }
      }
    }
  } // while loop repeats until the bits are set so they match selection number
}

Remarks

This sketch demonstrates using EEPROM wear leveling, saving a small integer value selection in EEPROM, and setting a variable that is able to span resets and power ups.

Pressing the button attached to pin 12 is making a menu selection, used for deciding the desired behavior of other functions or features in the sketch. The selected value can be used to light an appropriate LED, or display appropriate text indicating the selection made. Then, the rest of the sketch can do whatever that selection calls for.

The selection is stored in EEPROM using a wear-leveling algorithm so it will not put a lot of wear on any single bit. Each time a selection change is made by button, only one bit of EEPROM is changed. 1.6 million button presses can be recorded in the 4 bytes of EEPROM memory used to store the selection state before the rated 100,000 writes for any one cell is exceeded.

Since the algorithm for storing the current selection value keeps clearing bits until the current selection is represented properly, usually clearing only one bit, it does not matter if the EEPROM was initialized to any values in particular. It will automatically adjust and re-initialize the EEPROM bytes as needed.

This sketch uses EEPROMex.h library for reading/updating EEPROM. EEPROM.setMaxAllowedWrites is a feature of EEPROMex.h that is useful during development and debugging, to keep you from accidentally coding a run-away sequence of writes and wearing out your Arduino EEPROM memory within a couple of minutes. For production use you will want to disable the limit. See EEPROMex.h documentation for details. The link to get EEPROMex.h library is:

http://playground.arduino.cc/Code/EEPROMex

And this article describes how to install libraries:

https://www.arduino.cc/en/guide/libraries

I am also showing in this demo sketch how to do several things in the loop without blocking and without delays. There are numerous articles on the web and in the arduino.cc forum for doing several things at one time and looping without delays.

Any ATmega328P- or ATmega32U4-based Arduino, such as Uno, Pro Mini, Leonardo, Pro Micro, etc., should be able to use the sketch and store to the built-in EEPROM. For the Pro Micro, which does not have pins 11, 12, and 13 brought out to the header pins, adjust the sketch to use other pins. Pin 17 on the Pro Micro is the RXLED and can be used as an on-board LED, substituting for pin 13. The RXLED on the Pro Micro is on when the output pin goes low, while the pin 13 LED on the UNO is on when the output pin goes high. So, in order to see the LED glow dim on the Pro Micro you would need to flip the pulse width so the pin is high for longer duration than it is low. To do that you can change the reading of pin 17, in 2 places in the sketch, from

if (digitalRead(17))

to

if (!digitalRead(17))


Further reading:

http://www.mosaic-industries.com/embedded-systems/sbc-single-board-computers/freescale-hcs12-9s12-c-language/instrument-control/eeprom-lifetime-reliability-wear-leveling

http://stackoverflow.com/questions/10667491/is-there-a-general-algorithm-for-microcontroller-eeprom-wear-leveling

http://forum.arduino.cc/index.php?topic=223286.0

Simplified Version Which Does Not Use EEPROM

If you're looking for a simpler solution for making a menu selection from a button and don't need to preserve the selection during resets or power cycle, here it is with the EEPROM stuff removed:

#define NUMSELECTIONS 4

// global variables
int currentSelection = 0;
boolean button1Pressed = 0;
boolean previousButton1Pressed = 0;
unsigned long debounceCount;
unsigned long blinkMillis;
unsigned int blinkInterval;

void setup() {

  pinMode(12, INPUT_PULLUP);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(13, OUTPUT);
  digitalWrite(2, 1);
  digitalWrite(3, 1);
  digitalWrite(4, 1);
  digitalWrite(5, 1);
  digitalWrite(13, 1);

  // turn on the LED for the selection
  digitalWrite(currentSelection + 2, 0);

}  // end of setup

void loop() {

  // read the button no more often than once every 20 ms, debouncing
  if (millis() > 20 && debounceCount < millis() - 20) {
    debounceCount = millis();
    button1Pressed = !digitalRead(12);
  }

  // if the button has been pressed, increment and store selection
  if (previousButton1Pressed != button1Pressed) {
    previousButton1Pressed = button1Pressed;
    // only take action when button is pressed, not when released
    if (button1Pressed) {
      // turn off the LED for the old selection
      digitalWrite(currentSelection + 2, 1);
      currentSelection++;
      currentSelection %= NUMSELECTIONS;
      // turn on the LED for the new selection
      digitalWrite(currentSelection + 2, 0);
      // Serial.print("Current Selection ");
      // Serial.println(currentSelection);
    }
  }

  // Here in the loop is where we can do different things depending on
  // what selection was made with the button.
  // In this case we blink the Arduino on-board LED at different intervals
  // depending on the selection.
  // This also demonstrates main loop processing without delay blocking.
  if (currentSelection == 0) {
    // Dim the LED by blinking it rapidly, bit-banging PWM
    if (digitalRead(13)) {
      blinkInterval = 0;
    }
    else {
      blinkInterval = 10;
    }
  }
  else if (currentSelection == 1) {
    // Blink once per second
    blinkInterval = 500;
  }
  else if (currentSelection == 2) {
    // Blink every 2 seconds
    blinkInterval = 1000;
  }
  else if (currentSelection == 3) {
    // Quick flash every 1.5 seconds, this is also bit-banging PWM, very slow
    if (digitalRead(13)) {
      blinkInterval = 50;
    }
    else {
      blinkInterval = 1450;
    }
  }
  if (millis() > blinkInterval && blinkMillis < millis() - blinkInterval) {
    blinkMillis = millis();
    digitalWrite(13, !digitalRead(13));
  }

}  // end of loop