Another guide working on air quality sensor with Senserion SPS30. SPS30 is a Particulate Matter (PM) sensor that uses an optical sensing base on laser scattering resistance mechanism technology. SPS30 is highly sensitive in particles that feed in featuring a high quality, long lasting, and accurate measurements suitable for environmental outdoor monitoring that last up-to 8 years in field.
This device has size of 41X41X12 mm with integrated advance algorithms for accurate measurements for different Particulate Matter types and high-resolution particle size binning. For more technical details please refer to the datasheet below.
On this guide we will going wire-up the sensor with the help of microcontroller, we will going to use the Arduino MCU for this demo, however you can also use other MCU like ESP32, Curiosity Nano, Nano Every, etc.
Requirements
- Arduino IDE | Atmel Studio | Energia | Processing
- Sony SPRESENSE Dev Board
- Please refer to this link
- ESPRESSIF
- ESP8266 12, 12E, ESP32, ESP8266 NodeMCU, ESPDuino
- Microchip
- ATMEGA4809 Curiosity Nano
- ARDUINO
- PRO, FIO, NANO, EVERY, UNO, MINI, MEGA, PRO MINI, LEO, BT, DUE, ETHERNET,LILYPAD, NodeMCU, Teensy Board, TeensyDuino, ATMEGA328 16/12, ATMEGA32u4 16/8/ MHz, ESP8266, MSP430 ,ATMEGA250 16 MHz, ATSAM3x8E, STM32, LinkItOne.
- Note: For AVR (please see the flash size of the MCU and respective pin-outs & bus configuration )
- Note: The Diagram below is using ATMEGA328TQFP & ATMEGA4809. (please refer to each MCU’s respective pin-outs & bus configuration)
- Others
- Teensy 3, MSP430 Launchpad, Flora, Metro, Trinket, Pro Trinket
- Senserion SPS30 Particulate Matter Optical Sensor
- Resistors (See below required value)
As you can see the diagram below there are two options, it was hookup to Arduino Nano Every and the second one is connected to Arduino Nano. If you want to use ESP chip series or Sony Spresense. Please refer to this link provide.
Wiring Diagram
SPS30 i2C Source Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 |
#include "sps30.h" /*define communication channel to use for SPS30 valid options: * I2C_COMMS use I2C communication * SOFTWARE_SERIAL Arduino variants and ESP8266 (NOTE) * SERIALPORT ONLY IF there is NO monitor attached * SERIALPORT1 Arduino MEGA2560, Sparkfun ESP32 Thing : MUST define new pins as defaults are used for flash memory) * SERIALPORT2 Arduino MEGA2560 and ESP32 * SERIALPORT3 Arduino MEGA2560 only for now */ #define SP30_COMMS SERIALPORT1 /* define RX and TX pin for softserial and Serial1 on ESP32 * can be set to zero if not applicable / needed */ #define TX_PIN 26 #define RX_PIN 25 /* define new AUTO Clean interval * Will be remembered after power off * default is 604800 seconds * 0 = disable Auto Clean * -1 = do not change current setting */ #define AUTOCLEANINTERVAL -1 /* Perform a clean NOW ? * 1 = yes * 0 = NO */ #define PERFORMCLEANNOW 0 #define DEBUG 0 // function prototypes (sometimes the pre-processor does not create prototypes themself on ESPxx) void serialTrigger(char * mess); void ErrtoMess(char *mess, uint8_t r); void Errorloop(char *mess, uint8_t r); void GetDeviceInfo(); bool read_all(); void SetAutoClean(); // create constructor SPS30 sps30; void setup() { Serial.begin(115200); serialTrigger("SPS30-Example2: Basic reading + clean. press <enter> to start"); Serial.println(F("Trying to connect")); // set driver debug level sps30.EnableDebugging(DEBUG); // set pins to use for softserial and Serial1 on ESP32 if (TX_PIN != 0 && RX_PIN != 0) sps30.SetSerialPin(RX_PIN,TX_PIN); // Begin communication channel; if (sps30.begin(SP30_COMMS) == false) { Errorloop("could not initialize communication channel.", 0); } // check for SPS30 connection if (sps30.probe() == false) { Errorloop("could not probe /connect with SPS30.", 0); } else Serial.println(F("Detected SPS30.")); // reset SPS30 connection if (sps30.reset() == false) { Errorloop("could not reset.", 0); } // read device info GetDeviceInfo(); // do Auto Clean interval SetAutoClean(); // start measurement if (sps30.start() == true) { Serial.println(F("Measurement started")); } else Errorloop("Could NOT start measurement", 0); // clean now requested if (PERFORMCLEANNOW) { // clean now if (sps30.clean() == true) Serial.println(F("fan-cleaning manually started")); else Serial.println(F("Could NOT manually start fan-cleaning")); } serialTrigger("Hit <enter> to continue reading"); if (SP30_COMMS == I2C_COMMS) { if (sps30.I2C_expect() == 4) Serial.println(F(" !!! Due to I2C buffersize only the SPS30 MASS concentration is available !!! \n")); } } void loop() { read_all(); delay(3000); } /** * @brief: read and display device info */ void GetDeviceInfo() { char buf[32]; uint8_t ret; //try to read serial number ret = sps30.GetSerialNumber(buf, 32); if (ret == ERR_OK) { Serial.print(F("Serial number : ")); if(strlen(buf) > 0) Serial.println(buf); else Serial.println(F("not available")); } else ErrtoMess("could not get serial number.", ret); // try to get product name ret = sps30.GetProductName(buf, 32); if (ret == ERR_OK) { Serial.print(F("Product name : ")); if(strlen(buf) > 0) Serial.println(buf); else Serial.println(F("not available")); } else ErrtoMess("could not get product name.", ret); // try to get article code ret = sps30.GetArticleCode(buf, 32); if (ret == ERR_OK) { Serial.print(F("Article code : ")); if(strlen(buf) > 0) Serial.println(buf); else Serial.println(F("not available")); } else ErrtoMess("could not get article code.", ret); } /** * @brief: Get & Set new Auto Clean Interval * */ void SetAutoClean() { uint32_t interval; uint8_t ret; // try to get interval ret = sps30.GetAutoCleanInt(&interval); if (ret == ERR_OK) { Serial.print(F("Current Auto Clean interval: ")); Serial.print(interval); Serial.println(F(" seconds")); } else ErrtoMess("could not get clean interval.", ret); // only if requested if (AUTOCLEANINTERVAL == -1) { Serial.println(F("No Auto Clean interval change requested.")); return; } // try to set interval interval = AUTOCLEANINTERVAL; ret = sps30.SetAutoCleanInt(interval); if (ret == ERR_OK) { Serial.print(F("Auto Clean interval now set : ")); Serial.print(interval); Serial.println(F(" seconds")); } else ErrtoMess("could not set clean interval.", ret); // try to get interval ret = sps30.GetAutoCleanInt(&interval); if (ret == ERR_OK) { Serial.print(F("Current Auto Clean interval: ")); Serial.print(interval); Serial.println(F(" seconds")); } else ErrtoMess("could not get clean interval.", ret); } /** * @brief: read and display all values */ bool read_all() { static bool header = true; uint8_t ret, error_cnt = 0; struct sps_values val; // loop to get data do { ret = sps30.GetValues(&val); // data might not have been ready if (ret == ERR_DATALENGTH){ if (error_cnt++ > 3) { ErrtoMess("Error during reading values: ",ret); return(false); } delay(1000); } // if other error else if(ret != ERR_OK) { ErrtoMess("Error during reading values: ",ret); return(false); } } while (ret != ERR_OK); // only print header first time if (header) { Serial.println(F("-------------Mass ----------- ------------- Number -------------- -Average-")); Serial.println(F(" Concentration [μg/m3] Concentration [#/cm3] [μm]")); Serial.println(F("P1.0\tP2.5\tP4.0\tP10\tP0.5\tP1.0\tP2.5\tP4.0\tP10\tPartSize\n")); header = false; } Serial.print(val.MassPM1); Serial.print(F("\t")); Serial.print(val.MassPM2); Serial.print(F("\t")); Serial.print(val.MassPM4); Serial.print(F("\t")); Serial.print(val.MassPM10); Serial.print(F("\t")); Serial.print(val.NumPM0); Serial.print(F("\t")); Serial.print(val.NumPM1); Serial.print(F("\t")); Serial.print(val.NumPM2); Serial.print(F("\t")); Serial.print(val.NumPM4); Serial.print(F("\t")); Serial.print(val.NumPM10); Serial.print(F("\t")); Serial.print(val.PartSize); Serial.print(F("\n")); } /** * @brief : continued loop after fatal error * @param mess : message to display * @param r : error code * * if r is zero, it will only display the message */ void Errorloop(char *mess, uint8_t r) { if (r) ErrtoMess(mess, r); else Serial.println(mess); Serial.println(F("Program on hold")); for(;;) delay(100000); } /** * @brief : display error message * @param mess : message to display * @param r : error code * */ void ErrtoMess(char *mess, uint8_t r) { char buf[80]; Serial.print(mess); sps30.GetErrDescription(r, buf, 80); Serial.println(buf); } /** * serialTrigger prints repeated message, then waits for enter * to come in from the serial port. */ void serialTrigger(char * mess) { Serial.println(); while (!Serial.available()) { Serial.println(mess); delay(2000); } while (Serial.available()) Serial.read(); } |
Downloads
- Download SPS30 Datasheets | PDF
- Download SPS30 Product Catalog | PDF
- Download SPS30 ESP32 / Arduino / Code Library | Zip