Rotary Encoder Menu Code

I've been working on a project (an aquarium controller) that uses a 16x2 LCD display and a rotary encoder to navigate the menus. Some people asked if I could post the code, so here it is. I've stripped out most of the rest of the program, but there is still a fair amount of 'extra' code in there, mainly to make it so I can cut and paste it without much modification. I used a rotary encoder like this one: http://www.mouser.com/ProductDetail/Bourns/PEL12T-4225S-S1024/?qs=sGAEpiMZZMsWp46O%252bq11WTU2mLe3dQ9dMsin9UeDZ%252bM%3D The switch is combined with common cathode LEDs, so you have to add a pull-down resistor to a digital pin that is pulled high when the switch is pressed.

The text for the LCD menus takes up a lot of memory, so I put it all in Flash and access it using strcpy_P to save SRAM. I use the oopinchaneint library and adaencoder library from Adafruit, along with liquidTWI i2c LCD library.

The first bunch of code is just all the variable definitions, etc and some rudimentary code in loop() to get you into the actual menu routines. Feel free to comment, critique, use, or abuse it as you wish. I am not a professional programmer and do not claim this to be the best code. I'm simply posting it in the hopes it may help others.

Part 1: variable definitions, setup() and void() routines:

#include <avr/pgmspace.h>  // library for keeping variables in Flash RAM
#include <LiquidTWI.h>     // i2c liquid crystal library
#include <ooPinChangeInt.h>
#include <AdaEncoder.h>    // adafruit.com eoncoder routine.
#include <Wire.h>

#define menuTimeout 10000  //delay before settings menus time out & return

#define sumpLevelSwitchPin 2
#define EncoderPin1 6        // Rotary Encoder Pins
#define EncoderPin2 7        //
#define buttonPin A2         // pin for encoder button

#define ATOlevel 20 

#define FanPin 0
#define Heater1Pin 1  
#define Heater2Pin 2
#define Light1Pin 3
#define Light2Pin 4
#define Light3Pin 5
#define ATOPin 6
#define SkimmerPin 7
#define Powerhead1Pin 8
#define Powerhead2Pin 9
#define Powerhead3Pin 10
#define ReturnPumpPin 11
#define CircPumpPin 12
#define Doser1Pin 13
#define Doser2Pin 14
#define AuxPin 15

unsigned long lastTime;
unsigned long lastMills=0;
unsigned long timeout;

byte time[] = {
  12, 00, 00, 00}; // hr, min, sec, msec
byte Date[] = {
  01, 01, 13};     // month, day, year
byte Light1On[] = {
  9, 00};      // hr, min
byte Light1Off[] = {
  22, 00};    // hr, min
byte Light2On[] = {
  10, 00};
byte Light2Off[] = {
  21, 00};
byte Light3On[] = {
  21,00};
byte Light3Off[] = {
  9,00};
byte ATO[] = {
  1, 30};           // full, empty depth of ATO in cm
byte Doser1On[] = {
  0,0};
byte Doser1Off[] = {
  0,0};
byte Doser2On[] = {
  0,0};
byte Doser2Off[] = {
  0,0};

byte Outlets[16] = {
  1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1};
char buffer[18];
prog_char mainMenu1[] PROGMEM = "1.Water Change ";
prog_char mainMenu2[] PROGMEM = "2.Feed         ";
prog_char mainMenu3[] PROGMEM = "3.Man Override ";
prog_char mainMenu4[] PROGMEM = "4.Settings     ";
prog_char mainMenu5[] PROGMEM = "5.Exit         ";
PROGMEM const char *mainMenu[] = {
  mainMenu1, mainMenu2, mainMenu3, mainMenu4, mainMenu5};

prog_char OverrideMenu0[] PROGMEM = "1. Fan         ";
prog_char OverrideMenu1[] PROGMEM = "2. Heater 1    ";
prog_char OverrideMenu2[] PROGMEM = "3. Heater 2    ";
prog_char OverrideMenu3[] PROGMEM = "4. Light1      ";
prog_char OverrideMenu4[] PROGMEM = "5. Light2      ";
prog_char OverrideMenu5[] PROGMEM = "6. Light3      ";
prog_char OverrideMenu6[] PROGMEM = "7. ATO         ";
prog_char OverrideMenu7[] PROGMEM = "8. Skimmer     ";
prog_char OverrideMenu8[] PROGMEM = "9. Powerhead 1 ";
prog_char OverrideMenu9[] PROGMEM = "10.Powerhead 2 ";
prog_char OverrideMenu10[] PROGMEM = "11.Powerhead 3 ";
prog_char OverrideMenu11[] PROGMEM = "12.Return Pump ";
prog_char OverrideMenu12[] PROGMEM = "13.Circ Pump   ";
prog_char OverrideMenu13[] PROGMEM = "14.Dosing Pump1";
prog_char OverrideMenu14[] PROGMEM = "15.Dosing Pump2";
prog_char OverrideMenu15[] PROGMEM = "16.Aux Outlet  ";
prog_char OverrideMenu16[] PROGMEM = "17.Exit        ";
PROGMEM const char *OverrideMenu[] = {
  OverrideMenu0, OverrideMenu1, OverrideMenu2, OverrideMenu3, OverrideMenu4, OverrideMenu5,
  OverrideMenu6, OverrideMenu7, OverrideMenu8, OverrideMenu9, OverrideMenu10, OverrideMenu11,
  OverrideMenu12, OverrideMenu13, OverrideMenu14, OverrideMenu15, OverrideMenu16};

