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

warren is asking a question about air-quality
Follow this topic

by warren | October 26, 2018 16:50 | #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



10 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.

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...


@Cbarnes9 are you at all interested in something like this, where it'd be a PMS7003 connected to an Arduino to just get the data off as quickly as possible? Just wondering!

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

Reply to this comment...


Reply to this comment...


Log in to comment