Dust Sensor - PMS 5003/6003/7003

I ordered the PMS7003 the other day via eBay (I tried to order it via AliExpress but the amount of personal information they wanted to “verify” me made me pause). Will let you know when I get it and have had time to try it out.

Hopefully the smaller size doesn’t impact quality.

@peekay123 @ScruffR
Need help making this device work with Photon! Its the PMS5003 G5!
Spec sheet here: http://www.one-xin.cn/uploads/soft/160121/PMS5003(G5).pdf

The above (first) code should work as is on the Photon too - just replace #include <Arduino.h> with #include "Particle.h" (and some corrections in data types, but that should be easy by reading the error messages - change unsigned char for char and Serial.find(0x42) for Serial.find("B")).
And of course Serial needs to be replaced for Serial1 for the communication with the sensor.
But how does Serial work on the Arduino to talk to both - the sensor and the serial monitor?

Some bits of that code could be written “nicer”, but in general it should do as is.

In your second code I can’t see any attempt of yours to send any of your data to Blynk.
How would you expect should the Blynk lib know what variables you want to upload? :confused:

BTW: I can’t read Chinese, so the given link to the datasheet is no good for me - can you read it? Or have you not even had a look at it?

Ok…
Will use #include "Particle.h"
Will use Serial1.find("B") in place of Serial.find(0x42)
Then, should I be using something else in place of buf[0] == 0x4d

Arduino Serial works without issues. But I had to disconnect TX/RX of the sensor to be able to upload/modify code to Arduino. After upload is done, I connect the TX/RX back, and it works!

My bad! I removed the

Blynk.virtualWrite(V11, PM01Value);
Blynk.virtualWrite(V12, PM2_5Value);
Blynk.virtualWrite(V13, PM10Value);

while trying to isolate an issue where the concentration value not getting updated to Serial Monitor… When I added the Blynk part, Blynk didn’t update values and also Serial Monitor stopped updating concentration value after just 2 cycles. So I removed the virtualWrite part to debug the issue. An posted here at that moment… Now I doubt if this is the Serial communication issue you mentioned earlier… “How Arduino talk to both - the sensor and the serial monitor?”

Of course I did and that’s how I confirmed all the wiring… Other than that, I didn’t understand a thing about the byte ordering and all that. I don’t understand Chinese. I use Google Translate…

I could browse and add any document to translate. There’s no option to save the translated doc, otherwise I would’ve provided that. So I had to attach the untranslated version, I could’ve taken a screenshot and attached it as a picture though… But the translated doc is just all text and no images. We need to correlate the docs before and after translation to get an idea.

I was under the assumption that you guys must have come across lots of Chinese spec sheets and have been translating them. Sorry!

I found a datasheet
https://github.com/avaldebe/AQmon/blob/master/Documents/PMS5003_LOGOELE.pdf

Getting values:
PM1.0: 12 ug/m3
PM2.5: 15 ug/m3
PM1 0: 18 ug/m3

With this code:

SYSTEM_THREAD(ENABLED);
//******************************
 //*Abstract: Read value of PM1,PM2.5 and PM10 of air quality
 //
 //*Version:V3.1
 //*Author:Zuyang @ HUST
 //*Modified by Cain for Arduino Hardware Serial port compatibility
 //*Date:March.25.2016
 //******************************
#include <Particle.h>
#define LENG 31   //0x42 + 31 bytes equal to 32 bytes
char buf[LENG];
 
int PM01Value=0;          //define PM1.0 value of the air detector module
int PM2_5Value=0;         //define PM2.5 value of the air detector module
int PM10Value=0;         //define PM10 value of the air detector module
 
 
void setup()
{
  Serial1.begin(9600);   //use serial0
//   Serial1.setTimeout(1500);    //set the Timeout to 1500ms, longer than the data transmission periodic time of the sensor
    Serial.begin(57600);
}
 
