• Question: How can we connect a PMS5003 dust sensor to an Arduino?

    warren is asking a question about air-quality: Subscribe to answer questions on this topic

    warren asked on October 26, 2018 16:50
    151 views | 3 answers | #17406


    Are there some code and wiring diagrams folks have found to connect one of these Plantower sensors (that is inside the Purple Air) to an Arduino? And how to read the data off of it?

    * $30 shipped, laser particle sensor with fan - https://www.amazon.com/Beaster-particle-Digital-Purifier-Precision/dp/B07F2X6RVF

    Thanks!

    image descriptionimage description



    9 Comments

    @guolivar @BrandonFeenstra @GreenFrogg @rockets @nanocastro do you have some information on this? Some folks are looking to build some sensor/dataloggers in Providence and I know some of you have worked with this one.

    Is this a question? Click here to post it to the Questions page.

    Reply to this comment...


    This sensor comes with a serial output. Adafruit has an excellent tutorial on how to interface to an Arduino:https://learn.adafruit.com/pm25-air-quality-sensor/arduino-code

    Reply to this comment...


    Wow, this got a reply on my posted question real fast. Thank you!!!! 

    On Friday, October 26, 2018 at 12:45:33 PM UTC-4, notifications wrote:

    --
    Post to this group at plots-airquality@googlegroups.com
     

    Public Lab mailing lists (http://publiclab.org/lists) are great for discussion, but to get attribution, open source your work, and make it easy for others to find and cite your contributions, please publish your work at http://publiclab.org

    You received this message because you are subscribed to the Google Groups "plots-airquality" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to plots-airquality+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/d/optout.

    > How can we connect a PMS5003 dust sensor to an Arduino? was tagged with #air-quality by warren

    Read and respond to the post here: https://publiclab.org/notes/warren/10-26-2018/how-can-we-connect-a-pms5003-dust-sensor-to-an-arduino

    You received this email because it was tagged with: air-quality.

    To change your preferences, please visit https://publiclab.org/subscriptions.

    Report spam and abuse to: moderators@publiclab.org

    Check out the blog at https://publiclab.org/blog | Love our work? Become a Public Lab Sustaining Member today at https://publiclab.org/donate

    Is this a question? Click here to post it to the Questions page.

    Hi... This is Viking Karlsson and I am new here and I need support from you peoples. I was surfing the net and at random land over here and find the stuff useful.


    Reply to this comment...


    So, great timing, I was just working on doing this with an Arduino mega today (here are some pictures).

    IMG_20181026_142527.jpg Screenshot_20181026-142212.png

    I was going to do a more extensive write up on this, but I'm not quite ready since I haven't put it in a case and sent it out for testing yet. Just FYI I'm using the AirCasting app and a cheap android phone for data logging, though I assume you can do it just fine with an SD card slot or other method, this was just the lowest effort way.

    Here's the breakout I printed up (definitely looking for some feedback): https://oshpark.com/shared_projects/afBXgzqa

    Here's the socket for the cables: https://www.digikey.com/product-detail/en/molex-llc/0530480810/WM1748-ND/242870

    Here's the code I modified and cobbled together from Adafruit and a couple of other sources, I added in functionality for two PMS5003s and a bme280 as well as support for the AirCasting app using a HC-06 bluetooth module:

    // On Leonardo/Micro or others with hardware serial, use those!
    // uncomment this line:
        [#define](/tag/define) pmsSerialA Serial1
        [#define](/tag/define) pmsSerialB Serial2
    
    // For UNO and others without hardware serial, we must use software serial...
    // pin [#2](/n/2) is IN from sensor (TX pin on sensor), leave pin [#3](/n/3) disconnected
    // comment these two lines if using hardware serial
    //#include <SoftwareSerial.h>
    //#include <AltSoftSerial.h>
    //AltSoftSerial pmsSerial;
    [#define](/tag/define) mySerial Serial3 // for bluetooth module
    
    [#include](/tag/include) <Wire.h>
    [#define](/tag/define) BME280_ADDRESS 0x76
    
    unsigned long int hum_raw,temp_raw,pres_raw;
    signed long int t_fine;
    
    uint16_t dig_T1;
     int16_t dig_T2;
     int16_t dig_T3;
    uint16_t dig_P1;
     int16_t dig_P2;
     int16_t dig_P3;
     int16_t dig_P4;
     int16_t dig_P5;
     int16_t dig_P6;
     int16_t dig_P7;
     int16_t dig_P8;
     int16_t dig_P9;
     int8_t  dig_H1;
     int16_t dig_H2;
     int8_t  dig_H3;
     int16_t dig_H4;
     int16_t dig_H5;
     int8_t  dig_H6;
    
    void setup() {
      // our debugging output
      Serial.begin(4800);
      mySerial.begin(115200);
    
    
      // sensor baud rate is 9600
      pmsSerialA.begin(9600);
      pmsSerialB.begin(9600);
    
      // its BME280 things
        uint8_t osrs_t = 1;             //Temperature oversampling x 1
        uint8_t osrs_p = 1;             //Pressure oversampling x 1
        uint8_t osrs_h = 1;             //Humidity oversampling x 1
        uint8_t mode = 3;               //Normal mode
        uint8_t t_sb = 5;               //Tstandby 1000ms
        uint8_t filter = 0;             //Filter off 
        uint8_t spi3w_en = 0;           //3-wire SPI Disable
    
        uint8_t ctrl_meas_reg = (osrs_t << 5) | (osrs_p << 2) | mode;
        uint8_t config_reg    = (t_sb << 5) | (filter << 2) | spi3w_en;
        uint8_t ctrl_hum_reg  = osrs_h;
    
        Wire.begin();
        writeReg(0xF2,ctrl_hum_reg);
        writeReg(0xF4,ctrl_meas_reg);
        writeReg(0xF5,config_reg);
        readTrim();                    
    
    }
    
    struct pms5003adata {
      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 pms5003bdata {
      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 pms5003adata dataA;
    struct pms5003bdata dataB;
    
    void loop() {
        if (readPMSdataA(&pmsSerialA)) {
        // reading data was successful!
    
        Serial.println();
        Serial.println("A-------------------------------------A");
        Serial.println("Concentration Units (standard)");
        Serial.print("PM 1.0: "); Serial.print(dataA.pm10_standard);
        Serial.print("\t\tPM 2.5: "); Serial.print(dataA.pm25_standard);
        Serial.print("\t\tPM 10: "); Serial.println(dataA.pm100_standard);
        Serial.println("---------------------------------------");
        Serial.println("Concentration Units (environmental)");
        Serial.print("PM 1.0: "); Serial.print(dataA.pm10_env);
        Serial.print("\t\tPM 2.5: "); Serial.print(dataA.pm25_env);
        Serial.print("\t\tPM 10: "); Serial.println(dataA.pm100_env);
        Serial.println("---------------------------------------");
        Serial.print("Particles > 0.3um / 0.1L air:"); Serial.println(dataA.particles_03um);
        Serial.print("Particles > 0.5um / 0.1L air:"); Serial.println(dataA.particles_05um);
        Serial.print("Particles > 1.0um / 0.1L air:"); Serial.println(dataA.particles_10um);
        Serial.print("Particles > 2.5um / 0.1L air:"); Serial.println(dataA.particles_25um);
        Serial.print("Particles > 5.0um / 0.1L air:"); Serial.println(dataA.particles_50um);
        Serial.print("Particles > 50 um / 0.1L air:"); Serial.println(dataA.particles_100um);
        Serial.println("A-------------------------------------A");
    
        double temp_act = 0.0, press_act = 0.0,hum_act=0.0, fah_act= 0.0;
        signed long int temp_cal;
        unsigned long int press_cal,hum_cal;
    
        readData();
    
        temp_cal = calibration_T(temp_raw);
        press_cal = calibration_P(pres_raw);
        hum_cal = calibration_H(hum_raw);
        temp_act = (double)temp_cal / 100.0;
        press_act = (double)press_cal / 100.0;
        hum_act = (double)hum_cal / 1024.0;
        fah_act = temp_act * 9/5 +32;
        Serial.print("TEMP : ");
        Serial.print(temp_act);
        Serial.print(" Deg C, ");
        Serial.print(fah_act);
    
    
        Serial.print("Deg F PRESS : ");
        Serial.print(press_act);
        Serial.print(" hPa  HUM : ");
        Serial.print(hum_act);
        Serial.println(" %"); 
    
    
        delay(1000);
    
    
       //AirCasting Output (the AB2 is a PMS 7003 sensor as well, though the PA II is a 5003)
    
       mySerial.print(dataA.pm25_standard);
       mySerial.print(";PuprpleAirII;PMS5003-2.5A;Particulate Matter;PM 2.5;Micrograms Per Meter Cubed;ug/m^3;0;12;35;55;150");
       mySerial.print("\n"); 
    
       /* Taking out the 10 output for now
       mySerial.print(dataA.pm100_standard);
       mySerial.print(";PuprpleAirII;PMS5003-10A;Particulate Matter;PM 10;Micrograms Per Meter Cubed;ug/m^3;0;12;35;55;150");
       mySerial.print("\n"); 
       */
    
       mySerial.print(fah_act);
       mySerial.print(";BME280T;BME280T;Temperature;Temp;deg F; deg F;0;32;50;70;120;");
       mySerial.print("\n");
    
       mySerial.print(hum_act);
       mySerial.print(";BME280H;BME280H;Humidity;Hum;%;%;0;25;50;75;100;");
       mySerial.print("\n");
    
      }
    
      if (readPMSdataB(&pmsSerialB)) {
        // reading data was successful!
    
        Serial.println();
        Serial.println("B-------------------------------------B");
        Serial.println("Concentration Units (standard)");
        Serial.print("PM 1.0: "); Serial.print(dataB.pm10_standard);
        Serial.print("\t\tPM 2.5: "); Serial.print(dataB.pm25_standard);
        Serial.print("\t\tPM 10: "); Serial.println(dataB.pm100_standard);
        Serial.println("---------------------------------------");
        Serial.println("Concentration Units (environmental)");
        Serial.print("PM 1.0: "); Serial.print(dataB.pm10_env);
        Serial.print("\t\tPM 2.5: "); Serial.print(dataB.pm25_env);
        Serial.print("\t\tPM 10: "); Serial.println(dataB.pm100_env);
        Serial.println("---------------------------------------");
        Serial.print("Particles > 0.3um / 0.1L air:"); Serial.println(dataB.particles_03um);
        Serial.print("Particles > 0.5um / 0.1L air:"); Serial.println(dataB.particles_05um);
        Serial.print("Particles > 1.0um / 0.1L air:"); Serial.println(dataB.particles_10um);
        Serial.print("Particles > 2.5um / 0.1L air:"); Serial.println(dataB.particles_25um);
        Serial.print("Particles > 5.0um / 0.1L air:"); Serial.println(dataB.particles_50um);
        Serial.print("Particles > 50 um / 0.1L air:"); Serial.println(dataB.particles_100um);
        Serial.println("B-------------------------------------B");
    
    
       //AirCasting Output (the AB2 is a PMS 7003 sensor as well, though the PA II is a 5003)
    
       mySerial.print(dataB.pm25_standard);
       mySerial.print(";PuprpleAirII;PMS5003-2.5B;Particulate Matter;PM 2.5;Micrograms Per Meter Cubed;ug/m^3;0;12;35;55;150");
       mySerial.print("\n"); 
    
       /* Taking out the 10 output for now
       mySerial.print(dataB.pm100_standard);
       mySerial.print(";PuprpleAirII;PMS5003-10B;Particulate Matter;PM 10;Micrograms Per Meter Cubed;ug/m^3;0;12;35;55;150");
       mySerial.print("\n"); 
       */
    
      }
    }
    
    boolean readPMSdataA(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 *)&dataA, (void *)buffer_u16, 30);
    
      if (sum != dataA.checksum) {
         Serial.println("Checksum failure");
        return false;
      }
      // success!
      return true;
    }
    
    boolean readPMSdataB(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 *)&dataB, (void *)buffer_u16, 30);
    
      if (sum != dataB.checksum) {
        Serial.println("Checksum failure");
        return false;
      }
      // success!
      return true;
    }
    
    //BME280 functions
    
    void readTrim()
    {
        uint8_t data[32],i=0;
        Wire.beginTransmission(BME280_ADDRESS);
        Wire.write(0x88);
        Wire.endTransmission();
        Wire.requestFrom(BME280_ADDRESS,24);
        while(Wire.available()){
            data[i] = Wire.read();
            i++;
        }
    
        Wire.beginTransmission(BME280_ADDRESS);
        Wire.write(0xA1);
        Wire.endTransmission();
        Wire.requestFrom(BME280_ADDRESS,1);
        data[i] = Wire.read();
        i++;
    
        Wire.beginTransmission(BME280_ADDRESS);
        Wire.write(0xE1);
        Wire.endTransmission();
        Wire.requestFrom(BME280_ADDRESS,7);
        while(Wire.available()){
            data[i] = Wire.read();
            i++;    
        }
        dig_T1 = (data[1] << 8) | data[0];
        dig_T2 = (data[3] << 8) | data[2];
        dig_T3 = (data[5] << 8) | data[4];
        dig_P1 = (data[7] << 8) | data[6];
        dig_P2 = (data[9] << 8) | data[8];
        dig_P3 = (data[11]<< 8) | data[10];
        dig_P4 = (data[13]<< 8) | data[12];
        dig_P5 = (data[15]<< 8) | data[14];
        dig_P6 = (data[17]<< 8) | data[16];
        dig_P7 = (data[19]<< 8) | data[18];
        dig_P8 = (data[21]<< 8) | data[20];
        dig_P9 = (data[23]<< 8) | data[22];
        dig_H1 = data[24];
        dig_H2 = (data[26]<< 8) | data[25];
        dig_H3 = data[27];
        dig_H4 = (data[28]<< 4) | (0x0F & data[29]);
        dig_H5 = (data[30] << 4) | ((data[29] >> 4) & 0x0F);
        dig_H6 = data[31];   
    }
    void writeReg(uint8_t reg_address, uint8_t data)
    {
        Wire.beginTransmission(BME280_ADDRESS);
        Wire.write(reg_address);
        Wire.write(data);
        Wire.endTransmission();    
    }
    
    
    void readData()
    {
        int i = 0;
        uint32_t data[8];
        Wire.beginTransmission(BME280_ADDRESS);
        Wire.write(0xF7);
        Wire.endTransmission();
        Wire.requestFrom(BME280_ADDRESS,8);
        while(Wire.available()){
            data[i] = Wire.read();
            i++;
        }
        pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4);
        temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4);
        hum_raw  = (data[6] << 8) | data[7];
    }
    
    
    signed long int calibration_T(signed long int adc_T)
    {
    
         signed long int var1, var2, T;
        var1 = ((((adc_T >> 3) - ((signed long int)dig_T1<<1))) * ((signed long int)dig_T2)) >> 11;
        var2 = (((((adc_T >> 4) - ((signed long int)dig_T1)) * ((adc_T>>4) - ((signed long int)dig_T1))) >> 12) * ((signed long int)dig_T3)) >> 
    14;
    
        t_fine = var1 + var2;
        T = (t_fine * 5 + 128) >> 8;
        return T; 
    }
    
    unsigned long int calibration_P(signed long int adc_P)
    {
        signed long int var1, var2;
       unsigned long int P;
        var1 = (((signed long int)t_fine)>>1) - (signed long int)64000;
        var2 = (((var1>>2) * (var1>>2)) >> 11) * ((signed long int)dig_P6);
        var2 = var2 + ((var1*((signed long int)dig_P5))<<1);
        var2 = (var2>>2)+(((signed long int)dig_P4)<<16);
        var1 = (((dig_P3 * (((var1>>2)*(var1>>2)) >> 13)) >>3) + ((((signed long int)dig_P2) * var1)>>1))>>18;
        var1 = ((((32768+var1))*((signed long int)dig_P1))>>15);
        if (var1 == 0)
        {
            return 0;
        }    
        P = (((unsigned long int)(((signed long int)1048576)-adc_P)-(var2>>12)))*3125;
        if(P<0x80000000)
        {
           P = (P << 1) / ((unsigned long int) var1);   
        }
        else
        {
            P = (P / (unsigned long int)var1) * 2;    
        }
        var1 = (((signed long int)dig_P9) * ((signed long int)(((P>>3) * (P>>3))>>13)))>>12;
        var2 = (((signed long int)(P>>2)) * ((signed long int)dig_P8))>>13;
        P = (unsigned long int)((signed long int)P + ((var1 + var2 + dig_P7) >> 4));
        return P;
    }
    
    unsigned long int calibration_H(signed long int adc_H)
    {
        signed long int v_x1;
    
        v_x1 = (t_fine - ((signed long int)76800));
        v_x1 = (((((adc_H << 14) -(((signed long int)dig_H4) << 20) - (((signed long int)dig_H5) * v_x1)) + 
                  ((signed long int)16384)) >> 15) * (((((((v_x1 * ((signed long int)dig_H6)) >> 10) * 
                  (((v_x1 * ((signed long int)dig_H3)) >> 11) + ((signed long int) 32768))) >> 10) + (( signed long int)2097152)) * 
                  ((signed long int) dig_H2) + 8192) >> 14));
       v_x1 = (v_x1 - (((((v_x1 >> 15) * (v_x1 >> 15)) >> 7) * ((signed long int)dig_H1)) >> 4));
       v_x1 = (v_x1 < 0 ? 0 : v_x1);
       v_x1 = (v_x1 > 419430400 ? 419430400 : v_x1);
       return (unsigned long int)(v_x1 >> 12);   
    }
    

    Is this a question? Click here to post it to the Questions page.

    Reply to this comment...


    Thanks, everyone!

    Reply to this comment...


    There is another version of this sensor with the same specifications and the same price but it is about half the size. Does anyone know any reason not to use this one instead? It is the PMS7003 instead of PMS5003. Both are available on eBay for $20.

    https://www.amazon.com/Lychee-PLANTOWER-PMS7003-Digital-Concentration/dp/B075WR5YKL/

    Chris

    Is this a question? Click here to post it to the Questions page.

    Reply to this comment...


    Here is a page about the PMS5003 and 7003 ... https://aqicn.org/sensor/pms5003-7003/

    The difference seems to be in their internal layout. In that link they show a 5003 opened after several months outdoor and the light sensor seems to be "dust free" which the 1003 suffered from ... don't know the internal layout of the 7003 but unless you're "size constrained" I'd go with the ones that have been shown to keep themselves clean inside.

    Reply to this comment...


    Hi. Sorry for the late answer. We are using the PMS7003 but the 5003 is almost the same except for the size. We were using Arduino Mega with three different sensors (SDS011, PMS7003 and Shinyei) and bluetooth. Here is the doc of hardware and firmware After some tests we decided to move to NodeMCU and build two luftdaten.info devices. More info on the project here

    Reply to this comment...


    Log in to comment