I actually built-out a really neat menu for a past project of mine.
It involved a rotary encoder and LCD screen.
I ended up re-building the code a few times until I was happy with ultimately making it a state-machine.
Here is everything from my project that involved the rotary encoder:
//libraries to include:
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// rotary encoder variables:
const byte rotPin1 = 2; // A
const byte rotPin2 = 3; // B
volatile int rotPin1Value = 0;
volatile int rotPin2Value = 0;
volatile int oldRotPin1Value = 0;
volatile int oldRotPin2Value = 0;
volatile int rotPosition = 0;
volatile int oldRotPosition = 0;
volatile int rotTurn = 0;
volatile int oldRotTurn = 0;
volatile int rotTurnCount=0;
volatile int rotValue;
// note: this is the value that the rotary encoder function
// is updating with every turn.
// rotary encoder button pin:
const byte rotButton = A3;
// note: This button will be the main button
// keep track of when the button is pressed:
boolean lastRotButtonState;
boolean rotButtonState;
// note: we use these in the edgeDetectRotButton() function.
// to keep track of your state-machines state:
// variables for the state-machine:
boolean sOIntroScreen = false;
boolean sOAnotherMenuScreen = false;
//... and so on and so fourth.
// state-machine (default menu values):
// intro:
byte introChoice = 2;
// 0=ABOUT, 1=OPTIONS, 2=start a game (default)
byte anotherMenuScreen = 1;
// 0=BACK, 1=Whatever, 2=Whatever
//... and so on and so fourth
void setup()
{
// set the rotary encoder button pin as an input:
pinMode(rotButton, INPUT);
// set the rotary Pin #1 to be an "input":
pinMode(rotPin1, INPUT);
// turn on the internal pull-up resistor:
digitalWrite(rotPin1, HIGH);
// set the rotary pin #2 to be an "input":
pinMode(rotPin2, INPUT);
// turn on the internal pull-up resistor:
digitalWrite(rotPin2, HIGH);
//call rotEncoder() when any high/low changed seen
//on interrupt 0 (pin 2), or interrupt 1 (pin 3)
attachInterrupt(0, rotEncoder, CHANGE);
attachInterrupt(1, rotEncoder, CHANGE);
}
void loop()
{
// loop through looking for the "S"tate "O"f:
// note: this a "state machine".
// intro:
if(sOIntroScreen == true)
{
introScreen();
sOIntro = false;
}
// another menu screen:
if(sOAnotherMenuScreen == true)
{
anotherMenuScreen();
sOAnotherMenuScreen = false;
}
//...and so on and so fourth.
}
// example of one of the "sates" of the state-machine:
void introScreen()
{
// setup the LCD screen:
lcd.clear();
lcd.setCursor(0,0);
lcd.print(F(" INTRO SCREEN "));
// set the starting rotary encoder value:
rotValue = introChoice;
// while the rotary button remains un-pressed:
while (edgeDetectRotButton() == false)
{
// keep the rotary value constrained:
rotValue = constrain(rotValue, 0, 2);
// note: 0=ABOUT, 1=OPTIONS, 2=start a game
// update the LCD screen:
// 0=ABOUT:
if(rotValue == 0)
{
lcd.setCursor(0,1);
lcd.print(F(" - ABOUT - "));
lcd.setCursor(0,3);
lcd.print(F("*--"));
}
// 1=OPTIONS:
else if(rotValue == 1)
{
lcd.setCursor(0,1);
lcd.print(F(" - OPTIONS - "));
lcd.setCursor(0,3);
lcd.print(F("-*-"));
}
// 2=start a game:
else if(rotValue == 2)
{
lcd.setCursor(0,1);
lcd.print(F(" - Start a Game - "));
lcd.setCursor(0,3);
lcd.print(F("--*"));
}
}
// decide what to do now that the rotary button has
// been pressed:
// update the introChoice variable:
introChoice = rotValue;
// 0=ABOUT:
if(introChoice == 0)
{
// change the State Of:
sOAbout = true;
// get out of this function:
return;
}
// 1=OPTIONS:
else if(introChoice == 1)
{
// change the State Of:
sOSelectOptions = true;
// get out of this function:
return;
}
// 2=start a game:
else if(introChoice == 2)
{
// change the State Of:
sOSelectTimerType = true;
// get out of this function:
return;
}
}
void rotEncoder()
{
// original: https://gist.github.com/medecau/154809
// interrupt: bildr article: http://bildr.org/2012/08/rotary-encoder-arduino/
// helpful: http://playground.arduino.cc/Main/RotaryEncoders#Example1
// get the rotary encoder pin values:
rotPin1Value = digitalRead(rotPin1);
rotPin2Value = digitalRead(rotPin2);
// Check if there is any change to either of the pin values:
if(rotPin1Value != oldRotPin1Value
|| rotPin2Value != oldRotPin2Value)
{
// There are four possible rotary-encoder positions for a
// "quadrature" rotary encoder. These 4 possible positions
// are between each "click". The reason there are 4 positions
// between each click instead of just 1 position for each
// click is so we can quickly detect which direction the rotary
// encoder is turning.
// the four posible positions are designated by the following
// rotary encoder pin values:
// see: http://playground.arduino.cc/Main/RotaryEncoders#Example1
// check for the first position:
if(rotPin1Value == 1 && rotPin2Value == 1)
{
// this is the stationary position of the rotary encoder:
rotPosition = 0;
}
// check for the second position:
else if(rotPin1Value == 0 && rotPin2Value == 1)
{
// this is the stationary position of the rotary encoder:
rotPosition = 1;
}
// check for the third position:
else if(rotPin1Value == 0 && rotPin2Value == 0)
{
// this is the stationary position of the rotary encoder:
rotPosition = 2;
}
// check for the fourth position:
else if(rotPin1Value == 1 && rotPin2Value == 0)
{
// this is the stationary position of the rotary encoder:
rotPosition = 3;
}
// set the turn value:
rotTurn = rotPosition - oldRotPosition;
// For example: if the old rotary position was 3, and the
// current rotary position is 2, then the rotary encoder has
// turned -1 positions (2-3).
// if turn is indicating 2, then we must have missed a
// position, and we are no longer able to tell which direction
// we are turning.
// check that we didn't miss a positon:
if(abs(rotTurn) != 2)
{
// check for a turn value that indicates clockwise:
if(rotTurn == -1 || rotTurn == 3)
{
// increment the turn count variable:
rotTurnCount++;
}
// check for a turn value that indicates counter-clockwise:
else if(rotTurn == 1 || rotTurn == -3)
{
// decrement the turn count variable:
rotTurnCount--;
}
}
// now check to see if we have made a complete "click" of the
// rotary encoder: (aka: check whether or not we are back in
// the stationary position)
if(rotPosition == 0)
{
// check the turn count value to see which direction we have
// just finished turning:
if(rotTurnCount > 0)
{
rotValue++;
}
else if(rotTurnCount < 0)
{
rotValue--;
}
// reset the turn count variable:
rotTurnCount = 0;
}
// log all the positions as old:
oldRotPin1Value = rotPin1Value;
oldRotPin2Value = rotPin2Value;
oldRotPosition = rotPosition;
}
}
// FUNCTION:
boolean edgeDetectRotButton()
{
// check if the button has been pressed:
boolean reading = digitalRead(rotButton);
if(reading != lastRotButtonState)
{
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay)
{
if(reading != rotButtonState)
{
rotButtonState = reading;
if(rotButtonState == HIGH)
{
return true;
}
}
return false;
}
// set last button state = to reading.
lastRotButtonState = reading;
}
if you wanted to see the menu in-action, you can check out this video:
(warning: it's a long video...)
Hope this helps!
(this is my first contributing post on the forum, all my other posts have been questions)
-Josh!