void loop()
{
  if(Serial1.find("B")){    //start to read when detect 0x42
    Serial1.readBytes(buf,LENG);
 
    if(buf[0] == 0x4d){
      if(checkValue(buf,LENG)){
        PM01Value=transmitPM01(buf); //count PM1.0 value of the air detector module
        PM2_5Value=transmitPM2_5(buf);//count PM2.5 value of the air detector module
        PM10Value=transmitPM10(buf); //count PM10 value of the air detector module 
      }
    }
  }
 
  static unsigned long OledTimer=millis();
    if (millis() - OledTimer >=1000)
    {
      OledTimer=millis();
      
      Serial.print("PM1.0: ");
      Serial.print(PM01Value);
      Serial.println("  ug/m3");
      
      Serial.print("PM2.5: ");
      Serial.print(PM2_5Value);
      Serial.println("  ug/m3");
      
      Serial.print("PM1 0: ");  
      Serial.print(PM10Value);
      Serial.println("  ug/m3");   
      Serial.println();
    }

}
char checkValue(char *thebuf, char leng)
{
  char receiveflag=0;
  int receiveSum=0;
 
  for(int i=0; i<(leng-2); i++){
  receiveSum=receiveSum+thebuf[i];
  }
  receiveSum=receiveSum + 0x42;
  
  if(receiveSum == ((thebuf[leng-2]<<8)+thebuf[leng-1]))  //check the serial data 
  {
    receiveSum = 0;
    receiveflag = 1;
  }
  return receiveflag;
}
 
int transmitPM01(char *thebuf)
{
  int PM01Val;
  PM01Val=((thebuf[3]<<8) + thebuf[4]); //count PM1.0 value of the air detector module
  return PM01Val;
}
 
//transmit PM Value to PC
int transmitPM2_5(char *thebuf)
{
  int PM2_5Val;
  PM2_5Val=((thebuf[5]<<8) + thebuf[6]);//count PM2.5 value of the air detector module
  return PM2_5Val;
  }
 
//transmit PM Value to PC
int transmitPM10(char *thebuf)
{
  int PM10Val;
  PM10Val=((thebuf[7]<<8) + thebuf[8]); //count PM10 value of the air detector module  
  return PM10Val;
}

I’m using 2 serials at 9600 and 57600. And getting values from Serial Monitor using both baud rates. But if I decide to use just one, I’m not getting any values… Is the Serial part of the code efficient? Or can I improve the code? Also I’ll be trying to make this work with SparkCorePolledTimer… And then on to power saving operation. And then BLYNK :smiley:

Serial and Serial1 are completely different interfaces - one hasn’t got anything to do with the other!
While Serial (the only one that can be seen on Serial Monitor) communicates via the USB port and a Virtual COM protocol (hence you can actually use any baud rate - the speed will always be the same USB speed), Serial1 only communicates via the RX/TX pins and there the baud rate has to match the baud rate of your connected device.

I’d write this

      
      Serial.print("PM1.0: ");
      Serial.print(PM01Value);
      Serial.println("  ug/m3");
      
      Serial.print("PM2.5: ");
      Serial.print(PM2_5Value);
      Serial.println("  ug/m3");
      
      Serial.print("PM1 0: ");  
      Serial.print(PM10Value);
      Serial.println("  ug/m3");   
      Serial.println();

rather like this

  Serial.printlnf("PM  1.0: %5d µg/m³\r\n"
                  "PM  2.5: %5d µg/m³\r\n"
                  "PM 10.0: %5d µg/m³\r\n"
                  ,PM01Value
                  ,PM2_5Value
                  ,PM10Value);

Why not use the native Software Timers?

This part of the code in loop() function is blocking other functions, but gives proper dust values…

    if(Serial1.find("B")) {    //start to read when detect 0x42
        Serial1.readBytes(buf,LENG);
        if(buf[0] == 0x4d) {
            if(checkValue(buf,LENG)) {
                PM01Value=transmitPM01(buf); //count PM1.0 value of the air detector module
                PM2_5Value=transmitPM2_5(buf);//count PM2.5 value of the air detector module
                PM10Value=transmitPM10(buf); //count PM10 value of the air detector module 
            }
        }
    }

Dust values update every 2 seconds as intended!

Tried moving this code to a timer function that executes every 2 seconds and the dust values don’t update and giving me:
PM1.0: 12 ug/m3
PM2.5: 15 ug/m3
PM1 0: 18 ug/m3
like forever!

Can anyone suggest a solution?

I think Serial1 is not a good citizen for software timers (or ISRs), and particlularly not find() and readBytes() as they are blocking for up to timeout (default 1000) milliseconds.
So you best keep that in loop() (or wrap it in a function which is called from loop), or you replace the blocking calls (especially find) with a non-blocking solution.
If you want other stuff to happen in your code, this would be required and is better practice anyway.

