Close

Rotary Encoder Blink

A project log for Polling a Push Button Rotary Encoder

A push button rotary encoder is a neat input device for an Arduino. Unfortunately interfacing to it is not!

agp.cooperagp.cooper 02/04/2018 at 00:270 Comments

Rotary Encoder Blink

Final log.

I have stripped the code down and wriiten a programmable Blink sketch.

It is still a complicated beast but that is the way it is:

/* 
  Rotary Encoder Blink
  ====================
  Written by Alan Cooper (agp.cooper@gmail.com)
  This work is licensed under the 
  Creative Commons Attribution - Non Commercial 2.5 License.
  This means you are free to copy and share the code (but not to sell it).
  Also it is good karma to attribute the source of the code.
*/

/*
  ROTARY ENCODER AND PUSH BUTTON POLLING CODE
    Uses Timer0 without upsetting millis(), delay() etc.
    You lose PWM on Arduino/Nano pin 5 (D5).
    Don't turn the encoder too fast as it will not work!
*/
#define PinA 5
#define PinB 4
#define SW   3
volatile bool UpdateSwitch=false;
volatile byte Switch=HIGH;
volatile int EncoderPos=0;
ISR(TIMER0_COMPB_vect) {
  static byte testPinA=(PIND>>PinA)&1;
  static byte testPinB=(PIND>>PinB)&1;
  static byte lastPinA=LOW;
  static byte lastPinB=LOW;
  static bool flagPinA=false;
  static bool flagPinB=false;
  static bool encoderFlag=true;
  static int encoderDir=0;
  static byte testSW=HIGH;
  static byte statusSW=HIGH;
  static byte cntSW=0;  
  
  // Update Encoder Position
  lastPinA=testPinA;             // Save PinA
  lastPinB=testPinB;             // Save PinB
  testPinA=(PIND>>PinA)&1;       // Get PinA
  testPinB=(PIND>>PinB)&1;       // Get PinB
  
  /* If your encoder jumps in steps of two, uncomment this code */
  // if ((testPinA==HIGH)&&(testPinB==HIGH)) encoderFlag=true; // Encoder is in detent
  // if ((testPinA==LOW)&&(testPinB==LOW)) encoderFlag=false;  // Encoder is between detents
  
  if (encoderFlag) {             // First transition (leaving detent) only
    if (testPinA!=lastPinA) {    // Change in PinA?
      flagPinA=true;             // Flag PinA has changed
      encoderDir=-1;             // Assume it is the last flag to change
    }
    if (testPinB!=lastPinB) {    // Change in PinB?
      flagPinB=true;             // Flag PinB has changed
      encoderDir=1;              // Assume it is the last flag to change
    }
    if (flagPinA&&flagPinB) {    // Both flags have changed
      EncoderPos+=encoderDir;
      flagPinA=false;            // Reset PinA flag
      flagPinB=false;            // Reset PinB flag
    }
  }
  
  // Update switch with 10 ms debounce
  testSW=(PIND>>SW)&1;
  if (testSW!=statusSW) {
    encoderFlag=true;            // Reset encoder flag (precaution)
    statusSW=testSW;
    cntSW=10;
  }
  if (cntSW>0) {
    cntSW--;
    if (cntSW==0) {
      Switch=statusSW;
      UpdateSwitch=true;
    }
  }
}


/* MENU SET UP */
enum NoYes {N,Y};
enum MenuLevel {Top,Menu,Set};
enum MenuItem      { Exit_Menu  , Delay_MS  };
char* menuName[]  ={"Exit Menu ","Delay ms "};
char menuNumeric[]={         N  ,        Y  };
int menuValue[]   ={         Y  ,      500  };
int menuValueMin[]={         N  ,        1  };
int menuValueMax[]={         Y  ,    32766  };
int menuSize=sizeof(menuName)/sizeof(char*);
int menuLevel=Menu;

// Our variable to set the delay period
unsigned long intervalMillis=1000;