prog_char OverrideSelect0[] PROGMEM = "1. Auto        ";
prog_char OverrideSelect1[] PROGMEM = "2. Off         ";
prog_char OverrideSelect2[] PROGMEM = "3. On          ";
PROGMEM const char* OverrideSelect[]={
  OverrideSelect0, OverrideSelect1, OverrideSelect2};

prog_char settingsMenu0[] PROGMEM = "1. Time        ";
prog_char settingsMenu1[] PROGMEM = "2. Date        ";
prog_char settingsMenu2[] PROGMEM = "3. Light Times ";
prog_char settingsMenu3[] PROGMEM = "4. Temps       ";
prog_char settingsMenu4[] PROGMEM = "5. ATO Level   ";
prog_char settingsMenu5[] PROGMEM = "6. Dosing Pumps";
prog_char settingsMenu6[] PROGMEM = "7. Return      ";
PROGMEM const char* settingsMenu[]={
  settingsMenu0, settingsMenu1, settingsMenu2, settingsMenu3, settingsMenu4, settingsMenu5, settingsMenu6};

prog_char lightTimeMenu0[] PROGMEM = "1. Low On      ";
prog_char lightTimeMenu1[] PROGMEM = "2. High On     ";
prog_char lightTimeMenu2[] PROGMEM = "3. High Off    ";
prog_char lightTimeMenu3[] PROGMEM = "4. Low Off     ";
prog_char lightTimeMenu4[] PROGMEM = "5. Return      ";
PROGMEM const char* lightTimeMenu[]={
  lightTimeMenu0, lightTimeMenu1, lightTimeMenu2,  lightTimeMenu3, lightTimeMenu4};
prog_char tempMenu0[] = "1. Heater On   ";
prog_char tempMenu1[] = "2. Fan On      ";
prog_char tempMenu2[] = "3. Lights Off  ";
prog_char tempMenu3[] = "4. Return      ";
PROGMEM const char* tempMenu[]={
  tempMenu0, tempMenu1, tempMenu2, tempMenu3};
prog_char ATOMenu0[] = "1. Set Full    "; 
prog_char ATOMenu1[] = "2. Set Empty   ";
prog_char ATOMenu2[] = "3. Add 1 cm    ";
prog_char ATOMenu3[] = "4. Return      ";
PROGMEM const char* ATOMenu[]={
  ATOMenu0, ATOMenu1, ATOMenu2, ATOMenu3};

byte heatOnTemp = 77; // Temp to turn Heat on
byte fanOnTemp = 79; // Temp to turn fan on
byte lightOffTemp = 82;

char id=0;
int total = 0;
int8_t clicks;
char *LCDline1[4]; // pointer array for pointers to LCD line 1 strings
char Line1Msg0[] = "00:00  Temp 88 F";
char Line1Msg1[] = "***Temp High ***";
char Line1Msg2[] = "*** Temp Low ***";
char Line1Msg3[] = "*** ATO  Low ***";
char Line1Msg4[] = "**No Temp Probe*";
char Line1Msg5[] = "**No Echo Probe*";
char LCDline2[] =  "Fan: Off  Htr: Off  ATO: 100%  ";

LiquidTWI lcd(0);

AdaEncoder encoderA = AdaEncoder('a', EncoderPin1, EncoderPin2);
AdaEncoder *knob=NULL;

void setup(){
  Serial.begin(9600);
  pinMode(buttonPin, INPUT);
  digitalWrite(buttonPin, LOW);
  lcd.begin(16,2);
  Serial.print("free ram: ");
  Serial.println(freeRam());


}

void loop(){
  lcd.clear();
  lcd.print("push button");
  lcd.setCursor(0,1);
  lcd.print("to start...");
  while(!digitalRead(buttonPin)){
  }
  knob = AdaEncoder::genie();  // check for input from rotary encoder
  if (knob != NULL || digitalRead(buttonPin)){
    while(digitalRead(buttonPin));
    delay(20);
    lcd.clear();
    lcd.print("going to menu");
    delay(1000);
    menu();
  }
}

