Programmable Particulate (P2.5, P10) Monitoring With an SDS011, Arduino Nano, and a 3D Printed Case

About: Retired tinkerer, programmer ...

Recently I have become much more concerned about the air quality in my home, especially in my work area. I often work with 3D printers, CNC machines, soldering, grinders and polishers.

Effects from these sources of air pollutants/particulate can be experienced immediately after exposure or, possibly, years later. Immediate effects can include irritation of the eyes, nose, and throat, headaches, dizziness, and fatigue.

Understanding and ameliorating the health effects of exposure to these fine and ultra-fine pollutants is indeed an issue, but the first problem is to actually identify the scope of the issue. A complication, the pollutants here are, for a large part, often invisible to the naked eye and may be odorless and colorless.


Particulate matter is the sum of all solid and liquid particles suspended in air, many of which can be hazardous. This complex mixture includes both organic and inorganic particles, such as dust, pollen, soot, smoke, and liquid droplets.

PM10 is particulate matter 10 micrometers or less in diameter and generally described as fine particles. Fine particles are also known to trigger or worsen chronic diseases such as asthma, heart attack, bronchitis as well as other respiratory problems.

Particles less than or equal to 10 micrometers in diameter are so small that they can get into the lungs, getting by the nasal protections, potentially causing serious health problems.

Particles less than 2.5 µm (micrometers) are called PM 2.5. They are approximately 1/30th the average width of a human hair. Since they are so small and light, these particles tend to stay longer in the air than heavier particles.

PM2.5 particles in air pollution have been widely considered associated with respiratory and cardiovascular diseases. Recent studies have shown that PM2.5 can also cause central nervous system diseases, including a variety of neurodegenerative diseases, such as Alzheimer's.

PM2.5 can destroy the integrity of the blood-brain barrier and also enter the gastrointestinal tract, causing imbalances in the intestinal microecology.

WHO Guidelines

The WHO guidelines stipulate that:

  • PM2.5 not exceed 10 μg/m3 annual mean, or 25 μg/m3 24-hour mean
  • PM10 not exceed 20 μg/m3 annual mean, or 50 μg/m3 24-hour mean.


I started off by wanting to build a simple counter/sensor to measure and display the particulate levels on an LCD.

A simple buzzer alarm was then added to be triggered when some programmed particulate levels were exceeded. This quickly evolved into to a programmable and savable (EEPROM) alarm level as well as an expanded operational mode control function utilizing a Rotary Encoder.

An external 5V logic-level (High-Low) output was then added to allow the monitor to trigger external actions, i.e., to drive a higher amperage DC device, via a Mosfet/Transistor switch, or a DC-AC switched Relay/SSR for higher amperage AC devices such as a ventilator or a light or similar signalling device for the hearing impaired.

The Sensor and Monitor

I settled on the SDS011 as the sensor to use due to reviews and after testing a few other “dust counters“.

After measuring, the results are displayed on a 1602 LCD via an I2C interface; a 2004 might be used if more screen space is required. I did trim the I2C pins in order to assure snug fit into the 3D printed LCD holder.

As a micro-controller, an Arduino Nano 5V clone was used as the SDS011 sensor needs 5V power and the Nano has a 3.3V pin which can be used as an input to a Logic level converter (3.3V is required by the sensor’s RX-TX interface). The Nano clones are nearly as cheap as the more popular Pro Mini but you don’t need an FTDI board for programming. I also did not see the need for a WiFi connection as yet.

A KY-040 Rotary Encoder is used to allow both Mode and Alarm/External Device activation/deactivation particulate levels to be changed. Changes are automatically saved and restored from EEPROM.

Power Approach

The Arduino NANO 5V clone is powered via an MCP-1702 250 mAh 5V regulator and either a 5-13V AC Adapter or a similar battery.

There is no battery-backup added and no effort was made to reduce the milliamperes used while running, which is currently ~120-130 mAh on average, as it just wasn’t required for my work area. If there is no power, there really is no need to monitor as nothing is running and monitoring would be off.

Running on battery as primary power source would require a battery with between 5.15 and 13.2 Volts, any battery below 5.15V would require a step-up board to get to the required input range of the MCP-1702. Below is an example of a battery lifetime calculation for a non-step up battery solution.

