Introduction: Easy Arduino Menus for Rotary Encoders

Rotary encoders with centre-push buttons are cool input hardware for projects, especially if you want to be able to scroll through menus and select options. Hopefully this Instructable will give you the confidence to try a basic menu system out and combine it with a rotary encoder to give a professional feel to your next project!

Why menus and rotary encoders need code

I wanted to have a menu in an upcoming project and use a rotary encoder with a centre push button as my input hardware. This is really similar to the LCD controllers for RAMPS and Arduino MEGA 3D printers. The rotary encoder will allow me to scroll through the menu options, i.e. navigate the menu, select sub-menus and also change values within sub-menus/settings - very versatile for one hardware interface! A microcontroller is needed to manage all of this and microcontrollers need instructions, AKA code!

Other options

The problem I had with existing Arduino menu libraries and menu code is that for simple menus they were overly complicated. Another drawback of many alternatives was that the code was designed for LCD screens and momentary push buttons, not rotary encoders and adaptable to other display outputs. These menus were geared around selecting between a small number of modes and incrementing values relatively slowly. We know that rotary encoders are a great hardware input option because they afford relatively fast input value changes while retaining fine control at slow speed. I wanted to write code which would allow unambiguous top level menu navigation but also allow you to quickly scroll through a large range of values within each sub-menu/setting, exploiting the strengths of the rotary encoder.

The approach

I decided to follow some advice to use if() statements for a simple menu structure and keep it sketch-based. The resultant code builds on my previous Instructable which sought to reliably read the rotation pulses and direction. Please check it out for background.

In this sketch, we add the reading of the centre push button on the rotary encoder shaft, using code that Nick Gammon developed to record button state changes with debouncing and without relying on the Arduino's delay() function that prevents the microcontroller from executing other code and would potentially introduce noticeable delay in our sketch, e.g. slow display refresh rates. Button state change code is much more useful than just reading digital logic high or low when using a button to select something once, like a menu option, as it can help you prevent unintentional multiple selections for each button press.

Let's take a look at what you need to set up to use this example code in Step 1.

Step 1: Preparation

If you haven't yet, please see my other Instructable on rotary encoder reading to find out how to set up your hardware and Arduino IDE software.

Hardware

The additional hardware connections you need to make use of centre push button are shown in the pictures. I used Fritzing to draw the diagram but it didn't have a rotary encoder component which represented the most likely pin layout, so just use that diagram in conjunction with the notes and look at the photo of the rotary encoder to see what you are more likely to be looking for in terms of rotary encoder pin layout.

One of the two pins on one side of the rotary encoder (as opposed to the side with three pins) needs to be connected to ground and another to a digital pin on the Arduino. I have used D4 for the example sketch. If you choose a different pin, don't forget to change the value of buttonPin in the sketch.

Next comes the code in Step 2.

Step 2: Code

This is the code. By looking at the structure and the comments I hope you will find it easy to adapt for your specific needs!

/*******Interrupt-based Rotary Encoder Menu Sketch*******
 * by Simon Merrett, based on insight from Oleg Mazurov, Nick Gammon, rt and Steve Spence, and code from Nick Gammon
 * 3,638 bytes with debugging on UNO, 1,604 bytes without debugging
 */
// Rotary encoder declarations
static int pinA = 2; // Our first hardware interrupt pin is digital pin 2
static int pinB = 3; // Our second hardware interrupt pin is digital pin 3
volatile byte aFlag = 0; // let's us know when we're expecting a rising edge on pinA to signal that the encoder has arrived at a detent
volatile byte bFlag = 0; // let's us know when we're expecting a rising edge on pinB to signal that the encoder has arrived at a detent (opposite direction to when aFlag is set)
volatile byte encoderPos = 0; //this variable stores our current value of encoder position. Change to int or uin16_t instead of byte if you want to record a larger range than 0-255
volatile byte oldEncPos = 0; //stores the last encoder position value so we can compare to the current reading and see if it has changed (so we know when to print to the serial monitor)
volatile byte reading = 0; //somewhere to store the direct values we read from our interrupt pins before checking to see if we have moved a whole detent
// Button reading, including debounce without delay function declarations
const byte buttonPin = 4; // this is the Arduino pin we are connecting the push button to
byte oldButtonState = HIGH;  // assume switch open because of pull-up resistor
const unsigned long debounceTime = 10;  // milliseconds
unsigned long buttonPressTime;  // when the switch last changed state
boolean buttonPressed = 0; // a flag variable
// Menu and submenu/setting declarations
byte Mode = 0;   // This is which menu mode we are in at any given time (top level or one of the submenus)
const byte modeMax = 3; // This is the number of submenus/settings you want
byte setting1 = 0;  // a variable which holds the value we set 
byte setting2 = 0;  // a variable which holds the value we set 
byte setting3 = 0;  // a variable which holds the value we set 
/* Note: you may wish to change settingN etc to int, float or boolean to suit your application. 
 Remember to change "void setAdmin(byte name,*BYTE* setting)" to match and probably add some 
 "modeMax"-type overflow code in the "if(Mode == N && buttonPressed)" section*/