bool processMenu(void) {
  static int lastPos=Exit_Menu;
  static int lastMenuLevel=Top;
  
  // Disable polling
  TIMSK0&=~(1<<OCIE0B);
  
  // Pre-empt menu level display 
  if (menuLevel!=lastMenuLevel) {
    lastMenuLevel=menuLevel;
    if (menuLevel==Menu) {
      Serial.print("Menu: ");
      Serial.print(menuName[EncoderPos]);
      if (menuNumeric[EncoderPos]==Y) {
        Serial.println(menuValue[EncoderPos]);
      } else {       
        if (menuValue[EncoderPos]==N) {
          Serial.println("N");      
        } else {
          Serial.println("Y");      
        }
      }          
    } else if (menuLevel==Set) {
      Serial.print("Set:  ");
      Serial.print(menuName[lastPos]);
      if (menuNumeric[lastPos]==Y) {
        Serial.println(menuValue[lastPos]);
      } else {       
        if (menuValue[lastPos]==N) {
          Serial.println("N");      
        } else {
          Serial.println("Y");      
        }
      }      
    } else {
      // How did you get here?      
    }
  }
  
  // If push button pushed toggle menu level
  if (UpdateSwitch) {
    UpdateSwitch=false;
    if (Switch==LOW) {
      // Re-enter menu if button pushed (for long enough)
      if (menuLevel==Top) {
        menuLevel=Menu;
        lastMenuLevel=Top;                         
        lastPos=Exit_Menu;                               
        EncoderPos=Exit_Menu;
        menuValue[Exit_Menu]=Y;
      } else {
        // Toggle menu level
        if (menuLevel==Menu) {
          menuLevel=Set;
        } else {
          menuLevel=Menu;
        }
        if (menuLevel==Menu) {
          // Restore item menu position
          EncoderPos=lastPos;
          
          /* Exit menu if done! */
          if ((EncoderPos==Exit_Menu)&&(menuValue[Exit_Menu]==Y)) {
            menuLevel=Top;
            // Set the delay
            intervalMillis=menuValue[Delay_MS];
            Serial.println("Menu Exited!");
          }
          
        } else {
          // Set value for edit menu
          EncoderPos=menuValue[lastPos];
        }
      }
    }
  }
  
  // If encoder turned
  if (menuLevel==Menu) { // Select menu item
    if (lastPos!=EncoderPos) {
      if (EncoderPos>=menuSize) EncoderPos=0;
      if (EncoderPos<0) EncoderPos=menuSize-1;
      lastPos=EncoderPos;          
      Serial.print("Menu: ");
      Serial.print(menuName[lastPos]);
      if (menuNumeric[lastPos]==Y) {
        Serial.println(menuValue[lastPos]);
      } else {       
        if (menuValue[lastPos]==N) {
          Serial.println("N");   
        } else {
          Serial.println("Y");      
        }
      }

    }
  } else if (menuLevel==Set) { // Set/edit menu item value
    if (menuValue[lastPos]!=EncoderPos) {
      if (EncoderPos>menuValueMax[lastPos]) EncoderPos=menuValueMin[lastPos];
      if (EncoderPos<menuValueMin[lastPos]) EncoderPos=menuValueMax[lastPos];
      menuValue[lastPos]=EncoderPos;
      Serial.print("Set:  ");
      Serial.print(menuName[lastPos]);
      if (menuNumeric[lastPos]==Y) {
        Serial.println(menuValue[lastPos]);
      } else {       
        if (menuValue[lastPos]==N) {
          Serial.println("N");   
        } else {
          Serial.println("Y");      
        }
      }
      
    }
  }
  // Enable polling
  TIMSK0|=(1<<OCIE0B);
  
  return (menuLevel!=Top); // Return true if menu active
}

void Blink(int Delay) {
  
   
  
}

void setup() {
  // Setup for Keyes rotary encoder and push button
  pinMode(5,INPUT_PULLUP); // Rotary PinA or Clk
  pinMode(4,INPUT_PULLUP); // Rotary PinB or DT
  pinMode(3,INPUT_PULLUP); // Rotary SW
  pinMode(2,OUTPUT);       // Power for onboard pullup resistors
  digitalWrite(2,HIGH);    // Turn on power

  // Set up Blink
  pinMode(LED_BUILTIN,OUTPUT);
  
  // Turn on polling ISR
  OCR0B=0xA0;
  TIMSK0|=(1<<OCIE0B);

  // Initialise Serial
  Serial.begin(9600); // Stardard serial speed
  while (!Serial);    // Wait for the Serial system to come up
  
  // Print welcome messeage
  Serial.println("Rotary Blink");
  Serial.println("Hints:");
  Serial.println("  1 Turn the encoder to navigate the menu");
  Serial.println("  2 Push the button to change the setting");
  Serial.println("  3 Turn the encoder to change the setting");
  Serial.println("  4 Don't turn the the encoder too fast!");
  Serial.println("  5 Push the button to save the setting (i.e. Y)");
  Serial.println();
  Serial.println("  6 Select 'Exit Menu'");
  Serial.println("  7 Push the button to change the setting");
  Serial.println("  8 Push the button to save the setting");
  Serial.println("  9 You should have exited the menu and Blink is now running");
  Serial.println();
  Serial.println("  10 Push the button to re-enter the menu after 'Exit Menu'");
  Serial.println();
}

void loop() {
  static unsigned long previousMillis=0;
  unsigned long currentMillis;

  if (!processMenu()) {
    /* Run this when not in menu */
    
    // Blink without delay (intervalMillis is set by the rotary encoder)
    currentMillis=millis();
    if (currentMillis-previousMillis>=intervalMillis) {
      previousMillis=currentMillis;
      digitalWrite(LED_BUILTIN,!digitalRead(LED_BUILTIN));
    }
  }
}

Don't turn the encoder too fast as it will not keep up!


Here is the breadboard setup:

And:

Magic!

Discussions