int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}

Part 2: main menu routines

// Routines for settings menus:
// - menu()
// - waterChange()
// - feed()
// - Override()
// - settings()
// - dateSet()
// - setLightTimes()
// - setTemps()
// - setATO()
// - setDosers()
// - setTime

void menu(){
  int item;
  timeout = millis() + menuTimeout;
  do{
    item = menuSelect(mainMenu, 5);
    switch (item) {
    case 0:
      waterChange();
      break;
    case 1:
      feed();
      break;
    case 2:
      overrideMenu(); // man override...
      break;
    case 3:
      settings();
      break;
    case 4:
      return;
      break;
    }
  }
  while (item != 3 && timeout > millis());
}
// ****************************************************

void waterChange(){
  i2cWrite(SkimmerPin%8, 0, SkimmerPin/8+1);  // Skimmer off
  i2cWrite(ATOPin%8, 0, ATOPin/8+1);          // ATO off
  i2cWrite(Powerhead1Pin%8, 0, Powerhead1Pin/8+1);  // Powerheads off
  i2cWrite(Powerhead2Pin%8, 0, Powerhead2Pin/8+1);
  i2cWrite(Powerhead3Pin%8, 0, Powerhead3Pin/8+1);
  i2cWrite(ReturnPumpPin%8, 0, ReturnPumpPin/8+1);  // Return pump off

  lcd.clear();
  lcd.print(F("Pwrhds, Skimmer"));
  lcd.setCursor(0,1);
  lcd.print(F("ATO, return off"));
  delay(3000);
  lcd.clear();
  lcd.print(F("Press to turn on"));
  lcd.setCursor(3,1);
  lcd.print(F("Return pump"));
  while(!digitalRead(buttonPin));  // wait for button to be pressed
  delay(30);                       // debouncing delay
  while(digitalRead(buttonPin)); // now wait for release
  delay(30);
  i2cWrite(ReturnPumpPin%8, Outlets[ReturnPumpPin]%2, ReturnPumpPin/8+1);

  lcd.setCursor(3,1);
  lcd.print(F("Powerheads "));
  while(!digitalRead(buttonPin));
  delay(30);
  while(digitalRead(buttonPin)); // now wait for release
  delay(30);
  i2cWrite(Powerhead1Pin%8, Outlets[Powerhead1Pin]%2, Powerhead1Pin/8+1);
  i2cWrite(Powerhead2Pin%8, Outlets[Powerhead2Pin]%2, Powerhead2Pin/8+1);
  i2cWrite(Powerhead3Pin%8, Outlets[Powerhead3Pin]%2, Powerhead3Pin/8+1);

  lcd.setCursor(3,1);
  lcd.print(F(" Skimmer   "));
  while(!digitalRead(buttonPin));
  delay(30);
  while(digitalRead(buttonPin)); // now wait for release
  delay(30);
  Outlets[SkimmerPin] = 0b001 + 0b10*(digitalRead(sumpLevelSwitchPin)); //Skimmer on
  i2cWrite(SkimmerPin%8, Outlets[SkimmerPin]%2, SkimmerPin/8+1);

  lcd.setCursor(3,1);
  lcd.print(F("  ATO   "));
  while(!digitalRead(buttonPin));
  delay(30);
  while(digitalRead(buttonPin)); // now wait for release
  delay(30);
  i2cWrite(ATOPin%8, Outlets[ATOPin]%2, ATOPin/8+1);

  return;
}

// ****************************************************

void feed(){
  // Return pump and skimmer off
  i2cWrite(ReturnPumpPin%8, 0, ReturnPumpPin/8+1);
  i2cWrite(SkimmerPin%8, 0, SkimmerPin/8+1);
  lcd.clear();
  lcd.print(F("Pump/skimmer off"));
  lcd.setCursor(0,1);
  lcd.print(F("press to cont..."));
  while(!digitalRead(buttonPin));
  delay(30);
  while(digitalRead(buttonPin)); // now wait for release
  delay(30); // debounce
  timeout = millis() -1; // exit out of menus on return
  return; // pup and skimmer will be returned to their normal states in the main loop
}

// ****************************************************

void overrideMenu(){
  int item;

  do{
    item = menuSelect(OverrideMenu, 17);
    if (item == 16) return;    // 17 = exit;
    int mode = menuSelect(OverrideSelect, 3);
    Outlets[item] = Outlets[item]%2 + 2*mode;
    // 'item' indexes the pointer to the proper spot; mode will be
    // 0 for auto, 1 for always off, 2 for always on; LSB (=*outlet%2)
    // is the default value, so it stays unchangedd. 2nd bit (2^1) override off, 
    // 3rd bit (2^2) override on, so multiply menuSelect by 2 for bit values
    // and add.
    lcd.clear();
    lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(OverrideMenu[item]))) + 3);
    lcd.setCursor(0,1);
    lcd.print(F("Set to "));
    lcd.print(strcpy_P(buffer, (char*)pgm_read_word(&(OverrideSelect[mode]))) + 3);
    delay(2000);    
  } 
  while (item != 16);
}

