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
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.