void setup() {
  //Rotary encoder section of setup
  pinMode(pinA, INPUT_PULLUP); // set pinA as an input, pulled HIGH to the logic voltage (5V or 3.3V for most cases)
  pinMode(pinB, INPUT_PULLUP); // set pinB as an input, pulled HIGH to the logic voltage (5V or 3.3V for most cases)
  attachInterrupt(0,PinA,RISING); // set an interrupt on PinA, looking for a rising edge signal and executing the "PinA" Interrupt Service Routine (below)
  attachInterrupt(1,PinB,RISING); // set an interrupt on PinB, looking for a rising edge signal and executing the "PinB" Interrupt Service Routine (below)
  // button section of setup
  pinMode (buttonPin, INPUT_PULLUP); // setup the button pin
  // DEBUGGING section of setup
  Serial.begin(9600);     // DEBUGGING: opens serial port, sets data rate to 9600 bps
}

void loop() {
  rotaryMenu();
  // carry out other loop code here 
}

void rotaryMenu() { //This handles the bulk of the menu functions without needing to install/include/compile a menu library
  //DEBUGGING: Rotary encoder update display if turned
  if(oldEncPos != encoderPos) { // DEBUGGING
    Serial.println(encoderPos);// DEBUGGING. Sometimes the serial monitor may show a value just outside modeMax due to this function. The menu shouldn't be affected.
    oldEncPos = encoderPos;// DEBUGGING
  }// DEBUGGING
  // Button reading with non-delay() debounce - thank you Nick Gammon!
  byte buttonState = digitalRead (buttonPin); 
  if (buttonState != oldButtonState){
    if (millis () - buttonPressTime >= debounceTime){ // debounce
      buttonPressTime = millis ();  // when we closed the switch 
      oldButtonState =  buttonState;  // remember for next time 
      if (buttonState == LOW){
        Serial.println ("Button closed"); // DEBUGGING: print that button has been closed
        buttonPressed = 1;
      }
      else {
        Serial.println ("Button opened"); // DEBUGGING: print that button has been opened
        buttonPressed = 0;  
      }  
    }  // end if debounce time up
  } // end of state change

  //Main menu section
  if (Mode == 0) {
    if (encoderPos > (modeMax+10)) encoderPos = modeMax; // check we haven't gone out of bounds below 0 and correct if we have
    else if (encoderPos > modeMax) encoderPos = 0; // check we haven't gone out of bounds above modeMax and correct if we have
    if (buttonPressed){ 
      Mode = encoderPos; // set the Mode to the current value of input if button has been pressed
      Serial.print("Mode selected: "); //DEBUGGING: print which mode has been selected
      Serial.println(Mode); //DEBUGGING: print which mode has been selected
      buttonPressed = 0; // reset the button status so one press results in one action
      if (Mode == 1) {
        Serial.println("Mode 1"); //DEBUGGING: print which mode has been selected
        encoderPos = setting1; // start adjusting Vout from last set point
      }
      if (Mode == 2) {
        Serial.println("Mode 2"); //DEBUGGING: print which mode has been selected
        encoderPos = setting2; // start adjusting Imax from last set point
      }
      if (Mode == 3) {
        Serial.println("Mode 3"); //DEBUGGING: print which mode has been selected
        encoderPos = setting3; // start adjusting Vmin from last set point
      }
    }
  }
  if (Mode == 1 && buttonPressed) {
    setting1 = encoderPos; // record whatever value your encoder has been turned to, to setting 3
    setAdmin(1,setting1);
    //code to do other things with setting1 here, perhaps update display  
  }
  if (Mode == 2 && buttonPressed) {
    setting2 = encoderPos; // record whatever value your encoder has been turned to, to setting 2
    setAdmin(2,setting2);
    //code to do other things with setting2 here, perhaps update display   
  }
  if (Mode == 3 && buttonPressed){
    setting3 = encoderPos; // record whatever value your encoder has been turned to, to setting 3
    setAdmin(3,setting3);
    //code to do other things with setting3 here, perhaps update display 
  }
} 

// Carry out common activities each time a setting is changed
void setAdmin(byte name, byte setting){
  Serial.print("Setting "); //DEBUGGING
  Serial.print(name); //DEBUGGING
  Serial.print(" = "); //DEBUGGING
  Serial.println(setting);//DEBUGGING
  encoderPos = 0; // reorientate the menu index - optional as we have overflow check code elsewhere
  buttonPressed = 0; // reset the button status so one press results in one action
  Mode = 0; // go back to top level of menu, now that we've set values
  Serial.println("Main Menu"); //DEBUGGING
}

