I'm trying to save the output of an air quality sensor to a SD card, but I'm having a few issues.

The sensor I'm using is a Plantower PMS5003 which puts out its data via serial. I've heavily tweaked the example sketch from Adafruit (here) as I'd like to use the Low Power library to sleep the unit, take a sample and note the time, write it to SD, then return to sleep.

I am getting the timestamp written no issue, but the data is coming out repeated as below:

22:13:10 - 34,55,61
22:13:18 - 34,55,61
22:13:27 - 34,55,61
22:13:36 - 34,55,61
22:13:44 - 34,55,61
22:13:53 - 34,55,61
22:14:1 - 34,55,61
22:14:10 - 34,55,61
22:14:18 - 34,55,61
22:14:27 - 34,55,61
22:14:35 - 34,55,61
22:14:44 - 34,55,61

The 8s timer appears to be working fine, but the repetition of data has me confused. I've tried adding delays and the like in case it was a lack of data but when I run that example sketch it comes screaming in over serial.

I also ditched using the software serial as I couldn't get it to stop messing with the low power library, it wouldn't ever sleep.

That and any general advice on the sketch is appreciated, it seems like its not the best way to accomplish this.

#include "LowPower.h"
#include <SdFat.h>
#include "RTClib.h"
#include <Wire.h>

SdFat SD;
RTC_DS3231 rtc;

File test;
String tString = "";
String sString = "";

void setup() {

  pinMode(10, OUTPUT);

  if (!rtc.begin()) while (1);

  if (!SD.begin(10)) while (1);

struct pms5003data {
  uint16_t framelen;
  uint16_t pm10_standard, pm25_standard, pm100_standard;
  uint16_t pm10_env, pm25_env, pm100_env;
  uint16_t particles_03um, particles_05um, particles_10um, particles_25um, particles_50um, particles_100um;
  uint16_t unused;
  uint16_t checksum;

struct pms5003data data;

void loop() {
  LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);

  DateTime now = rtc.now();
  test = SD.open("test.txt", FILE_WRITE);

  tString = String(now.hour()) + ":" + String(now.minute()) + ":" + String(now.second()) + " - ";

  sString = String(data.pm10_standard) + "," + String(data.pm25_standard) + "," + String(data.pm100_standard);

if (test) {


boolean readPMSdata(Stream *s) {
  if (! s->available()) {
    return false;

  // Read a byte at a time until we get to the special '0x42' start-byte
  if (s->peek() != 0x42) {
    return false;

  // Now read all 32 bytes
  if (s->available() < 32) {
    return false;

  uint8_t buffer[32];
  uint16_t sum = 0;
  s->readBytes(buffer, 32);

  // get checksum ready
  for (uint8_t i = 0; i < 30; i++) {
    sum += buffer[i];

  for (uint8_t i = 2; i < 32; i++) {
    Serial.print("0x"); Serial.print(buffer[i], HEX); Serial.print(", ");

  // The data comes in endian'd, this solves it so it works on all platforms
  uint16_t buffer_u16[15];
  for (uint8_t i = 0; i < 15; i++) {
    buffer_u16[i] = buffer[2 + i * 2 + 1];
    buffer_u16[i] += (buffer[2 + i * 2] << 8);

  // put it into a nice struct :)
  memcpy((void *)&data, (void *)buffer_u16, 30);

  if (sum != data.checksum) {
    Serial.println("Checksum failure");
    return false;
  // success!
  return true;
  • 1
    add Serial output to the question. are you sure that the 3 values you log are changing? – Juraj Aug 20 '18 at 5:40
  • Yeah they can change quite wildly in the basic example sketch if I introduce smoke to the sensor. – B Wulf Aug 20 '18 at 5:48
  • 1
    You don't use the return value to check if the data was valid. If the function readPMSdata returns false, write an error message instead of the wrong data. The normal way is to store every received byte in a buffer instead of waiting for 32 bytes. For 9600 baud with an arduino uno, you can try the AltSoftSerial. Which arduino board do you use? – Jot Aug 20 '18 at 13:11
  • See I had wondered about buffer, and I recall a much different method offered for serial data on the Arduino forum itself in one of the long tutorials. Using a regular 328 arduino at the moment, likely stick with the 328 if I move this away from the arduino. Do have quite a few alternates around however. – B Wulf Aug 21 '18 at 9:30
  • AltSoftSerial was a god send as well, its at least allowing the LowPower library to function as desired during this debug period – B Wulf Aug 25 '18 at 22:47

There is a mismatch in the code.
The Arduino loop function assumes that readPMSdata needs to be called just once for valid data but the function readPMSdata itself assumes that it will be called over and over again until a enough bytes are received and are valid.

The solution is to let the Arduino loop call readPMSdata over and over again, until it has valid data. It returns true when it has valid data, so that can be used in a if statement.
This might cause troubles with the rest of the code in the loop. The delay(250); may no longer be used and I don't know the best place to go into sleep mode. Therefor I removed the powerdown.

void loop() {
  if( readPMSdata(&Serial))        // call it many times to collect data
    // there is valid data from the sensor
    DateTime now = rtc.now();
    test = SD.open("test.txt", FILE_WRITE);

    tString = String(now.hour()) + ":" + String(now.minute()) + ":" + String(now.second()) + " - ";

    sString = String(data.pm10_standard) + "," + String(data.pm25_standard) + "," + String(data.pm100_standard);

    if (test) {
  • This I had not considered at all! I do love Adafruit for what they do, but they leave quite a bit unexplained in the long run. But the moment I read the post my mind started ticking on more appropriate ways to go about this. Thank you! – B Wulf Aug 21 '18 at 9:31
  • So I'm having a bit of trouble figuring this out, how would you recommend looping readPMSdata until I get the data? – B Wulf Aug 25 '18 at 21:35
  • @BWulf, I have added it to my answer. Reading serial input and going to sleep don't go well together. – Jot Aug 26 '18 at 6:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.