I’ve been trying to get the Adafruit PM 2.5 sensor to work with my Particle Photon. I used the example code off of Adafruit’s website. The problem is it was written for Arduino IDE. Because I couldn’t use SoftwareSerial, I added the ParticleSoftSerial and SparkIntervalTimer library to the code. However, I kept getting the same error on the line where the buffer for the sensor data is created as an unsigned integer. Apparently Arduino lets you create a buffer as both a char and an unsigned integer but particle does not. I tried making the buffer a char and the code compiled, but when I looked at the data it was blank. Any ideas for how I can make this work on Particle Web IDE?
Here’s the website I got the code from.
And here’s the original code.
// On Leonardo/Micro or others with hardware serial, use those!
// uncomment this line:
// #define pmsSerial Serial1
// For UNO and others without hardware serial, we must use software serial...
// pin #2 is IN from sensor (TX pin on sensor), leave pin #3 disconnected
// comment these two lines if using hardware serial
#include <SoftwareSerial.h>
SoftwareSerial pmsSerial(2, 3);
void setup() {
// our debugging output
Serial.begin(115200);
// sensor baud rate is 9600
pmsSerial.begin(9600);
}
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() {
if (readPMSdata(&pmsSerial)) {
// reading data was successful!
Serial.println();
Serial.println("---------------------------------------");
Serial.println("Concentration Units (standard)");
Serial.print("PM 1.0: "); Serial.print(data.pm10_standard);
Serial.print("\t\tPM 2.5: "); Serial.print(data.pm25_standard);
Serial.print("\t\tPM 10: "); Serial.println(data.pm100_standard);
Serial.println("---------------------------------------");
Serial.println("Concentration Units (environmental)");
Serial.print("PM 1.0: "); Serial.print(data.pm10_env);
Serial.print("\t\tPM 2.5: "); Serial.print(data.pm25_env);
Serial.print("\t\tPM 10: "); Serial.println(data.pm100_env);
Serial.println("---------------------------------------");
Serial.print("Particles > 0.3um / 0.1L air:"); Serial.println(data.particles_03um);
Serial.print("Particles > 0.5um / 0.1L air:"); Serial.println(data.particles_05um);
Serial.print("Particles > 1.0um / 0.1L air:"); Serial.println(data.particles_10um);
Serial.print("Particles > 2.5um / 0.1L air:"); Serial.println(data.particles_25um);
Serial.print("Particles > 5.0um / 0.1L air:"); Serial.println(data.particles_50um);
Serial.print("Particles > 10.0 um / 0.1L air:"); Serial.println(data.particles_100um);
Serial.println("---------------------------------------");
}
}
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) {
s->read();
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];
}
/* debugging
for (uint8_t i=2; i<32; i++) {
Serial.print("0x"); Serial.print(buffer[i], HEX); Serial.print(", ");
}
Serial.println();
*/
// 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;
}
No need to add SparkIntervalTimer explicitly since ParticleSoftSerial has that dependency set internally.
BTW, when you get error messages it's always good to quote them verbatim (with contextual messages like note messages) to allow for explicit advice how to resolve the issue.
That would be a way but whether or not this works as expected depends on the sending device too.
And of course you’d need to setup the interface via Serial1.begin(9600) and wire the correct pins.
Yes, you need to track back till you get some raw data, see where that contradicts your expectation, find out why and how to work around that and then move forward again from there.
Well, I know the buffer does receive data. What is the process for using the start byte to distribute that data into separate variables? Maybe a way without memcpy?
One possible take on that could look like that (not tested, just off the top of my head )
bool readPMSdata(Stream *s) {
int i, sum = 0;
uint8_t buf[sizeof(pms5003data)];
uint8_t* d = &data:
while ((i = s->read()) != 0x42) // wait for start byte (flushing any intermediate byte in the process)
if (i < 0) return false; // on end-of RX buffer bail out
if (s->readBytes(buf, sizeof(buf)) < sizeof(buf))
return false; // when read timed out -> bail out
for (i = 0; i < sizeof(buf); i += 2) {
// swap bytes to adjust endianness (and add up in the process)
sum += (d[i + 1] = buf[i + 0])
+ (d[i + 0] = buf[i + 1]);
}
// for convenience we also added up the last two bytes
sum -= (buf[--i] + buf[--i]); // so we have to subtract checksum bytes again here
if (sum != data.checksum) {
Serial.println("Checksum failure");
return false;
}
return true;
}
Thanks for putting together that code for me! There were some compiling errors so I type casted a few functions. However, the data is still blank.
bool readPMSdata(Stream *s) {
int i, sum = 0;
uint8_t buf[sizeof(pms5003data)];
uint8_t* d = (unsigned char*) &data;
while ((i = s->read()) != 0x42) // wait for start byte (flushing any intermediate byte in the process)
if (i < 0) return false; // on end-of RX buffer bail out
if (s->readBytes((char*)buf, sizeof(buf)) < sizeof(buf)) {
return false; // when read timed out -> bail out
}
for (i = 0; i < sizeof(buf); i += 2) {
// swap bytes to adjust endianness (and add up in the process)
sum += (d[i + 1] = buf[i + 0])
+ (d[i + 0] = buf[i + 1]);
}
// for convenience we also added up the last two bytes
sum -= (buf[--i] + buf[--i]); // so we have to subtract checksum bytes again here
if (sum != data.checksum) {
Serial.println("Checksum failure");
return false;
}
return true;
}
I was also wondering why you had an if statement that waits for sizeof buf to be less than size of buf
if (s->readBytes((char*)buf, sizeof(buf)) < sizeof(buf)) {
Actually that statement doesn't wait to be less than the expected number. The s->readBytes() call itself waits for up to 32 bytes or a timeout condition. The if() statement merely checks what the result of that waiting was.
readBytes() will read up to size(buf) bytes but if doesn't get all the bytes within the timeout period (see StreamClass::setTimeout()) the function tells you how many bytes you actually got. If that number doesn't fit the expected value, you know your read attempt timed out and there will be little use in carrying on with the rest of the function, which you would do without checking the return value.
Then you may want to print out the raw values you actually get and also some intermediate status/debug messages to know where the actual results start to differ from your expectations.