//Rotary encoder interrupt service routine for one encoder pin
void PinA(){
  cli(); //stop interrupts happening before we read pin values
  reading = PIND & 0xC; // read all eight pin values then strip away all but pinA and pinB's values
  if(reading == B00001100 && aFlag) { //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
    encoderPos --; //decrement the encoder's position count
    bFlag = 0; //reset flags for the next turn
    aFlag = 0; //reset flags for the next turn
  }
  else if (reading == B00000100) bFlag = 1; //signal that we're expecting pinB to signal the transition to detent from free rotation
  sei(); //restart interrupts
}

//Rotary encoder interrupt service routine for the other encoder pin
void PinB(){
  cli(); //stop interrupts happening before we read pin values
  reading = PIND & 0xC; //read all eight pin values then strip away all but pinA and pinB's values
  if (reading == B00001100 && bFlag) { //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
    encoderPos ++; //increment the encoder's position count
    bFlag = 0; //reset flags for the next turn
    aFlag = 0; //reset flags for the next turn
  }
  else if (reading == B00001000) aFlag = 1; //signal that we're expecting pinA to signal the transition to detent from free rotation
  sei(); //restart interrupts
}
// end of sketch!

I have used "DEBUGGING" at the start of every comment on any line which isn't critical for the menu to do its thing. If you are happy with the menu's function, you might want to comment out or delete these lines for smaller compiled sketch size.

Be aware that a key part of menu navigation is feedback to the user while they are scrolling through the option and settings. Therefore if you choose not to include the DEBUGGING lines, you should probably use another visual indicator (e.g. LCD text display, LEDs) that encoder inputs are navigating the menu and changing settings.

If I comment out the DEBUGGING lines (noting that some visual feedback would still be needed for menu navigation) the compiled code is around 1,650 bytes for the Arduino Uno, hopefully leaving plenty of room on your ATMEGA328P for the more exciting parts of your sketch!

Go to Step 3 to find out how the menu system works.

Step 3: Operation and Conclusion

Operation

If you open the serial monitor in Arduino after uploading this sketch, and start to turn the encoder shaft, you should see the top level menu rotating through the number of sub-menus/options you have (limited using the modeMax variable). If you press the centre-push button you will see that the mode/sub-menu you have scrolled to is selected and now you have free reign to scroll through 0-255 values in that sub-menu. Now, if you press the centre-push button you will set that value to setting1 or setting2 or setting3 etc. The Arduino automatically and instantaneously returns you to the top level menu once this has happened.

While powered up, the Arduino remembers what you set each setting to and if you go back to the sub-menu for a setting you have already set a value to, it will start your encoder adjustments from the last value you chose!

Conclusion

I set out to write some sketch-based code that would allow rotary encoders to navigate a basic menu for Arduinos. I also tried to make it readable so that, unlike some alternatives, someone could see the menu structure and know what changes to the code they would need to make to tailor the menu to their needs.

This code is basic and generic, specifically to demonstrate the functionality while being easily adaptable to your own application. It uses the serial monitor as a basic debugging tool which also removes the need for a separate display if you want to see how the code works. I hope you find it useful and are inspired to edit, adapt and improve it!

Please share anything you do with it in the comments!

Comments

author
alfabcd6 made it!(author)2017-05-29

hi simon

first of all let me tell you , thank you so much by share the code , i think is really awesome how you made it , but i would like know if you could help me , i am writing with your code an irrigation system , but i want to have inside the submenus numbers since 0 to 10 and not 1 to 255 i have not known how to do it , please i really apreciate if could help me with that...this is my code https://drive.google.com/drive/folders/0B9LSKG78XbpyREw2T0stU3ZpQlE?usp=sharing .....thank u so much.

author
SimonM83 made it!(author)2017-05-29

Hi alfabcd6, if you look at the code you can see modeMax and this sets the maximum value of the main menu. It sounds like you want to set the maximum value of your submenus. If so, you can add in modeMax1, modeMax2, modeMax3 etc. Then, in each case also include the modeMax code, as in: if(encoderPos > modeMax1+10){encoderPos = modeMax1};
and:
if(encoderPos > modeMax1) {encoderPos = 0};
and then do that for each submenu. I hope that helps - let me know.

author
RonF57 made it!(author)2017-03-19

Hi SimonM83

Problem solved. I got the library structure sorted. And, the key to the compiler issue was the need to edit the .h code to select the screen 128x64, which was commented out, and the 128x32 was live. This is sad, since the top lines of comment talks to being for 128x64 !!! You would think they would make that live as default. Anyway, thanks so much for all your email support. To all out there, just stay focussed and committed, humble enough to ask and grateful when someone, like Simon and others, offer appreciated help. Off to code my first lines of text !!!

author
RonF57 made it!(author)2017-03-18

