Sketch: FeatherFlame

Program the Feather M0 datalogger via Arduino IDE to read and record 1-6 thermocouple probes.

This sketch provides subroutines to complete the following operations with a Feather M0 Adalogger:

  • Read temperature from 6 (K-type) thermocouple probes
  • Collect a timestamp from a precision real-time clock
  • Read inner case temperature from a TMP36
  • Write all data to a microSD card
  • Display timestamp and thermocouple readings to OLED

Requirements

Software

  • Arduino IDE
  • Adafruit boards manager (https://adafruit.github.io/arduino-board-index/package_adafruit_index.json)

Hardware

See the FeatherFlame post

The sketch assumes the wiring is like so:

FeatherFlame fritzing

Arduino sketch

Complete .ino file on GitHub

Walk through the sketch

Libraries

The following libraries contain necessary functions. All are available through the Arduino IDE.

#include <SPI.h>
#include <Wire.h>
#include <SD.h>
#include <Adafruit_MAX31855.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "RTClib.h"

Definitions

First, the OLED screen and the real-time clock:

Adafruit_SSD1306 oled = Adafruit_SSD1306();
RTC_DS3231 rtc;

Define two integer counters and the logging interval (milliseconds):

int StartSec = 0; 
int counter = 0;
int LogInt = 250 ; 

The Feather M0 Adalogger communicates with its microSD card on pin 4:

const int chipSelect = 4;

If monitoring the inner case temperature is desired, the sketch reads a TMP36 on A0:

int TempSense = A0 ; 

Identify the thermocouple inputs.

This sketch is written for 6 (K-type) thermocouples connected to MAX3185 breakout boards. Users can reduce or increase the number of probes as desired.

#define DO   10
#define CLK  12
#define CS1   A1
#define CS2   A2
#define CS3   A3
#define CS4   A4
#define CS5   A5
#define CS6   6

// Set number of thermocouples
int i ; 
int SensNum =7; // n + 1
double temp[7];

Initialize thermocouples. The Adafruit functions will do all of the Seebeck effect maths and return the temperature in degrees C.

Adafruit_MAX31855 thermocouple1(CLK, CS1, DO);
Adafruit_MAX31855 thermocouple2(CLK, CS2, DO);
Adafruit_MAX31855 thermocouple3(CLK, CS3, DO);
Adafruit_MAX31855 thermocouple4(CLK, CS4, DO);
Adafruit_MAX31855 thermocouple5(CLK, CS5, DO);
Adafruit_MAX31855 thermocouple6(CLK, CS6, DO);

Create an object called SixLoggerData for data storage:

struct dataStruct{
  String timestamp ; 
  float tc1 ;
  float tc2;
  float tc3 ;
  float tc4 ;
  float tc5 ;
  float tc6 ;
  float CaseTempC ; 
   }SixLoggerData;

setup

Turn everything on via initialization sub-routines

If testing the system while connected to a computer, one can uncomment while (!Serial); to have the sketch wait until Serial port is ready.

void setup()   {                
 // while (!Serial);  
 Serial.begin(115200);

  oledInit() ;
  rtc.begin();
  RTCcheck();
  SDcheck() ; 
  delay(100);
 
}

loop

For brevity, the sketch calls a series of sub-routines (described below) in this order:

void loop() {

 TimeStamper(); 
 CaseTemp() ; 
 TempStruct6() ;
 TempLog() ;
 oledSixLogger() ; 
 SerialDisplay() ;
 delay(LogInt) ; 
 
}

Initialization subroutines

These subroutines are called in this order in setup().

Initialize the OLED display:

void oledInit (void) {
  oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // I2C addr 0x3C 
  oled.display();
  delay(500);
  oled.clearDisplay();
  oled.display();
}

Check the real-time clock (RTC). In case the RTC has lost power (if the coin battery died or was pulled out), this part of the sketch is supposed to reset to the current time. If you have issues with incorrect timestamps, I have another sketch on GitHub that I use to force the RTC to reset, and then I go back to the FeatherFlame sketch we’re going through here.

void RTCcheck (void) {
  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    while (1);
  }

  if (rtc.lostPower()) {
    Serial.println("RTC lost power. Resetting date/time.");
    // following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    DateTime now = rtc.now();
     Serial.println("Date/time updated. Current date/time from new reference: ");
     Serial.print(now.year(), DEC); Serial.print('-');
     if (now.month() <10) Serial.print('0');
        Serial.print(now.month(), DEC); Serial.print('-');
     if (now.day() <10) Serial.print('0');
        Serial.print(now.day(), DEC); Serial.print(' ');
  Serial.print(now.hour(), DEC); Serial.print(':');
    if (now.minute() <10) Serial.print('0');
      Serial.print(now.minute(), DEC); Serial.print(':');
    if (now.second() <10) Serial.print('0');
  Serial.println(now.second(), DEC);
    delay(1000);
  }
    else {
      //rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
      DateTime now = rtc.now();
     Serial.println("RTC initialized. Using previous time reference.");
     Serial.println("Current date/time:");
     if (now.month() <10) Serial.print('0');
        Serial.print(now.month(), DEC); Serial.print('-');
     if (now.day() <10) Serial.print('0');
        Serial.print(now.day(), DEC); Serial.print('-');
  Serial.print(now.year(), DEC); Serial.print(' ');
  Serial.print(now.hour(), DEC); Serial.print(':');
    if (now.minute() <10) Serial.print('0');
      Serial.print(now.minute(), DEC); Serial.print(':');
    if (now.second() <10) Serial.print('0');
  Serial.println(now.second(), DEC);

    oled.setTextSize(1);
    oled.setTextColor(WHITE);
    oled.setCursor(0,0);
    oled.clearDisplay();
         delay(1);
   oled.println("RTC initialized w/");
   oled.println("previous time ref.");
     oled.println("Current date/time:");
     if (now.month() <10) oled.print('0');
        oled.print(now.month(), DEC); oled.print('-');
     if (now.day() <10) oled.print('0');
        oled.print(now.day(), DEC); oled.print('-');
  oled.print(now.year(), DEC); oled.print(' ');
  oled.print(now.hour(), DEC); oled.print(':');
    if (now.minute() <10) oled.print('0');
      oled.print(now.minute(), DEC); oled.print(':');
    if (now.second() <10) oled.print('0');
  oled.println(now.second(), DEC);
     oled.display();
       delay(3000);
      }
}

