In a series of three posts, I share my road towards a PM sensor system with help of Arduino and the SPS30 Sensirion PM sensor. This second post: storing the data on an SD card.
In the previous post I described how I got the sensor data on my computer. Preferably, measurements are done without continuously having a computer around. I wanted to add local data storage to the sensor system with a memory (micro SD) card.
1 Materials
Next to the setup part 1 materials, I bought the following materials:
- DS3231 Real Time Clock
- SD card module
- Micro SD card (in bulk from Aliexpress much cheaper)
- 10 jumper cables
I found out that for a memory card, it was better to have a relatively small card (< 1GB). Furthermore, after running into battery failure of the real-time clock multiple times, I learned that the DS3231 charging system could destroy non-rechargeable batteries. To resolve this, the charging circuit had to be disabled (for example by removing resistor 201 from the DS3231 shield).
2 Software
Both the real-time clock and the SD card work with libraries that by default are included in the Arduino IDE installation. Additional software was not needed.
3 Connections
I made the following connections between the Aduino Mega and the SD and real-time clock modules:
SD module...Mega
GND.........GND
VCC.........5V
MISO........50
MOSI........51
SCK.........52
CS..........53
DS3231......Mega
GND.........GND
VCC.........5V
SDA.........SDA
SCL.........SCL
I also put the micro SD card in the SD module.
4 Sketch
I used the following sketches for the components:
Example1_sps30_BasicReadings
of the sps30 libraryds3231
of the RTClib librarySoftwareSPI
of the SDfat library
In the below sketch, these are combined.
See sketch
#include <SPI.h>
#include "SdFat.h"
#include <Wire.h>
#include "RTClib.h"
#include "sps30.h"
SPS30 sps30;
SdFat SD;
RTC_DS3231 rtc;
#define SD_CS_PIN SS
#define SP30_COMMS I2C_COMMS
#define DEBUG 0
File dataFile;
String data_filename= "measurement_data";
String month_cov = "";
String dataheader = "counter;datetime;PM1;PM2_5;PM4;PM10;NumPM1;NumPM2_5;NumPM4;NumPM10;PartSize";
String data ="";
String serialdata = "";
struct sps_values val;
String pmString = "";
long counter = 0; // For every restart, a counter is included
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
Serial.print("Initializing RTC module...");
if (! rtc.begin()) {
Serial.println("Couldn't find RTC");
while (1);
}
Serial.print("Initializing SD card...");
if (!SD.begin(SD_CS_PIN)) {
Serial.println("initialization failed!");
return;
}
Serial.println("initialization done.");
// set driver debug level
sps30.EnableDebugging(DEBUG);
// Begin communication channel TO sp30;
if (!sps30.begin(SP30_COMMS))
{
while(1);
}
// check for SPS30 connection
if (!sps30.probe())
{
while(1);
}
// reset SPS30 connection
if (!sps30.reset()){
while(1);
}
// start measurement
sps30.start();
delay(10);
}
void loop()
{
DateTime now = rtc.now();
sps30.GetValues(&val);
long pm1 = static_cast<long>(floor(val.MassPM1+0.5));
long pm2 = static_cast<long>(floor(val.MassPM2+0.5));
long pm4 = static_cast<long>(floor(val.MassPM4+0.5));
long pm10 = static_cast<long>(floor(val.MassPM10+0.5));
long num1 = static_cast<long>(floor(val.NumPM1+0.5));
long num2 = static_cast<long>(floor(val.NumPM2+0.5));
long num4 = static_cast<long>(floor(val.NumPM4+0.5));
long num10 = static_cast<long>(floor(val.NumPM10+0.5));
pmString = String(pm1)+";"+String(pm2)+";"+String(pm4)+";"+String(pm10)+';'+String(num1)+';'+String(num2)+';'+String(num4)+';'+String(num10)+';'+String(val.PartSize);
data = String(String(counter)+';'+now.timestamp(DateTime::TIMESTAMP_FULL))+";"+pmString;
serialdata = String(String(counter)+'\t'+now.timestamp(DateTime::TIMESTAMP_FULL))+'\t'+pmString;
Serial.println(serialdata);
// Decide whether headers need to be included
bool datafile_exists = SD.exists(data_filename);
dataFile = SD.open(data_filename, FILE_WRITE);
// if the file opened okay, write to it:
if (dataFile) {
if(!datafile_exists) {
dataFile.println(dataheader);
}
dataFile.println(data);
// close the file:
dataFile.close();
} else {
// if the file didn't open, print an errors:
Serial.println("error opening datafile");
}
++counter;
delay(8500);
}
5 Operation and next
After uploading the sketch to the Arduino board, it started to collect data and store it on the SD card. I could get the data from the SD card. This is already sufficient for many measurement projects, but online real-time data would be even nicer. Post three: adding LoRa functionality to the sensor system.