This is the second Instructable in the series: How to Make and Calibrate a Portable, Accurate, Low Cost, Open Source Air Particle Counter. The first installment, How to Build a Portable, Accurate, Low-cost, Open Source Air Particle Counter, can be found here. The third installment, How to Build a Monodisperse Particle Generator for around $300, can be found here.

This is a project by Rundong Tian, Sarah Sterman, Chris Myers, and Eric Paulos, members of the Hybrid Ecologies Lab at UC Berkeley.

To test a particle counter, one easy way is to inject particles into a sealed chamber containing a reference sensor and the sensor you are testing. The particle concentration will decay naturally, and large amounts of data through a broad range of particle concentrations can be collected by both sensors and compared.

This Instructable presents a template for setting up such a chamber, from readily available parts. To greatly simplify the fabrication of the test chamber, we use an off-the-shelf weather-tight trunk as the starting point. While we present the method for building our chamber in detail, it is not meant to be a set of prescriptive requirements. Everything is modifiable. The steps presented here will cover all the main components necessary; the details can be tailored to best fit your particular needs.

Various types of particles can be tested using this chamber:

Burning wood can be used to generate polydispersed (wide range of sizes) particles, and can be easily generated by lighting a match, or burning toothpicks/barbecue skewers. The particle size distribution of wood smoke has been well studied, so we have a rough idea of the size of particles we are generating.

For monodispersed particles (particles are of known and uniform diameter), polystyrene latex (PSL) beads can be used. By using particles of known sizes, we can calibrate our sensor’s voltage response for specific sizes of particles. This is important for sorting particles measured in the real world into approximate size bins.

Though PSL beads are very expensive (~$50-70 for a 1 mL bottle depending on the size of the particles), it is overall the cheapest way to dispense particles of known and uniform sizes. Barring any spills, one bottle should last forever. Nebulizing monodispersed particles is covered in our third Instructable: How to Build a Monodisperse Particle Generator for less than $300.

With all the materials and equipment at hand, building this test chamber should take ~1 weekend.

Step 1: High Level Overview

By using a ‘weather-tight’ trunk, we can quickly build a relatively air-tight box. Because we are only concerned about making relative readings between sensors, it doesn’t matter if we are losing particles to, or gaining particles from, the outside environment. We considered making the box anti-static (with anti-static spray) to decrease the rate of particle loss to the surfaces of the chamber, but did not think it was necessary.

Power and USB cables are passed through using sealed bulkhead connectors, and fans which mix the air are mounted to the lid to maximize floor space for the sensors. A larger bulkhead for a HEPA filter is also attached, so that air can pass through when we are vacuuming or blowing compressed air into the chamber to clean it.

From our experience, we found that it was much easier to reduce the particle concentration inside the chamber by blowing in clean air, rather than vacuuming air through the filter. The first option requires a compressed air supply with filters for conditioning, and is covered in our third Instructable. The setup for the second option is covered in this Instructable.

The reference monitors we used were the MetOne HHPC6 and the Dylos DC1100 Pro.

Tests to Run

We used this chamber to conduct tests with injected smoke and nebulized PSL beads. The chamber was also used to transport all the sensors for outdoor tests. For a more in depth description of the tests we conducted with this setup, please see: https://github.com/rutian/MyPart/wiki/Tests.

Step 2: Files/Materials/Tools


All the design files for the test chamber can be found here. The mechanical components were designed in Inventor Fusion 360 (for the physical threads of the bulkhead connectors) and Autodesk Inventor.


Please see the BOM for the full list.


  • Hole saw or spade drill bit
  • Power drill
  • Soldering iron
  • Wire cutters/strippers
  • Jigsaw blade, or similar
  • 3D printer (optional)
  • Circuit board mill (optional)
  • Laser Cutter (optional)

Step 3: Modify the Box - Access Holes

