My friend has trouble keeping plants alive, so I made her this!
In the June 2012 issue of Nuts & Volts, they did a spread about making a soil moisture sensor with an iPhone interface:
And when I heard my friend say that she has trouble remembering to water her plants, I put two and two together.
Most of the N&V article discussed the code required to interface with the iPhone, but the important part for me was that their soil moisture sensor was nothing more than a simple conductivity sensor. The more water in the soil, the lower its resistance.
Such a sensor doesn't measure soil moisture in any kind of universal unit, but for a single sensor in a particular soil sample, the measured conductivity is repeatable and proportional to the moisture level. I call these conductivity units "AMU"s or "Arbitrary Moisture Units".
So I wanted to create a device that reminds her to water her plant when it gets dry. Such a device needs a user interface where she can set her AMU threshold and an alarm to alert her when the plant slips below that. It also needs to read out the current moisture level so she can know where to set the threshold. Simply taking a measurement before and after watering should provide good basis for determining threshold.
Once set, the device needs to stay put; moving it in and out of the soil will make it very difficult to get the same AMU reading twice. Besides, if she could remember to insert the sensor on regular intervals, she wouldn't have trouble watering the plant in the first place!
Leaving the sensor in place offers a interesting challenge. For the most part, this device doesn't need to do anything. Soil moisture changes extremely slowly, and anything over a .1Hz sample rate is hardly necessary. Also, the device needs to stay on for months or even years at a time. Plugging it into a wall is inconvenient if there isn't a wall outlet near the ideal plant spot, so the device needs to be battery powered. The question is, how do you get batteries to last for months or years?
Another sub-objective of this project was to make it happen entirely using parts that I already had on hand.
The display is a multiplexed pair of 7-segments LED modules. I had some left over 7-segment displays from a commissioned piece that ended up falling through. They're from Mouser, and as beautiful and blue as they are, at $4.68 a pop, they're ridiculously expensive. I basically designed this thing to find a use for the ones I already had since I couldn't return them.
One note: the connections as they appear in the schematic are reversed. I went through all of the effort of creating a symbol that lines up with the physical part just to mirror the symbol on the schematic and mess everything up. I don't think you can specify schematic symbols as "un-mirrorable", but this particular orientation made my schematic cleaner anyway, so I just had to be careful when writing my firmware not to reverse the digits.
Driving it is fairly straightforward. The datasheet specifies that it's loudest at 2.73kHz, so I just needed to feed it a 3-ish Volt square wave at approximately this frequency. D3 is included as a freewheeling diode. Since this device draws such a large amount of current, I had to make sure that the voltage spike associated with the sharp current change didn't blow up the input to my micro controller.
The buttons are in a simple pull-down configuration. SW1 is connected to INT1 and SW2 is connected to INT0. INT0 and INT1 are the external interrupt pins. This allows the user to pull the device out of sleep manually by pushing a button. More on that later.
Similar to my longboard wheel display, this device has no power switch, but rather drops into a super low power sleep mode when not in use. Of course, this was a little annoying during development when the device would keep beeping to let me know that it was too dry (or that there's no soil at all for that matter), but yanking out a battery was easy enough to do to shut it up.
U2 is a 3V linear voltage regulator that I also used in...actually, I just spent the last 30 minutes trying to figure out why I bought this thing in the first place. Apparently, I purchased it on April 18, 2012, but I can't remember why. My guess is that it had something to do with an earlier draft of the EL Shirt fiasco, but I'm not sure...
Anyway, because my device needs to measure current through soil, it needs a steady voltage source to make sure it gets a steady current. Otherwise, as the batteries begin to die, their lower voltage will affect the measurement. In addition to supplying the current that actually travels through the soil, it also connects to the AREF pin of the AVR which provides the scale for the ADC on board. With this configuration, the ADC will read the highest value when the input voltage is 3V.
PROBE 1 and PROBE 2 are what actually goes into the soil. Q1 is configured as an emitter follower similar to what I saw used in the transistor clock. When current leaks out of PROBE 1 and trickles into the base of Q1 through PROBE 2, Q1 passes current through R1 causing its voltage to rise. The ADC7 pin of the AVR measures this voltage and uses it to determine the moisture level.
This circuit is driven by an ATMega48 microcontroller. I've used this device many times before. In this circuit, it's running off its internal 8MHz oscillator, but I also included an external 32kHz oscillator to keep it running while it's in Power-Save mode.
You'll notice looking at the schematic that all of my FETs have pull up or pull down resistors on their gates. This is to keep them off while the micro-controller is asleep. To reduce power draw during sleep, I switch all microcontroller pins to high-impedance inputs, so these external components are required to keep the FETs off.
Likewise, the 3V regulator's enable pin is pulled down. According to its datasheet, it draws a maximum of 1$\mu$A of current when in shutdown mode which is small enough for my needs.
Because of the complexity of the displays, this circuit required a lot of vias. I decided to try out my new .0135" drill bit which worked like a charm. To give you an idea of how small this thing is, here's me picking Lincoln's nose on a U.S. Penny:
This thing was so small that I could only thread the holes with a single fiber of copper from a stranded piece of wire.
I used the liquid electrical tape from the Strongerer Glasses to protect the fragile unshielded copper strands and keep them from shorting together.
This is also the first project where I've laid parts out at a 45 degree angle. It just turned out to be easier for this particular layout. Getting Eagle to do it is kind of tricky though. The program by default will only rotate a part in multiples of 90 degrees, so I had to manually type in the angle of 45 and then let it rotate at 90 degree intervals starting from there.
The two sensor prongs are just giant exposed copper planes. Ideally, I'd coat them in something that both won't corrode and won't poison the plants, but I don't have anything like that on hand at the moment. Can you say proof of concept?!
Many of the elements involved in writing this firmware are things that I've done before in other projects with a few exceptions.
The device only has two buttons which are assigned to "up" and "down". When initially powered up, the device displays the current AMU reading. Pressing the up or down button will temporarily display the threshold value and allow you to adjust it. This is the kind of interface I've seen used in some thermostats, so it seemed appropriate.
Leaving the buttons alone for about 1 second will cause the display to switch back to the AMU reading, and leaving them alone for 30 seconds will put the device to sleep where it will wake up periodically to take measurements and beep if the moisture is too low.
Pressing either button immediately wakes the device up and sets it back to the original mode.
The buzzer component needs a square wave of frequency 2.73kHz. To generate such a tone, I used Timer1A configured as a CTC timer. This means that the timer increments until it reaches OCR1A and then resets while triggering an interrupt. This interrupt only toggles the output pin, so I actually had to generate twice the desired frequency (5.46kHz).
With a base clock of 8MHz and a 1/64 prescaler on Timer 1, I set OCR1A to 23. That works out to:
Which when divided by two gave me a square wave output of approximately 2.747kHz.
Not exactly 2.73kHz, but as you can see on the datasheet:
There's a small flat zone around the optimal point, so I should be fine.
Getting the power draw of this device down to as low as possible was the biggest challenge of this design. I've done a few experiments with sleep-mode devices before, but most of my projects are woken up by a user pressing a button. This device needs to wake itself up to take periodic measurements of the soil. I've only done this once before, and that was for a quick demo on a breadboard and was like a billion years ago.
With an external low-power 32kHz crystal, the ATMega48 can go into "Power-Save" mode where it shuts down just about everything except for Timer2 which counts in time with the external oscillator. Once this Timer2 overflows, the device wakes up to handle the interrupt and stays awake until it's told to do otherwise.
Given that soil dries out on a time scale of the order hours instead of seconds (or milliseconds), the device needs to stay in Power-Save mode as much as possible. In order to extend this time, I configured Timer2 to interrupt on overflow (256) with a 1/1024 prescaler. This sets my sleep interval to:
So every 8 seconds, the device wakes up. I designed the software to immediately go back to sleep unless a certain number of 8 second intervals have already passed at which point it takes a measurement of the soil and beeps if it's too dry.
I noticed an interesting bug during development where it seemed to ignore this programmed number of intervals and take a measurement earlier than it was supposed to. It was always earlier in multiples of 8 seconds, but always some arbitrary multiple.
I soon discovered that my device was actually waking up, updating its current count of 8 second intervals, and then going back to sleep all before the 32kHz clock ticked once! Because the 8MHz clock is so much faster than the 32kHz clock, it's perfectly possible for the device to go back to sleep before the next 32kHz tick. The flag that wakes up the microcontroller is still set until that next tick, so just as it woke up the first time, the part will immediately wake up again as soon as it goes back to sleep.
Unfortunately, there's no way to directly determine when the next clock tick happens. I could have my code wait in a delay loop for a specified amount of time, but as it's burning battery every millisecond it's awake, making this delay any longer absolutely necessary is inefficient.
To fix this, I found a neat tip on page 156 of the ATMega48a datasheet. The ASSR register is provided to deal with many of the features of an asynchronous clock (the 32kHz clock is asynchronous). There are a few bits of the register that indicate when changes to clock settings (TCCR2x, TNCT2, etc) go into effect. They always go into effect on the next clock edge, so by changing one of those registers and then waiting for the proper bit of the ASSR register to change, I can indirectly determine when the next clock edge has occurred and it's safe to go back to sleep. Of course, the registers are already set where I want them, so I just had it redundantly set a register to its original value.
On the way to solving this issue, I also realized that leaving my ADC on causes the device to draw substantial power (.3mA) even while asleep. I thus changed my code to only enable the ADC while taking a measurement.
With all of these precautions in place, the device draws an incredibly low amount of power while in sleep mode:
Assuming the average AAA battery has about 1000mAh of capacity, that's going to give you over 15 years of use. Now, the device does have to wake up every 8 seconds to do its thing, and it'll draw even more power if it has to run the buzzer to let you know the plant is dying, but these operations take a very short period of time (a number of milliseconds at the slowest), so the user can at least expect a few years of use before having to replace the batteries.
So nothing terribly ground-breaking with this design, but it was a quick and fun refresher, and I learned a few more things about the different power down modes of the ATmega48.
It's also a surprisingly practical device coming from me. I can't wait to hear what she thinks of it once she puts it to use!
Project files can be found here: Soil Moisture Sensor v1.0