Hi again. No, at least not on purpose. I am sure I'm not using the downloads folder for my arduino sketches. To be honest, the file structure is so screwed up, I wonder if I should delete everything my by only .ino that matters and start over. Thing is, I don't recall (memory issues aren't helping me), where or how I even did it. Can you point to a resource of what the file structure should be? I've learned that for each .ino sketch, there needs to be a folder of the same root name. I assume nothing else goes in that folder. Just the one file. So where then do these library and 1396 files go. This is where I think the problem is. Not only is adafruit a bit vague in that they say download and unzip, rename and that's probably where I went wrong. Trying now to google help for file folder structure, but your advice is even more appreciated.

author
RonF57 made it!(author)2017-03-18

SimonM83

While you were being so kind, I too found that verbose code. Sorry, it's all here. Skip to the end (****************)

Arduino: 1.8.1 (Mac OS X), Board: "Arduino/Genuino Uno"

/Applications/Arduino.app/Contents/Java/arduino-builder -dump-prefs -logger=machine -hardware /Applications/Arduino.app/Contents/Java/hardware -tools /Applications/Arduino.app/Contents/Java/tools-builder -tools /Applications/Arduino.app/Contents/Java/hardware/tools/avr -built-in-libraries /Applications/Arduino.app/Contents/Java/libraries -libraries /Users/ronfinlay1/Documents/Arduino/libraries -fqbn=arduino:avr:uno -vid-pid=0X2A03_0X0043 -ide-version=10801 -build-path /var/folders/lq/2t0vyq_j001418csm6hnb_l80000gp/T/arduino_build_928943 -warnings=all -prefs=build.warn_data_percentage=75 -prefs=runtime.tools.avr-gcc.path=/Applications/Arduino.app/Contents/Java/hardware/tools/avr -prefs=runtime.tools.avrdude.path=/Applications/Arduino.app/Contents/Java/hardware/tools/avr -prefs=runtime.tools.arduinoOTA.path=/Applications/Arduino.app/Contents/Java/hardware/tools/avr -verbose /Users/ronfinlay1/Downloads/Adafruit_SSD1306-master-6/examples/DisplayTest_spi/DisplayTest_spi.ino

/Applications/Arduino.app/Contents/Java/arduino-builder -compile -logger=machine -hardware /Applications/Arduino.app/Contents/Java/hardware -tools /Applications/Arduino.app/Contents/Java/tools-builder -tools /Applications/Arduino.app/Contents/Java/hardware/tools/avr -built-in-libraries /Applications/Arduino.app/Contents/Java/libraries -libraries /Users/ronfinlay1/Documents/Arduino/libraries -fqbn=arduino:avr:uno -vid-pid=0X2A03_0X0043 -ide-version=10801 -build-path /var/folders/lq/2t0vyq_j001418csm6hnb_l80000gp/T/arduino_build_928943 -warnings=all -prefs=build.warn_data_percentage=75 -prefs=runtime.tools.avr-gcc.path=/Applications/Arduino.app/Contents/Java/hardware/tools/avr -prefs=runtime.tools.avrdude.path=/Applications/Arduino.app/Contents/Java/hardware/tools/avr -prefs=runtime.tools.arduinoOTA.path=/Applications/Arduino.app/Contents/Java/hardware/tools/avr -verbose /Users/ronfinlay1/Downloads/Adafruit_SSD1306-master-6/examples/DisplayTest_spi/DisplayTest_spi.ino

Using board 'uno' from platform in folder: /Applications/Arduino.app/Contents/Java/hardware/arduino/avr

Using core 'arduino' from platform in folder: /Applications/Arduino.app/Contents/Java/hardware/arduino/avr

Detecting libraries used...

"/Applications/Arduino.app/Contents/Java/hardware/tools/avr/bin/avr-g++" -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -flto -w -x c++ -E -CC -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10801 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR "-I/Applications/Arduino.app/Contents/Java/hardware/arduino/avr/cores/arduino" "-I/Applications/Arduino.app/Contents/Java/hardware/arduino/avr/variants/standard" "/var/folders/lq/2t0vyq_j001418csm6hnb_l80000gp/T/arduino_build_928943/sketch/DisplayTest_spi.ino.cpp" -o "/dev/null"

"/Applications/Arduino.app/Contents/Java/hardware/tools/avr/bin/avr-g++" -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -flto -w -x c++ -E -CC -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10801 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR "-I/Applications/Arduino.app/Contents/Java/hardware/arduino/avr/cores/arduino" "-I/Applications/Arduino.app/Contents/Java/hardware/arduino/avr/variants/standard" "-I/Applications/Arduino.app/Contents/Java/hardware/arduino/avr/libraries/SPI/src" "/var/folders/lq/2t0vyq_j001418csm6hnb_l80000gp/T/arduino_build_928943/sketch/DisplayTest_spi.ino.cpp" -o "/dev/null"

"/Applications/Arduino.app/Contents/Java/hardware/tools/avr/bin/avr-g++" -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -flto -w -x c++ -E -CC -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10801 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR "-I/Applications/Arduino.app/Contents/Java/hardware/arduino/avr/cores/arduino" "-I/Applications/Arduino.app/Contents/Java/hardware/arduino/avr/variants/standard" "-I/Applications/Arduino.app/Contents/Java/hardware/arduino/avr/libraries/SPI/src" "-I/Applications/Arduino.app/Contents/Java/hardware/arduino/avr/libraries/Wire/src" "/var/folders/lq/2t0vyq_j001418csm6hnb_l80000gp/T/arduino_build_928943/sketch/DisplayTest_spi.ino.cpp" -o "/dev/null"

"/Applications/Arduino.app/Contents/Java/hardware/tools/avr/bin/avr-g++" -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -flto -w -x c++ -E -CC -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10801 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR "-I/Applications/Arduino.app/Contents/Java/hardware/arduino/avr/cores/arduino" "-I/Applications/Arduino.app/Contents/Java/hardware/arduino/avr/variants/standard" "-I/Applications/Arduino.app/Contents/Java/hardware/arduino/avr/libraries/SPI/src" "-I/Applications/Arduino.app/Contents/Java/hardware/arduino/avr/libraries/Wire/src" "/var/folders/lq/2t0vyq_j001418csm6hnb_l80000gp/T/arduino_build_928943/sketch/DisplayTest_spi.ino.cpp" -o "/var/folders/lq/2t0vyq_j001418csm6hnb_l80000gp/T/arduino_build_928943/preproc/ctags_target_for_gcc_minus_e.cpp"

************************************************************************

/Users/ronfinlay1/Downloads/Adafruit_SSD1306-master-6/examples/DisplayTest_spi/DisplayTest_spi.ino:21:26: fatal error: Adafruit_GFX.h: No such file or directory

#include <Adafruit_GFX.h>

^

compilation terminated.

Using library SPI at version 1.0 in folder: /Applications/Arduino.app/Contents/Java/hardware/arduino/avr/libraries/SPI

Using library Wire at version 1.0 in folder: /Applications/Arduino.app/Contents/Java/hardware/arduino/avr/libraries/Wire

exit status 1

Error compiling for board Arduino/Genuino Uno.

*************************************************************************************

I think the problem is this /Users/ronfinlay1/... master-6 (the 6th failed attempt to follow adafruit's instructions)

As you can see below, the files are in the same folder, higher up in the structure.

I'm not understanding this renaming (to what) or where to store/move them, not the connection per my structure to master-6. I think this will fix it, if you can just guide me a bit more. I really appreciate this. It's killing me !!!

Screen shot 2.png
author
SimonM83 made it!(author)2017-03-18

Ron, please tell me you haven't selected your downloads folder as the default place to save Arduino sketches? The file path in the error message suggests it is trying to look there. Anyway, a few more things to try:

1) try installing version 1.0.5 of the Arduino IDE - I know I can compile and upload to SSD1306 OLEDs from that version.

2) in the file naming, make sure you remove the hyphens and master and number suffixes, so "Adafruit_SSD1306-master-6" folder would become "Adafruit_SSD1306". Make sure this is the version in your legitimate Arduino libraries folder, not something you unzipped into the downloads folder.

