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