Tutorial: Air Quality on the Raspberry Pi with the HM3301

hm3301

Tutorial: Air Quality on the Raspberry Pi with the HM3301

Introduction

The Laser PM2.5 Sensor (HM3301) is a next generation of laser dust detection sensor, which is used for continuous and real-time detection of dust in the air.   It is an inexpensive, yet accurate, way of measuring air quality in terms of AQI,

It is very different from the older versions of dust detectors and Different from the pumping dust detection sensor, and uses a fan to drive air during sensing and the air flowing through the detection chamber is used as a test sample to perform real-time and continuous test on dust of different particle sizes in the air.

SwitchDoc Labs is looking at using the HM3301 in its next generation weather stations.  We currently use the less expensive PPD42NS Dust Detector, but when the HM3301 Laser Detector became available, we had to check it out.

You can buy the Grove HM3301 here on shop.switchdoc.com.

 

The HM3301 Raspberry Pi Problem

We are designing weather stations based on the open source software model on the Raspberry Pi, such as SkyWeather.  In evaluating the HM3301, we found several Python based drivers for the I2C based HM3301.  One of these, for example, is the SeeedStudio python driver here.

One of the requirements of the SeeedStudio driver is that you have to set the I2C frequency on the Raspberry to 20,000 baud, which is 20% of the basic rate of 100,000 baud (100KHz) that I2C drivers usually use.   The 80% reduction of speed is bad enough (we use lots of I2C devices in our design – the speed reduction hurts) but the real problem is that you can’t reliably reduce the I2C to 20,000 baud on all Raspberry Pi devices (Zero, W, 3B+, 4B) and it just doesn’t work at all on the new Pis and the Buster OS.   The driver will not work.

The solution turned out to be using the pigpen library to do what is called bit-banging on GPIO ports to simulate a 20KHz I2C interface.   More on that below after a brief discussion of the HM3301

The HM3301

The HM-3301 Dust Sensor is based on the advanced Mie scattering light theory. When light passes through particles with quantity same as or larger than wavelength of the light, it will produce light scattering. The scattered light is concentrated and focused on a highly sensitive photodiode, which is then amplified and analyzed by the internal circuitry.   Using a specific mathematical model and algorithm you can obtain the count concentration and mass concentration of the dust particles.   Very nice.

The HM3301 is composed  a fan, an infrared laser source, a condensing mirror, a photosensitive tube, a signal amplifying circuit and a signal sorting circuit.

HM3301
Block Diagram

Following is a graph showing the performance of the HM3301 against other dust analyzers.  It performs quite well.

The Solution

The solution to the i2c problem on the Raspberry Pi was surprisingly subtle.   After spending about 8 hours on trying to get the hardware Raspberry Pi solution to work, we finally came to the conclusion that we would have to disable the hardware I2C interface and implement a software interface on the same pins.   This was a giant kludge and would effect every other I2C device that we use and no doubt break some of them (like the very temperamental AM2315).   However, this line of thinking led us to look at implementing an software I2C interface (a bit-banging interface where all the transitions of the I2C bus is handled by software rather than hardware) on a set of two GPIO pins, thus leaving the hardware I2C interface alone and working normally.

To figure out exactly what was happening on the I2C bus on the Raspberry Pi we used our excellent Saleae logic analyzer (learn what this is and how to use it here) and used the data to debug our statements.   Following is a picture of the completed and correct I2C sequence from the Saleae logic analyzer.

HM3301 Raspberry Pi Logic Analysis

This bit-banging was accomplished by using a great GPIO library that allows accurate timing on the GPIO pins on the Raspberry Pi, pigpio.   We used the Python interface for pigpio here.

The commands needed for bit-banging are few but fairly complex to use.

I2C BIT BANG
bb_i2c_open Opens GPIO for bit banging I2C
bb_i2c_close Closes GPIO for bit banging I2C
bb_i2c_zip Performs multiple bit banged I2C transactions

You can review all our Python code here on GitHub. https://github.com/switchdoclabs/SDL_Pi_HM3301

The two most complex parts of the driver are the two sets of I2C sequenced commands:

This is implemented in the following code:

 

(count, data) = self.pi.bb_i2c_zip(
             self.SDA, [4, self.I2C_Address, 2,7,1, 0x80,2,7,1,0x88,3,0])

The commands in this string are from the pigpio I2C Bit Banging interface:

Name Cmd & Data Meaning
End 0 No more commands
Escape 1 Next P is two bytes
Start 2 Start condition
Stop 3 Stop condition
Address 4 P Set I2C address to P
Flags 5 lsb msb Set I2C flags to lsb + (msb << 8)
Read 6 P Read P bytes of data
Write 7 P … Write P bytes of data

The address, read, and write commands take a parameter P. Normally P is one byte (0-255). If the command is preceded by the Escape command then P is two bytes (0-65535, least significant byte first).

Next we implemented the read data command:

(count, data) = self.pi.bb_i2c_zip(
             self.SDA, [4,self.I2C_Address,2,7,1,0x81,3, 2,6,DATA_CNT,3,0   ])

We used a Pi2Grover board to make the connection easy.

HM3301 Raspberry Pi

 

And then we received the data in a byte array. A little bit of data formatting and we were ready to go:

sudo python3 testDustHM3301.py
data= [1, 1, 3, 1, 1, 3]
PM1.0 Standard particulate matter concentration Unit:ug/m3 = 1
PM2.5 Standard particulate matter concentration Unit:ug/m3 = 1
PM10  Standard particulate matter concentration Unit:ug/m3 = 3
PM1.0 Atmospheric environment concentration ,unit:ug/m3 = 1
PM2.5 Atmospheric environment concentration ,unit:ug/m3 = 1
PM10  Atmospheric environment concentration ,unit:ug/m3 = 3
 
 
 
AQI= 4

Note that you may have to change your GPIOs for SDA and SCL. In this example, we have the HM3301 SDA connected to GPIO 21 and SCL connected to GPIO 20.

hm3301 = SDL_Pi_HM3301.SDL_Pi_HM3301(21,20)