do i need more power? or is my code inefficient?

For Adafruit customers who seek help with microcontrollers

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
mboehm
 
Posts: 9
Joined: Fri Jun 06, 2014 12:46 pm

do i need more power? or is my code inefficient?

Post by mboehm »

Hello. I am attempting to build and program a PID temperature control device with data logging capabilities for my fermentation chamber. The project uses a 20x4 LCD character screen, the adafruit SD data logging shield, a rotary encoder with push-button for user input, a pair of temperature probes using the dallas one-wire protocol, and a third-party solenoid relay board. The program allows the user to enter various PID control states as well as enable and disable data logging. I still wish to implement tuning-parameter scheduling, simple comma separated temperature programs that may be loaded from the SD card, as well as a second instance of the PID to control a heating element, and possibly some other things.

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;
  }
}

User avatar
phild13
 
Posts: 247
Joined: Mon Sep 10, 2012 1:05 pm

Re: do i need more power? or is my code inefficient?

Post by phild13 »

This is not directly answering the question posed, but I thought I would throw these ideas out there. The may be helpful.

Have you looked at the osPID controller?
http://www.ospid.com/blog/
It was designed for just the task you mentioned. I don't know if data logging is part of the equation.but it might be fairly easy to add it.
Besides the kits they sell, the circuits, software, etc are all available for download to roll your own. Though it is probably a bit cheaper to buy a kit, I was thinking about doing one from scratch using their downloads for fun.

There is also an arduino uno based solder station controller based on the osPID and it's software that is open source. It seems to be much like what you describe your to be. A look at it's hardware/software may give you more of an idea how to write yours.

This is the link from the Dangerous prototypes page with the links to the project pages
http://dangerousprototypes.com/forum/vi ... 139#p56413
More direct link, but sometimes the page links to the other sections of the project don't work correct
http://smokedprojects.blogspot.com/2013 ... dware.html

User avatar
adafruit_support_mike
 
Posts: 67446
Joined: Thu Feb 11, 2010 2:51 pm

Re: do i need more power? or is my code inefficient?

Post by adafruit_support_mike »

We also have a PID project in the Learning System you can use for comparison:

https://learn.adafruit.com/sous-vide-po ... /sous-vide

and this tutorial has some sample code you can use to measure your memory footprint:

https://learn.adafruit.com/memories-of- ... ree-memory

It looks like you've already used the biggest easy space-saver by moving your strings to program memory with the F() macro, and you do seem to have several devices working togehter. Depending on what the free memory reporting function says, you may need the extra processing resources of a Mega.

User avatar
adafruit_support_bill
 
Posts: 88088
Joined: Sat Feb 07, 2009 10:11 am

Re: do i need more power? or is my code inefficient?

Post by adafruit_support_bill »

If you are really close, a Leonardo might even be sufficient with an extra 512 bytes of SRAM.

User avatar
phild13
 
Posts: 247
Joined: Mon Sep 10, 2012 1:05 pm

Re: do i need more power? or is my code inefficient?

Post by phild13 »

Ah I forgot all about the Adafruit pid project. Thanks Mike for the reminder. The other projects were fresh on my mind as I was looking for something to build based on SMD parts, that would be useful to me. I thought they might give some ideas on ways to implement or handle things..

User avatar
Renate
 
Posts: 291
Joined: Tue Nov 13, 2012 3:21 pm

Re: do i need more power? or is my code inefficient?

Post by Renate »

Determine how much flash and RAM each of those libraries are taking.
I'll bet that that LCD library is pretty big.
Having the interface pins configurable individually must use a bit of space.
Just rewrite the library so that it uses fixed, contiguous pins for the data bus.

I don't know where you are using all the RAM
Look at the elf binary and check on .data vs .bss

(Just my BANNED: any program that uses delay() is ugly.)

Locked
Please be positive and constructive with your questions and comments.

Return to “Microcontrollers”