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