// ****************************************************

void settings(){
  int item;
  do{
    item = menuSelect(settingsMenu, 7);
    switch (item) {
    case 0:
      setTime("Set Time: ", time);
      break;
    case 1:
      dateSet();
      break;
    case 2:
      setLightTimes();
      break;
    case 3:
      setTemps();
      break;
    case 4:
      setATO();
      break;
    case 5:
      setDosers();
      break;
    case 6:
      return;
      break;
    }
  }
  while (item != 6);
  return;
}

// ****************************************************

void dateSet(){
  lcd.clear();
  lcd.print(F("Date:   "));
  if (Date[0] < 10) lcd.print(F("0"));
  lcd.print(Date[0]); 
  lcd.print(F("/"));
  if (Date[1] < 10) lcd.print(F("0"));
  lcd.print(Date[1]); 
  lcd.print(F("/"));
  lcd.print(Date[2]);  
  Date[2] = lcdNumberChg(Date[2], 1, 24, 14); // get year first in case it's a leap year
  if (timeout < millis()) return;             // return if we've timed out

  Date[0] = lcdNumberChg(Date[0], 1, 12, 8);  // now get month
  if (timeout < millis()) return;             // return if we've timed out

  if (Date[0] == 4 || Date[0] == 6 || Date[0] == 9 || Date[0] == 11)
    lcdNumberChg(Date[1], 1, 30, 11); // 30 days hath september....
  else if (Date[0] == 2) {            //check for leap year
    if (Date[2]%4 == 0) lcdNumberChg(Date[1], 1, 29, 11);
    else lcdNumberChg(Date[1], 1, 28, 11);
  }
  else lcdNumberChg(Date[1], 1, 31, 11);
}

// ****************************************************

void setLightTimes(){
  int item;

  do{
    item = menuSelect(lightTimeMenu, 5);
    switch (item) {
    case 0:
      setTime("Low On:   ", Light1On );
      break;
    case 1:
      setTime("High On:  ", Light2On );
      break;
    case 2:
      setTime("High Off: ", Light2Off );
      break;
    case 3:
      setTime("Low Off:  ", Light1Off );
      break;
    case 4:
      return;
      break;
    }
  }
  while (item != 6);  
}

// ****************************************************

void setTemps(){
  int item;
  do{
    item = menuSelect(tempMenu, 4);
    lcd.clear();
    switch (item){
    case 0:
      lcd.print(F("Heat on at "));
      lcd.print(heatOnTemp);
      lcd.print(F(" F"));
      heatOnTemp = lcdNumberChg(heatOnTemp, 65, 80, 11);
      break;
    case 1:
      lcd.print(F("Fan on at "));
      lcd.print(fanOnTemp);
      lcd.print(F(" F"));
      fanOnTemp = lcdNumberChg(fanOnTemp, 75, 82, 10);
      break;
    case 2:
      lcd.print(F("Lights off: "));
      lcd.print(lightOffTemp);
      lcd.print(F(" F"));
      lightOffTemp = lcdNumberChg(lightOffTemp, 75, 85, 12);
      break;
    case 3:
      return;
      break;
    }
  } 
  while (item != 3);
}

// ****************************************************

void setATO(){
  boolean noATOflag = false;

  int item = menuSelect(ATOMenu, 4);
  lcd.clear();

  int ATOavg = 0;
  for (int i = 5; i > 0; i--){
    int newATO = 25; // getATO();
    ATOavg += newATO;
    if (newATO < 1) {
      ATOavg = -1;
      i = 0;      //break from loop
    }
  }
  if (ATOavg < 0){      // Make sure the echo probe is reading properly 1st
    lcd.print(F("No sensor found "));
    lcd.setCursor(0,1);
    lcd.print(F("No changes made "));
    delay(2000);
    return;
  } 
  ATOavg = ATOavg/5;  // take the average of the 5 values...

  switch (item){
  case 0:
    ATO[0] = ATOavg;
    lcd.print(F(" Full level set "));
    lcd.setCursor(0,1);
    lcd.print(F("to current level"));
    break;
  case 1:
    ATO[1] = ATOavg;
    lcd.print(F("Empty level set "));
    lcd.setCursor(0,1);
    lcd.print(F("to current level"));
    break;
  case 2:
    ATO[1] = ATOavg + 1;
    lcd.print(F("Empty level set "));
    lcd.setCursor(0,1);
    lcd.print(F("at current+1 cm "));
    break;
  }
  delay(2000);
  return;
}

// ****************************************************

