-
Rotary Encoder Blink
02/04/2018 at 00:27 • 0 commentsRotary 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!
-
Still Testing II
02/03/2018 at 06:18 • 0 commentsStill Testing II
A bit of code clean up!
Used enum types rather and constants as this makes changing the menu easier:
enum NoYes {N,Y}; enum MenuLevel {Top,Menu,Set}; enum MenuItem { Exit_Menu , Year , Month , Day , Hour , Minute , Second , Use_Time , Local_Hour , Local_Min , Use_UT , Sample_Min , Upload_Data }; char* menuName[] ={"Exit Menu ","Year ","Month ","Day ","Hour ","Minute ","Second ","Use Time ","Local Hour ","Local Min ","Use UT ","Sample Min ","Upload Data "}; int menuValue[] ={ Y , 2000 , 1 , 1 , 0 , 0 , 0 , Y , 8 , 0 , Y , 1 , Y }; char menuNumeric[]={ N , Y , Y , Y , Y , Y , Y , N , Y , Y , N , Y , N }; int menuValueMin[]={ N , 1970 , 1 , 1 , 0 , 0 , 0 , N , -12 , -59 , N , 1 , N }; int menuValueMax[]={ Y , 2100 , 12 , 31 , 23 , 59 , 59 , Y , 12 , 59 , Y , 1440 , Y }; int menuSize=sizeof(menuName)/sizeof(char*); int menuLevel=Menu;
The menu set up is very clear to read and easy to modify. Just stay linear in your menu layout!
Reworked the polling ISR to take the guess work out of detent status of the rotary encoder:
// Update Encoder Position lastPinA=testPinA; // Save PinA lastPinB=testPinB; // Save PinB testPinA=(PIND>>PinA)&1; // Get PinA testPinB=(PIND>>PinB)&1; // Get PinB // This rotary encoder updates twice per detent! 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 } }
NOTE: YOUR ROTARY ENCODER MAY BE DIFFERENT! You can comment out these two lines:
// This rotary encoder updates twice per detent! if ((testPinA==HIGH)&&(testPinB==HIGH)) encoderFlag=true; // Encoder is in detent if ((testPinA==LOW)&&(testPinB==LOW)) encoderFlag=false; // Encoder is between detents
Disabled polling during menu processing.
Reworked the start up for a local time at start up with a menu option for Universal Time (UT).
The code is long but not much I can do to reduce this (other than hide it in an include file):
/* Universal Time Clock with Periodic Sampler ========================================== 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. */ // Compile and upload time adjustment (secs) #define UploadTime 11 // Analog read on A7 #define Sensor A7 // Need the Wire library for RTC and AT24C32 #include <Wire.h> // Use RTC library #include "RTClib.h" RTC_DS1307 rtc; // Use AT24Cxx library #include <AT24Cxx.h> AT24Cxx AT24C32(0x50); // Useful union/structures to read/write A if (LEDTimer==-1) LEDTimer=menuValue[LED_Timeout];T24C32 data union { struct{ char Year; char Month; char Day; char Hour; char Minute; char Second; int Value; }; char data[8]; } AT24C32_Slot; union { struct{ int Value; }; char data[2]; } AT24C32_Int; // Use LCD library #include <LiquidCrystal.h> #define en 7 #define rs 8 #define d4 9 #define d5 10 #define d6 11 #define d7 12 #define led 13 LiquidCrystal lcd(rs,en,d4,d5,d6,d7); /* ROTARY ENCODER AND PUSH BUTTON POLLING CODE */ #define PinA 2 #define PinB 3 #define SW 4 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 // This rotary encoder updates twice per detent! 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 20 ms debounce testSW=(PIND>>SW)&1; if (testSW!=statusSW) { encoderFlag=true; // Reset encoder flag (precaution) statusSW=testSW; cntSW=20; } 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 , Year , Month , Day , Hour , Minute , Second , Use_Time , Local_Hour , Local_Min , Use_UT , Sample_Min , Download_Data , Reset_Data , LED_Timeout , Compile_Time }; char* menuName[] ={"Exit Menu ","Year ","Month ","Day ","Hour ","Minute ","Second ","Use Time ","Local Hour ","Local Min ","Use UT ","Sample Min ","Download Data ","Reset Data ","LED Timeout ","Compile Time "}; char menuNumeric[]={ N , Y , Y , Y , Y , Y , Y , N , Y , Y , N , Y , N , N , Y , N }; int menuValue[] ={ Y , 2000 , 1 , 1 , 0 , 0 , 0 , Y , 8 , 0 , Y , 1 , N , N , -1 , N }; int menuValueMin[]={ N , 1970 , 1 , 1 , 0 , 0 , 0 , N , -12 , -59 , N , 1 , N , N , -1 , N }; int menuValueMax[]={ Y , 2100 , 12 , 31 , 23 , 59 , 59 , Y , 12 , 59 , Y , 1440 , Y , Y , 3600 , Y }; int menuSize=sizeof(menuName)/sizeof(char*); int menuLevel=Menu; // AT24C32 usage char settings[8]; // Start address 32752 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; lcd.clear(); lcd.setCursor(0,0); if (menuLevel==Menu) { lcd.print("Menu:"); lcd.setCursor(0,1); lcd.print(menuName[EncoderPos]); if (menuNumeric[EncoderPos]==Y) { lcd.print(menuValue[EncoderPos]); } else { if (menuValue[EncoderPos]==N) { lcd.print("N"); } else { lcd.print("Y"); } } } else if (menuLevel==Set) { lcd.print("Set:"); lcd.setCursor(0,1); lcd.print(menuName[lastPos]); if (menuNumeric[lastPos]==Y) { lcd.print(menuValue[lastPos]); } else { if (menuValue[lastPos]==N) { lcd.print("N"); } else { lcd.print("Y"); } } } else { lcd.print("Rotary Encoder?"); } } // 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; // Save Local Time and Sample settings upon exit menu strncpy(settings,"UTC",3); settings[3]=menuValue[Local_Hour]; settings[4]=menuValue[Local_Min]; settings[5]=menuValue[Sample_Min]; settings[6]=menuValue[LED_Timeout]; settings[7]=menuValue[Compile_Time]; AT24C32.WriteMem(8*4094,settings,8); } // Check for use new time if ((EncoderPos==Use_Time)&&(menuValue[Use_Time]==Y)) { rtc.adjust(DateTime(menuValue[Year],menuValue[Month],menuValue[Day],menuValue[Hour],menuValue[Minute],menuValue[Second])); } // Check for use UT if ((EncoderPos==Use_UT)&&(menuValue[Use_UT]==Y)) { DateTime now=rtc.now(); rtc.adjust(DateTime(now+(-menuValue[Local_Hour]*3600-menuValue[Local_Min]*60))); } // Check for data download if ((EncoderPos==Download_Data)&&(menuValue[Download_Data]==Y)) { AT24C32.ReadMem(8*4095,AT24C32_Int.data,2); // Get sample slots used lcd.clear(); lcd.setCursor(0,0); lcd.print("Download Data: "); lcd.setCursor(0,1); lcd.print("Slots used "); lcd.print(AT24C32_Int.Value); delay(1000); Serial.begin(9600); while (!Serial); for (int i=0;i<AT24C32_Int.Value;i++) { AT24C32.ReadMem(i*8,AT24C32_Slot.data,8); Serial.print(AT24C32_Slot.Year+2000,DEC); Serial.print("/"); if (AT24C32_Slot.Month<10) Serial.print(0); Serial.print(AT24C32_Slot.Month,DEC); Serial.print("/"); if (AT24C32_Slot.Day<10) Serial.print(0); Serial.print(AT24C32_Slot.Day,DEC); Serial.print(" "); if (AT24C32_Slot.Hour<10) Serial.print(0); Serial.print(AT24C32_Slot.Hour,DEC); Serial.print(":"); if (AT24C32_Slot.Minute<10) Serial.print(0); Serial.print(AT24C32_Slot.Minute,DEC); Serial.print(":"); if (AT24C32_Slot.Second<10) Serial.print(0); Serial.print(AT24C32_Slot.Second,DEC); Serial.print(" = "); Serial.println(AT24C32_Slot.Value); } Serial.end(); lcd.clear(); lcd.setCursor(0,0); lcd.print("Download Data: "); lcd.setCursor(0,1); lcd.print("Done "); delay(1000); } // Check for data reset if ((EncoderPos==Reset_Data)&&(menuValue[Reset_Data]==Y)) { menuValue[Reset_Data]=N; AT24C32_Int.Value=0; AT24C32.ReadMem(8*4095,AT24C32_Int.data,2); // Reset sample slots used } } 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; lcd.clear(); lcd.setCursor(0,0); lcd.print("Menu:"); lcd.setCursor(0,1); lcd.print(menuName[lastPos]); if (menuNumeric[lastPos]==Y) { lcd.print(menuValue[lastPos]); } else { if (menuValue[lastPos]==N) { lcd.print("N"); } else { lcd.print("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; lcd.clear(); lcd.setCursor(0,0); lcd.print("Set:"); lcd.setCursor(0,1); lcd.print(menuName[lastPos]); if (menuNumeric[lastPos]==Y) { lcd.print(menuValue[lastPos]); } else { if (menuValue[lastPos]==N) { lcd.print("N"); } else { lcd.print("Y"); } } } } // Enable polling TIMSK0|=(1<<OCIE0B); return (menuLevel!=Top); // Return true if menu active } void setup() { // Initialise rotary encoder and push button pinMode(PinA,INPUT_PULLUP); // Rotary PinA pinMode(PinB,INPUT_PULLUP); // Rotary PinB pinMode(SW,INPUT_PULLUP); // Rotary SW pinMode(Sensor,INPUT); // LDR sensor (150k pullup) // Turn on polling ISR OCR0B=0xA0; TIMSK0|=(1<<OCIE0B); // Initialise LCD lcd.begin(16,2); pinMode(led,OUTPUT); digitalWrite(led,HIGH); // Print welcome message to the LCD lcd.setCursor(0,0);lcd.print("Clock & Sampler"); // Check RTC if (!rtc.begin()) { lcd.setCursor(0,1);lcd.print("RTC not found"); while (true); } else if (!rtc.isrunning()) { lcd.setCursor(0,1);lcd.print("RTC not running"); } else { lcd.setCursor(0,1);lcd.print("RTC found"); } // Check if AT24C32 is present if (!AT24C32.isPresent()) { lcd.setCursor(0,1);lcd.print("AT24C32 not installed"); while (true); } // Set Universal Time based on saved local time int localTimeHour=8; // 8 hours for Perth Western Australia int localTimeMin=0; // 0 minutes for Perth Western Australia int samplePeriodMin=1; // 1 minutes between samples int LEDTimeOut=-1; // Do not turn off LED int useCompileTime=Y; // Use compile time to set clock // Read saved settings AT24C32.ReadMem(8*4094,settings,8); if (strncmp(settings,"UTC",3)==0) { localTimeHour=settings[3]; localTimeMin=settings[4]; samplePeriodMin=settings[5]; LEDTimeOut=settings[6]; useCompileTime=settings[7]; } // Set time DateTime now; // Set compile time if (useCompileTime==Y) { useCompileTime=N; // Set compile time rtc.adjust(DateTime(F(__DATE__),F(__TIME__))); // Adjust for upload time now=rtc.now(); rtc.adjust(DateTime(now+UploadTime)); } // Set menu to current time now=rtc.now(); menuLevel=Top; // Start in Top (else start in Menu) menuValue[Exit_Menu]=Y; // Set "Exit Menu" status menuValue[Year]=now.year(); menuValue[Month]=now.month(); menuValue[Day]=now.day(); menuValue[Hour]=now.hour(); menuValue[Minute]=now.minute(); menuValue[Second]=now.second(); menuValue[Use_Time]=Y; // Set "Use UT" status menuValue[Local_Hour]=localTimeHour; menuValue[Local_Min]=localTimeMin; menuValue[Sample_Min]=samplePeriodMin; menuValue[Download_Data]=Y; menuValue[LED_Timeout]=LEDTimeOut; menuValue[Compile_Time]=useCompileTime; delay(1000); lcd.clear(); } void loop() { DateTime now; static int LEDTimer=-1; if (LEDTimer==-1) LEDTimer=menuValue[LED_Timeout]; if (!processMenu()) { // Menu not active // Present the results now=rtc.now(); // Print date on first line of LCD lcd.clear(); lcd.setCursor(0,0);lcd.print("Date "); lcd.print(now.year()); lcd.print('/'); if (now.month()<10) lcd.print(0); lcd.print(now.month()); lcd.print('/'); if (now.day()<10) lcd.print(0); lcd.print(now.day()); // Print time on second line of LCD lcd.setCursor(0,1);lcd.print("Time "); if (now.hour()<10) lcd.print(0); lcd.print(now.hour()); lcd.print(':'); if (now.minute()<10) lcd.print(0); lcd.print(now.minute()); lcd.print(':'); if (now.second()<10) lcd.print(0); lcd.print(now.second()); // Save data to RTC ATC32 (4094 slots) every sample period (minute) if ((now.second()==0)&&(now.minute()%menuValue[Sample_Min]==0)) { AT24C32_Slot.Year=now.year()-2000; AT24C32_Slot.Month=now.month(); AT24C32_Slot.Day=now.day(); AT24C32_Slot.Hour=now.hour(); AT24C32_Slot.Minute=now.minute(); AT24C32_Slot.Second=0; AT24C32_Slot.Value=analogRead(Sensor); AT24C32.ReadMem(8*4095,AT24C32_Int.data,2); AT24C32.WriteMem(AT24C32_Int.Value*8,AT24C32_Slot.data,8); // Update next address AT24C32_Int.Value++; if (AT24C32_Int.Value>4093) AT24C32_Int.Value=0; AT24C32.WriteMem(8*4095,AT24C32_Int.data,2); lcd.clear(); lcd.setCursor(0,0); lcd.print("Saved data "); lcd.print(AT24C32_Slot.Value); lcd.setCursor(0,1); lcd.print("Slots used "); lcd.print(AT24C32_Int.Value); delay(2000); } delay(1000); if (LEDTimer>0) { LEDTimer--; if (LEDTimer==0) digitalWrite(led,LOW); } } else { digitalWrite(led,HIGH); LEDTimer=menuValue[LED_Timeout]; } }
I think the code is working properly now (famous last words!).Pulled the board and rewired PinA (now D2) and PinB (now D3) and repositioned the RTC. I also removed the Power and LED (D13), LEDs from the Nano. D13 powers the LCD LED and the Power LED is pretty useless. It saves some power. I think the LCD uses about a 1 mA with the LCD LED off, so a power off after 15 seconds sounds like a good idea.
Magic
-
Still Testing
02/02/2018 at 13:28 • 0 commentsStill Testing
Those rotary encoders are still causing trouble, occasionally they get confused. I can't work out why but t must be a memory clash or an interrupt problem. I am using volatile variable without turning off interrupts so I should look at that first. But once it gets confused it stays that way (more likely a memory clash).
I have added a sampling and datalogging code using the RTC AT24C32 EEPROM. Why not, its come for free with the RTC I got. At the moment the sampler just records a random number that can be downloaded to the serial port:
/* * Universal Time Clock with Periodic Sampler */ #include <Wire.h> #include "RTClib.h" RTC_DS1307 rtc; #include <AT24Cxx.h> AT24Cxx AT24C32(0x50); // An anonymous union/structure to read/write RTC AT24C32 data union { struct{ char Year; char Month; char Day; char Hour; char Minute; char Second; int Value; }; char data[8]; } AT24C32_Slot; union { struct{ int Value; }; char data[2]; } AT24C32_Int; // Include LCD library and initialise #include <LiquidCrystal.h> // Define Adruino pin numbers for LCD #define en 7 #define rs 8 #define d4 9 #define d5 10 #define d6 11 #define d7 12 #define led 13 // Set LCD LiquidCrystal lcd(rs,en,d4,d5,d6,d7); /* ROTARY ENCODER AND PUSH BUTTON POLLING CODE */ #define PinA 3 #define PinB 2 #define SW 4 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 (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 // This rotary encoder updates twice per detent! if (encoderFlag) { // First transition only EncoderPos+=encoderDir; } encoderFlag=(!encoderFlag); 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 */ char* menuName[]={ "Exit Menu ", // 0 - Exit menu system "UT Year ", // 1 "UT Month ", // 2 "UT Day ", // 3 "UT Hour ", // 4 "UT Minute ", // 5 "UT Second ", // 6 "UT Save ", // 7 "Local Hour ", // 8 "Local Min ", // 9 "Upload Sec ", // 10 "Sample Min ", // 11 - Minutes between samples "Upload Data " // 12 - Upload data to serial port }; int menuSize=sizeof(menuName)/sizeof(char*); int menuValue[]= { 1,2000, 1, 1, 0, 0, 0, 0, 8, 0, 7, 6, 1}; // Non-numeric values are 'N'=0 and 'Y'=1 char menuNumeric[]={'N', 'Y','Y','Y','Y','Y','Y','N','Y','Y','Y','Y','N'}; int menuValueMin[]={ 0,1000, 1, 1, 0, 0, 0, 0,-59, 0, 0, 0}; int menuValueMax[]={ 1,2999, 12, 31, 23, 59, 59, 1, 59, 59, 60, 1}; int menuLevel=0; // AT24C32 usage char settings[8]; // 32752-32759 unsigned int sampleSlot=0; // Slot 0 to 4093 (2 Slots reserved) bool processMenu(void) { static int lastPos=0; static int lastMenuLevel=-1; // Pre-empt menu level display if (menuLevel!=lastMenuLevel) { lastMenuLevel=menuLevel; lcd.clear(); lcd.setCursor(0,0); if (menuLevel==0) { lcd.print("Menu:"); lcd.setCursor(0,1); lcd.print(menuName[EncoderPos]); if (menuNumeric[EncoderPos]=='Y') { lcd.print(menuValue[EncoderPos]); } else { if (menuValue[EncoderPos]==0) { lcd.print("N"); } else { lcd.print("Y"); } } } else if (menuLevel==1) { lcd.print("Set:"); lcd.setCursor(0,1); lcd.print(menuName[lastPos]); if (menuNumeric[lastPos]=='Y') { lcd.print(menuValue[lastPos]); } else { if (menuValue[lastPos]==0) { lcd.print("N"); } else { lcd.print("Y"); } } } else { lcd.print("Rotary Encoder?"); } } // 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==-1) { menuLevel=0; // Item menu lastPos=1; // Trigger menu update EncoderPos=0; // Done menuValue[0]=1; // 'Y' } else { // Toggle menu level menuLevel=1-menuLevel; if (menuLevel==0) { // Restore item menu position EncoderPos=lastPos; // Exit menu if done! if ((EncoderPos==0)&&(menuValue[0]!=0)) { menuLevel=-1; // Save Local Time and Sample settings strcpy(settings,"UTC"); settings[4]=menuValue[8]; settings[5]=menuValue[9]; settings[6]=menuValue[10]; settings[7]=menuValue[11]; AT24C32.WriteMem(32752,settings,8); } // Check for save new UT if ((EncoderPos==7)&&(menuValue[7]!=0)) { rtc.adjust(DateTime(menuValue[1],menuValue[2],menuValue[3],menuValue[4],menuValue[5],menuValue[6])); } // Check for data download if ((EncoderPos==12)&&(menuValue[12]!=0)) { menuValue[11]=1; // Reset to 'X' lcd.clear(); lcd.setCursor(0,0); lcd.print("Upload Data: "); lcd.setCursor(0,1); lcd.print("Slots used "); lcd.print(sampleSlot); delay(1000); Serial.begin(9600); while (!Serial); for (int i=0;i<sampleSlot;i++) { AT24C32.ReadMem(i*8,AT24C32_Slot.data,8); Serial.print(AT24C32_Slot.Year+2000,DEC); Serial.print("/"); if (AT24C32_Slot.Month<10) Serial.print(0); Serial.print(AT24C32_Slot.Month,DEC); Serial.print("/"); if (AT24C32_Slot.Day<10) Serial.print(0); Serial.print(AT24C32_Slot.Day,DEC); Serial.print(" "); if (AT24C32_Slot.Hour<10) Serial.print(0); Serial.print(AT24C32_Slot.Hour,DEC); Serial.print(":"); if (AT24C32_Slot.Minute<10) Serial.print(0); Serial.print(AT24C32_Slot.Minute,DEC); Serial.print(":"); if (AT24C32_Slot.Second<10) Serial.print(0); Serial.print(AT24C32_Slot.Second,DEC); Serial.print(" = "); Serial.println(AT24C32_Slot.Value); } Serial.end(); lcd.clear(); lcd.setCursor(0,0); lcd.print("Upload Data: "); lcd.setCursor(0,1); lcd.print("Done "); delay(1000); } } else { // Set value for edit menu EncoderPos=menuValue[lastPos]; } } } } // If encoder turned if (menuLevel==0) { // Select menu item if (lastPos!=EncoderPos) { if (EncoderPos>=menuSize) EncoderPos=0; if (EncoderPos<0) EncoderPos=menuSize-1; lastPos=EncoderPos; lcd.clear(); lcd.setCursor(0,0); lcd.print("Menu:"); lcd.setCursor(0,1); lcd.print(menuName[EncoderPos]); if (menuNumeric[EncoderPos]=='Y') { lcd.print(menuValue[EncoderPos]); } else { if (menuValue[EncoderPos]==0) { lcd.print("N"); } else { lcd.print("Y"); } } } } else if (menuLevel==1) { // 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; lcd.clear(); lcd.setCursor(0,0); lcd.print("Set:"); lcd.setCursor(0,1); lcd.print(menuName[lastPos]); if (menuNumeric[lastPos]=='Y') { lcd.print(menuValue[lastPos]); } else { if (menuValue[lastPos]==0) { lcd.print("N"); } else { lcd.print("Y"); } } } } return (menuLevel!=-1); // Return true if menu active } void setup() { // Initialise rotary encoder and push button pinMode(PinA,INPUT_PULLUP); // Rotary PinA pinMode(PinB,INPUT_PULLUP); // Rotary PinB pinMode(SW,INPUT_PULLUP); // Rotary SW // Turn on polling ISR OCR0B=0xA0; TIMSK0|=1<<OCIE0B; // Initialise LCD pinMode(led,OUTPUT); digitalWrite(led,HIGH); lcd.begin(16,2); // Print welcome message to the LCD lcd.setCursor(0,0);lcd.print("Universal Time"); // Check RTC if (!rtc.begin()) { lcd.setCursor(0,1);lcd.print("RTC not found"); while (true); } else if (!rtc.isrunning()) { lcd.setCursor(0,1);lcd.print("RTC not running"); } else { lcd.setCursor(0,1);lcd.print("RTC found"); } // Check if AT24C32 is present if (!AT24C32.isPresent()) { lcd.setCursor(0,1);lcd.print("AT24C32 not installed"); while (true); } // Set Universal Time based on saved local time int localTimeHour=8; // 8 hours for Perth Western Australia int localTimeMin=0; // 0 minutes for Perth Western Australia int uploadTimeSec=7; // 7 seconds for my computer int samplePeriodMin=1; // 1 minutes between samples // Read saved settings AT24C32.ReadMem(32752,settings,8); if (strcmp(settings,"UTC")==0) { localTimeHour=settings[4]; localTimeMin=settings[5]; uploadTimeSec=settings[6]; samplePeriodMin=settings[7]; } rtc.adjust(DateTime(F(__DATE__),F(__TIME__))); DateTime now=rtc.now(); rtc.adjust(DateTime(now+(uploadTimeSec-localTimeHour*3600-localTimeMin*60))); // Set menu to current time now=rtc.now(); menuLevel=-1; // Don't enter menu on start up menuValue[0]='Y'; // Set "Exit Menu" status menuValue[1]=now.year(); menuValue[2]=now.month(); menuValue[3]=now.day(); menuValue[4]=now.hour(); menuValue[5]=now.minute(); menuValue[6]=now.second(); menuValue[7]='Y'; // Set "UT Save" status menuValue[8]=localTimeHour; menuValue[9]=localTimeMin; menuValue[10]=uploadTimeSec; menuValue[11]=samplePeriodMin; delay(1000); lcd.clear(); } void loop() { if (!processMenu()) { // Menu not active // Present the results DateTime now=rtc.now(); // Print date on first line of LCD lcd.clear(); lcd.setCursor(0,0);lcd.print("Date "); lcd.print(now.year()); lcd.print('/'); if (now.month()<10) lcd.print(0); lcd.print(now.month()); lcd.print('/'); if (now.day()<10) lcd.print(0); lcd.print(now.day()); // Print time on second line of LCD lcd.setCursor(0,1);lcd.print("Time "); if (now.hour()<10) lcd.print(0); lcd.print(now.hour()); lcd.print(':'); if (now.minute()<10) lcd.print(0); lcd.print(now.minute()); lcd.print(':'); if (now.second()<10) lcd.print(0); lcd.print(now.second()); // Save data to RTC ATC32 (4094 slots) every samplePeriodMin if ((now.second()==0)&&(now.minute()%menuValue[11]==0)) { AT24C32_Slot.Year=now.year()-2000; AT24C32_Slot.Month=now.month(); AT24C32_Slot.Day=now.day(); AT24C32_Slot.Hour=now.hour(); AT24C32_Slot.Minute=now.minute(); AT24C32_Slot.Second=0; AT24C32_Slot.Value=random(32)*random(32); AT24C32.WriteMem(sampleSlot*8,AT24C32_Slot.data,8); // Update next address sampleSlot++; if (sampleSlot>=4094) sampleSlot=1; AT24C32_Int.Value=sampleSlot; AT24C32.WriteMem(32760,AT24C32_Int.data,2); lcd.clear(); lcd.setCursor(0,0); lcd.print("Saved data "); lcd.print(AT24C32_Slot.Value); lcd.setCursor(0,1); lcd.print("Slots used "); lcd.print(sampleSlot); delay(2000); } delay(1000); } }
Each sample (16 bit integer) is date stamped. The record (or slot as I have called it) is 8 byte long. One char for Year, Month, Day, Hour, Minute, Second and one 16 bit int for Value. I used this anonymous union/structure convert between formats for read and write to the AT24C32:
union { struct { char Year; char Month; char Day; char Hour; char Minute; char Second; int Value; }; char data[8]; } AT24C32_Slot;
Here is an example writing to the AT24C32:
AT24C32_Slot.Year=now.year()-2000; AT24C32_Slot.Month=now.month(); AT24C32_Slot.Day=now.day(); AT24C32_Slot.Hour=now.hour(); AT24C32_Slot.Minute=now.minute(); AT24C32_Slot.Second=0; AT24C32_Slot.Value=random(32)*random(32); AT24C32.WriteMem(sampleSlot*8,AT24C32_Slot.data,8);
Reading data from the AT24C32 is similar:
AT24C32.ReadMem(sampleSlot*8,AT24C32_Slot.data,8); Year=AT24C32_Slot.Year+2000; Month=AT24C32_Slot.Month; Day=AT24C32_Slot.Day; Hour=AT24C32_Slot.Hour; Minute=AT24C32_Slot.Minute; Second=AT24C32_Slot.Second; Value=AT24C32_Slot.Value;
So no explicit data conversions, neat.
At 1 minute between samples the AT24C32 can hold 68 hours of data. The sample period is programable from the menu (current range is 1 to 60 minutes).
Magic
-
Front Panel
02/01/2018 at 12:28 • 0 commentsFront Panel
The front panel did not go very smoothly despite the effort in setting it up. To ensure I did not waste my supply of Birch plywood I just made a prototype of the front panel with some fiberboard.
I forgot the LCD window on the first front panel!, even so it demonstrated that the Keyes rotary encoder was a bad choice. The encoder shaft collar has no thread and the two bolt holes on the PCB are on one side of the shaft. The encoder moves to the side when pressed. Other than gluing the encoder to the back of the front panel it is not going to work.
Made a second front panel (with an LCD window) for a bare rotary encoder. Made up a small strip board with legs for the rotary encoder to fit level with the LCD display :
The Nano sockets were removed and the Nano was soldered directly to the stripboard as it was no going to fit (in the image above, the red led thing under the rotay switch).
The RTC sockets will need to go as well.
Testing
At first the rotation increment/decrement was the wrong way. No problem I have swapped the A and B pins on the rotatry encoder. I fixed it in software for now and will rewire it later.
The main problem was that the menu and setting increments and decrements in steps of two? This puzzled me for a while until I realised it could only be the rotary encoder. Sure enough if you rotate the encode 1/2 a detent if increments once and then again at the detent. It reliably does this so the double increment is by design! How stupid! To fix I added a flag to the polling routine to only update the second increment (at detent).
It now work fine, upon boot up:
The menu (item) after one press (for about a second):
This is the Menu Item on the first line and the current setting on the second line (n.b. "X" means abort all changes while "Y" means accept and "N" means cancel this Menu Item.
Second press to change or set the menu item:
This Set Menu allows you to change a setting.
And a third press to accept the setting which in this case "X" will return you to the UT Clock (with no changes to the clock settings).
The location of the rotary encoder ideal for a right hander as it keeps your fingers from blocking your view of the LCD while rotating the knob.
Now the only problem I noticed was that the day number is wrong! Which means the timeSpan() does not like negative numbers. Fixed, don't use timeSpan()!
Updated Code
Here is the updated code. Note this code is configured for my stupid rotary encoder!
// Include RTC libraries and initialise #include <Wire.h> #include "RTClib.h" RTC_DS1307 rtc; // Include LCD library and initialise #include <LiquidCrystal.h> // Define Adruino pin numbers for LCD #define en 7 #define rs 8 #define d4 9 #define d5 10 #define d6 11 #define d7 12 #define led 13 // Set LCD LiquidCrystal lcd(rs,en,d4,d5,d6,d7); /* ROTARY ENCODER AND PUSH BUTTON POLLING CODE */ #define Clk 3 #define DT 2 #define SW 4 // Poll and debounces a Keyes rotary and push button switch // Inputs: // Clk (Pin A without a pullup resistor) // DT (Pin B without a pullup resistor) // SW (Switch without a pullup resistor) // Exports: // UpdateSwitch (bool) // Switch [LOW|HIGH] // EncoderPos (int) volatile bool UpdateSwitch=false; volatile byte Switch=HIGH; volatile int EncoderPos=0; volatile bool EncoderFlag=true; ISR(TIMER0_COMPB_vect) { static byte testClk=(PIND>>Clk)&1; static byte testDT=(PIND>>DT)&1; static byte lastClk=LOW; static byte lastDT=LOW; static bool flagClk=false; static bool flagDT=false; static int encoderDir=0; static byte testSW=HIGH; static byte statusSW=HIGH; static byte cntSW=0; // Update Encoder Position lastClk=testClk; // Save Clk lastDT=testDT; // Save DT testClk=(PIND>>Clk)&1; // Get Clk testDT=(PIND>>DT)&1; // Get DT if (testClk!=lastClk) { // Change in Clk? flagClk=true; // Flag Clk has changed encoderDir=-1; // Assume it is the last flag to change } if (testDT!=lastDT) { // Change in DT? flagDT=true; // Flag DT has changed encoderDir=1; // Assume it is the last flag to change } if (flagClk&&flagDT) { // Both flags have changed // This rotary encoder updates twice per detent if (EncoderFlag) { // First transition EncoderPos+=encoderDir; } EncoderFlag=(!EncoderFlag); flagClk=false; // Reset Clk flag flagDT=false; // Reset DT flag } // Update switch with 10ms debounce testSW=(PIND>>SW)&1; if (testSW!=statusSW) { statusSW=testSW; cntSW=10; } if (cntSW>0) { cntSW--; if (cntSW==0) { Switch=statusSW; UpdateSwitch=true; } } } /* MENU SET UP */ char* menuName[]={"Done ","Year ","Month ","Day ","Hour ","Minute ","Second "}; int menuSize=sizeof(menuName)/sizeof(char*); int menuValue[]= { 1,2000, 1, 1, 0, 0, 0}; char menuNumeric[]={'N', 'Y','Y','Y','Y','Y','Y'}; int menuValueMin[]={ 0,2000, 1, 1, 0, 0, 0}; // Non-numeric values 'N','X' and 'Y' int menuValueMax[]={ 2,2999, 12, 31, 23, 59, 59}; int menuLevel=0; void setup() { // Initialise rotary encoder and push button pinMode(Clk,INPUT_PULLUP); // Rotary Clk pinMode(DT,INPUT_PULLUP); // Rotary DT pinMode(SW,INPUT_PULLUP); // Rotary SW // Turn on polling ISR OCR0B=0xA0; TIMSK0|=1<<OCIE0B; // Initialise LCD pinMode(led,OUTPUT); digitalWrite(led,HIGH); lcd.begin(16,2); // Print a message to the LCD. lcd.setCursor(0,0);lcd.print("Universal Time"); // Check RTC if (!rtc.begin()) { lcd.setCursor(0,1);lcd.print("RTC not found"); while (true); } else if (!rtc.isrunning()) { lcd.setCursor(0,1);lcd.print("RTC not running"); } else { lcd.setCursor(0,1);lcd.print("RTC found"); } // Set Universal Time based on Compile Time int localTimeHour=-8; // Perth Western Australia int localTimeMin=0; // Perth Western Australia int uploadTimeSec=7; // For my computer rtc.adjust(DateTime(F(__DATE__),F(__TIME__))); DateTime now=rtc.now(); rtc.adjust(DateTime(now+TimeSpan(0,localTimeHour,localTimeMin,uploadTimeSec))); // Set menu to current time now=rtc.now(); menuLevel=-1; // Don't enter menu on start up menuValue[0]='X'; // Set "done" status to abort menuValue[1]=now.year(); menuValue[2]=now.month(); menuValue[3]=now.day(); menuValue[4]=now.hour(); menuValue[5]=now.minute(); menuValue[6]=now.second(); delay(1000); lcd.clear(); } bool processMenu(void) { static int lastPos=0; static int lastMenuLevel=-1; // Pre-empt menu level display if (menuLevel!=lastMenuLevel) { lastMenuLevel=menuLevel; lcd.clear(); lcd.setCursor(0,0); if (menuLevel==0) { lcd.print("Menu:"); lcd.setCursor(0,1); lcd.print(menuName[EncoderPos]); if (menuNumeric[EncoderPos]=='Y') { lcd.print(menuValue[EncoderPos]); } else { if (menuValue[EncoderPos]==0) { lcd.print("N"); } else if (menuValue[EncoderPos]==1) { lcd.print("X"); } else { lcd.print("Y"); } } } else if (menuLevel==1) { lcd.print("Set:"); lcd.setCursor(0,1); lcd.print(menuName[lastPos]); if (menuNumeric[lastPos]=='Y') { lcd.print(menuValue[lastPos]); } else { if (menuValue[lastPos]==0) { lcd.print("N"); } else if (menuValue[lastPos]==2) { lcd.print("Y"); } else { lcd.print("X"); } } } else { lcd.print("Rotary Encoder?"); } } // If push button pushed toggle menuLevel if (UpdateSwitch) { UpdateSwitch=false; if (Switch==LOW) { // Re-enter menu if button pushed (for long enough) if (menuLevel==-1) { menuLevel=0; // Item menu lastPos=1; // Trigger menu update EncoderPos=0; // Done EncoderFlag=false; // Detent menuValue[0]=1; // 'X' } else { // Toggle menu level menuLevel=1-menuLevel; if (menuLevel==0) { // Save item menu position EncoderPos=lastPos; // Exit menu if done! if ((EncoderPos==0)&&(menuValue[EncoderPos]!=0)) menuLevel=-1; // Any final work to be done? Set the time! if (menuValue[EncoderPos]==2) { rtc.adjust(DateTime(menuValue[1],menuValue[2],menuValue[3],menuValue[4],menuValue[5],menuValue[6])); } } else { // Set value for edit menu EncoderPos=menuValue[lastPos]; } } } } // If encoder turned if (menuLevel==0) { // Select menu item if (lastPos!=EncoderPos) { if (EncoderPos>=menuSize) EncoderPos=0; if (EncoderPos<0) EncoderPos=menuSize-1; lastPos=EncoderPos; lcd.clear(); lcd.setCursor(0,0); lcd.print("Menu:"); lcd.setCursor(0,1); lcd.print(menuName[EncoderPos]); if (menuNumeric[EncoderPos]=='Y') { lcd.print(menuValue[EncoderPos]); } else { if (menuValue[EncoderPos]==0) { lcd.print("N"); } else if (menuValue[EncoderPos]==2) { lcd.print("Y"); } else { lcd.print("X"); } } } } else if (menuLevel==1) { // 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; lcd.clear(); lcd.setCursor(0,0); lcd.print("Set:"); lcd.setCursor(0,1); lcd.print(menuName[lastPos]); if (menuNumeric[lastPos]=='Y') { lcd.print(menuValue[lastPos]); } else { if (menuValue[lastPos]==0) { lcd.print("N"); } else if (menuValue[lastPos]==2) { lcd.print("Y"); } else { lcd.print("X"); } } } } return (menuLevel!=-1); // Return true if menu active } void loop() { if (!processMenu()) { // Menu not active // Present the results DateTime now=rtc.now(); // Print date on first line of LCD lcd.clear(); lcd.setCursor(0,0); lcd.print(now.year()); lcd.print('/'); if (now.month()<10) lcd.print(0); lcd.print(now.month()); lcd.print('/'); if (now.day()<10) lcd.print(0); lcd.print(now.day()); // Print time on second line of LCD lcd.setCursor(0,1);lcd.print("UT: "); if (now.hour()<10) lcd.print(0); lcd.print(now.hour()); lcd.print(':'); if (now.minute()<10) lcd.print(0); lcd.print(now.minute()); lcd.print(':'); if (now.second()<10) lcd.print(0); lcd.print(now.second()); delay(1000); } }
Magic
-
UT Clock with Menu
01/30/2018 at 09:07 • 0 commentsUT Clock with Menu
Added the menu system to the UT Clock. Had to add an abort/cancel option (i.e. 'X') to the "Done" menu option. An obvious omission.
Here is the code:
// Include RTC libraries and initialise #include <Wire.h> #include "RTClib.h" RTC_DS1307 rtc; // Include LCD library and initialise #include <LiquidCrystal.h> // Define Adruino pin numbers for LCD #define en 7 #define rs 8 #define d4 9 #define d5 10 #define d6 11 #define d7 12 #define led 13 // Set LCD LiquidCrystal lcd(rs,en,d4,d5,d6,d7); /* ROTARY ENCODER AND PUSH BUTTON POLLING CODE */ #define Clk 2 #define DT 3 #define SW 4 // Poll and debounces a Keyes rotary and push button switch // Inputs: // Clk (Pin A with 10k pullup resistor) // DT (Pin B with 10k pullup resistor) // SW (Switch without a pullup resistor) // + (Power for pullup resistors) // Gnd // Exports: // UpdateSwitch (bool) // Switch [LOW|HIGH] // EncoderPos (int) volatile bool UpdateSwitch=false; volatile byte Switch=HIGH; volatile int EncoderPos=0; ISR(TIMER0_COMPB_vect) { static byte testClk=(PIND>>Clk)&1; static byte testDT=(PIND>>DT)&1; static byte lastClk=LOW; static byte lastDT=LOW; static bool flagClk=false; static bool flagDT=false; static int encoderDir=0; static byte testSW=HIGH; static byte statusSW=HIGH; static byte cntSW=0; // Update Encoder Position lastClk=testClk; // Save Clk lastDT=testDT; // Save DT testClk=(PIND>>Clk)&1; // Get Clk testDT=(PIND>>DT)&1; // Get DT if (testClk!=lastClk) { // Change in Clk? flagClk=true; // Flag Clk has changed encoderDir=-1; // Assume it is the last flag to change } if (testDT!=lastDT) { // Change in DT? flagDT=true; // Flag DT has changed encoderDir=1; // Assume it is the last flag to change } if (flagClk&&flagDT) { // Both flags have changed EncoderPos+=encoderDir; // Update the encoder flagClk=false; // Reset Clk flag flagDT=false; // Reset DT flag } // Update switch with 10ms debounce testSW=(PIND>>SW)&1; if (testSW!=statusSW) { statusSW=testSW; cntSW=10; } if (cntSW>0) { cntSW--; if (cntSW==0) { Switch=statusSW; UpdateSwitch=true; } } } /* MENU SET UP */ char* menuName[]={"Done ","Year ","Month ","Day ","Hour ","Minute ","Second "}; int menuSize=sizeof(menuName)/sizeof(char*); int menuValue[]= { 1, 0, 1, 1, 0, 0, 0}; char menuNumeric[]={'N','Y','Y','Y','Y','Y','Y'}; int menuValueMin[]={ 0, 0, 1, 1, 0, 0, 0}; // Non-numeric values 'N','X' and 'Y' int menuValueMax[]={ 2,999, 12, 31, 23, 59, 59}; int menuLevel=0; void setup() { // Initialise rotary encoder and push button pinMode(Clk,INPUT_PULLUP); // Rotary Clk pinMode(DT,INPUT_PULLUP); // Rotary DT pinMode(SW,INPUT_PULLUP); // Rotary SW // Turn on polling ISR OCR0B=0xA0; TIMSK0|=1<<OCIE0B; // Initialise LCD pinMode(led,OUTPUT); digitalWrite(led,HIGH); lcd.begin(16,2); // Print a message to the LCD. lcd.setCursor(0,0);lcd.print("Universal Time"); // Check RTC if (!rtc.begin()) { lcd.setCursor(0,1);lcd.print("RTC not found"); while (true); } else if (!rtc.isrunning()) { lcd.setCursor(0,1);lcd.print("RTC not running"); } else { lcd.setCursor(0,1);lcd.print("RTC found"); } // Set Universal Time based on Compile Time int localTimeHour=-8; // Perth Western Australia int localTimeMin=0; // Perth Western Australia int uploadTimeSec=7; // For my computer rtc.adjust(DateTime(F(__DATE__),F(__TIME__))); DateTime now=rtc.now(); rtc.adjust(DateTime(now+TimeSpan(0,localTimeHour,localTimeMin,uploadTimeSec))); // Set menu to current time now=rtc.now(); menuLevel=-1; // Don't enter menu on start up menuValue[0]='X'; // Set "done" status to abort menuValue[1]=now.year(); menuValue[2]=now.month(); menuValue[3]=now.day(); menuValue[4]=now.hour(); menuValue[5]=now.minute(); menuValue[6]=now.second(); delay(1000); lcd.clear(); } bool processMenu(void) { static int lastPos=0; static int lastMenuLevel=-1; // Pre-empt menu level display if (menuLevel!=lastMenuLevel) { lastMenuLevel=menuLevel; lcd.clear(); lcd.setCursor(0,0); if (menuLevel==0) { lcd.print("Menu:"); lcd.setCursor(0,1); lcd.print(menuName[EncoderPos]); if (menuNumeric[EncoderPos]=='Y') { lcd.print(menuValue[EncoderPos]); } else { if (menuValue[EncoderPos]==0) { lcd.print("N"); } else if (menuValue[EncoderPos]==1) { lcd.print("X"); } else { lcd.print("Y"); } } } else if (menuLevel==1) { lcd.print("Set:"); lcd.setCursor(0,1); lcd.print(menuName[lastPos]); if (menuNumeric[lastPos]=='Y') { lcd.print(menuValue[lastPos]); } else { if (menuValue[lastPos]==0) { lcd.print("N"); } else if (menuValue[lastPos]==2) { lcd.print("Y"); } else { lcd.print("X"); } } } else { lcd.print("Rotary Encoder?"); } } // If push button pushed toggle menuLevel if (UpdateSwitch) { UpdateSwitch=false; if (Switch==LOW) { // Re-enter menu if button pushed (for long enough) if (menuLevel==-1) { menuLevel=0; // Item menu lastPos=1; // Trigger menu update EncoderPos=0; // Done menuValue[0]=1; // 'X' } else { // Toggle menu level menuLevel=1-menuLevel; if (menuLevel==0) { // Save item menu position EncoderPos=lastPos; // Exit menu if done! if ((EncoderPos==0)&&(menuValue[EncoderPos]!=0)) menuLevel=-1; // Any final work to be done? Set the time! if (menuValue[EncoderPos]==2) { rtc.adjust(DateTime(menuValue[1],menuValue[2],menuValue[3],menuValue[4],menuValue[5],menuValue[6])); } } else { // Set value for edit menu EncoderPos=menuValue[lastPos]; } } } } // If encoder turned if (menuLevel==0) { // Select menu item if (lastPos!=EncoderPos) { if (EncoderPos>=menuSize) EncoderPos=0; if (EncoderPos<0) EncoderPos=menuSize-1; lastPos=EncoderPos; lcd.clear(); lcd.setCursor(0,0); lcd.print("Menu:"); lcd.setCursor(0,1); lcd.print(menuName[EncoderPos]); if (menuNumeric[EncoderPos]=='Y') { lcd.print(menuValue[EncoderPos]); } else { if (menuValue[EncoderPos]==0) { lcd.print("N"); } else if (menuValue[EncoderPos]==2) { lcd.print("Y"); } else { lcd.print("X"); } } } } else if (menuLevel==1) { // 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; lcd.clear(); lcd.setCursor(0,0); lcd.print("Set:"); lcd.setCursor(0,1); lcd.print(menuName[lastPos]); if (menuNumeric[lastPos]=='Y') { lcd.print(menuValue[lastPos]); } else { if (menuValue[lastPos]==0) { lcd.print("N"); } else if (menuValue[lastPos]==2) { lcd.print("Y"); } else { lcd.print("X"); } } } } return (menuLevel!=-1); // Return true if menu active } void loop() { if (!processMenu()) { // Menu not active // Present the results DateTime now=rtc.now(); // Print date on first line of LCD lcd.clear(); lcd.setCursor(0,0); lcd.print(now.year()); lcd.print('/'); if (now.month()<10) lcd.print(0); lcd.print(now.month()); lcd.print('/'); if (now.day()<10) lcd.print(0); lcd.print(now.day()); // Print time on second line of LCD lcd.setCursor(0,1);lcd.print("UT: "); if (now.hour()<10) lcd.print(0); lcd.print(now.hour()); lcd.print(':'); if (now.minute()<10) lcd.print(0); lcd.print(now.minute()); lcd.print(':'); if (now.second()<10) lcd.print(0); lcd.print(now.second()); delay(1000); } }
I am happy with the code. Time for the front panel.
Magic
-
Updated Rotary Encoder Menu
01/30/2018 at 08:24 • 1 commentUpdated Rotary Encoder Menu
I updated the Rotary Encoder menu for the LCD display:
// Include LCD library and initialise #include <LiquidCrystal.h> // Define Adruino pin numbers for LCD #define en 7 #define rs 8 #define d4 9 #define d5 10 #define d6 11 #define d7 12 #define led 13 // Set LCD LiquidCrystal lcd(rs,en,d4,d5,d6,d7); /* ROTARY ENCODER AND PUSH BUTTON POLLING CODE */ #define Clk 2 #define DT 3 #define SW 4 // Poll and debounces a Keyes rotary and push button switch // Inputs: // Clk (Pin A with 10k pullup resistor) // DT (Pin B with 10k pullup resistor) // SW (Switch without a pullup resistor) // + (Power for pullup resistors) // Gnd // Exports: // UpdateSwitch (bool) [true|false] // Switch (byte) [LOW|HIGH] // EncoderPos (char) [-128..127] volatile bool UpdateSwitch=false; volatile byte Switch=HIGH; volatile char EncoderPos=0; ISR(TIMER0_COMPB_vect) { static byte testClk=(PIND>>Clk)&1; static byte testDT=(PIND>>DT)&1; static byte lastClk=LOW; static byte lastDT=LOW; static bool flagClk=false; static bool flagDT=false; static char encoderDir=0; static byte testSW=HIGH; static byte statusSW=HIGH; static byte cntSW=0; // Update Encoder Position lastClk=testClk; // Save Clk lastDT=testDT; // Save DT testClk=(PIND>>Clk)&1; // Get Clk testDT=(PIND>>DT)&1; // Get DT if (testClk!=lastClk) { // Change in Clk? flagClk=true; // Flag Clk has changed encoderDir=-1; // Assume it is the last flag to change } if (testDT!=lastDT) { // Change in DT? flagDT=true; // Flag DT has changed encoderDir=1; // Assume it is the last flag to change } if (flagClk&&flagDT) { // Both flags have changed EncoderPos+=encoderDir; // Update the encoder flagClk=false; // Reset Clk flag flagDT=false; // Reset DT flag } // Update switch with 10ms debounce testSW=(PIND>>SW)&1; if (testSW!=statusSW) { statusSW=testSW; cntSW=10; } if (cntSW>0) { cntSW--; if (cntSW==0) { Switch=statusSW; UpdateSwitch=true; } } } /* MENU SET UP */ char* menuName[]={"Done ","Year ","Month ","Day ","Hour ","Minute ","Second ","Adjustment "}; int menuSize=sizeof(menuName)/sizeof(char*); char menuValue[]= { 0, 0, 1, 1, 0, 0, 0, 0}; char menuNumeric[]= {'N','Y','Y','Y','Y','Y','Y', 'Y'}; char menuValueMin[]={ 0, 0, 1, 1, 0, 0, 0,-126}; char menuValueMax[]={ 1,126, 12, 31, 23, 59, 59, 126}; char menuLevel=0; void setup() { // Initialise rotary encoder and push button pinMode(Clk,INPUT_PULLUP); // Rotary Clk pinMode(DT,INPUT_PULLUP); // Rotary DT pinMode(SW,INPUT_PULLUP); // Rotary SW // Turn on polling ISR OCR0B=0xA0; TIMSK0|=1<<OCIE0B; // Initialise LCD pinMode(led,OUTPUT); digitalWrite(led,HIGH); lcd.begin(16,2); lcd.print("Rotary Encoder"); delay(3000); } bool processMenu(void) { static char lastPos=0; static char lastMenuLevel=-1; // Pre-empt menu level display if (menuLevel!=lastMenuLevel) { lastMenuLevel=menuLevel; lcd.clear(); lcd.setCursor(0,0); if (menuLevel==0) { lcd.print("Menu:"); lcd.setCursor(0,1); lcd.print(menuName[EncoderPos]); if (menuNumeric[EncoderPos]=='Y') { lcd.print((int)menuValue[EncoderPos]); } else { if (menuValue[EncoderPos]!=0) { lcd.print("Y"); } else { lcd.print("N"); } } } else if (menuLevel==1) { lcd.print("Set:"); lcd.setCursor(0,1); lcd.print(menuName[lastPos]); if (menuNumeric[lastPos]=='Y') { lcd.print((int)menuValue[lastPos]); } else { if (menuValue[lastPos]!=0) { lcd.print("Y"); } else { lcd.print("N"); } } } else { lcd.print("Rotary Encoder?"); } } // If push button pushed toggle menuLevel if (UpdateSwitch) { UpdateSwitch=false; if (Switch==LOW) { // Re-enter menu if button pushed (for long enough) if (menuLevel==-1) { menuLevel=0; // Item menu lastPos=1; // Trigger menu update EncoderPos=0; // Done menuValue[0]=0; // N } else { // Toggle menu level menuLevel=1-menuLevel; if (menuLevel==0) { // Save item menu position EncoderPos=lastPos; // Exit menu if done! if ((EncoderPos==0)&&(menuValue[EncoderPos]!=0)) menuLevel=-1; } else { // Set value for edit menu EncoderPos=menuValue[lastPos]; } } } } // If encoder turned if (menuLevel==0) { // Select menu item if (lastPos!=EncoderPos) { if (EncoderPos>=menuSize) EncoderPos=0; if (EncoderPos<0) EncoderPos=menuSize-1; lastPos=EncoderPos; lcd.clear(); lcd.setCursor(0,0); lcd.print("Menu:"); lcd.setCursor(0,1); lcd.print(menuName[EncoderPos]); if (menuNumeric[EncoderPos]=='Y') { lcd.print((int)menuValue[EncoderPos]); } else { if (menuValue[EncoderPos]!=0) { lcd.print("Y"); } else { lcd.print("N"); } } } } else if (menuLevel==1) { // 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; lcd.clear(); lcd.setCursor(0,0); lcd.print("Set:"); lcd.setCursor(0,1); lcd.print(menuName[lastPos]); if (menuNumeric[lastPos]=='Y') { lcd.print((int)menuValue[lastPos]); } else { if (menuValue[lastPos]!=0) { lcd.print("Y"); } else { lcd.print("N"); } } } } return (menuLevel!=-1); // Return true if menu active } void loop() { if (!processMenu()) { // Menu not active // Present the results int i; lcd.clear(); lcd.setCursor(0,0); lcd.print("Running"); lcd.setCursor(0,1); for (i=0;i<menuSize;i++) { if (menuNumeric[i]=='Y') { lcd.print((int)menuValue[i]); } else { if (menuValue[i]!=0) { lcd.print("Y"); } else { lcd.print("N"); } } lcd.print(" "); } delay(3000); } }
The main change was to pre-empt the menu level display. Previously it was only updated after the rotary button was turned or the push button pushed. Not very intuitive!
Pretty happy with the menu system now. Time to merge in the UT Clock code.
Magic
-
The Universal Time Clock
01/30/2018 at 07:16 • 0 commentsUniversal Time
Universal Time (UT) is the time in Greenwich UK. It is used for astromonical observations.
This is a simple real time clock (rtc) project that uses and LCD to display the date and time in UT.
Setting the time is automatic, it uses the compile time set local time and then reset that time to UT based on an upload adjustment and local time adjustments.
In my case the upload tme is 7 seconds, and my local time adjustment is -8 hours and 0 minutes. I live in Perth Western Australia and the longitude is 115.9 degrees East or 115.9 degrees. The local adjustment to the nearest hour is =-int(long/15+0.5), or -8 hours. Unfortunately many places have 30 minute local adjstments as well.
Here is the code:
// Include RTC libraries and initialise #include <Wire.h> #include "RTClib.h" RTC_DS1307 rtc; // Include LCD library and initialise #include <LiquidCrystal.h> // Define Adruino pin numbers for LCD #define en 7 #define rs 8 #define d4 9 #define d5 10 #define d6 11 #define d7 12 #define led 13 // Set LCD LiquidCrystal lcd(rs,en,d4,d5,d6,d7); void setup() { // Turn on LCD LED pinMode(led,OUTPUT); digitalWrite(led,HIGH); // Setup LCD columns and rows lcd.begin(16, 2); // Print a message to the LCD. lcd.setCursor(0,0);lcd.print("Universal Time"); // Check RTC if (!rtc.begin()) { lcd.setCursor(0,1);lcd.print("RTC not found"); while (true); } else if (!rtc.isrunning()) { lcd.setCursor(0,1);lcd.print("RTC not running"); } else { lcd.setCursor(0,1);lcd.print("RTC found"); } // Set Universal Time based on Compile Time int localTimeHour=-8; // Perth Western Australia int localTimeMin=0; // Perth Western Australia int uploadTimeSec=7; // For my computer rtc.adjust(DateTime(F(__DATE__),F(__TIME__))); DateTime now=rtc.now(); rtc.adjust(DateTime(now+TimeSpan(0,localTimeHour,localTimeMin,uploadTimeSec))); delay(1000); lcd.clear(); } void loop() { DateTime now=rtc.now(); // Print date on first line of LCD lcd.setCursor(0,0); lcd.print(now.year()); lcd.print('/'); if (now.month()<10) lcd.print(0); lcd.print(now.month()); lcd.print('/'); if (now.day()<10) lcd.print(0); lcd.print(now.day()); // Print time on second line of LCD lcd.setCursor(0,1);lcd.print("UT: "); if (now.hour()<10) lcd.print(0); lcd.print(now.hour()); lcd.print(':'); if (now.minute()<10) lcd.print(0); lcd.print(now.minute()); lcd.print(':'); if (now.second()<10) lcd.print(0); lcd.print(now.second()); delay(1000); }
The only tricky bit here was setting UT after setting local time:
// Set Universal Time based on Compile Time int localTimeHour=-8; // Perth Western Australia int localTimeMin=0; // Perth Western Australia int uploadTimeSec=7; // For my computer rtc.adjust(DateTime(F(__DATE__),F(__TIME__))); DateTime now=rtc.now(); rtc.adjust(DateTime(now+TimeSpan(0,localTimeHour,localTimeMin,uploadTimeSec)));
This line sets the rtc to compile time:
rtc.adjust(DateTime(F(__DATE__),F(__TIME__)));
This line get the current time (now):
DateTime now=rtc.now();
This line set UT based on an upload adjustment and local time adjustments:
rtc.adjust( DateTime( now+TimeSpan( 0,localTimeHour,localTimeMin,uploadTimeSec ) ) );
Magic
-
Prototype (stripboard) done
01/29/2018 at 14:59 • 0 commentsSecond stripboad prototype done
I spent a lot of time getting this done.
Here is the first prototype that got the LCD display working:
And the final prototype design:
Here is the assembled strip-board without the modules installed:
And with the LCD and RTC running (it is checking my custom clock):
Next step
Next is to get the menu system integrated.
Front panel
Now that the components have been located I can design the front panel.
Finally
The idea is that once I have a fully worked out prototype (complete with housing and power supply), I will get some PCBs made so I can reuse the design for other projects.
Magic
-
Adding an LCD Display
01/26/2018 at 22:38 • 0 commentsAdding an LCD Display
What is so hard? Just use the LCD library. Actually the problem is hooking it up in a semi-permanent way rather than the using the LCD library.
Issues
- Which pins to use
- Controlling the LED
- Controlling the contrast
When I first layout the strip-board for the display adapter I first used the standard pin numbers as shown below:
Except I used D6 and D7 for "E" and "RS", and D8 to control the LED power.
The first problem was adding a rotary encoder. The first rotary encoder code used two rising level interrupts and on the Uno/Nano you have to use D2 and D3. So I shuffled all the LCD pin number down by three (D4 was to be used for the switch). Leaving D12, D13 (the LED) and the analog pins free.
As the design progressed I realised it would be nice to keep the interrupt and PWM pins free, and I decided to use polling as I had to deal with switch bounce anyway.
For contrast a 1k8 resistor to ground work okay but access for contrast adjustment was provided.
Here is my strip-board design:
Magic
-
Adding a Simple Menu System
01/26/2018 at 13:55 • 0 commentsA Simple Menu System
As a demo I added a clock set menu. Here is a drawing of the menu and the value ranges:
So it is pretty simple, two levels: "Menu Item" and "Item Value". Just roll the encoder to move from one item to the next, push the button to toggle between levels. To exit the menu just toggle the done value from "N" to "Y" and push the button again. To get back into the menu, push the button (at least as long as any delays in the main loop).
The "Item Value" do have minimum and maximum vaues enforced but it does not know that February cannot have 31 days! Enter 18 for the year 2018, etc.
Here are my menu arrays:
char* menuName[]={"Done ","Year ","Month ","Day ","Hour ","Minute ","Second "}; int menuSize=sizeof(menuName)/sizeof(char*); char menuValue[]= { 0, 0, 1, 1, 0, 0, 0}; char menuNumeric[]= {'N','Y','Y','Y','Y','Y','Y'}; char menuValueMin[]={ 0, 0, 1, 1, 0, 0, 0}; char menuValueMax[]={ 1,126, 12, 31, 23, 59, 59}; char menuLevel=0;
The menu item names include the necessary format spacing between the name and the value. And values can be numeric ('Y') or Y/N ('N').
Here is the code:
#define Clk 5 #define DT 4 #define SW 3 // Poll and debounces a Keyes rotary and push button switch // Inputs: // Clk (Pin A with 10k pullup resistor) // DT (Pin B with 10k pullup resistor) // SW (Switch without a pullup resistor) // + (Power for pullup resistors) // Gnd // Exports: // UpdateSwitch (bool) [true|false] // Switch (byte) [LOW|HIGH] // EncoderPos (char) [-128..127] volatile bool UpdateSwitch=false; volatile byte Switch=HIGH; volatile char EncoderPos=0; ISR(TIMER0_COMPA_vect) { static byte testClk=(PIND>>Clk)&1; static byte testDT=(PIND>>DT)&1; static byte lastClk=LOW; static byte lastDT=LOW; static bool flagClk=false; static bool flagDT=false; static char encoderDir=0; static byte testSW=HIGH; static byte statusSW=HIGH; static byte cntSW=0; // Update Encoder Position lastClk=testClk; // Save Clk lastDT=testDT; // Save DT testClk=(PIND>>Clk)&1; // Get Clk testDT=(PIND>>DT)&1; // Get DT if (testClk!=lastClk) { // Change in Clk? flagClk=true; // Flag Clk has changed encoderDir=-1; // Assume it is the last flag to change } if (testDT!=lastDT) { // Change in DT? flagDT=true; // Flag DT has changed encoderDir=1; // Assume it is the last flag to change } if (flagClk&&flagDT) { // Both flags have changed EncoderPos+=encoderDir; // Update the encoder flagClk=false; // Reset Clk flag flagDT=false; // Reset DT flag } // Update switch with 10ms debounce testSW=(PIND>>SW)&1; if (testSW!=statusSW) { statusSW=testSW; cntSW=10; } if (cntSW>0) { cntSW--; if (cntSW==0) { Switch=statusSW; UpdateSwitch=true; } } } char* menuName[]={"Done ","Year ","Month ","Day ","Hour ","Minute ","Second "}; int menuSize=sizeof(menuName)/sizeof(char*); char menuValue[]= { 0, 0, 1, 1, 0, 0, 0}; char menuNumeric[]= {'N','Y','Y','Y','Y','Y','Y'}; char menuValueMin[]={ 0, 0, 1, 1, 0, 0, 0}; char menuValueMax[]={ 1,126, 12, 31, 23, 59, 59}; char menuLevel=0; void setup() { // Keyes rotary encoder pinMode(Clk,INPUT_PULLUP); // Rotary Clk (has 10k pullup) pinMode(DT,INPUT_PULLUP); // Rotary DT (has 10k pullup) pinMode(SW,INPUT_PULLUP); // Rotary SW (has no pullup) pinMode(2,OUTPUT); // Rotary + (supply power to encoder) digitalWrite(2,HIGH); // Turn on power for rotary encoder // Turn on polling ISR OCR0A=0x80; TIMSK0|=1<<OCIE0A; Serial.begin(9600); while (!Serial); Serial.println("Rotary encoder and push button example"); Serial.println("ITEM VALUE"); Serial.print(menuName[EncoderPos]); if (menuNumeric[EncoderPos]=='Y') { Serial.println(menuValue[EncoderPos],DEC); } else { if (menuValue[EncoderPos]!=0) { Serial.println("Y"); } else { Serial.println("N"); } } } bool processMenu(void) { static char lastPos=0; // If push button pushed toggle menuLevel if (UpdateSwitch) { UpdateSwitch=false; if (Switch==LOW) { // Re-enter menu if button pushed (for long enough) if (menuLevel==-1) { menuLevel=0; // Item menu lastPos=1; // Trigger menu update EncoderPos=0; // Done menuValue[0]=0; // N } else { // Toggle menu level menuLevel=1-menuLevel; if (menuLevel==0) { // Save item menu position EncoderPos=lastPos; // Exit menu if done! if ((EncoderPos==0)&&(menuValue[EncoderPos]!=0)) menuLevel=-1; } else { // Set value for edit menu EncoderPos=menuValue[lastPos]; } } } } // If encoder turned if (menuLevel==0) { // Select menu item if (lastPos!=EncoderPos) { if (EncoderPos>=menuSize) EncoderPos=0; if (EncoderPos<0) EncoderPos=menuSize-1; lastPos=EncoderPos; Serial.print(menuName[EncoderPos]); if (menuNumeric[EncoderPos]=='Y') { Serial.println(menuValue[EncoderPos],DEC); } else { if (menuValue[EncoderPos]!=0) { Serial.println("Y"); } else { Serial.println("N"); } } } } else if (menuLevel==1) { // 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(menuName[lastPos]); if (menuNumeric[lastPos]=='Y') { Serial.println(menuValue[lastPos],DEC); } else { if (menuValue[lastPos]!=0) { Serial.println("Y"); } else { Serial.println("N"); } } } } return (menuLevel!=-1); // Return true if menu active } void loop() { int i; if (!processMenu()) { // Menu not active // Present the results for (i=0;i<menuSize;i++) { if (menuNumeric[i]=='Y') { Serial.print(menuValue[i],DEC); } else { if (menuValue[i]!=0) { Serial.print("Y"); } else { Serial.print("N"); } } Serial.print(" "); } Serial.println(); delay(3000); } }
Magic