You could try SerialEvent1() tho’

Moved the blocking part of the code into SerialEvent1:

void serialEvent1() {
        if(Serial1.find("B")) {    //start to read when detect 0x42
        Serial1.readBytes(buf,LENG);
        if(buf[0] == 0x4d) {
            if(checkValue(buf,LENG)) {
                PM01Value=transmitPM01(buf); //count PM1.0 value of the air detector module
                PM2_5Value=transmitPM2_5(buf);//count PM2.5 value of the air detector module
                PM10Value=transmitPM10(buf); //count PM10 value of the air detector module 
            }
        }
    }
}

I’ve set dust values to update to Blynk every 5 seconds. And, there’s a 1 second timer that executes every second precisely. The find() and readbytes() doesn’t seem to block now… Is this how its supposed to work?
What could have solved the issue?

The issue was that you are not allowed to block shared resources (like Serial1) in a software timer.
Moving that stuff into serialEvent1() isn’t really anything other than having the same code inside loop() since these “events” are no real events, but just a synchronous function that’s called between iterations of loop().

But this will still have impact on the execution speed of loop() whereas this (untested) code would be non-blocking

char buf[31];
int  bufIndex = -1;  // current index in message buffer (-1 indicates not started)

void SerialEvent1()
{
    int b = 0;
    
    // when not already started a message, flush everything up to first 'B'
    while (bufIndex < 0 && (b = Serial1.read()) >= 0 && b != 'B') Particle.process();  

    // if we got a start byte start a new message
    if ('B' == b) bufIndex = 0;  // had en error here, hence the following discussion (if (`B` = b) caused the problem)  

    // fill all available bytes into open message buffer
    while (0 <= bufIndex && bufIndex < sizeof(buf) && Serial1.available())
    {
        buf[bufIndex++] = Serial1.read();
        Particle.process();
    }
    
    if (bufIndex == sizeof(buf))
    {
        if(buf[0] == 0x4d && checkValue(buf,LENG)) 
        {
            PM01Value=transmitPM01(buf); //count PM1.0 value of the air detector module
            PM2_5Value=transmitPM2_5(buf);//count PM2.5 value of the air detector module
            PM10Value=transmitPM10(buf); //count PM10 value of the air detector module 
        }
        bufIndex = -1;  // mark as treated and ready for next message
    }
}

The reason why software timers are not impacted by a blocking app code is that these are handled by RTOS, which executes them “asynchronously” to the main application.

If you add a D7 blink code like digitalWrite(D7, (millis() >> 2) & 0x88); to loop() you should see frequent glitches in the pattern when your code blocks, but with the above, these glitches should be less noticeable.

Non-blocking code, gave these values initially:
PM1.0: 15 ug/m3
PM2.5: 20 ug/m3
PM1 0: 22 ug/m3
and these values remain forever. Would be nice if this works…

Will attempt to debug when I’m back from work…

This code:

void serialEvent1() {
        if(Serial1.find("B")) {    //start to read when detect 0x42
        Serial1.readBytes(buf,LENG);
        if(buf[0] == 0x4d) {
            if(checkValue(buf,LENG)) {
                PM01Value=transmitPM01(buf); //count PM1.0 value of the air detector module
                PM2_5Value=transmitPM2_5(buf);//count PM2.5 value of the air detector module
                PM10Value=transmitPM10(buf); //count PM10 value of the air detector module 
            }
        }
    }
}

it updates nicely. But as you said, I think it has an impact on the loop()… It didn’t completely hang or crash, but there was occasional WiFi disconnects with blinking cyan… Have to study more into this…

With suggested non-blocking code:

if (bufIndex == sizeof(buf))
    {
        if(buf[0] == 0x4d && checkValue(buf,LENG)) 
        {
            PM01Value=transmitPM01(buf); //count PM1.0 value of the air detector module
            PM2_5Value=transmitPM2_5(buf);//count PM2.5 value of the air detector module
            PM10Value=transmitPM10(buf); //count PM10 value of the air detector module 
        }
        bufIndex = -1;  // mark as treated and ready for next message
    }

Only once, the first time:
if (bufIndex == sizeof(buf)) came true
if(buf[0] == 0x4d) came true
Never again!

Could you determine the reason?

  • Was it that the buffer never got filled again, or
  • was next byte after B never an M (=0x4d)
  • what bytes did you get comming in after the first successful message?