void setDosers(){
  lcd.clear();
  lcd.print(F("     Not yet    "));
  lcd.setCursor(0,1);
  lcd.print(F("   implemented  "));
  delay(2000);
  return;
}

// ****************************************************

// Function to set the time on the LCD display
// Display will show  "[.label..]00:00"
//                    "          ^^   "
// label must be 10 characters so carats allign with digits
// time[] is an integer array with hrs & mins
void setTime(char* label, byte time[]){  // label must be 10 characters
  lcd.clear();
  // print label followed by time 
  lcd.print(label);
  if (time[0] < 10) lcd.print(F("0"));
  lcd.print(time[0]);
  lcd.print(F(":"));
  if (time[1] < 10) lcd.print(F("0"));
  lcd.print(time[1]);
  lcd.setCursor(10,1);
  lcd.print(F("^^"));  // carats under hr positio
  time[0] = lcdNumberChg(time[0], 0, 23, 10);
  time[1] = lcdNumberChg(time[1], 0, 59, 13);
  delay(2000);
}

Part 3: All the rest

// set a two digit number on the LCD display between Max & min (inclusive)
// at LCD column loc
// returns the nuber selected

int lcdNumberChg (int number, int numberMin, int numberMax, int loc){
  lcd.setCursor(loc,1);
  lcd.print(F("^^"));
  timeout = millis() + menuTimeout;

  byte inByte;
  do{
    AdaEncoder *thisEncoder=NULL;
    thisEncoder=AdaEncoder::genie();
    if (thisEncoder != NULL){
      timeout = millis() + menuTimeout;
      int clicks = thisEncoder->query(); // int instead of byte to make compatible with number var
      number += clicks;
      clicks = 0;
      if (number > numberMax) number = numberMin;
      if (number < numberMin) number = numberMax;
      lcd.setCursor(loc,0);
      if (number < 10) lcd.print('0');
      lcd.print(number);
    }
    if(digitalRead(buttonPin)){                   // if button's been pushed
      while (digitalRead(buttonPin)){delay(20);}  // wait for it to be released
      delay(20);                                  // delay for button debounce
      timeout = millis() - 1;                     // set timeout to exit while loop
    }
  } while (timeout > millis());
  lcd.setCursor(loc,1);
  lcd.print(F("  "));
  timeout = millis() + menuTimeout;
  return number;
}

// menuSelect - scroll through a menu, return # of selected item
//
int menuSelect(const char* menustring[], int menuItems){
  int currentItem = 0; //item that's currently selected/highlighted
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print(F(">")); 
  lcd.println(strcpy_P(buffer, (char*)pgm_read_word(&(menustring[0]))));
  lcd.setCursor(0,1);
  lcd.print(F(" ")); 
  lcd.println(strcpy_P(buffer, (char*)pgm_read_word(&(menustring[(1)%menuItems]))));
  timeout = millis() + menuTimeout;
  do {
    AdaEncoder *knob=NULL;
    knob=AdaEncoder::genie();

    if (knob != NULL){
      clicks=knob->query();
      currentItem += clicks;
      clicks = 0;
      timeout = millis() + menuTimeout; // reset timer if there's input
      currentItem = (currentItem + menuItems)%menuItems;  // wrap around 
      // '+ menuitems' makes equation work if curentitem <0
      lcd.setCursor(0,0);
      lcd.print(F(">")); 
      lcd.println(strcpy_P(buffer, (char*)pgm_read_word(&(menustring[currentItem]))));
      lcd.setCursor(0,1);
      if(currentItem<menuItems-1){          // we're not at the last selection ('exit')
        lcd.print(F(" ")); 
        lcd.println(strcpy_P(buffer, (char*)pgm_read_word(&(menustring[(currentItem +1)%menuItems]))));
      } else {                              // print the next selection
        lcd.println(F("                ")); // otherwise print a blank line on line 2
      } 
    }
    if(digitalRead(buttonPin)){                  // if button's been pushed
      while (digitalRead(buttonPin)){
        delay(20);                               // wait for it to be released
      } 
      delay(30);                                  //delay for btton debounce
      timeout = millis() + menuTimeout;          // reset timeout time
      return currentItem;
    }
  } 
  while (timeout > millis());   // end do

  timeout = millis() + menuTimeout;
  return (menuItems-1);
} 


void pause(char* msg){
  Serial.print(msg);
  Serial.println(F(", Proceed?"));
  while(!Serial.available());
  while(Serial.available()) Serial.read();
  return;
}

// ************************************
// routine to turn outlets on/off 
// Pins 0-7 are in outlet bank 1, 8-15 in bank 2
// pin%8 converts to 0-7, 
// pin/8+1 converts to i2c address 1 for 0-7, 2 for 8-15
// to allow for automatic assignment if pins are changed