A single DC Jack connector is used to pass a DC voltage through to an MCP1702 regulator for a clean 5V. This allows a wide range of AC adapters to be used (from 5+ up to 13.2 V). The draw of the sensor, LCD, and Nano would require quite a powerful battery, in turns of amperage, in order to support the draw, currently a bit under 130 milliamperes. (See battery lifetime calculation below).

Example Battery Life Calculation

(using 75% guideline and 9000mAh/ 12V capacity rechargeable battery

(9000 * .75) mAh / 130 mA = 52 hours on Battery

** usage of a 3.7V rechargeable would require a step-up.

3D Printed Case

To provide support for installation and protection for the sensor and circuit board, a 3D printable case was made and the STLs are provided and usage illustrated.

Teacher Notes

Teachers! Did you use this instructable in your classroom?
Add a Teacher Note to share how you incorporated it into your lesson.

Step 1: Operations

This project uses a rotary encoder, both turn and switch functionalities, to create a programmable sensor. The LCD is programmed to display the current particle counts and the programmable MODE and ALARM threshold settings.

On the first row, the LCD shows the current readings across P2.5 (U) and P10 (F) .

In the current Arduino sketch the readings are updated continuously with no loop delay.

On the second row the LCD shows the MODE at the left. To the right of the MODE are the current settings of the 2 alarm monitoring alarm levels.

These are adjustable by turning the rotary knob left or right. The alarm levels shown are P25 and P10. When the rotary is adjusted by turning the knob left or right. The P2.5 level increases or decreases by 5 while the P10 go up or down by 10.

The Modes supported are:

  • All - Both the alarm and external interfaces are active when set particulate levels are exceeded.
  • Alm - Only the alarm is active when set particulate levels are exceeded.
  • Ext - Only the external interface is active when set particulate levels are exceeded.
  • Off - Neither interface is active when set particulate levels are exceeded.

To toggle between modes, just press the KY-040 button switch. When either the Mode or Alarm level settings are adjusted, the changes are automatically saved to EEPROM and restored on power-up.

If you are adjusting the Alarm levels and are near to the Alarm level, you may want to set Mode to “Off” until you have reached the levels you want and then reset the Mode.

Although I used a couple of 1 uF caps to minimize encoder-bounce, I have seen it occur on occasion. This means that rarely the rotary knob is turned and the results are opposite to what is expected. I just adjust it again to get to where I want to go.

Step 2: Parts List

Step 3: Cables

Three cables were handmade for this project:

There are 2 cables with 4-Female Dupont connectors on one end and a 4-Pin Female MOLEX on the other.

The other cable has 5-Female Dupont connectors one end and 2 Female MOLEX connectors (a 3-Pin and a 2-Pin) on the other.

Step 4: The SDS 011 Sensor

The SDS011 Sensor was developed by inovafit, a spin-off from the University of Jinan (in Shandong). While other sensors tend to focus on shrinking the sensor size, the SDS011 has opted for a size trade-off allowing it to use a larger fan to get higher accuracy and stability.

This is the specification for the SDS011.

  • Output: PM2.5, PM10
  • Measuring Range: 0.0-999.9μg/m3
  • Input Voltage:5V
  • Maximum Current: 100mA
  • Response Time : 1 second
  • Serial Data Output Frequency: 1 time/second
  • Particle Diameter Resolution: ≤0.3μm
  • Relative Error: 10%
  • Temperature Range: -20~50°C
  • Physical Size: 71mm*70mm*23m

Step 5: SDS011 Interface

Step 6: Schematic

There is only one input voltage connector. This circuit is designed to support input in the range of 5+ to 13 VDC. No secondary battery connector is included but could be added if desired.

The input voltage is conditioned by the MCP1702 5V 250mA regulator. It is important not to try and run the SDS or the LCD off any Nano port, but rather directly connect them to the output of the MCP1702 as they could draw higher milliamperes than the Nano may safely supply.

Again, the SDS011 utilizes 3.3V for the UART interface (TX, RX), so a level shifter is used to connect the D9-D10 Nano ports to the SDA, SCL pins of the SDS.

The 3.3V pin of the Nano can be safely used as the LV (low voltage) of the shifter as very little power is required here. The 5V side is connected directly to the MCP1702.

The buzzer is powered directly off the D12 digital port as it draws less than 35 mAh, but using a different alarm, buzzer, etc. requires that you do not exceed the maximum draw the Arduino pin can handle (50 mAh).

The KY-040 Encoder is connected to the D2-D3 pins and interrupts the Nano when the rotary knob is turned in either direction, the sketch determines direction.

The switch of the KY-040 is connected to the D4 pin where it is polled in the main loop. Utilization of any type of low-power library (power saving for battery for example) would necessitate that this be changed to an interrupt routine as well or the mode handling would be badly affected.

MOLEX and JST 2.0 PH connectors were used to make it easier to assemble and disassemble for part replacement and/or program modifications.

Step 7: Wiring Up SDS011 and Level Shifter

The SDS011 communicates via a Serial interface. The Arduino hardware has built-in support for serial communication on pins 0 and 1 (which also goes to the computer via the USB connection).

The SDS011 library uses the SoftSerial library to allow serial communications on other digital ports. Here the D9 and D10 are being used for this purpose.

As the Arduino digital ports are 5V and the RXD and TXD ports on the SDS011 are 3.3V, a bi-directional Level Shifter is used to step up the 3.3V to 5V and vice versa.

The 3.3 LV is supplied from the Nano itself, while the 5V is supplied from the MCP1702 regulator.

Step 8: Wiring Up the KY-040 Rotary Encoder

The KY-040 rotary encoder is a input device that can interrupt a processor when the knob has been rotated. It also has a push-switch which is, in this design, pollable.

Events are signaled to the Nano via the interrupt pins D2, D3. The Switch is connected to D4 which is not an interrupt pin and is therefore polled.

To make wiring easier, the VDD and GND pins were connected to D5 (5V) and D6(GND) and then the pins were configured in code, more on that later.

To make connect/disconnect simple, 2 MOLEX connectors were used, a 3 pin and a 2 pin. The reason for 2 connectors was basically, I had them in stock and I like MOLEX connectors.

1uF capacitors were added to the CLK and DT connections to aid in debouncing, but that is backed up by code, again more on that in the code area.

Step 9: Wiring Up LCD (I2C)

The 1602 LCD Display used here utilizes an I2C interface, which means just 4 connections, VCC, GND, SDA & SCL, are required rather than the 11 or so that would be needed with a regular 16x2 LCD Display.

The I2C bus is connected to pins A4 and A5 pins on the Nano..

Attached here is also an I2C Scanner sketch which can be run to ascertain devices and addresses attached to the I2C bus, should any addressing problems arise, or just to ascertain the address of the LCD itself.

Step 10: The Prototype PCB Board Assembly

In the lower right is the power-in JST connector from the DC power in Jack. The input voltage is connected to the MCP1702 5V (250 mAh max) regulator with (2) 1uF smoothing capacitors.

To the left of the regulator, is the bi-directional Level Shifter. The LV (3.3V) side of the Level Shifter is connected to the 3.3V output of the Arduino Nano and GND. The HV (5V) side is connected to the MCP1702 output and ground. To the left of the Level Shifter is the 4-Pin Molex used to connect to the SDS011.

The RXD and TXD pins from the SDS011 are connected with to the LV1 and LV2 pins on the LV side of the shifter. The HV1 and HV2 pins are connected to the D9 and D10 pins of the Nano for SoftSerial communication. The 5V and GND pins on the SDS011 Molex are connected to the MCP1702.

On the Nano, the 5V and GND pins are connected to the MCP1702 output voltage and GND.

Pins D2-D6 are connected to the 3 Pin and 2 Pin Molex connectors for the cables from the KY-040 Rotary encoder.

Pins D12 and D11 are connected to the 5V Piezo buzzer and pins D11 and D13 are connected to another JST for the EXT 5V logic-level Jack.

The 4-Pin Molex in the lower left corner is connected to 5V, GND, and pins A4-A5 for the 1602 LCD I2C cable.

Step 11: Arduino Code

Attached is the Arduino IDE sketch used to run complete SDS011 sensor and the LCD. The code is explained in the following sections.

The sketch should be flashed to the Nano before the the PCB board is installed in the case.

Step 12: Arduino Code .. Libraries Used

Libraries Used


The micro-controller on the Arduino Nano board has EEPROM: memory whose values are kept when the board is turned off (like a tiny hard drive). This library enables you to read and write those bytes. EEPROM has a total lifetime of ~100,000 write cycles. The size of EEPROM is 1024 bytes on the Nano.


Arduino library for dust Sensor SDS011 (Nova Fitness Co.,Ltd)
This library uses SoftSerial or HardwareSerial to connect to the SDS011.


This library allows you to communicate with I2C / TWI devices.


LiquidCrystal Arduino library for I2C LCD displays

Step 13: Arduino Code: Variables and Defined Values

On the left image, the first fields are LCD Fields. Here the I2C address is defined as 0x3f for the LCD and I am using a 16 Cols by 2 Rows version. These addresses can vary and I have attached a scanner sketch above should your address be different.

After that, the Alarm and External Port Pins are then defined, and then the SDS011 control object is defined.

The next section defines the MODE handling structures. Here the different modes supported are defined as an ENUM and then a conversion struct is defined and initialized. This is used for display purposes.The support routine enum2str above handles the conversion between the enum MODE and string representation for display.

Next, the Alarm Level global variables are defined and provided with default values. These are updated in the following ways:

  • EEPROM values are read and overwrite these values
  • When the KY-040 knob is turned, these values are changed. They are also saved to EEPROM

The interruptDelay value is used to help debounce the KY-040 by basically ignoring secondary turns during this millisecond length period following an event. This is discussed further later.

The next fields are the KY-040 Rotary Encoder Vars. Here the pins used to connect to the KY-040 are defined.

These are followed by the variables used to track when the knob is turned. When the rotary knob is turned, the TurnDetected variable is set to true in the event handler. The interrupt handler also sets the up boolean to true or false depending upon direction of the turn.

These values are read and processed during the next pass through the main loop.

The last variable declarations are for the EEPROM configuration logic.

The ConfigObj struct contains the three fields we save and restore.

  • MODE
  • P25 Alarm Level
  • P10 Alarm Level

When the program starts up this structure is read in from EEPROM and overwrites the default values declared earlier. Whenever these values are changed, they are saved back to EEPROM.

Step 14: Arduino Code: Setup Routine

The setup routine is where initialization takes place.

We start by initializing the Serial.begin(115200) to allow debugging messages to be written to our serial port.

This is followed by the port initialization section. Most of the ports are output and set to LOW (Off).

The only output port that is set to HIGH is the PIN_ROTARY_5V. This will provide 5V to the Rotary Encoder.

The interrupt pins are set to INPUT. These include:

  • PinCLK
  • PinDT
  • PinSW

This is followed by the interrupt handler (isr) being attached to interrupt 0, which is pin D2.

After the LCD is initialized, the SDS011 sensor is initialized to use ports D9 and D10 as its RXD and TXD ports.


The following code section is for new installations. It creates a ConfigObj structure and sets some default values before writing them to EEPROM. This should be run only once as it will overwrite any previously saved values stored.

This is done by un-commenting the code, flashing the code, then re-commenting it out, and flashing once again. This can be done multiple times if desired. In production, though, these lines should be commented out or new programming will be ignored.

Next the EEPROM stored values are read and the MODE and Alarm Levels are initialized to the last saved values.

After this, the values are written to the LCD and the LCD is illuminated.

Step 15: Arduino Code: Loop

The main loop runs continuously with no pauses or low power settings.

The first task in the loop is to check to see if we have had a button push on the KY-040. If it has been pushed, the MODE has been changed and we need to save it to EEPROM and reflect the change on the LCD display.

Next we check to see if the KY-040 knob has been turned, this is updated via the event handler, isr.

If it has been turned, we update the Alarm Levels according to the direction turned and then sanity check the new settings. After the check, we save the new values to EEPROM and update the LCD display.

Next we actually read the SDS011 and if no error occurs, we:

  • Update the LCD display with the new values
  • Check the new values versus our Alarm Levels

Step 16: Arduino Code: Support Routines

The support routines:


Interrupt handler for the KY-040. It assists in debouncing events with a delay and then sets the global variables TurnDetected and up (direction) which are processed in the main loop. Time spent in the interrupt handler is minimized and should remain minimal.


This is the poll routine called from the main loop. It checks to see if the KY-040 button has been pushed and if it has, it updates the MODE to the next defined mode.


Controls the EXT and PIEZO alarms by checking the values passed in versus our programmed monitor levels. Based on MODE and values, it will turn on or off the alarms by setting the pins HIGH or LOW.

Step 17: Arduino Code: LCD Support Routines

The LCD support routines keep all the formatting logic in one place to make them easier to maintain.


Prints the labels for the Particle Sizes on the LCD.


Formats and displays the sensor values read and displays them on the LCD.


Displays the current Alarm Levels on the LCD.


Displays the current MODE on the LCD.

Step 18: Arduino Code: EEPROM Support

The EEPROM routines handle the reading and writing of the EEPROM storage on the Nano.


Write takes the ConfigObg object and updates the EEPROM with it. The values within it are set externally.


Read retrieves the vales from EEPROM and initializes the ConfigObj.

Step 19: 3D Print: Full Case

The Sensor device case is printed in two parts which are assembled using a basic slide-on design.

The case was designed with Fusion 360.

The case parts were printed on a Prusa MK3S using Prusament Galaxy Black PLA.


This design has 2 upper flanges for attachment to either a wall or shelve edge. As a 20mm piece of flexible tubing is used as an air intake, it is important not to have that tube pressed against any surface that could affect the air intake. Not allowing open air intake could result in the readings being skewed.

Below the STL's for the 3D printable parts are attached:

Step 20: 3D Print: PCB and SDS011 Holder

The SDS011 and the 5x7 PCB are screwed into the case to hold them in place. The bottom was printed without supports.

No holes are provided for the screws, so they must made by hand.

There are 2 holes cutout for an AC adapter/Battery power plug and an additional plug for an external 5V logic-level device such as an SSR. Both are countersunk to make it easier to thread on the holding nut from the the inside of the case.

Recommended to print with the bottom down (see picture 3) on the print bed .

Step 21: 3D Print: LCD Holder

The reverse view of the LCD holder-slide on top. The 1602 LCD w/ I2C board sits in the cutout and there is space to attach 2-4 screws as wished.

There is a cutout for the I2C board pins attached to the 1602 LCD, they may or may not need trimming to fit. There is also a cutout for the KY-040 encoder. The washer and nut are attached on the front and will be partially hidden by the rotary switch cap.

Step 22: Assembly - Insert Jacks and JST Cables

First solder the shortened JST male cables to the input and EXT jacks. The shorter prong is the positive on the Jacks I used.

Insert the Jacks into the countersunk holes and tighten with the nuts.

Step 23: Assembly - Attach PCB Board

I used 3 screws to attach the PCB board on the pads.

I then attached the input and output JST cables to the appropriate connectors on the PCB board.

Step 24: Assembly - Attach the SDS011

Next I attached the dupont-molex cable to the SDS011 and attached that to the pads using 3 screws.

Step 25: Assembly - Attach the SDS011 Cable

Plug in the SDS011 Molex cable to the proper connector on the PCB board

Step 26: Assembly - Attach LCD to LCD Cover

Using the LCD cover, attach the 1602 LCD in the slot using 2 to 4 screws as required.

Leave the I2C cable off for now.

Step 27: Assembly - Attach KY-040 Rotary Encoder to Front Cover

Attach the dupont-molex cable to the Ky-040 and insert it into the front cover in the space provided.

Attach the washer and nut on the front side and tighten.

Step 28: Assembly - Add I2C Cable to LCD

Here we just attach the I2C cable to the I2C board on the 1602 and the front cover is ready.

Step 29: Assembly - Attach LCD and KY-040 Cables

Attach the molex connectors from the 1602 and the KY-040 into the appropriate connectors.

Step 30: Assembly- Attach Cover

Attach the LCD cover to SDS011 Holder.

The upper side of the cover has space to allow easy slide on.

Step 31: Assembly - Closed Case

Carefully finish sliding on cover. Do not pinch cables.

Attach knob and flexible tubing and it is ready for test.

Be careful to plug the adapter into the proper jack. If you do not remember which is the input power jack, it is easy enough to slide the case open and see which JST is connected to the MCP1702.

Cheers ..


This design has 2 upper holders for attachment to either a wall or shelve edge. As a 20mm piece of flexible tubing is used as an air intake, it is important not to have that tube pressed against any surface that could affect the air intake. Not allowing open air intake could result in the readings being skewed.

Step 32: References

Click on the link to find the corresponding documents/libraries.

Arduino IDE Download




Bi-Directional Logic Level Shifter

Be the First to Share


    • Instrument Contest

      Instrument Contest
    • Make it Glow Contest

      Make it Glow Contest
    • STEM Contest

      STEM Contest