Something a little different from the DIY ESP wifi sensors you’d usually find here
So winter is coming and we needed an additional temperature and humidity sensor for a room we weren’t using before. I always liked the 433MHz sensors from Oregon Scientific, the THGN/THGR series. They’re small, inexpensive, have great range, a protocol that is supported by all major RF modules for HA (RFXCOM, RFLink, etc) and most important of all, their battery life is insane compared to today’s zigbee/zwave/whatever devices. I’ve a couple of sensors I haven’t changed the batteries for 4 years and they’re still up and running !
Sadly, they’re not manufactured anymore. So I decided to build a DIY clone which had to have the following properties:
- Same protocol as my existing sensors, readable by my current RF module (an RFXCOM RfxTrx433).
- Similar battery life (at least 3 years)
- I only had an evening to do this, so it had to be quick and with components I had in my spare parts bin.
The spare parts
I wanted the sensor to be powered by two AA batteries, totalling 3V. So I decided to use an Arduino Pro Mini as the heart of the sensor, running an ATMega328p MCU clocked at 8MHz. The 328p can actually run down to 1.8V, but doing this would require clocking it at a very low frequency, below 2MHz. At this frequency, it becomes complicated to keep the timing of the RF protocol, so I opted for an 8MHz clock at 3V. This also reduced power consumption. I also disabled the power LED by physically removing it, as it was pulling a lot of current.
The temperature and humidity sensor I used is a standard BME280 connected over the I2C bus. It has a pretty good power saving sleep mode integrated, using only a few µA and is rated from 1.7V to 3.6V. So that was a perfect match. And finally, I used a standard cheapo 433MHz transmitter module for the OOK modulated transmission. The nice thing about this module is that it doesn’t consume any power when no data is sent to it.
Low power design
If I wanted the batteries to last for years, I had to really cut down on power use. That meant aggressive use of sleep modes for everything, from the MCU, peripherals and the BME module. The sensor will transmit its data every 5 minutes and spend the rest of the time sleeping. The 328p has a mode where all clock oscillators are stopped, the main processor is put into deep sleep and then awaits an interrupt to wake up and resume execution where it left off. This interrupt can come from an external clock, but that would have meant more components and higher power use.
So I opted for the internal watchdog timer. This is a timer integrated into the MCU that fires after a predetermined period of time and wakes up the processor. Enabling and configuring it the way I wanted to involved a little low level bit manipulation in the processor control registers, but it’s all well documented in the datasheet. I also disabled BOD (brown out detection), which will stop the MCU if the voltage gets too low, reducing power use further.
void sleep(void)
{
// Turn off ADC during sleep
ADCSRA &= ~(1 << ADEN);
// Enable the Watchdog interrupt before going to sleep (no WDE)
WDTCSR |= _BV(WDIE);
// Use power down sleep mode, the lowest power one
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
// Get ready to disable all clock oscillators and brown out detection
cli();
sleep_enable();
sleep_bod_disable();
sei();
// Sleep the CPU
sleep_cpu();
// Continue execution here after the WDT fired, wake up the CPU now
sleep_disable();
// Enable interrupts
sei();
// Re-enable the peripherals
power_all_enable();
// Reenable ADC
ADCSRA |= (1 << ADEN);
}
So doing all this gave me an average power consumption of 620µA. That’s far too much. Batteries would not have lasted more than half a year with that. Not good. So I had to get it further down. So whats next ?
The BME280 needs to sleep too ! According to the BME280 datasheet, the sensor has a built-in sleep mode where it only consumes 0.1µA. Nice ! Of course I would have to add the consumption of the level shifters and other components on the breakout board I was using, but it sounded promising. In order to use this sleep, you have to operate the BME in forced mode. Waking up the BME, waiting for it to stabilize, taking one sample and going back to sleep. Thankfully the Adafruit BME library I was using had some functions available to easily perform these operations.
// Set BME to forced mode (take one sample and go to sleep)
bme.setSampling(Adafruit_BME280::MODE_FORCED);
// Read BME data, BME goes back to sleep after that
bme.takeForcedMeasurement();
float t = bme.readTemperature();
float h = bme.readHumidity();
I ended up with an average power consumption of 55µA. Using typical alkaline AA batteries with a capacity of 2500mAh, this gives a theoretical runtime of over 5 years on a single set of batteries. That’s probably longer than their own self discharge rate.
Putting it all together
I didn’t want to put in the time designing and ordering a PCB for this, so I just quickly soldered the modules together:
The code and the schematics are on my github. For the RF protocol, I used the library WlessOregonV2 by Olivier Lebrun and Dominique Pierre, who had already reverse engineered the Oregon Scientific transmission format. Without that I wouldn’t have been able to do this in one evening