The parts needed for this project:
- ATtiny25 microcontroller
- AM2302 temperature and humidity sensor x 2
- BMP085 or newer version
- GP2Y10 optical dust sensor
- Analogue UV sensor
- GM counter - this one has a serial output
- suitable case - I reused a jewellery box
- screws, spacers, wires and a pcb board
- 3 x pull up resistors
- 1x 150 resistor
- 220 uF electrolytic capacitor (for the GP2Y10)
- 0.1 uF ceramic capacitor
Here are the connections:
AM2302 - DHT
Let's start with the odd sensor. This is the AM2302, I used an Adafruit experimental driver to drive this device, works mostly well. Sometimes hangs, so you'll have to take care of killing it from shell script or fixing it. The downside is that you'll need a GPIO for each of the sensors. But you don't need to worry about I2C in return.
Since I wasn't satisfied with the BMP085 drivers already available for my purposes, I wrote one based on the datasheet. There is a generic version and a weather station version which gives the pressure at sea level, provided you know your altitude. You can find the altitude from this map. Make sure you correct it with the actual height the device will be at.
The connection of the GM counter was a piece of cake, since I have the mightyohm version, it sends the data over a simple serial protocol, so that is very straightforward to deal with. The code for this is on github, too.
I tried to bitbang the GP2Y10 over I2C, since I had an ADC chip, already. I run a couple of tests and I found that this protocol is simply too slow for this sensor, at least on the low frequencies I found it to be reliable, so I decided to include an ATtiny25 over I2C to deal with that.
I'm going to explain very briefly how this sensor works. The circuit of this sensor is divided into two parts. There's the IR emitter circuit and the IR receiver circuit. The IR diode flashes and lights the dust particles in the chamber. The IR receiver circuit measures the amount of the reflected light from the particles and gives out a voltage on the output terminal based on how much light was reflected. This has to be done quickly.
To get a reading, you have to do the following - this is based on the datasheet. (1) pull pin3 low, this switches ON the IR diode. The IR led is connected to the collector of a PNP transistor. You're pulling the base low to put the transistor in forward-active mode. (2) Wait for 0.28 ms, then (3) read the output voltage on pin5. (4) Wait another 0.04 ms, (5) pull pin3 high and (6) wait 9.68 ms, so one complete cycle is 10 ms altogether. Then repeat the whole procedure 1-6 to get the next reading. One simple measurement is implemented in the dust_measurement() function. Here is an example code you can run on the ATtiny25 to get a dust reading. Please note, this is combined with the UV code (see below), but you can safely ignore those parts.
An ADC conversion takes time, too, that is not included in the code, this is just an example code to understand how things work. This example code will work, though. The limitations are that you are not going to get a reading exactly at 0.28 ms - you'll get the same problem on an Arduino, too. If you look at the GP2Y10 datasheet, this is not going to cause a too big problem in terms of the output voltage. Just take more than one reading to get the dust measurement. To dig deeper in this field, check out the datasheet of the ATtiny25 to get an idea about ADC trigger modes, times for conversions, ADC cycles and interrupts.
The same limitations apply to an Arduino, too, since that runs pretty much the same ADC. So it doesn't matter whether you write a 280 us delay in your Arduino code, the "voltage reading" won't happen at that instant - just around that. Suppose you run your Arduino's ADC at the maximum resolution in free running mode, which is the basic mode, at the highest frequency possible maintaining highest precision. This is 200 kHz. One cycle is 1/200.000 = 5 us. One conversion takes 13.5 cycles = 67.5 us in free running mode, apart from the first which takes 25 cycles = 125 us. Therefore the ADC registers holding the value when you read it in your code after the 280 us wait can be as old as around 67.5 us, which is the value at 217.5 us after the flash. I said around 67.5 us, because there's a little extra time between conversions, too. When doing the first conversion it can be as bad as the value at 155 us after the flash. Of course, this can be tweaked by using a different trigger, not being in free running mode, etc, but just be aware of these issues when using timings these small with the ADC. Or you can go above the 200 kHz limit and run the ADC at 1 MHz, which according to Atmel doesn't cause significant loss in ADC resolution, however it's not recommended as they say.
After all the hassle I found that this GP2Y10 is not a very useful sensor on its own since it comes uncalibrated. It does respond to change in dust levels and it seems to be sensitive. I also noticed that the dust reading is highly dependent on the humidity, too.
I connected the UV sensor to an ADC input on the ATtiny25; that was sorted, too. If you just want an ADC, see this post. You can find the combined ATtiny25 example code for both, the GP2Y10 and the UV sensor here feel free to modify it, these are just working examples to get started. Eg. to reduce the errors on the readings , you might want to repeat the measurement a couple of times on the ATtiny itself. However, this leads to the perhaps unexpected problem of integer division on a microcontroller. Forget about floating point operations and conventional rounding here, that code will never fit on an ATtiny25. Here's a discussion I liked on this topic.
Client code - logging the weather data into a database
I created a python class for each of the sensors so they can be treated the same way, since all I need is an update from each of them at a certain time.
The generic structure for such code is simple. You need to check the output of a C program and define some instance variables to store the data in. I put all the C drivers and the python classes in a folder called sensors. All will have a method called update, so the client code just needs to call a MYSENSOR.update() to get the data from the sensors. All the python classes are here.
After that, since I have got access to a VPS, I just created a small html output form my logger script and I upload it regularly with a shell script to the VPS. The widget is ready to use. This is a very simple solution to put something on the web. You can see the output on the right here on the site (or at the bottom if you use the mobile version).