We will create three holes in the chamber for bulkhead fittings. One optional bulkhead fitting will allow the vacuum cleaner hose to be attached. The other two holes will be used for a power strip and two usb extenders.


  • Weathertight plastic trunk
  • Bulkhead fittings
  • Silicone caulking
  • USB extender cable (x2)
  • Power strip


  • Power drill
  • Hole drill bit
  • 3D printer (optional)

First, buy or 3D print the bulkhead fittings for the holes. Make sure that the internal diameter of one fitting is large enough to pass the power strip plug through. The bulkhead connector for the vacuum may need to be printed to get the right fit with the vacuum cleaner hose. For our 3D printed bulkhead fittings, we added a laser cut cork gasket for sealing. An O-ring of the appropriate size can be used instead.

Using a laser cutter, cut out spacers for holding the cables in the center of the bulkhead connector. This is optional, but we think it makes it a little easier when caulking the inside of the bulkhead.

Using a hole drill bit, cut three holes into one side of the box, sized for your fittings. We arranged them in a vertical row along the edge of one long side of the chamber.

Once the bulkhead fittings have been attached, insert the power strip through one fitting and the 2 USB extenders through the other fitting. Leave enough length on the inside of the chamber for the power strip and USB cable to reach their final positions; make sure the power strip can lay flat. Use the laser cut spacers to position the cables in the fitting so that it does not touch the wall of the fitting (optional). Fill in the bulkhead connectors with silicone caulking. This will make an airtight seal, preventing air and particles from escaping through the fittings.

For our tests, we attached one USB extender to a 4 way USB hub inside the chamber. The second USB extender was never used.


  1. If you do not choose to use a vacuum during a particular test, cover the vacuum fitting with an end cap. If you will never wish to use a vacuum, you can leave out this hole entirely.
  2. You may wish to include other cables, e.g. oscilloscope probes for obtaining analog signals.

Step 4: Modify the Box - Filter Bulkhead

