06/11/2015 at 21:13 •
Since one of the key features of this project is the low power consumption, I'll go into some more detail on how to archive similar results on AVR microcontrollers.
Use the macros provided by <power.h> to turn off modules that you don't need. My usual approach is disabling all modules when going to sleep and only activating modules as you need them.
Be careful though, registers of disabled modules can't be accessed! I spent a lot of time tracking down a bug where I set up a timer, and enabled it only before starting it.
Whenever you have to wait for an external event or just want to pause the program for a certain time, use sleep modes. As there are many different modes it's important to choose the correct one depending on the task. If you need to keep a peripheral running in the background, you can't use 'power down' mode, instead use 'idle' mode.
I used two flags in my status variable which indicate if either the timer used for debouncing or the USART are running. The main loop will enter 'power down' mode only if both flags are cleared.
If you need to wake up from a sleep mode after a predetermined time, you should use the watchdog timer instead of a normal timer where possible.
The advantage of the watchdog is that it has it's own 128kHz clock and will still continue to operate in 'power down' mode whereas the other timers rely on the I/O clock (aka the main oscillator) which uses a lot of power.
It does have it's disadvantages though, you can only choose from a small selection of timeouts ranging from 16ms to 8 seconds. Also, the 128kHz clock is not calibrated. If you need accurate timing, you'll have to use normal timers.
I used the watchdog to wake the processor from deep sleep after sending a measurement request to the sensor and waiting for the conversion to finish. Since the conversion takes around 53ms and happens every minute, there is a lot of power to be saved.
Before, I used a normal timer and put the processor to 'idle' mode. In this configuration, the average current over one minute was around 10µA. That means that the processor in 'idle' mode for 50ms alone used twice the power of the processor in 'power down' mode for one full minute and doing some processing and communication alongside the sensor doing a conversion and both other chips idling.
06/08/2015 at 18:48 •
While performing pretty well in terms of battery consumption (60µA), the old firmware was very buggy and only reacted poorly to the buttons, so I started working on the new Firmware.
It is available on GitHub, see the main project page.
Flaws of the old version:
- processor is woken up from deep sleep every second by the RTC to look for button presses
- menu completely blocks the processor, is very sluggish and hard to expand
- battery voltage is determined with a voltage divider
- no display timeout -> accidentaly drained the batteries in under a day
- peripherals are accessed during interrupts (not necessarily a bad thing, but only when timing is critical)
The main difference is that now, interrupts only set flags in the global flag struct which get processed in the main loop. Also the processor goes to deep sleep after it has finished drawing the display, while it is sending data via UART or while it is waiting for the HYT939 to finish it's measurement.
Instead of polling the buttons in a loop, the buttons now trigger a pin change interrupt. Every time this interrupt fires, a timer is reset back to zero, or started if it wasn't running already. When this timer hits a predetermined value, it fires an interrupt that stops the timer and sets the appropriate flags for the buttons.
This ensures that the inputs are only read when they didn't change their state for a while, which is a very effective way (both in responsiveness and required processing power) of debouncing buttons.
When the button flags are set, the main loop executes a function pointer which holds the currently selected button handler. This allows submenus to borrow the button events instead of having all the code in one ridiculously huge method.
When the LCD is turned off, the button handler gets reset to a method which either launches the menu or shows the current measurements.
All menu entries are now stored in an array that holds
- it's title
- a pointer to an optional value
- a pointer to a method that gets executed when the entry is selected
- a flag that determines whether the value points to a static variable or to a method that returns the value to be displayed
When an entry is executed, the method can reassign the button handler to whatever method it wants to allow for submenus.
After a button handler is executed, the processor returns to deep sleep.
Also the display now has a configurable timeout to prevent accidentally draining the battery.
When the RTC triggers it's interrupt and the countdown for setting the interval overflows, it sets a flag. In the main loop this triggers a measurement request to be sent to the HYT939. It also starts the watchdog in interrupt mode to trigger the post-measurement processing. The watchdog was chosen over a normal timer because it runs very often and a normal timer will leave the main system clock running (only supports idle mode vs power down mode) which ultimately used too much power.
Of the 14 bits that the sensor reports for each temperature and humidity, twelve are fitted into three bytes for each sample. Forty of these samples are stored in a buffer in RAM. Each of those packets also contains the time and date of the first sample, the battery voltage at the first sample, the currently selected sample interval, and the amount of actually used samples in the packet.
This makes each packet 128 bytes long, which coincides with the physical layout of the pages in the EEPROM. Writing one page at a time has many advantages, because the EEPROM can only erase it's data in entire pages. So even if you just stored a single byte, a whole page gets erased and newly written to, which wears out the EEPROM faster and takes more time and power.
The collected data can be sent via UART at a baudrate of 250,000. The controller reads an entire page from the EEPROM into a buffer and enables the UDRE interrupt which fires when there is room for another byte in the transceiver. In this interrupt , new data is put into the tranceiver. If it detects that the buffer is empty, it will again set a flag to tell the main loop to refill the buffer. This allows the core to go into idle mode to save power.
The heavy use of interrupts and sleep modes (not a single polling loop) along with the use of <power.h> to turn off the ATMega's unused modules drastically reduces the power consumption of the device.
To measure such a small power, I used a 10k resistor as a shunt and measured the voltage across it with my oscilloscope. This allowed me to integrate the current to get a precise average.
Blue Waveform: Current (10µA/div) Black Waveform: ∫ Current dt (100µAs/div) Timescale: 10s/div
This works out to a phenomenal power consumption of just 3.1µA this value even decreases with lower voltages. After loosing power, it will continue to measure for 8 more minutes just on the 220µF capacitor.
Doing some very basic calculations, the theoretical battery life would be over seventy years. In practice, the device also needs a fair bit of power to store the buffer into the EEPROM though I never measured how much exactly.
Further possible improvements
- find current leaks (probably the 220µF capacitor and the GoldCap)
- use a lithium ion cell instead of NiMhs (lower self discharge rate)
For now, I think the current setup is more than sufficient, so I will leave further improvements to readers who want to build one for themselves.
06/08/2015 at 18:13 •
I fixed some issues with the hardware:
the buttons reacted very badly, so I replaced them with tactile switches which have spacers glued to them. The old buttons were screwed in, the new ones were secured in true hacker fashion with excessive amounts of hot glue.
Reverse voltage protection
The schottky diode that was used to protect the circuit against reverse voltage, which can easily appear with 9V-clips dropped too much voltage under load, especially when the LCD was on, but also when sampling the battery voltage after storing the buffer to EEPROM which resulted in curiously low readings. While it was not too much of an issue for the controller, the LCDs contrast constantly had to be adjusted so I replaced the simple diode with a small MOSFET in this configuration:
Now the voltage drop is negligible, which also increases battery lifetime.
Battery Voltage Measurement
The schematic shows a voltage divider feeding into the ADC. The voltage was measured against the internal bandgap. In the new firmware I used bigjosh2's method of measuring the bandgap against the supply voltage and removed the voltage divider and the capacitor which saved me 4µA of constantly wasted power, more than the entire circuit uses with the new firmware.
06/08/2015 at 17:52 •
If you want to take a look at the old software you can find it here: http://martin2250.blogspot.de/2014/11/avr-temperaturehumidity-logger.html
Since I created this project log after finishing the improved software, I won't write about it.
06/08/2015 at 17:52 •
The Case is made from 4mm plywood and milled on my Shapeoko 2.
To design the case, I used Cadsoft EAGLE. I have only seen very few people use it for that purpose, although it is quite nice to use (at least if you're already used to the workflow) and the size restrictions only apply to components, not to drawings.
The design is pretty straightforward: a box with finger joints and a separator for the battery compartment.
The most exciting part is the battery cover: it slides in and out without using anything but the plywood and no extra parts.
The design files can also be found in the GitHub repo.
For CAM, I chose the best tool that I had: me. I traced out the toolpath in eagle with a line width of two millimeters which is also the size of the bit I used for routing. Then I used a program that I made for this very purpose (might get released on GitHub soon) to convert the line path directly to GCode.
Since this is almost as close to G-Code as writing it by hand, the generated toolpath is very efficient.