Adding some extra Serial.print() statements to see the behaviour of variables (e.g. bufIndex, buf[] and the data from Serial1) would be the first step to debug the cause.
I have no such sensor, so I can’t really test anything.

If you can expect full messages to come back to back - without garbage in between - you could try setting bufIndex = 0; instead of -1 after a successful read.

will try this…

When bufIndex = -1;
if(bufIndex == sizeof(buf)); came true once

Have set bufIndex = 0;
if(bufIndex == sizeof(buf)) came true twice

With this code:

void serialEvent1()
{
    int b = 0;
    
    // when not already started a message, flush everything up to first 'B'
    // Serial.print("Serial1 Read: ");
    // Serial.println(Serial1.read());
    while (bufIndex < 0 && (b = Serial1.read()) >= 0 && b != 'B'){
        Particle.process();
        // Serial.println("BufIndex is less than zero, AND b is eqial to Serial1 Read AND b is not equal to B");
    }

    // if we got a start byte start a new message
    if (b = 'B') {
        // Serial.println("b is equal to B");
        bufIndex = 0;
    }

    // fill all available bytes into open message buffer
    // Serial.print("Serial1 Available: ");
    // Serial.println(Serial1.available());
    while (0 <= bufIndex && bufIndex < sizeof(buf) && Serial1.available())
    {
        // Serial.println("Serial1 Read while 0<=bufIndex & bufIndex < sizeof(buf): ");
        // Serial.println(Serial1.read());
        Serial.print("bufIndex: ");
        Serial.println(bufIndex);
        buf[bufIndex++] = Serial1.read();
        Particle.process();
    }
    
    // Serial.print("Buffer Index: ");
    // Serial.println(bufIndex);
    // Serial.print("Size of buf: ");
    // Serial.println(sizeof(buf));
    
    if (bufIndex == sizeof(buf))
    {
        Serial.println("bufIndex == sizeof(buf)");
        // Serial.print("Buffer at Zero: ");
        // Serial.println(buf[0]);
        if(buf[0] == 0x4d) {
            Serial.println("buf[0] == 0x4d");
            if(checkValue(buf,LENG)) {
                // Serial.println("Checked Value buf,length");
                PM01Value=transmitPM01(buf); //count PM1.0 value of the air detector module
                PM2_5Value=transmitPM2_5(buf);//count PM2.5 value of the air detector module
                PM10Value=transmitPM10(buf); //count PM10 value of the air detector module
            }
        }
        bufIndex = 0;  // mark as treated and ready for next message
    }
}
Getting values: 
Sensors, ACTIVE!
bufIndex: 0
bufIndex: 1
bufIndex: 2
bufIndex: 3
bufIndex: 4
bufIndex: 5
bufIndex: 6
bufIndex: 7
bufIndex: 8
bufIndex: 9
bufIndex: 10
bufIndex: 11
bufIndex: 12
bufIndex: 13
bufIndex: 14
bufIndex: 15
bufIndex: 16
bufIndex: 17
bufIndex: 18
bufIndex: 19
bufIndex: 20
bufIndex: 21
bufIndex: 22
bufIndex: 23
bufIndex: 24
bufIndex: 25
bufIndex: 26
bufIndex: 27
bufIndex: 28
bufIndex: 29
bufIndex: 30
bufIndex == sizeof(buf)
buf[0] == 0x4d
bufIndex: 0
bufIndex: 1
bufIndex: 2
bufIndex: 3
bufIndex: 4
bufIndex: 5
bufIndex: 6
bufIndex: 7
bufIndex: 8
bufIndex: 9
bufIndex: 10
bufIndex: 11
bufIndex: 12
bufIndex: 13
bufIndex: 14
bufIndex: 15
bufIndex: 16
bufIndex: 17
bufIndex: 18
bufIndex: 19
bufIndex: 20
bufIndex: 21
bufIndex: 22
bufIndex: 23
bufIndex: 24
bufIndex: 25
bufIndex: 26
bufIndex: 27
bufIndex: 28
bufIndex: 29
bufIndex: 30
bufIndex == sizeof(buf)
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
PM1.0: 23  ug/m3
PM2.5: 26  ug/m3
PM1 0: 29  ug/m3

bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
PM1.0: 23  ug/m3
PM2.5: 26  ug/m3
PM1 0: 29  ug/m3

bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
PM1.0: 23  ug/m3
PM2.5: 26  ug/m3
PM1 0: 29  ug/m3

bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
bufIndex: 0
PM1.0: 23  ug/m3
PM2.5: 26  ug/m3
PM1 0: 29  ug/m3

This code is suspect:

    if (b = 'B') {
        // Serial.println("b is equal to B");
        bufIndex = 0;
    }

That’s not testing if (b == 'B'), it’s assigning 'B' to the variable b and always setting bufIndex to 0.

2 Likes

Works!

**Getting Serial Output:**
Sensors, ACTIVE!
bufIndex: 0
bufIndex: 1
bufIndex: 2
bufIndex: 3
bufIndex: 4
bufIndex: 5
bufIndex: 6
bufIndex: 7
bufIndex: 8
bufIndex: 9
bufIndex: 10
bufIndex: 11
bufIndex: 12
bufIndex: 13
bufIndex: 14
bufIndex: 15
bufIndex: 16
bufIndex: 17
bufIndex: 18
bufIndex: 19
bufIndex: 20
bufIndex: 21
bufIndex: 22
bufIndex: 23
bufIndex: 24
bufIndex: 25
bufIndex: 26
bufIndex: 27
bufIndex: 28
bufIndex: 29
bufIndex: 30
bufIndex == sizeof(buf)
buf[0] == 0x4d
bufIndex: 0
bufIndex: 1
bufIndex: 2
bufIndex: 3
bufIndex: 4
bufIndex: 5
bufIndex: 6
bufIndex: 7
bufIndex: 8
bufIndex: 9
bufIndex: 10
bufIndex: 11
bufIndex: 12
bufIndex: 13
bufIndex: 14
bufIndex: 15
bufIndex: 16
bufIndex: 17
bufIndex: 18
bufIndex: 19
bufIndex: 20
bufIndex: 21
bufIndex: 22
bufIndex: 23
bufIndex: 24
bufIndex: 25
bufIndex: 26
bufIndex: 27
bufIndex: 28
bufIndex: 29
bufIndex: 30
bufIndex == sizeof(buf)
buf[0] == 0x4d
PM1.0: 11  ug/m3
PM2.5: 15  ug/m3
PM1 0: 21  ug/m3

bufIndex: 0
bufIndex: 1
bufIndex: 2
bufIndex: 3
bufIndex: 4
bufIndex: 5
bufIndex: 6
bufIndex: 7
bufIndex: 8
bufIndex: 9
bufIndex: 10
bufIndex: 11
bufIndex: 12
bufIndex: 13
bufIndex: 14
bufIndex: 15
bufIndex: 16
bufIndex: 17
bufIndex: 18
bufIndex: 19
bufIndex: 20
bufIndex: 21
bufIndex: 22
bufIndex: 23
bufIndex: 24
bufIndex: 25
bufIndex: 26
bufIndex: 27
bufIndex: 28
bufIndex: 29
bufIndex: 30
bufIndex == sizeof(buf)
buf[0] == 0x4d
PM1.0: 13  ug/m3
PM2.5: 15  ug/m3
PM1 0: 20  ug/m3

bufIndex: 0
bufIndex: 1
bufIndex: 2
bufIndex: 3
bufIndex: 4
bufIndex: 5
bufIndex: 6
bufIndex: 7
bufIndex: 8
bufIndex: 9
bufIndex: 10
bufIndex: 11
bufIndex: 12
bufIndex: 13
bufIndex: 14
bufIndex: 15
bufIndex: 16
bufIndex: 17
bufIndex: 18
bufIndex: 19
bufIndex: 20
bufIndex: 21
bufIndex: 22
bufIndex: 23
bufIndex: 24
bufIndex: 25
bufIndex: 26
bufIndex: 27
bufIndex: 28
bufIndex: 29
bufIndex: 30
bufIndex == sizeof(buf)
buf[0] == 0x4d
PM1.0: 13  ug/m3
PM2.5: 15  ug/m3
PM1 0: 20  ug/m3

Thanks @rickkas7 @ScruffR

3 Likes

Thanks @rickkas7, that’s so embarrasing :blush:
In another thread myself suggested to another member to flip if statements round to if ('B' == b) to get build errors when failing to use the correct == :flushed:

@ilak2k, I think you can now put the -1 back in place, where it’s supposed to be.


I’ll correct the code above too

2 Likes