3) always restart the IDE if you make changes to the libraries or the folders containing them.

4) in the screenshot of your folder system, you appear to have moved the files Adafruit_GFX.cpp and Adafruit_GFX.h out of the Adafruit_GFX folder and into the Adafruit_SSD1306 folder. Move these two files back to the Adafruit_GFX folder.

5) once in IDE version 1.0.5, open File/Examples/Adafruit_SSD1306 and then the SPI version of the 128x64 OLED (I presume you haven't modified the OLED PCB traces for I2C communication protocol instead of the default SPI protocol it should have arrived set up for? You'd have had to solder something to change this.). Try compiling now, having opened the example sketch from the IDE Examples section NOT by File/Open or by double clicking the sketch file from somewhere like the downloads folder in your file explorer (or Mac equivalent).

Keep going!

author
RonF57 made it!(author)2017-03-18

Hi SimonM83. I hope this is the right place to ask again. I downloaded the code from adafruit for my 128x 64 mono chrome OLED and even that has a compile error. This is most frustrating when it's their own code. They also say to download GFX library, but then it complains that the library name can't have spaces, as in GFX_Library or GFXLibrary in my Mac Finder folder level. So, never mind encoder, I can't even get the display to prove it works. Please advise. Thanks.

Aslo, when I get to your first kind reply, even that leaves me lost. Jumping into all this code and changing something won't help. Sorry to be a pain, but I find the online world of these forums very cryptic and a lot is assumed to be understood to make sense of the valid comments such skilled people like you leave. No idea where to start. This coding is so complex. I appreciate your help.

author
SimonM83 made it!(author)2017-03-18

Ron, it sounds like you're trying to bite off a pretty challenging project for the level of skill you appear to have in this area. Even switching from LCD to OLED between your two comments would mean different technologies that have different driver chips and need different code to communicate with them.