void outlets(){
  i2cWrite(FanPin%8, Outlets[FanPin]%2, FanPin/8+1);
  i2cWrite(Heater1Pin%8, Outlets[Heater1Pin]%2, Heater1Pin/8+1);
  i2cWrite(Heater2Pin%8, Outlets[Heater2Pin]%2, Heater2Pin/8+1);
  i2cWrite(Light1Pin%8, Outlets[Light1Pin]%2, Light1Pin/8+1);
  i2cWrite(Light2Pin%8, Outlets[Light2Pin]%2, Light2Pin/8+1);
  i2cWrite(Light3Pin%8, Outlets[Light3Pin]%2, Light3Pin/8+1);
  i2cWrite(ATOPin%8, Outlets[ATOPin]%2, ATOPin/8+1);
  i2cWrite(SkimmerPin%8, Outlets[SkimmerPin]%2, SkimmerPin/8+1);
  
  i2cWrite(Powerhead1Pin%8, Outlets[Powerhead1Pin]%2, Powerhead1Pin/8+1);
  i2cWrite(Powerhead2Pin%8, Outlets[Powerhead2Pin]%2, Powerhead2Pin/8+1);
  i2cWrite(Powerhead3Pin%8, Outlets[Powerhead3Pin]%2, Powerhead3Pin/8+1);
  i2cWrite(ReturnPumpPin%8, Outlets[ReturnPumpPin]%2, ReturnPumpPin/8+1);
  i2cWrite(CircPumpPin%8, Outlets[CircPumpPin]%2, CircPumpPin/8+1);
  i2cWrite(Doser1Pin%8, Outlets[Doser1Pin]%2, Doser1Pin/8+1);
  i2cWrite(Doser2Pin%8, Outlets[Doser2Pin]%2, Doser2Pin/8+1);
  i2cWrite(AuxPin%8, Outlets[AuxPin]%2, AuxPin/8+1);
  Serial.println();

}

void i2cWrite(byte pin, byte value, byte addr){
  Serial.print("Write "); Serial.print(value);
  Serial.print(" to pin #"); Serial.print(pin);
  Serial.print(" at address ");Serial.println(addr);
}

I use the oopinchaneint library

The what?

Thanks SleepyDoc,

I went through last night and added all the required libraries that I was missing. When I attempted to compile the code I received the error message
no matching function for call to AdaEncoder::AdaEncoder(char, int, int)

When I checked the AdaEncoder.cpp file, I could not find a function called AdaEncoder::AdaEncoder. The closest I found with the required parameters was AdaEncoder::addEncoder(char, int, int).
I tried changing the function call in the code to that with no luck.

Here is part of the full error messages

In file included from C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/AdaEncoder.h:77,
                 from AdaEncoder_Menu.ino:4:
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/../PinChangeInt/PinChangeInt.h:169: error: redefinition of 'class PCintPort'
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\ooPinChangeInt/ooPinChangeInt.h:162: error: previous definition of 'class PCintPort'
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/../PinChangeInt/PinChangeInt.h:240: error: conflicting declaration 'volatile uint8_t PCintPort::curr'
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\ooPinChangeInt/ooPinChangeInt.h:214: error: 'PCintPort::curr' has a previous declaration as 'uint8_t PCintPort::curr'
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/../PinChangeInt/PinChangeInt.h:242: error: 'volatile uint8_t PCintPort::arduinoPin' is not a static member of 'class PCintPort'
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/../PinChangeInt/PinChangeInt.h:245: error: 'volatile uint8_t PCintPort::pinState' is not a static member of 'class PCintPort'
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/../PinChangeInt/PinChangeInt.h:301: error: redefinition of 'PCintPort portB'
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\ooPinChangeInt/ooPinChangeInt.h:216: error: 'PCintPort portB' previously declared here
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/../PinChangeInt/PinChangeInt.h:301: error: no matching function for call to 'PCintPort::PCintPort(int, int, volatile unsigned char&)'
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\ooPinChangeInt/ooPinChangeInt.h:164: note: candidates are: PCintPort::PCintPort(int, volatile uint8_t&)
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\ooPinChangeInt/ooPinChangeInt.h:162: note:                 PCintPort::PCintPort(const PCintPort&)
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/../PinChangeInt/PinChangeInt.h:304: error: redefinition of 'PCintPort portC'
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\ooPinChangeInt/ooPinChangeInt.h:220: error: 'PCintPort portC' previously declared here
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/../PinChangeInt/PinChangeInt.h:304: error: no matching function for call to 'PCintPort::PCintPort(int, int, volatile unsigned char&)'
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\ooPinChangeInt/ooPinChangeInt.h:164: note: candidates are: PCintPort::PCintPort(int, volatile uint8_t&)
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\ooPinChangeInt/ooPinChangeInt.h:162: note:                 PCintPort::PCintPort(const PCintPort&)
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/../PinChangeInt/PinChangeInt.h:307: error: redefinition of 'PCintPort portD'
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\ooPinChangeInt/ooPinChangeInt.h:224: error: 'PCintPort portD' previously declared here
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/../PinChangeInt/PinChangeInt.h:307: error: no matching function for call to 'PCintPort::PCintPort(int, int, volatile unsigned char&)'
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\ooPinChangeInt/ooPinChangeInt.h:164: note: candidates are: PCintPort::PCintPort(int, volatile uint8_t&)
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\ooPinChangeInt/ooPinChangeInt.h:162: note:                 PCintPort::PCintPort(const PCintPort&)
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/../PinChangeInt/PinChangeInt.h: In function 'PCintPort* lookupPortNumToPort(int)':
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/../PinChangeInt/PinChangeInt.h:321: error: redefinition of 'PCintPort* lookupPortNumToPort(int)'
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\ooPinChangeInt/ooPinChangeInt.h:243: error: 'PCintPort* lookupPortNumToPort(int)' previously defined here
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/../PinChangeInt/PinChangeInt.h: At global scope:
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/../PinChangeInt/PinChangeInt.h:366: error: prototype for 'void PCintPort::enable(PCintPort::PCintPin*, void (*)(), uint8_t)' does not match any in class 'PCintPort'
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\ooPinChangeInt/ooPinChangeInt.h:282: error: candidate is: void PCintPort::enable(PCintPort::PCintPin*, CallBackInterface*, uint8_t)
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\ooPinChangeInt/ooPinChangeInt.h:180: error: 'class PCintPort::PCintPin' is protected

