I have been working on the arduino uno r3 platform. Yesterday I was experiencing difficulty with the SD library when creating a new file. The library would create the file but return null leaving me nothing to manipulate. I eventually decided the problem was caused by limited SRAM space and tried to optimize the code as much as possible. I did eventually get file operations to work with the stripped down code but broke it again when trying to implement time proportioning to control the fridge compressor. It seems I'm nearing both the limits of what the uno r3 can do in terms of both flash and sram space. But perhaps my code is less efficient than i believe it is. I'm considering moving to a MEGA. Any advice?
Below is the code. I've been trying to condense the original rotary encoder interrupt code so that it piggybacks on the programState byte rather than an additional three but even though i swear my code does the exact same thing, I can't get it to increment the tracking variable (the original code is still present but has been commented out).
Code: Select all
// Notorious PID Fermentation Temperature Control v 0.5
#include <LiquidCrystal.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Wire.h>
#include <RTClib.h>
#include <PID_v1.h>
#include <SD.h>
#include <SPI.h>
const byte encoderPinA = 2; // rotary encoder right channel **interrupt pin**
const byte encoderPinB = 3; // rotary encoder left channel **interrupt pin**
const byte lcd_d7 = 4; // lcd D7
const byte lcd_d6 = 5; // lcd D6
const byte lcd_d5 = 6; // lcd D5
const byte lcd_d4 = 7; // lcd D4
const byte lcd_enable = 8; // lcd enable
const byte lcd_rs = 9; // lcd RS
const byte chipSelect = 10; // data logging shield
const byte pushButton = A0; // rotary encoder push button
const byte ONE_WIRE_BUS = A1; // one-wire data
const byte relay1 = A2; // relay 1
const byte relay2 = A3; // relay 2
OneWire oneWire(ONE_WIRE_BUS); // Setup a oneWire instance to communicate with any OneWire devices
DallasTemperature sensors(&oneWire); // Pass oneWire reference to Dallas Temperature library
LiquidCrystal lcd(lcd_rs, lcd_enable, lcd_d4, lcd_d5, lcd_d6, lcd_d7); // initialize the liquid crystal library
RTC_DS1307 RTC; //define RTC
volatile char encoderPos = 0; // a counter for the rotary encoder dial (char to conserve memory)
char lastReportedPos = 0; // change management
/*volatile boolean A_set = false;
volatile boolean B_set = false;
static boolean rotating = false;*/
volatile byte programState = 0b0001000; // 7 bit-flag program state -- (encoder A_set)(encoder B_set)(encoder rotating)(PID manual/auto)(PID direct/indirect)(datacapture on/off)(file close/open)
double Setpoint, Input, Output; // SP, PV, CO
PID myPID(&Input, &Output, &Setpoint, 2, 5, 5, DIRECT); //initialize PID
double T0; // temperature probe 1
double T1; // temperature probe 2
File logfile; // datalogging file object
double loghz = 1; // data logging frequency (hz)
unsigned long lastlog = 0; // millis() at last log
double samplehz = 0.25; // sensor sample frequency (hz)
unsigned long lastsample = 0; // millis() at last sample
//unsigned long windowsize = 5*60*1000; //both windowsize and time between PID computations (will compute to be 1/10 process time constant)
unsigned long windowstart = 0;
void setup() {
pinMode(chipSelect, OUTPUT); // select pin i/o
pinMode(encoderPinA, INPUT_PULLUP);
pinMode(encoderPinB, INPUT_PULLUP);
pinMode(pushButton, INPUT_PULLUP);
attachInterrupt(0, doEncoderA, CHANGE); // interrupt 0 (pin 2) triggered by change
attachInterrupt(1, doEncoderB, CHANGE); // interrupt 1 (pin 3) triggered by change
Wire.begin(); // initialize rtc communication
RTC.begin(); // start real time clock
lcd.begin(20, 4); // initialize lcd display
sensors.begin(); // start the dallas temperature library
sensors_update(); // update sensors
Input = T0;
Setpoint = 68; // load default PID settings
if (programState & 0b0001000) myPID.SetMode(AUTOMATIC);
else myPID.SetMode(MANUAL);
if (programState & 0b0000100) myPID.SetControllerDirection(DIRECT);
else myPID.SetControllerDirection(REVERSE);
myPID.SetSampleTime(32767);
myPID.SetOutputLimits(0, 32767);
if (SD.begin(chipSelect)) lcd.print(F("SDCard Init Success")); // verify and initialize SD card
else lcd.print(F("SDCard Failed/Not Present"));
delay(1500);
display_initialize();
}
void loop() {
if ((long)(millis() - lastsample - 1000/samplehz) >= 0) sensors_update(); // update temperature sensors with samplehz frequency (hz)
pid_compute(T0); //call PID indiscriminantly, it will decide wether it is time to update or not
display_update();
if ((programState & 0b0000010) && ((long)(millis() - lastlog - 1000/loghz) >= 0)) data_write(); // if data capture enabled, run data write routine with loghz frequency (hz)
if (!digitalRead(pushButton)) menu(); // load menu on rotary button-press
}
void menu() {
char menu_list [5][20] = {"Manual/Automatic", "Setpoint", "Direct/Reverse", "Data Capture", "BACK"};
char listSize = 5;
boolean exit = 0;
encoderPos = 0;
lastReportedPos = 1;
do {} while (!digitalRead(pushButton)); // wait for user to let go of button
do {
// rotating = true; // reset the debouncer
programState = programState | 0b0010000;
if (lastReportedPos != encoderPos) {
encoderPos = (encoderPos + listSize) % listSize;
lcd.clear();
lcd.print(menu_list[encoderPos]);
lastReportedPos = encoderPos;
}
if (!digitalRead(pushButton)) {
switch (encoderPos) {
case 0: // manual or automatic
do {} while (!digitalRead(pushButton));
listSize = 2;
encoderPos = (programState & 0b1000) >> 3;
lastReportedPos = encoderPos + 1;
do {
//rotating = true; // reset the debouncer
programState = programState | 0b0010000;
if (lastReportedPos != encoderPos) {
encoderPos = (encoderPos + listSize) % listSize;
if (encoderPos) programState = programState | 0b1000;
else programState = programState & 0b0111;
lcd.clear();
lcd.print(F("PID Mode: "));
if (programState & 0b1000) lcd.print(F("Automatic"));
else lcd.print(F("Manual"));
lastReportedPos = encoderPos;
}
} while (digitalRead(pushButton));
encoderPos = 0;
break;
case 1: // change PID setpoint in deg F
do {} while (!digitalRead(pushButton));
listSize = 100;
encoderPos = int(Setpoint);
lastReportedPos = encoderPos + 1;
do { // coarse-grained ajustment (integers)
//rotating = true; // reset the debouncer
programState = programState | 0b0010000;
if (lastReportedPos != encoderPos) {
encoderPos = (encoderPos + listSize) % listSize;
lcd.clear();
lcd.print(F("Setpoint: "));
lcd.print(encoderPos + Setpoint);
lastReportedPos = encoderPos;
}
} while (digitalRead(pushButton));
Setpoint = encoderPos + Setpoint;
encoderPos = lastReportedPos = (Setpoint - int(Setpoint)) * 100;
Setpoint = int(Setpoint);
do {} while (!digitalRead(pushButton));
do { // fine-grained ajustment (hundreths)
//rotating = true; // reset the debouncer
programState = programState | 0b0010000;
if (lastReportedPos != encoderPos) {
encoderPos = (encoderPos + listSize) % listSize;
lcd.clear();
lcd.print(F("Setpoint: "));
lcd.print(Setpoint + double(encoderPos)/100);
lastReportedPos = encoderPos;
}
} while (digitalRead(pushButton));
Setpoint = Setpoint + double(encoderPos)/100;
encoderPos = 1;
break;
case 2: // direct or reverse acting
do {} while (!digitalRead(pushButton));
listSize = 2;
encoderPos = (programState & 0b0100) >> 2;
lastReportedPos = encoderPos + 1;
do {
//rotating = true; // reset the debouncer
programState = programState | 0b0010000;
if (lastReportedPos != encoderPos) {
encoderPos = (encoderPos + listSize) % listSize;
lcd.clear();
lcd.print(F("PID Mode: "));
if (encoderPos) lcd.print(F("Direct"));
else lcd.print(F("Reverse"));
lastReportedPos = encoderPos;
}
} while (digitalRead(pushButton));
if (encoderPos) programState = programState | 0b0100;
else programState = programState & 0b1011;
encoderPos = 2;
break;
case 3: // start & stop data logging operations
do {} while (!digitalRead(pushButton));
listSize = 2;
encoderPos = (programState & 0b0010) >> 1;
lastReportedPos = encoderPos + 1;
do {
//rotating = true; // reset the debouncer
programState = programState | 0b0010000;
if (lastReportedPos != encoderPos) {
encoderPos = (encoderPos + listSize) % listSize;
lcd.clear();
lcd.print(F("Data Capture: "));
if (encoderPos) lcd.print(F("Enabled"));
else lcd.print(F("Disabled"));
lastReportedPos = encoderPos;
}
} while (digitalRead(pushButton));
if (encoderPos) {
if (!(programState & 0b0011)) programState = programState + 3; // start new logfile on menu exit
else if ((programState & 0b0011) == 1) programState = programState & 0b1100 + 2; // cancel pending file close and leave log running
}
else {
if ((programState & 0b0011) == 2) programState = (programState & 0b1100) + 1; // close current logfile on menu exit
else if ((programState & 0b0011) == 3) programState = programState & 0b1100; // cancel pending file opening
}
encoderPos = 3;
break;
case 4:
if ((programState & 0b0011) == 3) { // create a new comma seperated value logfile
char filename[] = "LOGGER00.CSV";
for (int i = 0; i < 100; i++) {
filename[6] = i/10 + '0';
filename[7] = i%10 + '0';
if (!SD.exists(filename)) { // only open a new file if it doesn't exist
logfile = SD.open(filename, FILE_WRITE);
lcd.clear();
if (logfile) lcd.print(filename);
else lcd.print(F("File creation failed!"));
delay(1500);
break;
}
}
logfile.println(F("datetime,ms,PV0,PV1,SP,CO")); //print header to file
logfile.flush();
}
if ((programState & 0b0011) == 1) logfile.close(); // close logfile
programState = programState & 0b1111110; // reset file change flag
exit = 1; // exit the menu loop
break;
}
do {} while (!digitalRead(pushButton));
listSize = 5;
lcd.clear();
lcd.print(menu_list[encoderPos]);
}
} while (!exit);
display_initialize();
}
void data_write() {
sensors_update();
lastlog = millis();
DateTime time = RTC.now();
logfile.print(time.year(), DEC);
logfile.print(F("/"));
logfile.print(time.month(), DEC);
logfile.print(F("/"));
logfile.print(time.day(), DEC);
logfile.print(F(" "));
logfile.print(time.hour(), DEC);
logfile.print(F(":"));
logfile.print(time.minute(), DEC);
logfile.print(F(":"));
logfile.print(time.second(), DEC);
logfile.print(F(","));
logfile.print(lastlog, DEC);
logfile.print(F(","));
logfile.print(T0, DEC);
logfile.print(F(","));
logfile.print(T1, DEC);
logfile.print(F(","));
logfile.print(Setpoint, DEC);
logfile.print(F(","));
logfile.println(Output, DEC);
logfile.flush();
}
void pid_compute(double PV) {
Input = PV;
if (myPID.Compute()) windowstart = millis();
if (!((long)(millis() - windowstart - Output) >= 0)) digitalWrite(relay1, LOW);
else digitalWrite(relay1, HIGH);
}
void sensors_update() {
sensors.requestTemperatures();
lastsample = millis();
T0 = sensors.getTempCByIndex(0) * 1.8 + 32;
T1 = sensors.getTempCByIndex(1) * 1.8 + 32;
}
void display_initialize() {
lcd.clear();
lcd.print(F(" PID v 0.5"));
lcd.setCursor(1, 1);
lcd.print(F("T0="));
lcd.setCursor(1, 2);
lcd.print(F("T1="));
lcd.setCursor(11, 1);
lcd.print(F("SP="));
lcd.setCursor(11, 2);
lcd.print(F("CO="));
}
void display_update() {
lcd.setCursor(12, 0);
if (programState & 0b1000) lcd.print(F("A "));
else lcd.print(F("M "));
if (programState & 0b0100) lcd.print(F("D "));
else lcd.print(F("R "));
if (programState & 0b0010) lcd.print(F("SD"));
lcd.setCursor(4, 1);
lcd.print(T0);
lcd.setCursor(4, 2);
lcd.print(T1);
lcd.setCursor(14, 1);
lcd.print(Setpoint);
lcd.setCursor(14, 2);
lcd.print(int(Output));
DateTime time = RTC.now();
lcd.setCursor(4, 3);
lcd.print((time.month() - (time.month() % 10))/10);
lcd.print(time.month() % 10);
lcd.print(F("."));
lcd.print((time.day() - (time.day() % 10))/10);
lcd.print(time.day() % 10);
lcd.print(F(" "));
lcd.print((time.hour() - (time.hour() % 10))/10);
lcd.print(time.hour() % 10);
lcd.print(F(":"));
lcd.print(time.minute()/10 % 6);
lcd.print(time.minute() % 10);
}
/*void doEncoderA(){
if (rotating) delay(1); // wait a little until the bouncing is done;
if (digitalRead(encoderPinA) != A_set) { // Test transition, did things really change? debounce once more
A_set = !A_set;
if (A_set && !B_set) encoderPos++; // A leads B
rotating = false; // no more debouncing until loop() hits again
}
}
void doEncoderB(){
if (rotating) delay(1);
if (digitalRead(encoderPinB) != B_set) {
B_set = !B_set;
if (B_set && !A_set) encoderPos--; // B leads A
rotating = false;
}
}*/
void doEncoderA(){
if (programState & 0b0010000) delay(1); // wait a little until the bouncing is done;
if (digitalRead(encoderPinA) != ((programState >> 6) & 1)) { // Test transition, did things really change? debounce once more
programState = (programState & 0b0111111) + (!programState & 0b1000000);
if ((programState & 0b1100000) == 64) encoderPos++;
programState = programState & 0b1101111; // no more debouncing until loop() hits again
}
}
void doEncoderB(){
if (programState & 0b0010000) delay(1);
if (digitalRead(encoderPinB) != ((programState >> 5) & 1)) {
programState = (programState & 0b1011111) + (!programState & 0b0100000);
if ((programState & 0b1100000) == 32) encoderPos--;
programState = programState & 0b1101111;
}
}