Let's try getting the OLED working. I don't use Mac, so some bits may be specific to the OS. What adafruit product do you have? Can you post a link? Have you searched for other people having and solving issues with connecting to it? Someone might have a much better ready-made answer already on the arduino or other forum than I could come up with. If you've managed to download the relevant libraries and install them correctly in the arduino IDE, I assume you've opened an example sketch for the display. Do you have the correct Arduino board selected in the IDE when you compile? It's a long shot but perhaps worth checking. Which Arduino are you using? You'll need to post much more information before people can help you get further with this. I'd recommend setting up an Arduino forum thread on this issue if you can't find a similar one that has already been started.

author
RonF57 made it!(author)2017-03-18

Hi SimonM83 Sorry, here's more of what you wisely asked for.

I have the Adafruit original UNO, not a clone. I am delighted to support them as a source, not go offshore looking for a bargain.

Other sketches I made/copied using the Arduino project book kit from adafruit worked (blinking LED and so on.) This OLED test program is the first large code I've downloaded. I assume I have the files and directories right, but again, not sure how they must be in a hierarchy, or if simply being in the same folder, even if in different subfolders is still OK. I have the pins hooked up per adafruit's site, but, without compiling, that won't matter yet.

Not sure where I would specify which arduino board I have in the code. Not understanding what you mean.

I'll look into a thread forum and see what that brings. Thanks again.

author
SimonM83 made it!(author)2017-03-18

Ron, I've just tried compiling this in both versions of the Arduino IDE that I have. I have Windows but I noticed that I got the same error as you did when you used version 1.8 when I used version 1.6.9. I tried it with the much older version 1.0.5. and it compiled. Can you get hold of version 1.0.5 for Mac? Worth a shot.

author
RonF57 made it!(author)2017-03-18

Hi SimonM83

Your'e right, it's a lot to take in from my starting point of zero. I am using the Adafruit SSD1306 (their part number is 326) monochrome OLED (not LCD, my bad). I think I have my libraries and folders all set up.

https://learn.adafruit.com/monochrome-oled-breakou...

This is the page where I assumed I would get code to at least demo the unit, but, the attached image shows what I'm getting. I hope you can read it.

There are so many forums, and people all over the world on this platform, it's overwhelmingly wonderful. I would just like to meet someone in Toronto who can help me for an hour to two. My project is not that complex to someone who understands this coding, and I'm sure I can pick it up once I have a framework.

OLED Compile Error Screen 1.png
author
SimonM83 made it!(author)2017-03-18

Ron, it's a good sign that you have the other sketches working (I presume other than blink, which comes preloaded?).

When you went and installed the SSD1306 and GFX libraries, did you follow these instructions from adafruit:

Rename the uncompressed folder Adafruit_SSD1306 and check that the Adafruit_SSD1306 folder contains Adafruit_SSD1306.cpp and Adafruit_SSD1306.h

Place the Adafruit_SSD1306 library folder your arduinosketchfolder/libraries/ folder.
You may need to create the libraries subfolder if its your first library. Restart the IDE.We also have a great tutorial on Arduino library installation at:
http://learn.adafruit.com/adafruit-all-about-arduino-libraries-install-use

Because that's pretty fundamental during compilation. Do you know if you have a libraries folder in the place where you usually save sketches (try Save As on the blink sketch and see if you can see a libraries folder when it opens up the default location. If not, follow the libraries advice in the adafruit link.

Good move including the screenshot. In Arduino IDE can you go to File/Preferences and tick the box for verbose output during compile? Then try and compile again (which should spit out lots more detailed text at the bottom of the screen) and press the button to the right of the orange error message bar which says "Copy error messages" and then Google that text to see if you have a common problem, paste it here and use it in any Arduino forum thread you write on.

author
ayushs12 made it!(author)2016-08-12

well done,good work:)

author
SimonM83 made it!(author)2016-08-12

Thank you!

author
RonF57 made it!(author)2017-03-17

Hi SimonM83. You and all those who've commented on your code are brilliant. I am lost with this coding, but want to create menus and sub menus, using a push button/encoder, and have them display on 128x64 LCD adafruit LCD. Am I missing something because I read the 4 pages of your writeup, followed the idea (your coding is impressive!), but don't see where the switch part (select) or LCD, or code to create menu items are. I want to scroll through a range of patterns, then colours for Neo Pixel rings and sticks, then push to select at each level. Thanks for any direction to this noob, who is fascinated by you young people. Amazing.

author
SimonM83 made it!(author)2017-03-18

Hi Ron, here are the sections of code which refer to the push button/switch that represents a "select" input from the user:

const byte buttonPin = 4; // this is the Arduino pin we are connecting the push button to
void rotaryMenu() { ...
...// Button reading with non-delay() debounce - thank you Nick Gammon!
byte buttonState = digitalRead (buttonPin);... (then the following logic in the rest of the rotaryMenu function)


If you want to use the lcd, you need to put some code in that displays what you want, when you want. The code I've suggested isn't for an lcd, it uses the computer screen to display text in the Arduino IDE's serial monitor window when the Arduino is connected to the computer. If you look at the comments from TheMariuzaz below and my responses to them, you should see what I mean by some code to make the lcd display something. The adafruit tutorial for your lcd should give you the correct kind of code to use.

author
TheMariuzaz made it!(author)2017-01-10

Nice code, but because I'm a total noob at Arduino, I want to understand how to refresh LCD in other mode than main menu when I twist the rotary encoder?

author
SimonM83 made it!(author)2017-01-10

Have you tried putting an lcd update term in place of every Serial.println() and Serial.print()?

author
TheMariuzaz made it!(author)2017-01-13

My code portion that should refresh looks like this: http://pastebin.com/PxAQGuwJ

I haven't modified anything else in your code, only added the standby code, that would show the time on the LCD, and that works perfectly.

author
SimonM83 made it!(author)2017-01-13

Are you saying you still have the same problem? I don't know if this is an issue but:

if(setting1 <0){

is confusing me. Are you supposed to check if a value is LESS than zero in your program?

author
TheMariuzaz made it!(author)2017-01-13

I've made a typo, but it doesn't work even fixed.

author
SimonM83 made it!(author)2017-01-13

I'm struggling to see where your code fits into mine from the pastebin link.

if (setting1 > 0) {
lcd.setCursor(0,1);
lcd.write(" ");
lcd.setCursor(11,1);
lcd.write((uint8_t)0);
}
Should it be:
if (setting1 > 0) {
lcd.setCursor(0,1);
lcd.write(" ");
lcd.setCursor(11,1);
lcd.write((uint8_t)setting1);
}
?
It seems like both conditions of setting1 =0 OR setting1 > 0 lead to the lcd displaying 0.

author
TheMariuzaz made it!(author)2017-01-15

It's supposed to move the cursor according to the setting1 (the cursor itself shows propely, but it won't change position in real-time). I've modified the code to display setting1 on the LCD, it still won't refresh in real-time. BUT the setting1 gets saved as it should, and when i go to the menu, it displays the saved setting, but again it won't refresh in real-time.

author
TheMariuzaz made it!(author)2017-01-15

Oh i found a way and going to try that soon. I'll try to use the debugging code:

//DEBUGGING: Rotary encoder update display if turned

if(oldEncPos != encoderPos) { // DEBUGGING

Serial.println(encoderPos);// DEBUGGING. Sometimes the serial monitor may show a value just outside modeMax due to this function. The menu shouldn't be affected.

oldEncPos = encoderPos;// DEBUGGING

}// DEBUGGING

to display the pointer seperatly when needed.

author
TheMariuzaz made it!(author)2017-01-15

Aaaaand IT WORKS! Anyway, thanks a lot for help and forgive me for my stupidness :D

author
SimonM83 made it!(author)2017-01-18

TheMariuzaz I'm glad it worked - I'm not sure I helped at all but it's great that you worked things through!

author
ArkadyG made it!(author)2017-01-06

Thanks a lot!

I copyied part of code with Button reading.

author
SimonM83 made it!(author)2017-01-06

Glad to hear it's useful. The button reading part is all Nick Gammon's - he's got a great website full of useful information, especially about atmega328p arduinos.

author
ArkadyG made it!(author)2017-01-07

Thanks for advise. I must visit his site!

author
MF34 made it!(author)2016-11-07

realy nice code will deffinetly use this for my project was just looking for some thing just like it going to use it with my hydration project "forgetting to water plants :-) " will be perfect to sett my pump duration and sensor reading intervalls and all readeble on a 160x128 full color tft THX

author
SimonM83 made it!(author)2016-11-08

Cool, glad you think it will be helpful - please post a picture of the screen when you get it working.

author
MF34 made it!(author)2016-12-14

Hi i have been thinking do u think it would be possible to make sub menus im not a good coder soo im a bit stuck at the moment but im still learning if u want i can send u my code and u can also se how i buchered some of your :-)

author
MF34 made it!(author)2016-12-07

Here is some photos of the meny at present state press it it goes in to main then rotate to 1 or 2 ant then in each setting a simpel value change press again it jumps out to front page then to settup next thing enter menu again then next

20161121_181939.jpg20161121_181917.jpg20161121_181908.jpg20161121_211037.jpg
author
SimonM83 made it!(author)2016-12-07

Awesome! Congratulations and thank you for sharing - you've designed an nice screen layout there.

author
InvIoT made it!(author)2016-11-21

I use an lcd screen 20x4 on my board.

https://www.instructables.com/howto/inviot/

or

http://www.inviot.com/u1

author
burgarwulf made it!(author)2016-12-13

Firstly thanks for the code! Out of the available options I found its far and away the easiest to utilize.

I've been playing with the code a bit the last few hours, and I've got a mostly functional menu working that'll show temp (in C/F) and humidity on sub-menus 1 & 2 respectively.

Problem I'm having is that when the mode is set, they do not update the reading from the sensor. So I've tried a few different arguments (FOR, DO-WHILE) to get the reading to update while in the sub-menu, but then it locks me out of going back to Main Menu.