A HEPA filter is added to the wall of the chamber to clean the air entering the chamber when vacuuming, or filter the exhausted air when compressed air is blowing in.


  • HEPA filter
  • Laser cuttable wood for frames (we used 1/4in econowood)
  • M3 screws and nuts


  • Jigsaw
  • Laser cutter
  • Power drill


    1. Using a jigsaw blade, cut out a rectangular hole in one short end of the chamber, the size of your HEPA filter.
    2. Apply a layer of foam tape around the perimeter of the HEPA filter to seal around the bulkhead.
    3. Make a frame for the filter (we laser cut layers of 1/4" econowood and glued them together to the proper thickness). A gasket was laser cut using some cork gasket material we had on hand. Silicone caulking could also be used here to seal the bulkhead with the chamber.
    4. Once the glue has dried, use the frame as a drill template for the holes on the chamber that the bulkhead will be secured with.
    5. Insert the filter into the frame, noting the preferred direction of air flow for the filter.
    6. Attach the frame and filter to the box with the M3 hardware.

    Step 5: Make a Ceiling Fan

    In order to mix the particles inside the chamber during a test, we mount an array of computer cooling fans on the ceiling of the chamber. These fans can be placed elsewhere; we chose the ceiling to maximize the amount of available floor space for other electronics.


    • Screws and standoffs, or threaded rod and nuts
    • Computer fans (x4)
    • Stranded hook up wire
    • Laser cuttable wood for mounting plate (we used 3mm MDF)
    • Solder


    • Laser cutter
    • Power drill
    • Soldering iron
    • Wire cutters


    1. Laser cut the mounting plate for the fans. Mount the fans to the plate using the screws included with the fans; the fans should blow downward when mounted into the chamber.
    2. (optional) For wire management, the mounting plate has some holes to attach the extra lengths of wire with zip ties.
    3. Solder the control PCB. This board has an npn transistor for driving the four fans, LED for power indication, barrel jack connector for power, screw terminal for control signal, and headers to connect to the fans. Please see the Eagle file for more details.
    4. Mount the control board to the fan plate. Holes for the control PCB are not in the current dxf file. You may want to mount the PCB facing away from the lid so that the screw terminals can be easily accessed.
    5. Cut 2 pieces of stranded wire to connect ground and signal from the control board, long enough to reach the floor of the chamber (and then some; you may want to set the top of the box down without removing the wires). These wires will later connect to the Arduino that will control the fans.
    6. Using the fan plate as a guide, drill four holes into the lid of the box.
    7. The fan mounting plate can now be suspended from those holes using either a series of stacked standoffs or a threaded rod with nuts to create the proper spacing. The threaded rod is more robust, but the standoffs are easier to assemble.

    Step 6: Put a Servo on the Dylos (*optional)

    The Dylos has a limited lifespan, and cannot be turned off programmatically via serial. In order to run long tests (e.g. collect samples once every 10 minutes for a day) without wasting the Dylos’s lifespan, we mount a servo motor to the Dylos to press the power button to turn it on and off between sampling cycles.


    • Velcro
    • Small C-clamp
    • Servo

    Find the proper positioning on the left side of the Dylos such that your servo arm can press the power button. Using velcro tape, attach the servo to the Dylos. This will constrain planar movement on the Dylos case and allow us to adjust the position of the servo. Tighten the C-clamp over the servo, pressing it to the Dylos to constrain movement in the third axis, off the case.

    The servo motion is defined in talk_to_arduino_via_serial.ino. To modify the motion, you can change the globals off_degree (the angle of the servo when not pressing the button) and on_degree (the angle of the servo while pressing the button) at the top of the file. Optimal values may depend on the placement of your servo.

    Step 7: Test Chamber Electrical Setup


    Setting up the electronics inside and outside the box requires some device modification, some breakout board building, some Arduino wiring, and plugging in a bunch of cables.

    Chamber Setup

    Having finished the hardware setup for the chamber, you should already have a USB extender cable running into the chamber through the wall, as well as a power strip. Plug the USB expander into the USB extender on the inside of the chamber, and affix the expander firmly to the chamber wall or an out of the way spot on the chamber floor. Gather the power adapters for the internal devices, and plug them into the power strip on the inside:

    • 9V for Dylos
    • 12V for fans
    • 5V for USB hub
    • 12V for MetOne (for charging)

    Step 8: Vacuum Hacking (*optional)

    You may want to use a vacuum to cycle the air out of the chamber, either during tests or to clean the chamber afterwards. To programmatically control your vacuum from the testing script, you need to add a relay to the vacuum.


    • Vacuum
    • Wires
    • Relay
    • Solder


    • Soldering iron
    • Wire strippers


    1. Open up the vacuum
    2. Find the switch. Cut/strip/solder new wires in parallel with the switch, so that the vacuum can be controlled either manually, or with the relay.
    3. We mounted the relay onto the body of the vacuum with self tapping screws and some spacers.
    4. Attach the wires from the vacuum into the relay. One set of spacers has features that allows the wires to be zip tied to it to prevent accidental pullout of live wires.
    5. Attach three long wires to signal, ground, and microcontroller power on the relay. These will run up to the external Arduino.

    Step 9: Humidity Sensor Breakout Board (*optional)

    The humidity sensor we use to monitor temperature and humidity in the chamber is the Texas Instruments HDC1080. It is mounted on a breakout board with the Texas Instruments TCA6507 LED driver. You can either assemble this breakout board, or use a commercially available humidity and temperature sensor breakout board.*

    This was the same breakout we used to test the chips used on the MyPart device.

    To assemble this board, you need:


    • PCB blank
    • 6 pin header
    • TCA6507 LED driver
    • HDC1080 Humidity sensor
    • 0805 LED (x3)
    • 0805 10k resistors (x2)
    • 0805 .1 uF capacitor
    • 0805 resistors (x3) (of value appropriate for your LEDs)
    • Solder


    • Circuit milling machine
    • Soldering iron

    Find the eagle files for the breakout board here (humidity_led_breakout.brd/.sch).

    Mill the PCB on the circuit milling machine, and solder down all components. This board will later be attached to an Arduino shield, so we can monitor the ambient conditions of the chamber during tests.

    *If you choose to do the latter, you will need to modify the code. Relevant sections will be found in talk_to_arduino_via_serial.ino, and arduino_serial_com.py.

    In talk_to_arduino_via_serial.ino, replace the contents of the TAKE_AMBIENT_READS switch case with the code for your new sensor. Then, in arduino_serial_com.py, find the function read_from_arduino_sensors() and modify it to process the proper number and type of bytes from the serial port.

    Step 10: Connecting the MyPart Sensors

    Every MyPart has an FPC connector that gives programmatic access to the button that controls sampling and data reporting. In this step, we make the connectors to attach the internal Arduino to the FPC cable of each MyPart device.


    • PCB blank
    • 8 pin male header
    • FPC connector
    • Solder
    • Female connector wires
    • Male connector wires
    • Twist on wire connectors (x2)


    • Soldering iron
    • PCB mill


    1. Mill the small PCBs for connecting to the FPC cable; you will need one per MyPart sensor. Solder in the FPC connector and the header pins to the PCB.
    2. All MyPart sensors will be controlled by a single microcontroller pin.
    3. We made groups of connected cables for the ground and signal wires using twist on wire connectors.
    4. The signal pin will need to be pulled down to ground to tell the MyPart sensors to sample. Because the Arduino gpios cannot be configured as open drain, we will need to hook up an additional transistor (see next section on wiring the internal Arduino)

    Step 11: Wiring Internal Microcontroller

    The internal microcontroller has several jobs. It needs to run the ambient humidity and light sensors, connect to the MyPart sensors, toggle the Dylos servo, and run the fans. This can all be wired on a single Arduino prototyping shield:

    1. Attach the humidity sensor breakout board. This board talks over I2C, so we need to connect the SCL and SDA pins to the Arduino SCL and SDA, as well as 3V power and ground.
    2. Attach the TSL2561, light sensor breakout board. This board also talks over I2C, so we need to connect the SCL and SDA pins, as well as 5V power and ground.
    3. To connect to the MyPart sensors, we need to add a transistor. This will let us send button press signals to the MyPart sensors. The MyPart signal pin is pin 6, so we attach the transistor to ground, digital pin 6, and a single male header pin to which we will attach the MyPart sensors’ one-to-three breakout wire.
    4. To be able to easily connect and disconnect the servo, solder a 3-pin female connector onto the shield. Connect the pins to ground, 5V power, and digital pin 3. This will let you plug in and remove the wires from the servo easily. Attach a pull down resistor between the pin 3 connector and ground, to prevent twitching.
    5. Last, screw the power line for the fans into the shield terminal for pin 4, and attach the ground wire to the shield terminal for ground.

    Once the shield is finished, attach it to the internal Arduino, and plug the Arduino’s USB cable into the USB expander inside the chamber.

    Step 12: Wiring External Microcontroller (*optional)

    The vacuum relay needs to attach to the external microcontroller. Connect the signal wire to pin 4, and connect the ground wire to the Arduino’s ground. Plug the Arduino’s USB cable into the computer that will run the tests.

    Step 13: Hooking Up Dylos and MetOne

    Attach power cable and serial cable to the Dylos. The Dylos needs to be constantly powered at 9V. Connect the USB end of the serial cable to the USB expander inside the chamber.

    Attach USB to serial converter and serial to RJ45 converter cable to the MetOne. The MetOne can run off of battery power; if the battery is low, connect it to 12V power. Connect the USB end of the cable to the USB expander inside the chamber.

    Step 14: Obtaining Raw Analog Signals

    To obtain the raw analog signals from the MyPart sensors, we used a Saleae logic analyzer. This is used when we want to analyze the raw output of the sensors when PSL beads of known sizes are dispersed into the chamber. (See the third Instructable: How to Build a Monodisperse Particle Generator for around $300)

    The automated testing script is set up to allow automated readings from a Saleae Logic Analyzer. Solder test leads to the MyPart sensors (as described in the previous Instructable How to Make and Calibrate a Portable, Accurate, Low Cost, Open Source Air Particle Counter). Attach the signal and ground probes from the Saleae to these wires. Plug the Saleae into the USB expander.

    Alternatively, you can run oscilloscope leads through another bulkhead fitting, seal the bulkhead with silicone, and attach them to the MyPart test leads. In this way, you can keep your oscilloscope and power outside the chamber.

    Step 15: Final State

    Once all the previous steps are finished, you should have the Dylos, MetOne, and internal Arduino connected to the USB hub inside the chamber. The Dylos, fans, and USB hub should be plugged into the power strip. The MetOne should either be fully charged, or also plugged in. Ensure the MyPart sensors are fully charged.

    Arrange the sensors in the chamber so they are not expelling air directly into each others’ intakes, to the best of your ability.

    Outside the chamber, ensure the RFduino and the external Arduino (if you are using it) are plugged into the computer via USB. If you have extra space in your USB expander, you can also attach the RFduino inside the chamber for consistency’s sake.

    Step 16: Software Setup and Execution


    This code was written in Python 2.7. These are the libraries you will need to run the software:

    Python libraries:

    • time
    • datetime
    • os
    • serial
    • csv
    • struct
    • crc_algorithms (included in the MyPart repo)
    • array

    Arduino libraries:

    • Servo.h
    • Wire.h
    • Adafruit_Sensor.h
    • Adafruit_TSL2561_U.h
    • RFduinoGZLL.h

    Custom Arduino libraries:

    Step 17: ​Programming the Microcontrollers

    The test chamber involves 3 separate microcontrollers. The fans, ambient sensors, servo, and MyPart signaling controls will be hooked up to an Arduino inside the chamber. If you choose to attach a vacuum, this will be controlled by an Arduino outside the chamber. An RFduino outside the chamber will listen for data sent by the MyPart sensors.

    To set up these microcontrollers:

    1. Add the custom libraries to your Arduino environment by copying the folders into your Arduino library folder, and restarting the IDE.
      • The custom HDC1080_sensor library contains code to talk to the Texas Instruments HDC1080 humidity sensor chip, used for measuring ambient chamber conditions, and on the MyPart sensors.
      • The custom TCA6507_driver library contains code to run the Texas Instruments TCA6507 LED driver chip on the MyPart sensors, to control the indicator LEDs and to act as a GPIO expander.
    2. Load the microcontroller code. All microcontrollers can be programmed from the Arduino IDE. (If you have never programmed an RFduino from your machine, you will need to add RFduino to the Arduino board manager.) The microcontrollers should run the following files:
      • Internal Arduino: talk_to_arduino_via_serial.ino
        • This sketch listens for serial commands from the python script, then sends commands to hardware inside the chamber, and returns data over serial.
      • External Arduino: talk_to_arduino_via_serial.ino
        • The external arduino uses the same sketch as the internal, but will end up using fewer of the available commands.
      • RFduino: mypart_gzll_host.ino
        • This listens for data over the GZLL protocol, and reports it over serial for the python script to pick up.

    Step 18: Running the Testing Script

    The main testing file is sensor_reads.py. You should not need to modify anything outside that file (unless you want to customize behavior). There are three sets of parameters at the top of sensor_reads.py to modify before running your tests:

    1. Chamber setup / Serial Parameters
      • dylos_comport, hhpc_comport, internal_arduino_comport, external_arduino_comport, gzll_rfduino_comport
        • Update the comports to match those for your microcontrollers and the Dylos and MetOne HHPC sensors.
      • baud, tm, mypart_tm
        • These likely will not need to change, however you can specify baud rate and timeout for serial reads/writes here. The MyPart sensors have a separate timeout since it is possible to miss data over bluetooth, and the shorter timeout allows us to retry.
    2. Data saving setup
      • csv_name
        • Update the name for the output csvs. The testing script will generate multiple output files -- one for each sensor type. They will all be named based on the string defined here.
      • parent_folder_path
        • Update the path to where you want the results saved.
    3. Testing parameters
      • cycles
        • Specify how many cycles to run. A cycle is made up of a certain number of consecutive samples -- each spaced one minute apart. Between cycles, you can choose to mix or vacuum the air in the chamber, or pause for a certain amount of time (e.g. wait for an hour to take the next reading).
      • samples
        • Specify how many samples per cycle. Each sample will record a reading from all the sensors in the chamber. Samples are spaced one minute apart.
      • vacuum_time
        • Specify how many seconds to vacuum the chamber between cycles. If this is zero, vacuuming will be skipped.
      • mix_time
        • Specify how many seconds the fans should turn on for between cycles. This will mix the air in the chamber. If you want the fans on continuously during measurement, enter -1. If mix_time is 0, the fans will not be turned on.
      • sleep_minutes
        • Specify how many minutes the chamber should wait between cycles. If this is greater than zero, the Dylos will turn off, and the fans will turn off. If this is zero, samples will be taken continuously with no pause between cycles.
      • num_myparts
        • Specify how many MyPart sensors are hooked up. The script will expect to receive data from this many sensors.
      • sensor_on
        • This dictionary lets you indicate which sensors you will be using; if you choose not to use one, set that sensor to False and the code will skip anything to do with that sensor. Note that the test timing depends on the Dylos, and if you choose not to use it, you should expect to see odd timing behavior unless you add your own timing delays.
      • manual_test
        • This is a flag to allow you to run tests manually. When true, the script will wait for terminal input, then run the entire testing process, then wait again for terminal input to run the testing again. We suggest manually turning off the Dylos if you intend to wait a long time between manual tests. From terminal, inputting ‘t’ runs a test, and ‘q’ ends the program.

    (If you are including an external Arduino for the vacuum, comment in the serial setup line for the external Arduino in the run_test function in sensor_reads.py.)

    Once the parameters have been set up, simply run sensor_reads.py.

    Step 19: Troubleshooting

    1. If it cannot find the right comport:
      • e.g. serial.serialutil.SerialException: [Errno 2] could not open port /dev/cu.fakeserialport
      • Double check that the comports in sensor_reads.py match the comports the devices are on. If you unplug and replug the cables, the comport name may change.
    2. If it cannot open a serial connection:
      • e.g. serial.serialutil.SerialException: [Errno 16] could not open port /dev/cu.serialportname: [Errno 16] Resource busy: '/dev/cu.serialportname’
      • Make sure that there is not another serial connection on that port already (e.g. a serial monitor open in the Arduino IDE)
    3. If it hangs or times out waiting for the Dylos:
      • Make sure the Dylos is plugged into the serial cable, and is turned on when you begin the test.
    4. If it can’t find the other python files:
      • Make sure you are running sensor_reads.py from the same directory as the supporting python files.
    5. MyParts are not recording data after the first sample, or timestamps are greater than one minute apart.
      • The timing has gotten out of sync. Make sure you haven’t increased any timeouts, delays, or retries beyond the available 59 seconds between Dylos reads.

    Step 20: Future Improvements

    • Make the chamber out of a clear box, rather than a frosted box.
      • It’s nice to be able to see inside without opening the box.
    • Use threaded rods instead of standoffs to mount fans.
    • Alternate sealant for the bulkheads.
      • Silicone is okay since we are operating at low pressure, but it doesn’t seem to stick super well. Other adhesives (epoxies?) may work better.
    • A larger box may be helpful
      • With all sensors + Saleae, it gets crowded in the box. Would also help with cable management.
    • The GZLL protocol for getting data from the MyPart devices can only support up to 7 devices with one host.
    This is really great.!

    About This Instructable




    Bio: I'm a grad student at UC Berkeley in the Hybrid Ecologies Lab.
    More by rawrdong:How to Build a Monodisperse Particle Generator for around $300 How to Build a Test Chamber for Air Particle Sensors How to Build a Portable, Accurate, Low Cost, Open Source Air Particle Counter 
    Add instructable to: