The EDU build we released in 2020 provides remarkable flexibility for courses in environmental monitoring. However an instructor still needs to invest about five days ordering parts, testing components, and preparing kits for a 15-20 seat course being run remotely. (only 1/2 that is needed for in-person courses where the students pin & test the parts themselves). While that’s not unusual for university-level lab based subjects it is something of a stretch for high school teachers. And thanks to COVID chip shortages, many modules that were only 99¢ at the beginning of this project could now set you back $5 each. So with all that in mind, we’ve continued development of a ‘lite’ version of our logger with the lowest possible prep. That new baby is now ready for release with data download & control managed through the IDE’s serial monitor window.
With just three core components as our starting point, the only hardware option was to remove the SD card. Groundwork for this change was already in place with our use of an EEprom to buffer data so that high-drain SD saves only occurred once per day. Getting rid of power hungry cards also opened up the possibility of running the entire unit from the coin cell on the RTC. But a power budget that small will necessarily add complexity to the base code, which must minimize run-time even though EEproms are notoriously slow devices. And most garden-variety memory chips have a lower limit of 2.7v – so a nominal 3v CR2032 can only be allowed to fall about 300mv under load before we run into trouble. That voltage drop increases over time because the internal resistance of a coin cell is only 10 ohms when new, but approaches 100 ohms by end of life. In addition, it’s not unusual to see a 50mv delta at the battery terminals for every 5°C change in ambient.
But if theres one thing I’ve learned on this project it’s that datasheets only tell you so much about system behavior in the real world – especially with stuff constructed from cheap modules carrying half a dozen unspecified bits. So let’s just build one and see how it goes…
Modifying the RTC module:
Cutting the VCC leg depowers most of the logic inside the DS3231. However the chip will still consume an average of 3µA through VBat to keep the oscillator, temperature compensation & comparator logic working. RTC current can spike as high as 650µA every 64 seconds when new temperature readings occur. Bridging VCC to Vbat also means a 3.3v UART will push some sub-milliamp reverse currents through an older cell. But I’ve yet to have a single problem (or even any detectable warming) after many days with loggers connected during development. Despite dire manufacturer warnings that reverse currents beyond 1µA will heat manganese-dioxide/lithium cells until they explode, the ones I’ve used so far survive the abuse without issue. I’ve no doubt the UART connected time is shortening the batteries lifespan slightly, in fact Panasonic specifies: “the total charging amount of the battery during it’s usage period must be kept within 3% of the nominal capacity of the battery”, so it’s a good idea to remove the battery if you are spending an extended time with the units connected to the serial line to keep the total reverse current time to a minimum. But given our tight operational margin I don’t think we can afford to lose two hundred and fifty millivolts over a Schottky protection diode. A proper solution would address this by OR-ing the two supplies with an ideal diode circuit, but on a practical level it’s easier to just to pop in a fresh battery before a long deployment. Drift on these DS3231 RTCs is usually a loss of ~4-5 seconds per month, but could be up to twice that for -M variants of the chip.
Modify the Pro Mini board:
An 8Mhz Pro Mini continues as the heart of our loggers because the 328p is still the easiest low-power option for projects that aren’t computationally demanding. These clone boards usually sleep below 1µA with the BOD turned off, but it’s worth noting there are some with fake Atmel chips that won’t go below 150µA. Cheaper boards usually ship with ceramic regulator caps (instead of tantalums) but that just makes them more resilient if you accidentally connect power the wrong way. At 8Mhz the ‘official’ lowest safe voltage for the 328p is 2.7v, so that’s where the default BOD is usualy set. But I sleep with BOD off because I’ve noticed that if the BOD does get triggered by low battery voltages then the processor goes into a high 1mA drain condition and this makes AA’s leak battery goop all over the inside of our normal 3-part loggers. But you still have the ‘default’ 2.7v BOD while the processor is operating so you probably want to stop logging when the rail starts reaching ~2750mv. Also keep in mind that there is range on that brownout threshold, from min. 2.5v to max. 2.9v. So one 328p may be more tolerant to running at low voltages than another.
Join the two components:
This two module combination usually sleeps around 1µA and most of that is the RTC’s (IBATT) timekeeping current as the 328p should only draw ~150nA in powerdown mode [with BOD off]. If we assume four readings per hour at 5mA for 10msec, the battery life calculator at Oregon Embedded estimates a 220mAh battery will last more than 10 years…which is ridiculous. We know from the datasheet that 575µA temperature conversions bring the RTC average up to 3µA – which isn’t showing up on this direct measurement. And there’s the battery self discharge of 1-3% per year. Perhaps most important there’s the complex relationship between pulsed loads and CR2032 internal resistance, which means we’ll be lucky to get half the rated capacity before hitting brown-out at 2.7v So a more realistic estimate would start with the assumption that the battery only delivers about 110mAh with our logger consuming whatever we measure + 3µA (RTC actual) + 0.3µA (self-discharge). We can round that up to 5µA continuous, with four 5mA*10millisecond sensor readings per hour, and we still get an estimated lifespan of about two years. So our most significant limitation is the amount of EEprom memory rather than battery power.
The Code: [posted on Github]
The most important difference between a coin cell powered logger and our AA powered units is that the battery has a high probability of being depleted to the point of a BOD restart loop. (which causes rapid flashing of the D13 LED) So we use a multi-step serial exchange in Setup() to protect data already present in the EEprom from being accidentally overwritten.
A UART connection is required at start-up so those menu-driven responses can occur through the IDE serial monitor. These have fairly aggressive timeouts to avoid running the CPU during unintentional restarts. The menu sequence can be re-entered at any time simply by closing & re-opening the serial monitor window:
The first menu asks if you want to download the contents of EEprom memory to the serial monitor window. This can take a bit of time with larger EEproms at 250000 baud, which is near the fastest rate an 8MHz ProMini can reliably sustain. Then copy/paste (Ctrl-A & Ctrl-V) everything from the IDE window into a text editor so you can save the data as a .txt file that Excel can import. While this process is a bit clunky, everyone already has the required cable and data retrieval is driven by the logger itself.
Battery replacement resets the DS3231 to Jan 1st 2000. So after a data download the logger checks the clocks Oscillator Stop Flag (OSF) and, if needed, asks the user to enter current time as YYYY, MM, DD, HH(24h), MM, and SS to reset the clock:
Index TimeStamp(s) are then written to the internal EEprom so the timestamp for each individual sensor reading can be reconstructed during data retrieval using the record number to create an offset that gets added to the index value. This technique saves a significant amount of our limited EEprom memory and all it takes is =(Unixtime/86400) + DATE(1970,1,1) to convert Unix stamps human readable time in Excel.
The on screen menu then asks the user to enter the ‘start’ command again. Only when that second ‘start’ confirmation is received are the EEprom(s) erased by pre-loading every location with ‘0’ values which will serve as End-Of-File markers during the next data download. The red D13 led then blinks slowly while the logger waits for the first sampling interval alarm to begin its run.
To save power, slow functions like digitalWrite() are replaced with much faster port commands. Careful attention is paid to pin states, peripheral shutdowns (power_all_disable(); saves ~0.3mA) and 15msec sleeps are used throughout for battery recovery. Waking the 328p from powerdown sleep takes 16,000 clock cycles (~2milliseconds @8MHz +60µS if BOD_OFF) and the ProMini draws ~250µA while waiting for the oscillator to stabilize. Care must be taken when using CLKPR to reduce system speed because the startup-time also gets multiplied by the divider.
( Note: For the following images a Current Ranger was used to convert µA to mV during a reading of the RTC’s temperature register at 1MHz. So 1mV on the oscilloscope means 1µA is being drawn from the Cr2032 )
Sensor readings are captured every time the RTC alarms at SampleIntervalMinutes, but the ‘lowest’ coin cell voltage is usually recorded only ‘Once-Per-Day’ at the midnight rollover. The logger can be forced to log the battery voltage at every EEprom event by setting BatteryReadingEveryCycle=true at the start of the program. Two 16-bit buffer arrays are used to reduce the number of EEprom writes: sensorDataBuffer[ ] and it’s associated pointer are filled & written to the I2C EEprom in the forward direction while the opdDataBuffer[ ] gets filled and written backwards from the end of the memory space. So both can share the same EEprom and when the two EEprMemPointer(s) overlap that storage space is full and the logger shuts down. If there is more than one chip on the system then the two data streams can be sent to separate EEproms via the two EEpromI2Caddr defines at the start of the code.
CR2032 voltage is only checked during the Write_i2c_eeprom_array function because an unloaded coin cell voltage does not change – even when the battery is almost dead. At 1MHz the ProMini adds adds about 1.4mA to the EEproms 3mA*10msec sustained write current. ADC prescaler ratio is changed to compensate for the reduced main system clock speed. Peak load is reduced by looping through SLEEP_MODE_ADC readings until the battery voltage starts rising after the write finishes. This assures that the lowest coincell voltage is captured though the exact timing of that minimum varies from one memory chip to the next. Logger shutdown gets triggered when the EEprom write brings the rail voltage below the 2750 mv cut-off, which happens when the internal resistance of the coincell rises at end of life.
The ‘RTC_ONLY’ configuration for this logger records a 0.25°C temperature record from the DS3231, index-encoded to use only one byte per reading. This allows ~4000 readings to be stored in the 4k EEprom on the RTC module. This works out to a little more than 40 days at a 15 minute sampling interval, but you can set SampleIntervalMinutes to any even divisor of 60.
That little AT24c32 doesn’t last very long with sensors that generate 2 or 4 byte integers. The solution is to combine them with larger 32k (AT24c256), or 64k (AT24c512)chips so the sensors arrive with the extra storage space they require. These EEprom modules can usually be found on eBay for ~$1 each and they work with the same page-write & addressing code as the 4k EEprom.
You must use low power sensors with a supply range that extends to our 2.7v EEprom/BOD limit. A good sensor to pair with this logger should sleep around 1µA and take readings below 1mA. Sometimes you can pin-power sensors that lack low current sleep modes although if you do that be sure to check for unexpected current leaks in other places such as bus pullup resistors. Choose libraries which allow non-blocking reads so you can sleep the ProMini while the sensor is gathering data and check that those libraries do not contain any delay() statements. In that regard my favorite sensor combination for this logger is an NTC thermistor & CDS cell which adds nothing to the sleep current. We explained how to read resistive sensors with Arduino’s Input Capture Unit in some detail back in 2019; so here I will simply show the hardware connections. Add these passives to the Pro Mini BEFORE joining it to the RTC module:
Space gets a little tight in that D9 corner so I then jumper D2->SQW with a piece of flexible wire which I can route between the boards to avoid covering the D13 LED. Don’t expect the NTC you have in your hands to match exactly the values provided by its manufacturer. Fortunately there are several online calculators to choose from when determining your NTC thermistor constants. For calibration data, I use a food dehydrator to heat the loggers to around 45-55°C, let it cool for a few hours to room temp. for the midpoint, and then put them in the refrigerator overnight for a cold point ~5°C.
Other common modifications to the basic 2-module logger include:
The additions shown above push measured sleep currents to 2-3µA (so ~6µA actual) but that still gives a >1 year estimates on 110mAh. With all due respect to Ganssle et al, the debate about whether buffering caps should be used to extend operating time is something of a MacGuffin because leakage currents are less important when you only have enough storage space for one year of operation. The 6.3v 1000µF tantalum shown above increased sleep current by about 1µA. That’s 1µA*24h*365days or about ~ 8.76 mAh/year in trade for keeping the system above our 2.7v cutoff as long as possible. That means we don’t need to lower the BOD with fuses & custom bootloaders that bork code portability. Pushing the limits of fuse optimization can get a little flaky on these cheap boards, so it’s good to have those ‘Get out of jail free‘ defaults available at reboot. When you only service your loggers once a year, any tweaks that require you to remember ‘special procedures’ in the field are things you’ll probably regret. (And many of those cheap eeproms on eBay also have a 2.7v lower limit)
Leakage scales linearly with capacitance so use the Falstad simulator to see what size you actually need. Capacitors rated 10x higher than the applied voltage reduce leakage significantly. So look for 16v to 30v rated caps if you can find them. While they are a bit bulky, electrolytics also work fine. The 220µF/25v caps I tested only added ~0.1µA to the loggers sleep current (whether tantalum or electrolytic) and these can be obtained on eBay for <50¢ each. So MLCCs with high voltage ratings get you closer to the low leakage values you’d get with Polypropylene, Polystyrene or Teflon film.
Note that as the buffering cap gets larger, you need to add more ‘recovery time’ sleep before the rail voltage is restored after a load. A large rail capacitor also protects the unit from impacts which might briefly disconnect the spring contact under the coin cell. This is a such common problem in our other loggers that we use a drop of hot glue to lock the batteries in place before deployment.
CLKPR brings the ProMini down to 1MHz and a current of ~1.3 mA however the energy cost per logger event actually increases when the system clock gets divided. But with our slim operating margin the growing internal resistance of the coin cell means we have to stay above 2.7v even if it means using less efficient code. Running from the internal oscillator might help but is avoided because our ICU timing method needs the thermal stability of an external oscillator and, the internal oscillator makes UART coms flakey. FRAM has much lower saving currents than standard EEproms but those expensive chips sleep around 30µA so they aren’t a viable option for low-power systems. (…unless you pin-power them so you can cut their power during sleeps)
In the next three images, a Current Ranger converts every 1µA drawn by the logger to 1mV for display on the scope. The last two spikes are transfers of buffer-array data into the 4K EEprom on the RTC module while the CPU takes ADC readings of the rail voltage. Note that our code staggers EEprom save events so they don’t normally occur in the same pass like this, but I forced them together for this test to illustrate the effect of repeated loads:
The basic RTC-only sensor configuration reached a very brief peak of ~2.7mA with no buffering cap, 1.5mA with 220µF and less than 1mA with 1000µF. The amount of voltage drop these currents create depend on the coin cells internal resistance but a typical unbuffered unit usually sees 15-30mV drops when the battery is new and this grows to ~200mV on old coin cells pulled from loggers that have been in service since 2018. The actual drop also depends on time, with subsequent current spikes having more effect than the first as the internal reserve gets depleted. The following images display the voltage droop on a very old coin cell pulled from a logger that’s been in service since 2016 (@3µA average RTC backup)
After many tests like those above, our optimal solution is to run the processor at 8MHz most of the time by breaking up the execution blocks with 15 millisecond POWER_DOWN sleeps before the CR2032 voltage has time to fall. The processor is brought down to 1MHz only during the EEprom save event where the block can not be divided (and that only happens when the buffering arrays are full….)
Even with fierce memory limitations we only use the 328’s internal 1k for a couple of index variables that get written while tethered to UART power. EEprom.put starts blocking the CPU from the second 3.3msec / byte, and internal EEprom writes adds an additional 8mA to the rest of the system load. Multi-byte page writes aren’t possible so data saved into the 328p costs far more power than storing those bytes in an external EEprom. However it’s worth noting that reading from the internal EEprom takes the same four ticks as an external, while PROGMEM takes three and RAM takes two clock cycles. So it doesn’t really matter to your runtime power budget where you put constants or even large lookup tables.
A simple optimization we haven’t done with the code posted on GitHub is to increase the I2C buffer. All AT-series EEproms are capable of 32-byte page-writes but the default wire library limits you to only 30 bytes per exchange because you lose two for the register location. So we used 16-byte buffer arrays in the starter code but you could increase those arrays to 32 bytes each by increasing the wire library buffer length to 34 bytes:
In wire.h (@ \Arduino\hardware\arduino\avr\libraries\Wire\src)
#define BUFFER_LENGTH 34
AND in twi.h (@ \Arduino\hardware\arduino\avr\libraries\Wire\src\utility)
#define TWI_BUFFER_LENGTH 34
With larger EEproms you could raise those buffers to 66 bytes for 64 data-byte transfers. That buffer gets replicated in five places so the wire library would then require an extra 138 bytes of ram over the 32-byte default. 128k EEproms often refresh entire 128-byte blocks no matter how many bytes are sent, so increasing the buffer reduces wear considerably for those chips, while 64k & below may perform partial page-writes more gracefully.
An average sleep current of ~5µA*86400 sec/d burns ~432 milliampseconds/day. With an eeprom write that draws 3mA*5msec, 12 array buffer writes per day will consume about 180mAs. Cutting that in half by doubling the size of the buffer is going to save you 90mAs per day, so it will only take 5 days to earn back the power for one more day of sleep. That return is even better with old 10 millisecond/write eeproms, and reducing the number of pulse-load events may extend battery life in some way other than the actual power saved. I always do the 34-byte I2C / 32-byte buffer increases to my loggers.
No matter what optimizations you make, battery life in the real world can also be shortened by temperature cycling, corrosion from moisture ingress, being chewed on by an angry dog, etc.
An important topic for a later is data compression. Squashing low-rez sensor readings into only one byte (like we do with the RTC temperature & battery voltage) is easy; especially if you can subtract a fixed offset from the data first. But doing the trick with variable high resolution thermistor readings is more of a challenge. Do you use ‘Frame of reference’ integer deltas, XOR’d mini-floats, or custom-packed BCD? We can’t afford much of an energy tradeoff for heavy calculations on our little 328p so I’m still looking for an ‘elegant’ solution to that puzzle.
Hopefully this new member of the Cave Pearl family goes some way toward answering people asking why we haven’t moved to a custom PCB: Using off-the-shelf parts is critical to helping other instructors base their courses on our work, and when you can build a logger in about 15 minutes, from the cheapest parts available, that still runs for a year on the coin cell… why bother? We do sampling dives all the time with those 50mL centrifuge tubes and I’ve yet to see ‘Nunc’s from Thermo leak at depths far beyond what an IP68 housing could handle. And again you are only talking about $1 each for those.
We’ve been having fun embedding these ‘Pro-Minillennium Falcons’ into rain gauges and other equipment that predates the digital era. There’s a ton of old field kit like that collecting dust in the corner these days.
Since we covered adding sensors, heres a couple of burn-down curves for the two configurations described above. Both were saving 4 bytes of data per record every 30 minutes giving a runtime storage capacity of about 150 days. Battery was logged each time the 16-byte buffer-arrays were moved to eeprom. Both loggers had a measured sleep current of ~1.5µA and were restarted to continue these burn tests on the same battery:
I’m running these tests without help from a rail buffering cap, to see the ‘worst case’ lifespan. A new Cr2032 has an internal resistance of ~10Ω, so a 3mA eeprom writing event will only drop the rail about 30mv. But once the cell reaches 60Ω we will see drops reaching 0.2volts for those events. The 2032’s shown above are plateauing near 3v, so we should see the rail droop to ~2800mv once the batteries really start to age. (ie: to only 50mv above the 2750mv shutdown in our code)
According to Maxell’s 1Meg-ohm (3.3µA) continuous discharge test, these coincells should stay at their 3v plateau until they have delivered 140mAh [~500,000 mAs] So the rail buffering caps arn’t really needed till the cell passes that point, but I still expect them to extend lifespan by at least 30%. The longest cointinuous run to date with a 220µF cap in place is nine months and the graph for that buffered unit is perfectly flat at 3.03v with no noise at all. Of course, if we reach a year without rail buffering caps, then we’ve likely filled the eeprom… so the extra stability provided by a rail cap may only be necessary with high-drain sensors or for low temperature deployments.
And here we compare our typical logging events to the power used during the RTC’s internal temperature conversion (with a 220µF/25v tantalum buffering the rail). On all three the horizontal division is 50 milliseconds, and vertical is 100µA (via translation with a current ranger).
The datasheet spec for the DS3231 temp conversion is 125-200ms at 575-600µA, but most of the units I tested drew less. This is such a long duration load that even with a 220µF on the rail I’d expect the overlap of RTC temp conversion & eeprom writing to double the vdrops I showed earlier because the reserve has been drained.
Another thing to watch out for is that with sleep current in the 1-2µA range, it takes a minute or more to run down even the little 4.7µF caps left on the ProMini board. If you have a 220uF buffering the rail the logger will run for almost 30 minutes with no battery before the RTC flags a power loss. This means you can usually perform a quick coincell swap without loosing the time, but if you are deliberately trying to reset the RTC you will need to briefly short Vcc to GND after removing the coincell. Note that we never use the brown out detection any more because on occasions where it does get triggered the 328p in often draws ~1mA in that state until your batteries leak. The whole idea of BOD is to protect the logger, but that leakage usually destroys the machine before we can get back into the field. This is a mystery I have yet to resolve as Brownout holds the MCU in reset, which floats all pins (even those that are being used as outputs) Since the internal pull-ups are disabled during reset I have no idea where that current leak is coming from though it’s worth noting that the datasheet says: “If low power consumption during reset is important, it is recommended to use an external pull-up or pull-down.”
Also Note: 99¢ eBay sensor modules are cheap for a reason. Relative accuracy spec for the BMP280 is supposed to be ±0.12 millibar, but when I run a batch of them side-by-side I usually see a range of ±2 to 3 millibar between the records. So it’s good to do a huddle test of new loggers so you can normalize them to each other.
Heliosoph: Arduino powered by a capacitor
Nick Gammon: Power Saving techniques for microprocessors
Jack Ganssle: Hardware & Firmware Issues Using Ultra-Low Power MCUs
Using a $1 DS3231 Real-time Clock Module with Arduino
An Arduino-Based Platform for Monitoring Harsh Environments
Oregon Embedded Battery Life Calculator
WormFood’s AVR Baud Rate Calculator