For what its worth, this code runs an SD card, OLED, Thermocouple and RTC on a SAMD21 M0 mini. I don't think the SAMD21 is needed but I was learning it so I used it here.
Perhaps you can dissect it and get yours working.
John
/*
Furnace Monitor App (author jrf aka JohnRob)
Writes furnace data to SD Card with every furnace data input change.
Samples at 100ms rate with a DEBOUNCE samples.
see: FurnaceLogNotes.docx/pdf
see Lucidchart for flowcharts.
!!!! Cannot set RTC date or time. Must use "SetDateTime" and the included library !!!!
Revisions:
v0.1 2023-05-21 based on FurnaceApp v14, changed pinout
v0.2 2023-06-11 added run/pause switch.
v0.3 2023-06-24 changes in display formatting, added pause switch, sorted out error signals
v0.5 2023- made array buffer for data to SD.
TODO:
buffer the data going to the SD card to reduce the Number of writes.
EIC Interrupt Control:
EIC->CTRL.bit.ENABLE = 1; and EIC->CTRL.bit.ENABLE = 0;
Target Processor / Board:
SAMD21 M0 mini - a Variant of the Arduino M0 (RobotDyn version)
Hardware:
1) MAX31855k,4 SPI, performs conversion when CS goes high, No library req'd
2) DataLoggerV11 board:
- SD Card, SPI, caution some boards don't tri-state MISO, SDFat library
- DS3132 RTC, No library required.
- OLED 128 x 32 ASCII1306 Library
3) Hand made Isolator board for 24Vac Inputs
*/
#define VERSION "Ver: 0.5"
#define DEBUG
#define bufferSize 128
#include <Wire.h>
#include <SPI.h> // SD Card & TypeK
#include "SdFat.h"
SdFat SD; // keep existing code that used SD but still use the SdFat library
// OLED
#include "SSD1306Ascii.h"
#include "SSD1306AsciiWire.h"
#include "BoilerApp.h"
#include "oledMap.h" // no code, just display information
// *** declare Functions Prototypes ********************************************
// ***************************************************************************
void init_pins_int(void);
bool initSD_Card(void);
bool init_RTC(void);
bool init_TypeK(void);
void init_OLED(void);
void ConvertSectoHour(int32_t n);
void WriteDataSD(void);
bool WriteFileHeader(void);
uint16_t ReadRawTemperature(void);
// *** declare Objects *******************************************************
// ***************************************************************************
File myFile; // create instance of a "File" class for SD Card
SSD1306AsciiWire oled; // create an "oled" object
// *** declare gVariables ****************************************************
// ***************************************************************************
uint16_t hour;
uint8_t minute;
uint8_t second;
byte ourInputs[] = { BurnerPin, Level1Pin, Level2Pin, Level3Pin, Level4Pin };
#define NUMINPUTS sizeof(ourInputs)
uint8_t Data[NUMINPUTS + 2], prevData1[NUMINPUTS + 2], prevData2[NUMINPUTS + 2];
uint8_t tempData;
bool _BufferData = false; // goes true on input change resulting in the data written to SD
// Δ seconds, Temp °C, Burner, Level 1, Level 2, Level 3, Level 4
uint32_t _ΔSecBuffer[bufferSize];
float _tempBuffer[bufferSize];
bool _BurnerBuffer[bufferSize];
bool _Level1_Buffer[bufferSize];
bool _Level2_Buffer[bufferSize];
bool _Level3_Buffer[bufferSize];
bool _Level4_Buffer[bufferSize];
bool gSys_error = false;
bool gSD_error = false;
bool gRTC_error = false;
bool gTC_error = false;
// *** Setup ****************************************************************
// ***************************************************************************
void setup() {
SerialUSB.begin(115200);
while (!SerialUSB) {}
delay(2000);
SerialUSB.println(".....starting ");
SerialUSB.print(" number of inputs defined = ");
SerialUSB.println(NUMINPUTS);
// start I2C...
Wire.begin();
Wire.setClock(100000UL);
SPI.begin(); // not sure if this is needed for SD card?
// *** init Devices <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
init_pins_int();
init_OLED();
gTC_error = init_TypeK();
gSD_error = initSD_Card();
SerialUSB.println(gDataFile);
gRTC_error = init_RTC();
WriteFileHeader();
oled.print(VERSION);
oled.setCursor(0 * 8, 4);
if (gRTC_error) {
oled.print("RTC Error");
delay(20000);
} else {
oled.print(" setup complete");
delay(5000);
}
} //--- setup ---
uint32_t oldMillis = millis(); // aka "unsigned long"
// ***************************************************************************
// *** MAIN loop *************************************************************
// ***************************************************************************
void loop() {
while (digitalRead(Run_Pause)) {
oled.println(" paused ");
delay(2000);
}
if (millis() >= oldMillis + 100) {
for (uint8_t i = 0; i <= NUMINPUTS - 1; i = i + 1) {
// -----------------------------------------see end of h file
Data[i] = digitalRead(ourInputs[i]);
tempData = prevData2[i] << 2 | prevData1[i] << 1 | Data[i];
prevData2[i] = prevData1[i];
prevData1[i] = Data[i];
if ((tempData == 3) | (tempData == 4)) { _BufferData = true; }
} // --- for ---
// =======================================================================
// ===== log data here
// =======================================================================
if (_BufferData) {
WriteDataSD();
_BufferData = false;
++logCount;
oled.SSD1306Ascii::setCursor(10 * 8, 2);
oled.clearToEOL();
oled.print(logCount);
}
bool _BufferData = false; // goes true on input change resulting in the data written to SD
// Δ seconds, Temp °C, Burner, Level 1, Level 2, Level 3, Level 4
uint32_t _ΔSecBuffer[bufferSize];
float _tempBuffer[bufferSize];
bool _BurnerBuffer[bufferSize];
bool _Level1_Buffer[bufferSize];
bool _Level2_Buffer[bufferSize];
bool _Level3_Buffer[bufferSize];
bool _Level4_Buffer[bufferSize];
// "Δ seconds, Temp °C, Burner, Level 1, Level 2, Level 3, Level 4"
// uint32_t , float, bool, bool, bool,bool, bool
// =======================================================================
// =====
// =======================================================================
oldMillis = millis();
} // -- 100 millis --
if (gDisplaySeconds >= displayUpdate) {
if (gSD_error | gRTC_error | gTC_error) {
gSys_error = true;
}
else{
gSys_error = false;
}
UpdateDisplay();
gDisplaySeconds = 0;
}
} // --- loop
// ************************************************************
// *** interrupt routine(s) *************************************
// ************************************************************
void ispSeconds() {
++SQWseconds;
++gDisplaySeconds;
}
// **********************************************************************
// *** functions ********************************************************
// **********************************************************************
// RTC communication ------------------------------------------------------------------
uint8_t I2C_readByte(const uint8_t addr) {
uint8_t data;
Wire.beginTransmission(RTC_ADDRESS);
Wire.write(addr);
Wire.requestFrom(RTC_ADDRESS, (uint8_t)1);
data = Wire.read();
RTC_Error = Wire.endTransmission(); //returned status; 0 = success
return data;
} // readByte()
//--------------------------------------------------------------------------------------
void I2C_writeByte(const uint8_t addr, const uint8_t data) {
Wire.beginTransmission(RTC_ADDRESS);
Wire.write(addr);
Wire.write(data);
RTC_Error = Wire.endTransmission();
} // writeByte()
//--------------------------------------------------------------------------------------
void UpdateDisplay(void) {
// Display1 line
oled.home();
oled.print("run h:m ");
oled.clearToEOL();
ConvertSectoHour(SQWseconds);
oled.print(hour);
oled.print(":");
if (minute < 10) oled.print("0");
oled.print(minute);
//oled.print(":");
//if (second < 10) oled.print("0");
//oled.print(second);
// Display1 line 2
oled.setCursor(0 * 8, 2);
oled.print("# rdgs ");
// Display1 line 3
oled.setCursor(0 * 8, 4);
oled.print("Temp ");
oled.clearToEOL();
uint16_t RTemp;
RTemp = (ReadTemperature() >> 2) * 0.25;
oled.print(RTemp);
oled.print(" $C");
// Display1 line 4
oled.setCursor(0 * 8, 6);
oled.print("Status:");
if (gSD_error | gRTC_error | gTC_error) {
oled.print(" Error");
} else {
oled.print("No Error");
}
} // --- end of Display1 ---
// void clearField (uint8_t col, uint8_t row, uint8_t n)
// void clearToEOL ()
//--------------------------------------------------------------------------------------
uint16_t ReadTemperature(void) {
uint16_t gRawData = 0;
digitalWrite(TC_CSn, LOW); //stop measurement/conversion
delayMicroseconds(10);
digitalWrite(TC_CSn, HIGH); //start measurement/conversion
delay(200);
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0)); // ?? see .h line 35
digitalWrite(TC_CSn, LOW); // because the SD and T/C SPI's operate at different speeds, we must not assert
// CS\ until after the SPI.beginTransaction. This allows time to change the SPI
// clock before attempting to read data.
// read MSB only:
gRawData = SPI.transfer16(0x0000); // TypeK is read only, doesn't matter what is sent in SPI.transfer16(0x000)
digitalWrite(TC_CSn, HIGH); // de-select and start a new conversion to be read the next time through
SPI.endTransaction();
SerialUSB.print("gRawData= ");
SerialUSB.println(gRawData); // for debug only
gTC_error = false;
if (gRawData & 0b01) {
gTC_error = true;
}
return gRawData;
}
//--------------------------------------------------------------------------------------
void WriteDataSD(void) {
#ifdef DEBUG
SerialUSB.print(Data[0]);
SerialUSB.print(", ");
SerialUSB.print(Data[1]);
SerialUSB.print(", ");
SerialUSB.print(Data[2]);
SerialUSB.print(", ");
SerialUSB.print(Data[3]);
SerialUSB.print(", ");
SerialUSB.print(Data[4]);
SerialUSB.print(", ");
SerialUSB.println();
#endif
myFile = SD.open(gDataFile, FILE_WRITE);
SerialUSB.print(" myfile results= ");
SerialUSB.println(myFile);
if (myFile) {
myFile.print(SQWseconds);
myFile.print(", ");
myFile.print((ReadTemperature() >> 2) * 0.25);
myFile.print(", ");
//SerialUSB.println("writing to SD");
myFile.print(Data[0]);
myFile.print(", "); // Burner
myFile.print(Data[1]);
myFile.print(", "); // Level 1
myFile.print(Data[2]);
myFile.print(", "); // Lavel 2
myFile.print(Data[3]);
myFile.print(", "); // Level 3
myFile.print(Data[4]);
myFile.print(", "); // Level 4
myFile.println("end of data");
myFile.close();
} // if (myFile), Loop time was: 18 ~ 19 ms with a few at 25 ms
else {
SD_Error = true; // --- if (myFile) else
}
} // WriteDataSD
//--------------------------------------------------------------------------------------
// function convert second into hour minutes seconds
//--------------------------------------------------------------------------------------
void ConvertSectoHour(uint32_t n) {
hour = n / 3600;
n = n % 3600;
minute = n / 60;
n %= 60;
second = n;
}
// --- Toggle LED --------------------------------------------
//digitalWrite(ledPin,!digitalRead(ledPin));
// ************************************************************
// *** initialization functions *******************************
// ************************************************************
void init_pins_int(void) {
// Setup CS pins first....
pinMode(TC_CSn, HIGH); // enables pull-up immediately keeping pin high
digitalWrite(TC_CSn, HIGH);
pinMode(SD_CSn, HIGH);
digitalWrite(SD_CSn, HIGH);
pinMode(SS, OUTPUT); // not sure this is required.
// Note even if you use a different chip select pin, the hardware SS pin must be kept as an output
// or the SD library functions will not work.
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, 0);
pinMode(Level4Pin, INPUT_PULLUP);
pinMode(Level3Pin, INPUT_PULLUP);
pinMode(Level2Pin, INPUT_PULLUP);
pinMode(Level1Pin, INPUT_PULLUP);
pinMode(BurnerPin, INPUT_PULLUP);
pinMode(intSQWPin, INPUT);
attachInterrupt(digitalPinToInterrupt(intSQWPin), ispSeconds, RISING);
pinMode(Run_Pause, INPUT_PULLUP);
return;
}
//--- OLED Initialization ------------------------------------------------------------------------
void init_OLED() {
oled.begin(&SH1106_128x64, OLEDADDRESS);
oled.setFont(ZevvPeep8x16); // Screen = 4 (0,2,4,6) lines X 16 characters (0 to 15)
oled.clear();
}
// --- SD Card initialization function ---
bool initSD_Card(void) {
bool error = false;
if (!SD.begin(SD_CSn)) {
error = true; //returns 1 on success, 0 on failure.
}
if (digitalRead(SD_Detectn)) {
oled.setCursor(0 * 8, 4);
oled.print(" no SC Card installed");
}
// loop until we find a file that doesn't already exist.......
do {
itoa(gFileNumb, gDataFile, 10); // (value, Array, base)
const char *extension = ".csv";
strcat(gDataFile, extension); // syntax: strcat(dest, source)
++gFileNumb;
} while (SD.exists(gDataFile)); // assume will Rtn false if no communication.
myFile = SD.open(gDataFile, FILE_WRITE);
if (myFile) { // if the file opened okay, write to it:
myFile.print(" init...");
//myFile.println(gDataFile);
myFile.close();
} else {
error = true; // if the file didn't open, print an error:
}
return error;
} // --- initSD_Card ---
//--------------------------------------------------------------------------------------
bool init_RTC(void) {
bool error;
gReg0x0F = I2C_readByte(RTC_0F); // (address, data)
if (gReg0x0F & 0b10000000) { // test OSF bit
error = true;
} else {
I2C_writeByte(RTC_0E, 0x0);
I2C_writeByte(RTC_0F, 0x0);
error = false;
}
return error;
} // --- init_RTC ---
//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
bool init_TypeK(void) {
// doesn't really initialize, simply reads the error bit
int16_t gRawData = 0;
bool error = false;
//digitalWrite(TC_CSn, LOW);
//delayMicroseconds(10); // spec is 1µs
digitalWrite(TC_CSn, HIGH);
//delay(1);
// read the MSB only (i.e. D31 - D16)
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0)); // ??? see .h line35
digitalWrite(TC_CSn, LOW); // because the SD and T/C SPI's operate at different speeds, we must not assert
// CS\ until after the SPI.beginTransaction. This allows time to change the SPI
// clock before attempting to read data.
gRawData = SPI.transfer16(0x0000); // TypeK is read only, doesn't matter what is sent in SPI.transfer16(0x000)
digitalWrite(TC_CSn, HIGH); // deselect
SPI.endTransaction();
if (gRawData & 0b0001) {
error = true;
} // in the MSB, the fault bit (D16) is availiable
return error;
}
//--------------------------------------------------------------------------------------
bool WriteFileHeader() {
bool error = false;
Wire.beginTransmission(RTC_ADDRESS);
Wire.write(0x00); // This is the first register address (Seconds)
// ... read from here 7 uint8_t's
Wire.endTransmission(false);
Wire.requestFrom(RTC_ADDRESS, 7);
uint8_t ss = bcd2bin(Wire.read() & 0x7F); // why mask 8th bit here?
uint8_t mm = bcd2bin(Wire.read());
uint8_t hh = bcd2bin(Wire.read());
Wire.read(); // day of week, not used
uint8_t d = bcd2bin(Wire.read());
uint8_t m = bcd2bin(Wire.read());
uint16_t y = bcd2bin(Wire.read()) + 2000;
myFile = SD.open(gDataFile, FILE_WRITE);
if (myFile) {
myFile.print("DataFile Name: ");
myFile.println(gDataFile);
myFile.println();
myFile.print("Firmware Version: ");
myFile.println(VERSION);
myFile.println();
myFile.print("Creation Date - Time: ");
myFile.print(y);
myFile.print("-");
myFile.print(m);
myFile.print("-");
myFile.print(d);
myFile.print(" ");
myFile.print(hh);
myFile.print(":");
myFile.print(mm);
myFile.print(":");
myFile.println(ss);
myFile.println();
myFile.println("Δ seconds, Temp °C, Burner, Level 1, Level 2, Level 3, Level 4");
myFile.close();
// 0394 hex for delta symbol in excel
} else {
error = true;
}
return error;
}
// --- end of code ---
/*
Abbreviated function explanation:
Goal is to log every furnace change with time, temperature and input states.
Time is in elapsed seconds since startup. On startup, the full time an date is written to
SD file becoming the zero seconds reference. All data is then recorded as seconds from this
time - date.
Furnace inputs are read every 100 ms (time could may changed). On reading, a count is initialized
for any changed input. If an input is still shown as changed after a 2 count of samples, the program
accepts this change as valid and logs the data to the SD card. Now that we are reading the thermostat
output, we might change this to a 20ms delay and reread.
Note 1: Since we only want Temperature from the MAX31855k we only need to Read
the MSB which contains the temperature and a failure indication.
Note 2: TypeK is read every <displayUpdate> seconds. The conversion is actually immediately
after the temperature is read. So <gTK_RawTemperature> is <displayUpdate> seconds old.
Note 3: OLED Display:
Health status of:
TypeK - Status from "beginTransmission" ~line 62, 182
RTC and RTC current time (i.e. ofs): ~lines 149, 159, 295
SD Card @lines 109, 128
Temperature
# of changes written to SD
HHH:MM:SS
OLED Line 1:
1234567890123456
ttt°C,Rdg ######
OLED Line 2:
1234567890123456
SRT000 HHH:MM:SS
*/
// --- eof ---