And the rest

C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/../PinChangeInt/PinChangeInt.h:366: error: within this context
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/../PinChangeInt/PinChangeInt.h:377: error: prototype for 'int8_t PCintPort::addPin(uint8_t, void (*)(), uint8_t)' does not match any in class 'PCintPort'
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\ooPinChangeInt/ooPinChangeInt.h:293: error: candidate is: int8_t PCintPort::addPin(uint8_t, CallBackInterface*, uint8_t)
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/../PinChangeInt/PinChangeInt.h:421: error: prototype for 'int8_t PCintPort::attachInterrupt(uint8_t, void (*)(), int)' does not match any in class 'PCintPort'
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\ooPinChangeInt/ooPinChangeInt.h:330: error: candidate is: static int8_t PCintPort::attachInterrupt(uint8_t, CallBackInterface*, int)
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/../PinChangeInt/PinChangeInt.h:439: error: redefinition of 'static void PCintPort::detachInterrupt(uint8_t)'
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\ooPinChangeInt/ooPinChangeInt.h:348: error: 'static void PCintPort::detachInterrupt(uint8_t)' previously defined here
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/../PinChangeInt/PinChangeInt.h:473: error: redefinition of 'void PCintPort::PCint()'
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\ooPinChangeInt/ooPinChangeInt.h:378: error: 'void PCintPort::PCint()' previously defined here
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/../PinChangeInt/PinChangeInt.h: In function 'void __vector_3()':
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/../PinChangeInt/PinChangeInt.h:563: error: redefinition of 'void __vector_3()'
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\ooPinChangeInt/ooPinChangeInt.h:419: error: 'void __vector_3()' previously defined here
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/../PinChangeInt/PinChangeInt.h: In function 'void __vector_4()':
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/../PinChangeInt/PinChangeInt.h:573: error: redefinition of 'void __vector_4()'
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\ooPinChangeInt/ooPinChangeInt.h:426: error: 'void __vector_4()' previously defined here
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/../PinChangeInt/PinChangeInt.h: In function 'void __vector_5()':
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/../PinChangeInt/PinChangeInt.h:583: error: redefinition of 'void __vector_5()'
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\ooPinChangeInt/ooPinChangeInt.h:433: error: 'void __vector_5()' previously defined here
AdaEncoder_Menu.ino: At global scope:
AdaEncoder_Menu:151: error: no matching function for call to 'AdaEncoder::AdaEncoder(char, int, int)'
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/AdaEncoder.h:170: note: candidates are: AdaEncoder::AdaEncoder()
C:\Users\Ross\Desktop\Arduino 1.0.4\arduino-1.0.4\libraries\AdaEncoder/AdaEncoder.h:139: note:                 AdaEncoder::AdaEncoder(const AdaEncoder&)
AdaEncoder_Menu.ino: In function 'void loop()':
AdaEncoder_Menu:172: error: cannot convert 'encoder*' to 'AdaEncoder*' in assignment
AdaEncoder_Menu.ino: In function 'int lcdNumberChg(int, int, int, int)':
AdaEncoder_Menu:548: error: cannot convert 'encoder*' to 'AdaEncoder*' in assignment
AdaEncoder_Menu:551: error: 'class AdaEncoder' has no member named 'query'
AdaEncoder_Menu.ino: In function 'int menuSelect(const char**, int)':
AdaEncoder_Menu:586: error: cannot convert 'encoder*' to 'AdaEncoder*' in assignment
AdaEncoder_Menu:589: error: 'class AdaEncoder' has no member named 'query'