Check that the logger is ready to go (microSD card in, file ready). Status is printed to the OLED screen, and serial, if connected to computer.

void SDcheck (void) {
if (!SD.begin(chipSelect)) {
   Serial.println("Card failed, or not present");
   oled.setTextSize(1.5);
    oled.setTextColor(WHITE);
    oled.setCursor(0,0);
    oled.clearDisplay();
         delay(1);
   oled.println("Card failed...");
   oled.println("...or not in?");
       oled.display();
     
 return;
 }

     Serial.println("card initialized.");
        oled.setTextSize(1.5);
    oled.setTextColor(WHITE);
    oled.setCursor(0,0);
    oled.clearDisplay();
         delay(1);
   oled.println("Card initialized.");
       oled.display();
     delay(1000);
  
  File dataFile = SD.open("log.txt", FILE_WRITE);
  if (dataFile) {
  Serial.println("File open. Logging!");
         oled.setTextSize(2);
    oled.setTextColor(WHITE);
    oled.setCursor(0,0);
    oled.clearDisplay();
         delay(1);
   oled.println("Logging!");
       oled.display();
     delay(1000);
  delay(1000);
  }  
  // If the file is not open, return error message
  else {
  Serial.println("No file! Not logging!");
          oled.setTextSize(1.5);
    oled.setTextColor(WHITE);
    oled.setCursor(0,0);
    oled.clearDisplay();
         delay(1);
   oled.println("No file!");
   oled.println("Not logging!");
       oled.display();
  delay(1000);
   return;
  }
}

loop sub-routines

These of course are where the action is. The order of operations is as follows:

  • Build the time stamp
  • Read the inner case temperature
  • Populate the data struct with readings from the thermocouples.
  • Print all three data elements to the microSD card, separated by commas, to enable human-readable .csv format.
  • Display current TC readings on OLED
  • Print to the serial port, too, if connected to computer

Build the time stamp and store in struct:

void TimeStamper (void) {
     DateTime now = rtc.now();
     // This little guy gives unique ID to sub-second samples
         int ThisSec = now.second(); 
              if(StartSec==ThisSec) {
                counter=counter+ 1;
                StartSec=StartSec;
              } else {
                counter=0;
                StartSec=now.second();
              }
   SixLoggerData.timestamp = String(String(now.year(), DEC)+
                                  "-"+String(now.month(), DEC)+
                                  "-"+String(now.day(), DEC)+
                                  " "+String(now.hour(), DEC)+
                                  ":"+String(now.minute(), DEC)+
                                  ":"+String(now.second(), DEC)+
                                  "."+String(counter, DEC));
                                  }

Get the interior case temperature & store to struct as degrees C:

void CaseTemp (void) {
   int InnerTherm = analogRead(TempSense); 
   float voltage = InnerTherm * 3.3;
   voltage /= 1024.0; 
  SixLoggerData.CaseTempC = (voltage - 0.5) * 100 ;  
}

Read thermocouple data, process electric signals into degree C, and write to struct:

void TempStruct6 (void) {
  SixLoggerData.tc1 = thermocouple1.readCelsius();
  SixLoggerData.tc2 = thermocouple2.readCelsius();
  SixLoggerData.tc3 = thermocouple3.readCelsius();
  SixLoggerData.tc4 = thermocouple4.readCelsius();
  SixLoggerData.tc5 = thermocouple5.readCelsius();
  SixLoggerData.tc6 = thermocouple6.readCelsius();
      }

Print the timestamp + current thermocouple temperatures to the OLED display:

void oledSixLogger (void) {

    oled.setTextSize(1);
    oled.setTextColor(WHITE);
    oled.setCursor(0,0);
    oled.clearDisplay();
       delay(1);
       oled.println(SixLoggerData.timestamp); 
       oled.print("1: "); 
          oled.print(SixLoggerData.tc1); 
              oled.print(", "); 
       oled.print("4: "); 
          oled.println(SixLoggerData.tc4); 
       oled.print("2: "); 
          oled.print(SixLoggerData.tc2); 
              oled.print(", "); 
       oled.print("5: "); 
          oled.println(SixLoggerData.tc5); 
       oled.print("3: "); 
          oled.print(SixLoggerData.tc3); 
              oled.print(", "); 
       oled.print("6: ");  
          oled.println(SixLoggerData.tc6); 
   oled.display();
}

And finally, if the Adalogger is connect to the computer, it will print the timestamp + thermocouple values to the serial monitor:

void SerialDisplay (void) {
  Serial.print(SixLoggerData.timestamp); Serial.print(", ");
  Serial.print(SixLoggerData.tc1);   Serial.print(", "); 
  Serial.print(SixLoggerData.tc2);   Serial.print(", "); 
  Serial.print(SixLoggerData.tc3);   Serial.print(", "); 
  Serial.print(SixLoggerData.tc4);   Serial.print(", "); 
  Serial.print(SixLoggerData.tc5);   Serial.print(", "); 
  Serial.print(SixLoggerData.tc6);   Serial.print(", "); 
  Serial.println(SixLoggerData.CaseTempC);
 }
comments powered by Disqus