What would be the best way to implement active readings to the menu system?

author
SimonM83 made it!(author)2016-12-14

I think there are three ways I'd try initially.
First, for periodic updates you could use a non-blocking timer such as the method used in "blink without delay" sketches and the button press debouncing code I used in this (Nick Gammon's). Just put the if(millis() - delay > lastTime) type of statement in the main loop.
Second, you can just add your "read temp, display temp" code in each of the if (Mode == X) statements for VERY frequent updates.
Thirdly, if you can wait for temp to update only when you press the select button, place your "read temp, display temp" code in the setAdmin function.
My preference would be the first option. Let us know how you get on.

author
cheeto4493 made it!(author)2016-11-06

I'd like to use this with the TVout library. Do you think it would work?

My plan is to use an Arduino in my vehicle, and use the aux input on the back-up-camera monitor and an encoder to let me change settings.

author
SimonM83 made it!(author)2016-11-06

I'm not familiar with any setups like that but it doesn't look like there's a pin conflict. Haven't had a look into whether there are interrupt conflicts but that'd be a good place to start if you run into issues. Go for it (and let us know how it goes)!

author
vitofish made it!(author)2016-10-27

excellent job! thank you for sharing

author
SimonM83 made it!(author)2016-10-27

You're welcome, great to hear it's appreciated!

author
msmilek made it!(author)2016-08-15

Hi wery nice work. But on MEGA not working. I mean this compilation OK, upload to board ok but working only push button. for rotating to left or riht , serial monitor no output. On Ard. UNO is all right ( is working perfectly) Hi Mirek.

author
SimonM83 made it!(author)2016-08-15

Hi, I haven't tested this but I expect the reason it doesn't work directly with the mega2560 is because the interrupt service routine relies on port manipulation and the mega2560's hardware interrupt pins aren't on the same ports as the atmega328. My code above could be easily adapted for the mega2560 if you change PIND to the correct port, eg PINE, to read port E. It looks like arduino interrupt pins are on 2 and 3, which appears to equate to PE4 and PE5. That means you also need to change the B0000XX00 to bit shifting/ masking/ comparing B00XX0000 (PE0 will be the right hand bit, PE7 will be the left hand bit) to deal with the readings from your encoder. Good luck with the edit - I think it's definitely doable. Please post the code you change once you get it working so other people can use this on the mega2560

author
Suppeschluerfer made it!(author)2016-09-10

Mega 2560 - Pin 2(INT0) and 3(INT1):

PIND & 0xC change to PINE & 0x30
B00001100 change to B00110000
B00000100 change to B00010000
B00001000 change to B00100000

author
SimonM83 made it!(author)2016-09-10

That's really helpful for everyone - thank you!

author
nmsr1196 made it!(author)2016-08-16

Very nice information.

I have a rotary encoder. But, it has 4 pins on one side and 3 pins on the other side.

How would I connect it to the UNO?

author
SimonM83 made it!(author)2016-08-16

It's pretty difficult to tell you without a datasheet or even a picture. Is it illuminated? The ones with 4 pins I can see in a Google image search often have illuminated knobs and the 4 pin side is for powering them and the button push contact.

I'd try putting a multimeter across different combinations of two pins on the side which has three pins. Use the multimeter in resistance measurement mode and rotate the knob to see if the resistance switches from high to zero as you click between the detents. Once you have the two combinations which do this, you will see that one pin is used in both combinations. This is the common or ground pin. The other two are your pin A and pin B - it doesn't matter which way round at the moment as you can swap the connection to the Uno pins if you want it to increment the position counter in a different direction.

Good luck!

author
nmsr1196 made it!(author)2016-08-17

The image is attached. As mentioned there's four pins on one side and 3 pins on the other side. Good call on the 'illumination knob'. In the picture, (on the four pin side) I applied ground on the far left. I applied a (+) voltage on the second pin the shaft illuminated green. I applied a (+) voltage on the third pin and the shaft turned red. I applied a (+) voltage on the fourth pin and nothing happened. Now that I see 3 of the four pins are for the illumination, There are still the three pins on the other side and the fourth pin. How would I connect it according to your diagram?

Thanks for your help (in advance)

Rotary.jpg
author
SimonM83 made it!(author)2016-08-17

Well done for working all that out. My original suggestion still remains - try it on the side with three pins.

author
nmsr1196 made it!(author)2016-08-17

Ok.

Thank you for you help.

I got it working. Just in case someone else has the same type of rotary encoder, the connections are listed below. Now the key is to fully understand the code and incorporated it into an existing/new project.

ROTARY CONNECTIONS

Four_Pin_Side /Arduino (UNO)

left_Pin: GND

Next_Pin: N/C (unless used for red shaft)

Next_Pin: N/C (unless used for green shaft)

Right_Pin: 4

Three_Pin_Side /Aruduino (UNO)

left_Pin: 2

Middle_Pin: GND

Right_Pin: 3