Do I need the pcchangeint library, the oopcchangeint library, or both?
I'm sorry, I am not an experienced enough in coding to understand what is going wrong.

PaulS:

I use the oopinchaneint library

The what?

Sorry, typo! ooPinChangeInt library, found here: Google Code Archive - Long-term storage for Google Code Project Hosting.

Basically allows interrupts on any of the arduino's pins, not just Int0 & Int1 (pins 2 & 3)

should just need the oopinchangeint library.

regarding the adaencoder function call, have you tried running the example sketch that came with the adaencoder library and does it run? Do you have both adaencoder.cpp and adaencoder.h installed? I believe the function is actually defined in the .h file.

Sleepydoc:
regarding the adaencoder function call, have you tried running the example sketch that came with the adaencoder library and does it run? Do you have both adaencoder.cpp and adaencoder.h installed? I believe the function is actually defined in the .h file.

The adaencoder library I had downloaded did not have any examples with it.
So I hunted around I found what I am guessing is the correct one, as it has examples and compiles happily now.

Now to start playing!
Thanks SleepyDoc!

I tested with the adaencoder example MyEncoder and it worked happily with my encoder, so I tried using your code, inserting my rotary code pin numbers as EncoderPin1 and EncoderPin2 and uploaded the code.

The menu is not able to be traversed with the encoder (the pushbutton part of the encoder works fine).

I have tried moving to different pins on the Arduino with the same result.

Ok - I posted a reply and it got lost;

The routines in my code are taken almost verbatim from the example supplied. If the encoder is working with the example, it should be wired correctly. Make sure you have the correct pin definitions and that the object initialization (lines 151 & 152) are correct. Does it enter the menu when you turn the knob or only when you press the button?

Try the code below. It is the same code from what I originally posted with all the extra stuff removed; it just prints the number of clicks to the serial monitor.

#include <ooPinChangeInt.h>
#include <AdaEncoder.h>    // adafruit.com eoncoder routine.


#define EncoderPin1 6        // Rotary Encoder Pins
#define EncoderPin2 7        //
#define buttonPin A2         // pin for encoder button
int8_t clicks;

AdaEncoder encoderA = AdaEncoder('a', EncoderPin1, EncoderPin2);
AdaEncoder *knob=NULL;

void setup(){
  Serial.begin(9600);
  pinMode(buttonPin, INPUT);
  digitalWrite(buttonPin, LOW);
}

void loop(){
  knob = AdaEncoder::genie();  // check for input from rotary encoder
  if (knob != NULL ){
    clicks = knob->query();
    Serial.print("clicks = ");
    Serial.println(clicks);
  }
}

Double checked pin definitions and initialization of the object matches the pin definition.

The code you just supplied works, it prints to the Serial Window "1" when the encoder is turned clockwise and "-1" when the encoder is turned counter clockwise.

I'm working on slowly adding the rest of the code. Hopefully I will find the problem.

Is there any chance that you could check that the menu code you supplied originally works on you end?

Just checked - it works on my end. Maybe something got missed when you cut & pasted?

Previously I had been using the LCD in 4 bit mode. I thought perhaps there may be a command in the TWI library that was not in or different to the LiquidCrystal library.
So I dug through my component drawers and found a MCP23008 I2C chip, wired it all up.
The LCD worked correctly but rotary encoder still has same problem.

Then I noticed that that the pins assigned to the rotary encoder (6,7) had duplicates for other IO devices, so I changed the rotary encoder pins to A0 and A1.
Still had the same problem.

Then I accidentally rotated the shaft of the encoder while pushing it down, and it worked!!

Turned out I had the push button wired the opposite to yours!
Thank you very much for your patience and help, I greatly appreciate it! :smiley:

Yeah, that would do it. My encoder has LEDs in it and the switch and LEDs are common cathode, so the switch has to be normally LOW and HIGH when pressed, opposite of how you'd normally wire a switch with Arduino. I said something in my original post, but it's not something you'd normally think of.

You're totally welcome. Glad it worked for you! Now you can start modifying for your own project.

Hy SleepyDoc,

thanks for this very good Menu Roling over Rotary,

my biggest problem is, that i cant navigate trought the menues. just idf i push the button.

Greetings from Germany

I also am trying this code I could only get it to work if I took out all the progmem stuff. I would like to use it to save space but the new avr won't compile it . Also I can't seem to get any outlets to turn on and off any help would b great.