Tag Archives: data logger

Easy 1-hour Pro Mini Classroom Datalogger [Build Update: Feb 2019]

Dupont jumper variant of the 2019 Classroom Pro Mini Data Logger from the Cave Pearl project: This version uses solderless dupont jumpers to reduce assembly time to about 1 hour

It’s only been a couple of weeks since the 2019 classroom logger was released, and we’re already getting feedback from instructors saying the extra soldering that we added to that build creates a resource bottleneck that might prevent some of them from using it:

“Our classroom has just two soldering stations, and the only reason there are two is I donated my old one from home. So we simply don’t have the equipment to build the logger you described. And even if we did, some of my students have physical / visual challenges that prevent them from working with a soldering iron safely…”

Or goal with the new design was to give students their first opportunity to practice skills that are vital for field research. However helping people do science on a budget is also important – so that feedback sent us back to the drawing board.  After a little head scratching we came up with a version that combines the Dupont jumpers we used in 2016, with this more flexible flat-box layout. In the following video, I assemble one of these ‘minimum builds’ in approximately one hour.  To put that in perspective, the soldered version takes 2 – 2.5 hours.

Note: After you’ve seen the video to get a sense of where you are headed, its usually much better to use the photos (below) when building your logger. Videos make it look easier than it actually is when you are just starting out. 

This variation of the 3-component logger is optimized for quick assembly so the soldering has been reduced to just pin headers and bridging the I2C bus.  An instructor could easily do that ahead of time with about 15 minutes of prep per unit, leaving the solder-less steps for their students. Wire connections are made simply by twisting stripped ends together and clamping them under screw terminals.

This time reduction involves a few trade-offs, and the bringing the I2C bus to A2&A3 leaves only two analog ports readily accessible ( although A6 & A7 are still available). Removing the regulator & battery voltage divider adds ~30% more operating lifespan, but it also forces you to deal with a changing rail voltage as the Lithium AA batteries age. That daily variation is quite small, but for quantitative comparisons on monthly scales you may need to correct for the change in Vcc (included in the data file automatically) over time.  Another proviso is that you have to add a few more components to the part list from the soldered build:

This pushes the cost for a complete unit close to $20 (before adding sensors) and the required lithium AA batteries are also more expensive.

Pro Mini Prep:

First, test your pro mini board with the blink sketch

Remove two SMD resistors with iron

Remove the voltage regulator with snips. few regs tolerate output voltage with no input.

Add pin headers

Bridge I2c bus connections with a resistor leg.

Connect A4->A2, A5->A3. Then disable digital inputs on A2&A3 in software.

Screw-Terminal Component Stack:

Add 3 layers to extend past the pins. On DeekRobot boards the two ground terminals are connected.

Gently rock the Pro Mini back & forth until the pins are fully inserted.

IMPORTANT! Align RX&TX corner. The GND points on the ST board may be interconnected & should match ProMini’s GND pins.

Remove unused pin headers to make room …

…for the SD adapter

Remove bottom 3 resistors – leave the top one in place!

Separate Dupont Cable wires & click into housing

Color Pattern: Black GND, Purple MISO, Brown SCl, Orange MOSI, Grey CS, and Red Vcc

Attach SD module to the Screw Terminal board

Measure, cut & strip the SD modules 4 SPI bus wires

Attach Grey D10, Orange D11, Purple D12, Brown D13

Add three jumper wires to the SD modules power line, one with male end pin

Add 3 wires to the power line & heat-shrink for strain relief

Short Jumper recruits the orphan capacitor at Vin

Longer red jumper bridges power to other side of the board

Add two extra wires to the black GND bundle, one with male Dupont end pin.

The completed Pro Mini, ST board & SD module stack. Those Red & Blk jumpers should be a bit longer…

Note: You could connect the battery holder lead wires directly into the multi-wire Vcc & GND bundles: skipping the 2 jumpers to the other side of the ST shield.  But those jumpers provide extra Vcc/GND points & the ability to change the battery holder later if you have a battery leak.

RTC module:

Remove two resistors from the RTC board with the tip of your soldering iron

The DS3231 modules often have flux residue – remove this with 90% isopropyl


Cable shroud retainer clips facing up & no wire on the RTC’s 32K output.

First tape layer

Tape stage 2

Write the date on the coincell with a black marker

Final Assembly:

Attach stack & RTC to housing.

Trim wht & yellow I2C wires  & add extra jumper 2 each

Attach yellow beside Vcc connection, then white

The extra jumpers on Vcc, GND, & both I2C lines…

…get patched over to the breadboard so you can add sensors

RTC power line joins the short red jumper on Vin

Some the box bottoms have slight bowing. If something doesn’t stick, add another layer of tape.

GND & blue RTC alarm line to D2

Tape 2xAA battery holder down. Trim wires to length. Use 30lb Mounting Tape for the battery holders.

Battery wires joint the black and red jumpers from other side of the terminal board

Connections now complete except for the indicator LED

5050 RGB LED on pins D3=GND, D4-Bl, D5-Gr, D6-Red

Since this build is a variation of the soldered version, so you can test your new unit using the procedures at the end of that post. Also see that tutorial for a description of how we attach external sensors with a cable gland passing through the housing. Of course, for an indoor classroom project you could simply drill small a hole through the lid and stick the sensor/module on top of the housing with double sided tape. Remember that breadboard connections are very easy to bump loose, so once you have your prototype circuit working, re-connect the components directly to the screw terminals before real-world deployments.

Using the logger for experiments:

Logger mounted on a south-facing window and held in place with double sided tape. The top surface of the housing is covered with two layers of white label-maker tape to act as a diffuser.

Many types of sensors can be added to this logger and the RTC has a temperature register which automatically gets saved with the starter script. The transparent enclosure also makes it easy to do light-based experiments. Grounding the LED through pin D3 allows it to be used as both a status indicator, and as a very capable light sensor.   The human eye is maximally sensitive to green light so readings made with that LED channel approximate a persons impression of overall light levels.  Photosynthesis depends on blue and red light, so measurements using those two color channels can be combined for readings that compare well to the photosynthetically active radiation measurements made with “professional grade” sensors. In fact Forest Mimms (the man who discovered the light sensing capability of LEDs) has shown the readings from red LED’s alone are a good proxy for total PAR.  Photoperiod measurements also have important implications for plant productivity.

The code for using LEDS sensors is from the Arduino playground. This polarity reversal technique does not require the op-amps that people typically use to amplify the light sensing response but it does rely on the very tiny parasitic capacitance inside the LED. (~50-300pF) This means that the technique works better when the LED is connected directly to the logger input pins rather that through the protoboard (because breadboards add stray capacitance) . We have integrated this into the starter script which you can download from GITHUBThat method was used to generate this light exposure graph with a typical 5mm RGB LED, with a 4k7Ω limiter on the common ground connected to pin D3:

Red, Green & Blue channel readings from the indicator LED in the logger photo above.  The yellow line is from an LDR sensor the same unit, that was over-sampled to 16-bit resolution. The sensor has a logarithmic response and the left axis on the graph is a time- based measurement where more light hitting the LED sensor results in a lower number. The blue channel is somewhat less sensitive than the red & green, but that actually gives you more useful information.  LED’s work well with natural light, but their sharp frequency sensing bands can give you trouble with the odd spectral distribution of some indoor light sources. You may be able to use a floating point mapping function like fscale to linearize your data – depending on the range of output from your particular LED.

Characterizing light absorption and re-emission is one of the primary climate science techniques. For example, measuring light intensity just after sunset with LEDs inside a heat-shrink tube pointed straight up can provide a measure of suspended particles in the stratosphere. An “ultra bright” LED has more than enough sensitivity to make collumnated readings, and on bright sunny days you usually have to place the LED-sensor beneath a good thickness of white diffusing material to prevent it from being over-saturated.  Older LEDs that emit less light can sometimes be easier to work with because they are less sensitive, so the readings do not go to zero in high-light situations. Other sensor experiments are possible with LED’s in the IR spectrum which can be used to detect total atmospheric water vapor. Chlorophyll fluorescence is another interesting application, and the response of plants to UV is fascinating.

One thing to watch out for is that full sun exposure can cook your logger, so consider adding a bit of reflective film to protect the electronics. Add a couple of desiccant pouches to control condensation.  You might need to add a few holes to the housing as tie-down points:

Pro Mini Logger Project for the Classroom [ EDU Jan: 2019 ]

“Instrumentation is a central facet of student, amateur and professional participation in science. STEM education, recruitment of scientists and experimental research are thus all hampered by lack of access to appropriate scientific hardware. Access restrictions occur because of: 1) lack of capital to purchase or maintain high-cost equipment, and/or 2) the nature of proprietary ‘black box’ instrumentation, which cannot be fully inspected, understood or customised…
…In addition to reducing opportunities for people to engage with science, this lack of access to appropriate hardware restricts scientist’s creativity in experimental designs.”

From: Journal of Open Hardware
Expanding Equitable Access to Experimental Research and STEM Education

by Supporting Open Source Hardware Development

Last year’s intense deployment schedule focused on getting more sensors into the field, which left little time for development of new approaches to the logger itself.  Now that everything is settling into the school term routine, it’s time to update the “classroom edition” of the Cave Pearl Logger with feedback from three years in the trenches: 

The 2016 build achieved it’s goal reducing construction time, but it was low on important skills like soldering. Limited lab time meant that something had to give if we want students to “pay the iron price” for their data, so we’ve added a pre-made enclosure box. Though it’s not as robust as the PVC housings, it provides more room inside the housing. Past student projects have required things like 555’s, ADS1115 modules, display screens, etc. and the proto-board will make it easier to integrate those additional components.


Bill of Materials: $18.35
Plano 3440-10 Waterproof Stowaway Box
Usually cheaper at Amazon as “add-on” items.  $4.96 at Walmart and there are a selection of larger size boxes in the stowaway series. 6″ Husky storage bins are an alternative option.
4Pin 24AWG IP65 Black Waterproof Cable Connector OD 4mm
Better quality version is available at Adafruit for $2.50 each, wBL-RED-Wht-Yel colors used here for the I2C bus.
M12 IP68 Nylon Cable Gland
Adjustable for 3mm-6mm diameter. You need two for the build. Make sure they include O-rings.
3/4″ Schedule 40 PVC Cap
Diameter will depend on the size of your sensor breakout board. Get ones with FLAT ends.
White 170 Tie-Points Prototype Breadboard
Available in other colors.
Pro Mini Style clone 3.3v 8mHz
Get the ones with A6 & A7 broken out at the back edge of the board.
Nano V1.O Screw Terminal Expansion Board
Note: To save time, you can spend an extra $1 for pre-assembled boards by Deek Robot, Keyes, & Gravitech (CHECK: some of them have the GND terminals interconnected)  Have a few small flat head screw drivers handy.  
DS3231 IIC RTC with 4K AT24C32 EEprom (zs-042)
Some ship with CR2032 batteries already installed.  These will pop if you don’t disable the charging circuit!  
CR2032 lithium battery  $0.40
4 poles/4 Pin 2.54mm 0.1” PCB Universal Screw Terminal Block Connector
These things look “open” when they are “closed”, and you need a very small screw driver to open them.
SPI Mini SD card Module for Arduino AVR
Buy the ones with four ‘separate’ pull-up resistors so that you can remove them.
Sandisk or Nokia Micro SD card 256mb-512mb 
 Test used cards from eBay before putting them in service. Older Nokia cards have much lower write currents in the 50-75mA range. This is less than half the current you see on more common larger sized cards.
3×1.5V AAA Battery Batteries Holder w Wire Leads
The Pro Mini regulator will handle battery packs holding from 3 to 8 AA or AAA batteries. If you are using alkaline AAA batteries, changing this to a 4xAA battery holder doubles the run time.
Common Cathode Bright RGB LED 5mm 
( & 4k7 limit resistor) 
3M Dside Mounting Tape10MΩ resistors & 3MΩ resistors, 22awg silicone wireheader pins, etc… $0.50
Donation to Arduino.cc
If you don’t use a ‘real’ Pro Mini from Sparkfun to build your logger, you should at least consider sending a buck or two back to the mothership to keep the open source hardware movement going…so more cool stuff like this can happen!
Comment:   You might need some extra parts to get started:                (not included in the total above)
2in1 862D+ Soldering Iron & Hot Air station Combination
a combination unit which you can sometimes find as low as $40 on eBay.
Or get the Yihua 936 iron alone for about $25.
3.3V 5V FT232 Module
  ***Be sure to set the UART module jumpers to 3.3v before using it!*** and you will also need a USB 2.0 A Male to Mini B cable.
Micro SD TF Flash Memory Card Reader
Get several, as these things get lost easily. My preferred at the moment is the SanDisk MobileMate SD+ SDDR-103 which can usually be found on the ‘bay for ~$5.

Connection Diagram:

This logger uses the same three components described in the paper from 2018, but we now connect those core modules via a screw-terminal expansion shield, rather than soldering them directly to the pins:

Watch through the videos as a complete set first so you know where you are going, and then use the images below the videos to remind you of the key steps while you do the assembly; It usually goes much faster working from a photo where you can see all the connections at once. The times listed are estimates for people with soldering experience. If you’ve never built a circuit before, then taking 3-4x that long is completely normal. Don’t worry about it – you will be surprised how much faster you get with a little practice!

Screw Terminal board:  (~40min)  or ( 5min with pre-assembled board)

Don’t forget to measure the values of the resistors before soldering that voltage divider. You will need those values to calculate the battery voltage based on the ADC readings from pin A6.

These screw terminal boards are designed for an Arduino Nano, but if you orient the board to the Tx/Rx pins, the labels on digital side of the shield will be correctly aligned with the Pro Mini:

The most common beginner errors at this stage are crooked headers & not heating the pad/pins long enough for solder to flow properly.  This is often because students are trying to use an iron tip that has “gone dry” so the heat is not transferring properly to the pins. You must protect soldering iron tips with fresh solder every time you put it in the stand to prevent oxidation. Tip Tinner can sometimes restore those burnt tips.   {Click images for larger versions}

Common soldering errors – which are easily fixed by re-heating with more solder & flux.

Divider runs between GND & RAW Vin, with output to A6 pin.

Always apply conformal coating in a well ventilated space, such as a fume hood.

It is better to err on the side of using a little too much heat, because partial connections to the screw terminals will cause you no end of debugging grief later: Cold solder joins can “sort of” work “sometimes”, but cause mysterious voltage drops over those points because they can act like randomly variable resistors in your circuit.  Note: This voltage divider uses meg-Ω size resistors and takes >1 second to charge the capacitor when the unit is first powered on – so you can’t take the first battery reading until that much time has passed.

The Pro Mini Board:   (~40 min)

~5-10% of the cheap Pro Mini clones from eBay are flaky, and it is quite annoying to discover one of those that after you have assembled a logger. So test your board with the blink sketch before you remove pin13 LED resistor.  These limit resistors move around from one manufacturer to the next, so you might have to go hunting for them on your particular board.  You also need to remove the RESET switch from the board, or that button will be compressed when you put the SD card adapter in place:

Test w blink sketch!

You can skip the reset pins at this stage, or you can pull those pins out of the plastic rails later with pliers, or simply cut them off.

SCL & SDA jumpers to the 2 extra pins on the digital side.

Connect A6-7 & GND to analog side pins with the leg of a scrap resistor.

(Note: Credit goes to Brian Davis for the idea of using “extra header pins” when patching to the unused to screw terminals.)    

The SD Card Adapter:   (~15 min)

This SD card adapter comes with small surface mount pull-up resistors on the MOSI, MISO & SCK (clock) lines (removed from the dashed red line area photo 2 below).  The Arduino SDfat library uses SPI mode 0 communication, which sets the SCK line low when the logger is sleeping. This would cause a constant drain (~0.33mA) through the 10k SCK pullup on the module if we did not remove it.  I prefer to pull MOSI & MISO high using the internal pull-ups on the Atmel328P processor, so those physical resistors on the breakout board can also be removed. Leave the top-most resistor of the four in place to pull up the unused DAT1 & DAT2 lines.  This keeps those unused pins on the SD card from floating, which can draw excess current.

Only remove the bottom three pullup resistors. keep the top one
The SPI connections:
RED:           3.3v regulated
Grey:          Cable select (to D10)
Orange:     MOSI   (to D11)
Brown:      SClocK (to D13)
Purple:      MISO   (to D12)
BLACK:     Ground

Attach SD adapter & Pro mini to the Screw Terminal Board:  (~20 min)

Label the Vcc & GND connections with a colored marker, then insert the Pro Mini into the headers on the screw-terminal shield. Be careful not to bend the pins – especially the “extension” pins at the back of the board. It’s easy to connect the board in the wrong orientation at this step. The voltage divider on the bottom of the screw terminal shield aligns with the ANALOG side of the Promini board. 

Label the Vcc & GND connections.

Then affix the SD adapter board to the top of the Pro Mini with a slight overhang, so that the jumper wires align with the screw terminals below. Trim the wires about 1cm past the edge of the board to provide enough stripped wire for the terminal connection.

The limit resistor for the common cathode RGB can range from 4k7 to > 30kΩ

LED on pins D4-D6, & GND. NOTE: Grounding the LED through D3 lets you use the LED as a sensor and support for this is included in the base code.  Also see: multiplexing wD9 GND

Add layers until the tape extends beyond the pins. This might require 3 layers of tape.

The RTC Module:  (~15min)

The simplest modification to these DS3231 RTC boards is to remove the charging circuit resistor and power LED limit resistor from the circuit board (the red squares in the first picture).  A non-rechargeable CR2032 coin cell battery will supply the RTC with power for many years of operation.  Note: The 32K pin is not connected, and does not get a jumper wire. The four screw terminals go on the same side as the battery holder.


An alternative to the 4-pin 0.1” screw-terminal block is to solder the I2C pass-through wires directly to the module.

Cutting the RTCs Vcc pin reduces sleep current by ~40% – this step is completely OPTIONAL!

Add an extra layer of foam tape over the smaller 4K eeprom chip, so that the thickness matches the top surface of the DS3231 chip.  The RTC board already has 4.7kΩ pull-ups on the SDA (data) and SCL (clock) lines so you will not need to add them to the bus.  This module also has a 4.7k pull-up on the SQW alarm line.  (Adding the screw terminals to the small cascade port on the RTC module is another creative idea from Brian D.)

The Plano Stowaway housing: (~10 min)

In these photos, I’ve tapped some threads into the housing for the cable gland, but that is entirely optional. Glands much larger than PG7 (12mm) will not fit in the available space in that corner.  Strategically placed holes in the clips provide zip-tie locations to secure your logger.

Holes for Zip-tie securing


Using double sided tape to hold the parts inside the housing (rather than traditional stand-offs) makes this stage of the build remarkably quick.  Adding male Dupont pins allows you to join internal and external wires via the breadboard.  Be sure to use wires that are long enough to reach the mini breadboard. 

Bowing on some boxes can reduce the contact patch on the battery holder, if so try adding another layer of  tape, or upgrade to 30LB.

You can “reactivate” spent desiccant packs with a few zaps in the microwave – if they have indicator beads.

Connecting external sensors to the housing:

It’s worth mentioning the breadboard contacts are notoriously sensitive to vibration, etc. Once your testing stage is complete, and your prototype is working as it should, bring those  sensor wires directly over to the screw terminal connections for a more secure connection.  Also remember to put protective tape over any sensor ports that need to remain open before potting those sensor boards in epoxy. Otherwise you might clog the sensor by accidentally letting a drop fall into it.

Your Logger is ready!    (~2 to 2.5 hours)

Now you can test your new logger to confirm all the connections are working:

1. Test the LED – Edit & upload the default blink sketch, changing the pin numbers each time to match your RGB LED connections.

2. Scan the I2C bus – with the scanner from the Arduino playgound. The eeprom on the RTC module is at address 0x56 or 57 and the DS3231 should show up at address 0x68. If you don’t see those two devices when you run the scan, there is something wrong with your RTC or the way it’s connected.

3. Test the EEprom on the RTC module – We’ve updated BroHogan’s original code from the playground to this tester script. You may have to change the I2C address at the start of the code based on the numbers shown during your I2C bus scan. The AT24C32 will store 4Kbytes, and has a 32-byte Page Write mode which accommodates the maximum of 30 bytes you can transport the wire libraries I2C coms buffer.  Make sure you don’t do eeprom page-writes that pass over the physical page boundaries set by that eeproms 32 byte block size. If you are ready for the added code complexity, buffering data to the eeprom can dramatically cut down on the number of SD card saves, however the eeprom communications are so slow that sometimes it ends up using the same amount of power as simply writing your data directly to the SD card directly.

An alternative parts arrangement for the classroom logger that makes room for a larger 4xAAA battery holder and DUPONT style connectors. It’s easy to move things around to suit your own projects, and rotating the breadboard gives you room for larger battery holders & longer operating times. Note: For most 1.5V alkaline batteries, (voltage-1)*200 will give you the approximate percentage of total capacity remaining.

4. Set the RTC time, and check that the time was set – There are dozens of good Arduino libraries you could use to control the DS3231, and there is a script over at TronixLabs.com that lets you set the clock without a library. The trick with Tronix’s “manual” method is to change the parameters in setDS3231time(second, minute, hour, dayOfWeek, dayOfMonth, month, year);  to about 1-2 minutes before the actual time, and then wait to upload that code till about 10-15 seconds before your computers clock reaches that time (to compensate for the compiler delay). Open the serial window immediately after the upload finishes, and when you see the time being displayed, upload the examples>blink sketch to remove the clock setting program from memory – otherwise it will keep resetting the RTC every time the Arduino re-starts.  [ Note: hour  in 24-hour time, & year with two digits eg: setDS3231time(30,42,21,4,26,11,14);  ]

5. Check the SD card is working with Cardinfo – Changing chipSelect = 4; to chipSelect = 10;
Note that this logger requires the SD card to be formatted as fat16, so most 4GB or larger High Density cards will not work. Most loggers only generate 5 Kb of data per year anyway.

These breadboard connections are really vulnerable to vibration, so for quick back-yard tests of a rough prototype I sometimes add a tiny spot of hot-melt glue to stabilize the components. Breadboards add about 2-4pF of capacitance for side by side rows so the rule of thumb is you shouldn’t run protoboard circuits much faster than 1 MHz.

6. Check the sleep current – With an SD card inserted in the logger, upload this Pro Mini datalogger starter script with no changes (note: that code requires you to install three libraries as well). Then connect an ammeter between the positive battery connection and the Vin screw terminal (you will need an extra wire to do this) and run the logger from the AAA or AA battery pack.  After the initialization sequence has finished, the reading on the screen (in milliamps) is your loggers sleep current.

AAA cells usually provide about 1000 milliamp-hours of power to their rated 0.8v, but we are only using half of that from alkaline batteries with a regulator input cutoff up at 3.6v.  So dividing 500 by your loggers sleep current gives you a rough estimate of your loggers operating life (in hours).  For a more accurate estimate, you can use one of the Battery Life Calculators on the web with 250ms @5mA for your sample time.  Changing the power supply to 4xAAA cells lets you bring alkalines down to 0.9v/cell, extracting almost the entire 1000 mAh. Lithium AAAs deliver almost their entire capacity above 1.2v/cell so 3xAAA lithiums yield almost 1000 mAh capacity even with the 3.6v input cut-off.

Thermal response of 3x AAA’s (mV left axis) vs Temp (°C right axis) in the standard voltage-regulated classroom build running the starter code from GithubThe 90mv drops caused by SD card “controller housekeeping events” at 20°C  increase to ~220mV as temps near -15°C. These periodic high drain (100-200mA) events would likely trigger the low-voltage shutdown before anything else in the loggers normal duty cycle. (Note: Battery readings taken AFTER SD save, Sample interval: 1 min, sleep current for this test unit: 0.22mA )

Its worth noting that the starter program we’ve provided captures an ambient temperature record from the RTC in 0.25°C increments (example right).  New sensor readings can be added to the log file by inserting:
followed by a separator file.print(“,”);
in the main loop before you close the datafile on the SD card. Then change the text in the dataCollumnLabels[] declaration to add headers to match your new data. This starter script automatically generates required data files on the SD card at startup, and tracks the rail voltage for those adventurous enough to run without a regulator. SD memory is electrically more complex than the Pro Mini processor, with some using a 32 bit arm core.

Once you have your logger running, you might want to review our tutorial on adding sensors to your data logger. And after that you’ll find a few more advanced I2C sensor guides on this site as well (…and the list is constantly growing) We’ve also developed methods to add 5110 LCD & OLED screens to the Cave Pearl Loggers using the fewest system resources. These screens are easily accommodated on the proto-board in this build.

For people wanting to take their skills farther, you can explore the gritty details of how we optimize these loggers for multi-year deployments in the 2018 Sensors article (free to download). The research loggers are a more challenging build, that fits inside an underwater housing made from PVC pipe

Addendum: Power Management (Optional)

On the standard build described above, the Pro-Mini’s MIC5205 power regulator should deliver  sleep currents below 0.25 mA (Pro Mini ~0.05 mA + sleeping SDcard ~0.05-0.09 mA + RTC ~0.09 mA). That should reach several months operation on 3 AAA cells before the batteries reach the regulators 3.4v input cut-off.  I usually have regulated loggers go into shutdown mode at ~3.6v to reduce the chance of leaks, because alkaline batteries often spill their guts when they reach 1v/cell.

There are a couple of relatively simple modifications to the basic logger that more than double the operational time – but they both come with important implications you should understand fully before adding them to your project. The RTC mod is relatively safe, but running a datalogger from a raw battery supply is not for the faint of heart. 

1) Cutting the VCC leg on the DS3231 chip forces the RTC to run from the coin-cell. >Set Bit 6 of CONTROL_REG 0x0E to enable wake alarms from the backup battery!

2) Remove the regulator: few LDO’s have reverse current protection & most regs leach 30-90 uA if the voltage on output line is  higher than on their input

2) Then connect the positive wire from a 2X LITHIUM AA battery pack directly to the Vcc rail on the Pro Mini

The DS3231 RTC was designed to handle power supply failures by switching over to the backup coin-cell battery, and it enters a special 3uA “timekeeping mode” to use less power in that situation. However the chip is still fully capable of generating alarms (provided you set the Battery-Backed Square-Wave Enable bit of CONTROL_REG 0x0E to 1) , and of responding to the I2C bus at 400 mHz. So if you cut the main power leg on the RTC you reduce the loggers sleep current by almost 0.1mA (~40%). The trade-off is that your loggers operation is now entirely dependent on the 200mAh CR2032 coincell to keep the clock delivering wake-up alarms when you are also asking it to deal with pulsed loads in the 80 uA range every time you communicate over the I2C bus. The RTC also can not generate temp-corrected frequency outputs on the 32kHz pin when operating from the coin cell.

Here drops of hot glue secure the RTC coin cell battery against accidental resets.

Another quid pro quo here is that coin-cell holders occasionally lose contact very briefly under vibration, so if you cut the Vcc input leg – add  a  0.1 μF capacitor (ceramic 104) across the coin-cell holder pins. That will give you about 80 ms coverage, which should be longer than the holder will lose contact. Otherwise a hard bump can reset the RTC back to its Jan 01 2000 default.  The wake-up alarms usually continue after that kind of reset, however fixing an entire years worth of time series data based on your field notes is a pain in the backside.  Real world installations often involve this kind of rough handling, so I prefer a more advanced RTC modification that keeps two power lines feeding the RTC.  But for more “gentle” deployments, simply cutting the chip’s vcc leg & adding a couple of drops of hot glue OK. Write the installation date on the coin cell with a black marker. I do this to all of my batteries now…

For loads in the 0.1mA range, the MIC5205 is less than 60% efficient. So the other modification is to remove the voltage regulator and run the entire system from 2x LITHIUM AA batteries.  Lithium batteries (like the Energizer L91) have two characteristics that make them well suited to this approach: 1) they have an extremely flat discharge curve, yielding >75% of their power before the voltage falls below 1.5v/cell and 2) a pair of brand new lithium cells gives a combined voltage right at 3.6 volts. If you look at any of the SD card manufacturers’ specifications, they all specify a voltage range of 2.7v to 3.6v.  At 8 MHz the ATmega328P processor on the ProMini supports voltage levels between 2.7 V and 5.5 V. Both of these ranges overlap beautifully with the lithium cell’s discharge curve.

Here I’ve done both modifications to the basic build, and brought the sleep current (with no sensors) from an unmodified starting point of 228μA, down to 80μA. Most of that remaining power is due to the sleeping SD card, since the Pro Mini only draws about 5μA in deep sleep mode. Using only 1/2 of the 3000 mAh capacity of a typical AA Lithium pack would keep a logger that sleeps at 0.1mA alive for more than a year, and my rough estimate is that the RTC mod will get you at least twice that much time from a new Cr2032 cell – with a typical 5-15 minute sampling interval.      [NOTE: I’m using an EEVblog uCurrent here to display the μA sleep current on a DVM as milivolts. It’s an exceedingly useful tool that lets you read sleep currents into the nano-amp range without adding the burden voltage you’d see from putting your meter directly into the circuit ]

Even with one less cell powering the system, getting rid of the MIC5205 yields a 25-30% reduction in sleep current for a typical build. While this is very close to the reduction you get from changing to a more efficient regulator like the MCP1702, the other cool thing about this mod is that the processor on the Pro Mini can take a reading of it’s rail voltage by comparing it to the 1.1v internal band-gap reference. So all you need is a little bit of code, and you can keep track of your rail=battery level without a voltage divider, although for accuracy you should take a reading of the actual band-gap voltage using a utility like the CalVref sketch from openenergymonitor. (note that Atmel provides no spec on the long term stability of the bandgap ref) 

Unlike alkaline batteries, lithium cells have very little voltage droop with brief loads below 100mA, but it’s still a good idea to protect your SD card from potentially data corrupting brief low voltage spikes with a capacitor.  The formula for figuring out how much the voltage across a capacitor will drop is  ΔV = current(A)*time(sec)/C(farads) – but you need to decide how much your supply can drop to know how big to make the capacitor.

Adding a 47uF (or larger) on the microSD rails should keep the shortest transients under control, and once the system reg. has been removed running a jumper between Vcc & Raw recruits the orphan (10uF tantalum) capacitor from the input side of the (now removed) regulator.  Always check the supply voltage before the data saving begins and perform a low voltage shutdown when the lithium pack reaches 2875mv. (ie: at least 150mv above the SD’s 2.7v safe writing minimum). Generate a new data file every couple of weeks so only the last one is vulnerable during the write process. 

2x LITHIUM AA’s (mV left axis) vs Temp (°C right axis) supplying power to an unregulated build running the same code from GithubThe key observation is that the initial 50mv supply voltage variation recorded at 22°C increases  ~100mV at temps near -15°C. For Lithium chemistry batteries with flat discharge curves, the voltage-drop under load is often a better indicator of the remaining battery capacity than the cell voltage. (Readings taken AFTER SD CARD SAVE, Sampling every 1 min, overall sleep current 0.17mA   Note: the 200mv baseline drop on the cells is slightly exaggerated here because low temps increase the bandgap slightly) 

Of course everything has a price, and removing the regulator means you’ve not only lost your reverse voltage protection – you also need to think about how all the components in your system will respond to a decreasing supply voltage over time. Ratiometric analog circuits using the rail as Aref handle this well, but even if you want to use the 1.1v bandgap ref – you already know what the rail voltage is, so you can easily throw a compensation factor into the calculation.  The real problem is that when 2.7 – 3.6v is listed on the spec sheet for a digital sensor – that’s no guarantee the readings will be consistent when the supply voltage changes.  You could have very different error percentages at the high & low end and ambient temperature fluctuations could push your batteries through significant voltage changes every single day (lithium cells are more resistant to this than alkaline).  Testing is the only way to find out if your sensors can handle the variation, and if you don’t have time for that it might be worth keeping that voltage regulator on board despite the reduced lifespan. For field units, I usually replace the MIC5205 with a more efficient MCP1700 regulator because I want all the data consistency I can get.  Another thing to keep in mind when you are hoping for a really low-power build is leakage currents through leftover flux – it’s always worth the time to get your parts squeaky-clean during construction.

Addendum 2019-02-21:

A teacher friend asked us for a version that was less dependent on soldering because they didn’t have enough money for a complete classroom set of equipment. So we’ve worked out a “minimum build” of the classroom logger that uses crimped Dupont jumpers to reduce assembly time to about 1 hour (before sensors):

We’ve also added support to the starter code for using the indicator LED as a light sensor, but this requires that you ground the indicator LED’s common cathode through pin D3, rather than directly to GND as shown in the tutorial above. Then you can gather three-channel light level data like this:

Readings from an RGB LED deployed outside in our back yard on an overcast day with two light snow fall events. The yellow line is from an LDR sensor in the same logger that was over-sampled to 16-bit. 

If you go down that rabbit-hole, you might want to calibrate your diy sensor output against the NOAA observations for your area, which includes hourly data for thousands of stations. The data includes temperature, dew point (from which you can compute relative humidity), wind speed and direction, and you can calculate solar position.


Tutorial: Using an MS5803 pressure sensor with Arduino

With the DS18B20 temperature sensors in place, it was time to add the ‘depth’ part of the standard CDT suite.  After reading an introduction to the Fundamentals of Pressure Sensor Technology, I understood that most of the pressure sensors out there would not be suitable for depth sensing because they are gauge pressure sensors, which need to have a “low side” port vented to atmosphere (even if the port for this is hidden from view).

This is one of our earliest pressure loggers with a 3″ pvc housing. We now use 2″ PVC pipes (shown at the end of this post) which are much easier to to construct. For an exploded view of the new housing see the appendix at the end of the article in Sensors.

I needed an “absolute” pressure transducer, that has had it’s low side port sealed to a vacuum. I found a plethora of great altimiter projects in the rocketry & octocopter world, (with Kalman filtering!) but far fewer people doing underwater implementations in caves.  But there are a few DIY dive computer  projects out there, at various stages of completion, that use versions of the MS5541C & MS5803 pressure sensors from Measurement Specialties, or the MPX5700 series from Freescale. Victor Konshin had published some code support for the MS5803 sensors on Github, but his .cpp was accessing them in SPI mode, and I really wanted to stick with an I2C implementation as part of my quest for a system with truly interchangeable sensors. That lead me to the Open ROV project were they had integrated the 14 bar version of the MS5803 into their IMU sensor package. And they were using an I2C implementation. Excellent! I ordered a couple of 2 bar, and 5 bar sensors from Servoflo ($25 each +shipping..ouch!) , and a set of SMT breakout boards from Adafruit. A little bit of kitchen skillet reflow, and things were progressing well. (note: I mount these sensors now by hand, which is faster after you get the hang of it it)

Breaking out the I2C interface traces to drive my pressure sensor. My first "real" hack on the project so far.

My first “real” hack on the project so far. (Note: This material was posted in early 2014 , and the only reason I did this hack was that at the time I was running the Tinyduino stack directly from an unregulated 4.5 battery. On my more recent loggers, built with regulated 3.3v promini style boards, I can just use the Vcc line to power the MS5803 sensors, without all this bother…)

But as I dug further into the MS5803 spec sheets I discovered a slight complication. These sensors required a supply voltage between 1.8 – 3.6 V, and my unregulated Tinyduino stack, running on 3 AA’s, was swinging from 4.7 down to 2.8v.  I was going to need some sort of voltage regulator to bring the logic levels into a range that the senor’s could tolerate, with all the attendant power losses that implied… And then it dawned on me that this same problem must exist for the other I2c sensors already available on the Tinyduino platform. So perhaps I might be able to hack into those board connections and drive my pressure sensor? (instead of burning away months worth of power regulating the entire system) The Tiny Ambient Light Sensor shield carried the  TAOS TSL2572 which had nearly identical voltage and power requirements to my MS5803.

I used JB weld to provide support for those delicate solder connections.

I used JB weld to provide support for those delicate solder connections.

So their voltage regulator, and level shifter, could do all the work for me if I could lift those traces.  But that was going to be the most delicate soldering work I have ever attempted. And I won’t pull your leg, it was grim, very grim indeed. Excess heat from the iron  conducted across the board and melted the previous joints with each additional wire I added.  So while the sensors themselves came off easily with an drywall knife, it took two hours (of colorful language…) to lift the traces out to separate jumper wires. I immediately slapped on a generous amount  of JB weld, because the connections were so incredibly fragile. I produced a couple of these breakouts, because I have other sensors to test, and I face this same logic level/voltage problem on the I2C lines every time I power the unregulated Tiny duino’s from a computer USB port.

With a connection to the mcu sorted, it was time to look at the pressure sensor itself. Because I wanted the sensor potted as cleanly as possible, I put the resistor, capacitor, and connections below the breakout board when I translated the suggested connection pattern from the datasheets to this diagram:

The viewed from above, with only one jumper above the plane of the breakout board.

This is viewed from above, with only one jumper above the plane of the SOIC-8 breakout. I used a 100nF (104) decoupling cap. The PS pin (protocol select) jumps to VDD setting I2C mode, and a 10K pulls CSB high, to set the address to 0x76. Connecting CSB to GND would set the I2C address to 0x77 so you can potentially connect two MS5803 pressure sensors to the same bus.

And fortunately the solder connections are the same for the 5 bar, and the 2 bar versions:

I've learned not waste time making the solder joints "look pretty". If they work, I just leave them.

I’ve learned not waste time making the solder joints “look pretty”. If they work, I just leave them.

After testing that the sensors were actually working, I potted them into the housings using JB plastic weld putty, and Loctite E30CL:

The Loctite applicator gun is damned expensive, but it does give you ultra-fine control.

The Loctite applicator gun is expensive, but it gives you the ability to bring the epoxy right to the edge of the metal ring on the pressure sensor.

So that left only the script. The clearly written code by by Walt Holm  (on the Open ROV github) was designed around the 14 bar sensor; great for a DIY submersible, but not quite sensitive enough to detecting how a rainfall event affects an aquifer.  So I spent some time modifying their calculations to match those on the 2 Bar MS5803-02 datasheet :

// Calculate the actual Temperature (first-order computation)
TempDifference = (float)(AdcTemperature – ((long)CalConstant[5] * pow(2, 8)));
Temperature = (TempDifference * (float)CalConstant[6])/ pow(2, 23);
Temperature = Temperature + 2000; // temp in hundredths of a degree C

// Calculate the second-order offsets
if (Temperature < 2000.0) // Is temperature below or above 20.00 deg C?

{T2 = 3 * pow(TempDifference, 2) / pow(2, 31);
Off2 = 61 * pow((Temperature – 2000.0), 2);
Off2 = Off2 / pow(2, 4);
Sens2 = 2 * pow((Temperature – 2000.0), 2);}


{T2 = 0;
Off2 = 0;
Sens2 = 0;}

// Calculate the pressure parameters for 2 bar sensor
Offset = (float)CalConstant[2] * pow(2,17);
Offset = Offset + ((float)CalConstant[4] * TempDifference / pow(2, 6));
Sensitivity = (float)CalConstant[1] * pow(2, 16);
Sensitivity = Sensitivity + ((float)CalConstant[3] * TempDifference / pow(2, 7));

// Add second-order corrections
Offset = Offset – Off2;
Sensitivity = Sensitivity – Sens2;

// Calculate absolute pressure in bars
Pressure = (float)AdcPressure * Sensitivity / pow(2, 21);
Pressure = Pressure – Offset;
Pressure = Pressure / pow(2, 15);
Pressure = Pressure / 100; // Set output to millibars

The nice thing about this sensor is that it also delivers a high resolution temperature signal, so my stationary pressure logger does not need a second sensor for that.

A Reef census is co-deployed with the pressure sensor to ground truth this initial test.

A Reefnet Census Ultra is co-deployed with my pressure sensor to ground truth this initial run.

So that’s it, the unit went under water on March 22, 2014, and the current plan is to leave it there for about 4 months. This kind of long duration submersion is probably way out of spec for the epoxy, and for the pressure sensors flexible gel cap. But at least we potted the sensor board with a clear epoxy,  so it should be relatively easy to see how well everything stands up to the constant exposure. (I do wonder if I should have put a layer of silicone over top of the sensor like some of the dive computer manufacturers)


Addendum 2014-03-30

I keep finding rumors of a really cheap “uncompensated” pressure sensor out there on the net for about 5 bucks: the HopeRF HSF700-TQ.  But I have yet to find any for sale in quantities less than 1000 units.  If anyone finds a source for a small number of these guys, please post a link in the comments, and I will test them out.  The ten dollar MS5805-02BA might also be pressed into service for shallow deployments using its extended range, if one can seal the open port well enough with silicone. And if all of these fail due to the long duration of exposure, I will go up market to industrial sensors isolated in silicon oil , like the 86bsd, but I am sure they will cost an arm and a leg. 

Addendum 2014-04-15

Looks like Luke Miller has found the the float values used in the calculations from the ROV code generates significant errors. He has corrected them to integers and posted code on his github. Unfortunately one of the glitches he found was at 22.5°C, right around the temperature of the water my units are deployed in. I won’t know for some months how this affects my prototypes. With my so many sensors hanging off of my units, I don’t actually have enough free ram left for his “long int” calculations, so I am just logging the raw data for processing later.

Addendum 2014-09-10

The unit in the picture above survived till we replaced that sensor with a 5-Bar unit on Aug 25th. That’s five months under water for a sensor that is only rated in the spec sheets for a couple of hours of submersion. I still have to pull the barometric signal out of the combined” readings, but on first bounce, the data looks good (mirroring the record from the Reefnet Sensus Ultra)  Since the 2-Bar sensor was still running, it was redeployed in Rio Secreto Cave (above the water table) on 2014-09-03. It will be interesting to see just how long one of these little sensors will last.

Addendum 2014-12-18

The 2Bar unit (in the photo above) delivered several months of beautiful barometric data from it’s “dry” cave deployment, and was redeployed for a second underwater stint in Dec 2014. The 5Bar unit survived 4 months of salt water exposure, but we only got a month of data from it because an epoxy failure on the temperature sensor drained the batteries instantly.  After a makeshift repair in the field, it has now been re-deployed as a surface pressure unit. The good news is that we had the 5Bar sensor under a layer of QSil 216 Clear Liquid Silicone, and the pressure readings look absolutely normal compared to the naked 2bar sensor it replaced. So this will become part of my standard treatment for underwater pressure sensors to give them an extra layer of protection.

Addendum 2015-01-16

I know the MCP9808 is a bit redundant here, but at only $5, it's nice to get to ±0.25C accuracy. The MS5803's are only rated to ±2.5ºC

I know the MCP9808 is a little redundant here, but at $5 each, it’s nice to reach ±0.25ºC accuracy. The MS5803’s are only rated to ±2.5ºC, and you can really see that in the data when you compare the two. The low profile 5050 LED still has good visibility with a 50K Ω limiter on the common ground line. Test your sensors & led well before you pour the epoxy! (Note: the 9808 temp sensor & LED pictured here failed after about 8 months. I suspect this was due to the epoxy flexing under pressure at depth because of the large exposed surface area. The MS5803 was still working ok.)

Just thought I would post an update on how I am mounting the current crop of pressure sensors.  My new underwater housing design had less surface area so I combined the pressure sensor, the temperature sensor, and the indicator LED into a single well which gives me the flexibility to use larger breakout boards. That’s allot of surface area to expose at depth, so I expect there will some flexing forces. At this point I have enough confidence  in the Loctite ECL30 to pot everything together, even though my open ocean tests have seen significant yellowing. The bio-fouling is pretty intense out there, so it could just be critters chewing on the epoxy compound. Hopefully a surface layer of Qsil will protect this new batch from that fate.

Addendum 2015-03-02

Just put a 4-5mm coating of Qsil over a few MS5803’s in this new single-ring mount, and on the bench the coating seems to reduce the pressure reading by between 10-50 mbar, as compared to the readings I get from the sensors that are uncoated. Given that these sensors are ±2.5% to begin with, the worst ones have about doubled their error.  I don’t know if this will be constant through the depth range, or if the offset will change with temperature, but if it means that I can rely on the sensor operating for one full year under water, I will live with it.

Addendum 2015-04-06

Just returned from a bit of fieldwork where we had re-purposed a pressure sensor from underwater work to the surface. That sensor had Qsil silicone on it, and while it delivered a beautiful record in the the flooded caves, where temperatures vary by less than a degree, it went completely bananas out in the tropical sun where temps varied by 20°C or more per day. I suspect that the silicone was expanding and contracting with temperature, and this caused physical pressure on the sensor that completely swamped the barometric pressure signal. 

Addendum 2016-02-01

Holding MS5803 sensor in place for soldering

Use the smallest with zip tie you can find.


I also use a wood block to steady my hand during the smd scale soldering.

Since these are SMD sensors, mounting them can be a bit of a pain so I though would add a few comments about getting them ready. I find that holding the sensor in place with a zip tie around the SOIC-8 breakout makes a huge difference.  Also, I find it easier to use the standard sharp tip on my Hakko, rather than a fine point  which never seem to transfer the heat as well.  I plant the point of the iron into the small vertical grooves on the side of the sensor.  I then apply a tiny bead of solder to the tip of the iron, which usually ends up sitting on top, then I roll the iron between my fingers to bring this the bead around to make contact with the pads on the board. So far this technique has been working fairly well, and though the sensors do get pretty warm they have all survived so far.  If you get bridging, you can usually flick away the excess solder if you hold the sensor so that the bridged pads are pointing downwards when you re-heat them.


Stages of MS5803 mounting procedure

After mounting the sensor to the breakout board, I think of the rest of the job in two stages: step one is the innermost pair (which are flipped horizontally relative to each other) , and step two by the outermost pair where I attach the incoming I2C wires.  Here SCL is yellow, and SDA is white.  In this configuration CSB is pulled up by that resistor, giving you an I2C address of 0x76.  If you wanted a 0x77 buss address, you would leave out the resistor and attach the now empty hole immediately beside the black wire to that GND line.

Sometimes you need to heat all of the contacts on the side of the sensor at the same time with the flat of the iron to re-flow any bridges that have occurred underneath the sensor itself. If your sensor does not work, or gives you the wrong I2C address, its probably because of this hidden bridging problem.

back side connection ms5803

Adafruit still makes the nicest boards to work with, but the cheap eBay SOIC8 breakouts (like the one pictured above)  have also worked fine and they let me mount the board in smaller epoxy wells.  Leave the shared leg of the pullup resistor/capacitor long enough to jump over to Vcc on the top side of the board .

Addendum 2016-03-08

Have the next generation of pressure sensors running burn tests to see what offsets have been induced by contraction of the epoxy.  I’ve been experimenting with different mounting styles, to see if that plays a part too:

These housings are still open, as it takes a good two weeks for the pvc solvent to clear out...

The housings are left open during the bookshelf runs as it takes a couple of weeks for the pvc solvent to completely clear out, and who knows what effect that would have on the circuits. (Note: for more details on how I built these loggers and housings, you can download the paper from Sensors )

The MS5803’s auto-sleep brilliantly, so several of these loggers make it down to ~0.085 mA between readings, and most of that is due to the SD card.  I’m still using E-30Cl, but wondering if other potting compounds might be better? There just isn’t much selection out there in if you can only buy small quantities at one time. The E30 flexed enough on deeper deployments that it eventually killed off the 5050 LEDs. (using standard 5mm body LEDs  now) And I saw some suspicious trends in the temp readings from the MCP9808 under that epoxy too…

Addendum 2016-03-09

Just a quick snapshot from the run test pictured above:


These are just quick first-order calculations (in a room above 20°C). Apparently no effect from the different mounting configurations, but by comparison to the local weather.gov records, the whole set is reading low by about 20 mbar. This agrees with the offsets I’ve seen in other MS5803 builds, but I am kicking myself now for not testing this set of sensors more thoroughly before I mounted them. Will do that properly on the next batch.

Addendum 2016-09-22

You can change the I2C address on these sensors to either 0x76 or 0x77 by connecting the CSB pin to either VDD or GND. This lets you connect two pressure sensors to the same logger, and I’ve been having great success putting that second sensor on cables as long as 25 m. This opens up a host of possibilities for environmental monitoring especially for things like tide-gauge applications, where the logger can be kept above water for easier servicing.

Addendum 2017-04-30

Given what a pain these little sensors are to mount, it’s been interesting to see the price of these pressure sensor breakout modules falling over time. This spring the 1 & 14 bar versions fell below $24 on eBay.  Of course they could be fake or defective in some way, but I’m probably going to order a few GY-MS5803’s to see how they compare to the real thing.

Buffering Logger Sensor Data to a 4K AT24C32 I²C EEprom

These MS5803 pressure sensors are my very first SMD reflow pieces. Hopefully I did not toast them on the kitchen skillet I was using...

These MS5803 pressure sensors are my first  attempt at diy SMD. Hopefully I did not toast them on the electric skillet I was using to reflow…

While I was waiting on the results of the week-long power consumption test of the SRAM buffering script, I continued thinking about other ways I might extend the operating lifespan of the Pearls. I had originally considered using the processors 1024 bytes of internal eeprom for temporary storage, but work over at the solar duino project showed me that using an external 24AA256 is faster and consumes less power than the internal eeprom.  AND the cheap DS3231 RTC boards I had just received from eBay had an AT24C32 eeprom chip: 4k of storage just sitting there waiting to be used…And the data sheets told me that the SD card could be drawing up to 80 ma for who knew how long, while the datasheet for the eeprom listed a paltry 2 ma per block write taking only 5 milliseconds…

Three days of heavy lifting later…I had cobbled together this script, using PSTRING to to dramatically simplify the concatenation of the sensor data into a 28 byte long char buffer (the wire library buffer is only 32 characters long and you need two bytes for the mem address + room for string termination characters, etc).  Pstring uses simple print statements, but more importantly, it never causes buffer overflows if you try to stuff in too much data, like a mishandled sprintf statement could. This also gives me some flexibility while I am still changing my sensors around, as I don’t quite know what the final data is going to look like yet.

So this code buffers each sensor read cycle to the I²C eeprom, using two page writes per cycle.  This simplifies the code a bit, as I have only one set of variables on the go. Then when the countlog = SamplesPerCycle, it does a reverse for loop to pull the data back out of the eeprom, and write it to the SD card. With a rated endurance of 1 million write cycles, I’m not worried about wearing the eeprom out either.

And the result? This script gives me ~700 sensor read cycles per 8mV drop on a 2 AA battery power supply. This is less than half the performance of the SRAM buffering code, which surprised me quite a bit, but I guess that *192 eeprom page writes (with the attendant I2C coms) +1 SD card write per day,  uses 2-3 times as much power as SRAM buffering with 8 SD card write cycles for that same days worth of records. On paper all that eeprom writing represents almost one second per day at 2 mA, which doesn’t seem like much. So either my tiny 128 mb SD cards are very quickly to going back into sleep mode, or keeping the CPU running during all that I2C traffic is using a significant amount of power…?

So what did I learn:  Well knowing that a fair bit of I²C & eeprom traffic will more than double the power drain is quite handy, as I now jump into connecting temperature, pressure, compass, and perhaps other sensors, to those same I2C lines. It will be interesting to see what the real world performance of these loggers is when the rubber meets the…ummm…cave diver.

Addendum 2015-04-18

I simply let the wires I am already using to tap the cascade port I2C lines poke up enough to give me solder points for the EEprom. Don't forget to remove the pullups on the EEprom board.

I simply let the wires I am already using to tap the cascade port I²C lines poke up enough to give me solder points for the EEprom. Don’t forget to remove the pullups on the EEprom board as you already have pullups on the RTC breakout.

The AT24c32 chip can only hold 4k of data –  if you write beyond 4096 bytes, it rewrites over the old data!  So once you have done 128 page writes, you need to flush to the sd card. In this code, I write two 32-byte pages per record. So I have a upper limit of 64 records before I hit that block limit and start to overwrite the data!  I could bump it up to 32k of external eeprom for only $1.50, so I will have to try a few experiments to see if that helps. That 32k eeprom is a code compatible, drop in replacement. All you have to do is change the I2C address for the eeprom for the new board.

Addendum 2014-07-16

The Code below has been posted to the projects GitHub. Look around as the more recent codebuilds are much more elegant than this crude early version, and include sensor support for an easy to build logger.

//Date, Time and Alarm functions using a DS3231 RTC connected via I2C and Wire lib by https://github.com/MrAlvin/RTClib 

// based largely on Jean-Claude Wippler from JeeLab’s excellent RTC library https://github.com/jcw
// clear alarm interupt from http://forum.arduino.cc/index.php?topic=109062.0
// get temp from http://forum.arduino.cc/index.php/topic,22301.0.html
// BMA250_I2C_Sketch.pde -BMA250 Accelerometer using I2C from http://www.dsscircuits.com/accelerometer-bma250.html
// internal Vcc reading trick //forum.arduino.cc/index.php/topic,15629.0.html
// and http://forum.arduino.cc/index.php?topic=88935.0
// free ram code trick: http://learn.adafruit.com/memories-of-an-arduino/measuring-free-memory
// power saving during sleep from http://www.gammon.com.au/forum/?id=11497
// I2C routine based on http://playground.arduino.cc/Code/I2CEEPROM#.UwrbpPldUyI
// New file name routine from http://forums.adafruit.com/viewtopic.php?f=31&t=17964

#include <Wire.h> // 128 byte Serial buffer
#include <SPI.h> // not used here, but needed to prevent a RTClib compile error
#include <avr/sleep.h>
#include <RTClib.h>
#include <PString.h>
#include <SdFat.h>
SdFat sd; // Create the objects to talk to the SD card
SdFile file;
const byte chipSelect = 10; //sd card chip select

#define SampleInterval 1 // power-down time in minutes before interupt triggers the next sample
#define SamplesPerCycle 5 // # of sample cycles to buffer in eeprom before writing to the sd card: MAX of 64! (do not exceed 128 page writes or data will be lost)
unsigned int countLogs = 0; // how many records written to each file
unsigned int fileInterval = 10; // #of log records before new logfile is made
/* count each time a log is written into each file. Must be less than 65,535
counts per file. If the sampleinterval is 15min, and fileInterval is 2880
seconds, then 96samples/day * 30days/month = 30 day intervals */

#define ECHO_TO_SERIAL // echo data that we are logging to the serial monitor
// if you don’t want to echo the data to serial, comment out the above define
//#define WAIT_TO_START
/* Wait for serial input in setup(), only if serial is enabled. You don’t want
to define WAIT_TO_START unless ECHO_TO_SERIAL is defined, because it would
wait forever to start if you aren’t using the serial monitor.
If you want echo to serial, but not wait to start,
just comment out the above define */

char FileName[] = “LOG00000.CSV”; //the first file name

#ifndef cbi //defs for stopping the ADC during sleep mode
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

#define DS3231_I2C_ADDRESS 104 //for the RTC temp reading function
#define EEPROM_ADDR 0x57 // I2C Buss address of AT24C32 32K EEPROM
#define EEPromPageSize 32 //32 bytes for the AT24c32 I am using

#define BMA250 0x18
#define BW 0x08 //7.81Hz bandwith
#define GSEL 0x03 // set range 0x03=2g, 0x05=4, 0x08=8g, 0x0C=16g

//I2C eeprom variables
unsigned int CurrentPageStartAddress = 0; //set to zero at the start of each cycle
char EEPROMBuffer[28]; //this buffer contains a string of ascii
//char EEPROMinBuffer[28]; // this buffer recieves numbers from the eeprom was an unsigned char
//note the data read from the eeprom is binary – not ascii characters!

byte Alarmhour = 1;
byte Alarmminute = 1;
byte dummyRegister;
volatile boolean clockInterrupt = false;
byte tMSB, tLSB; //for the RTC temp reading function
float RTCTempfloat;
char CycleTimeStamp[ ]= “0000/00/00,00:00:00”;
byte Cycle=0;

//variables for accellerometer reading
uint8_t dataArray[16];
int8_t BMAtemp;
float BMAtempfloat;
uint8_t wholeBMAtemp,fracBMAtemp;
int x,y,z; //acc readings range to negative values

int temp3231;
uint8_t wRTCtemp,fRTCtemp; //components for holding RTC temp as whole and fraction component integers
int Vcc;//the supply voltage via 1.1 internal band gap
byte ledpin = 13; //led indicator pin not used in this code
// the LED on pin 13 is also shared with the SPI SCLK clock, which is used by the microSD card TinyShield.
// So when you use a SPI device like the SD card, the LED will blink, and the SD library will override the digitalWrite() function call.
// If you need a LED for indication, you’ll need to hook up an external one to a different I/O pin.

void setup () {

digitalWrite(INTERRUPT_PIN, HIGH);//pull up the interrupt pin
pinMode(13, OUTPUT); // initialize the LED pin as an output.

clearClockTrigger(); //stops RTC from holding the interrupt low if system reset
// time for next alarm

#ifdef WAIT_TO_START // only triggered if WAIT_TO_START is defined at beging of code
Serial.println(F(“Type any character to start”));
while (!Serial.available());

delay(1000); //delay to prevent power stutters from writing header to the sd card
DateTime now = RTC.now();

DateTime compiled = DateTime(__DATE__, __TIME__);
if (now.unixtime() < compiled.unixtime()) { //checks if the RTC is not set yet
Serial.println(F(“RTC is older than compile time! Updating”));
// following line sets the RTC to the date & time this sketch was compiled
RTC.adjust(DateTime(__DATE__, __TIME__));

initializeBMA(); //initialize the accelerometer – do I have to do this on every wake cycle?

//get the SD card ready
pinMode(chipSelect, OUTPUT); //make sure that the default chip select pin is set to output, even if you don’t use it

Serial.print(F(“Initializing SD card…”));

// Initialize SdFat or print a detailed error message and halt
// Use half speed like the native library. // change to SPI_FULL_SPEED for more performance.
if (!sd.begin(chipSelect, SPI_HALF_SPEED)) {
Serial.println(F(“Cound not Initialize Sd Card”));

Serial.println(F(“card initialized.”));
Serial.print(F(“The sample interval for this series is: “));Serial.print(SampleInterval);Serial.println(F(” minutes”));
Serial.println(F(“Timestamp Y/M/D, HH:MM:SS,Time offset, Vcc = , X = , Y = , Z = , BMATemp (C) , RTC temp (C)”));

// open the file for write at end like the Native SD library
// O_CREAT – create the file if it does not exist
if (!file.open(FileName, O_RDWR | O_CREAT | O_AT_END)) {
Serial.println(F(“1st open LOG.CSV fail”));

file.print(F(“The sample interval for this series is: “));file.print(SampleInterval);file.println(F(” minutes”));
file.println(F(“YYYY/MM/DD HH:MM:SS, Vcc(mV), X = , Y = , Z = , BMATemp (C) , RTC temp (C)”));

digitalWrite(13, LOW);

void loop () {

// keep track of how many lines have been written to a file
// after so many lines, start a new file
if(countLogs >= fileInterval){
countLogs = 0; // reset our counter to zero
createLogFile(); // create a new file

CurrentPageStartAddress = 0;

for (int Cycle = 0; Cycle < SamplesPerCycle; Cycle++) { //this counts from 0 to (SamplesPerCycle-1)

if (clockInterrupt) {

read3AxisAcceleration(); //loads up the Acc data
DateTime now = RTC.now(); // Read the time and date from the RTC

sprintf(CycleTimeStamp, “%04d/%02d/%02d %02d:%02d:%02d”, now.year(), now.month(), now.day(), now.hour(), now.minute(), now.second());

wholeBMAtemp = (int)BMAtempfloat; fracBMAtemp= (BMAtempfloat – wholeBMAtemp) * 100; // Float split into 2 intergers
//can use sprintf(BMATempHolder, “%2d.%2d”, wholeBMAtemp[Cycle], fracBMAtemp[Cycle]) if we need to recompose that float
RTCTempfloat= get3231Temp(); wRTCtemp = (int)RTCTempfloat; fRTCtemp= (RTCTempfloat – wRTCtemp) * 100; // Float split into 2 intergers
Vcc = (readVcc());
if (Vcc < 2800){Serial.println(F(“Voltage too LOW”));error (“L”);}

//serial output for debugging – comment out ECHO_TO_SERIAL to eliminate
Serial.print(CycleTimeStamp); Serial.print(F(” Cycle “)); Serial.print(Cycle);Serial.print(F(“,”)); Serial.print(Vcc); Serial.print(F(“,”));
Serial.print(x); Serial.print(F(“,”));Serial.print(y); Serial.print(F(“,”)); ;Serial.print(z); Serial.print(F(“,”));
Serial.print(F(“, Ram:”));Serial.print(freeRam());
delay(40); //short delay to clear com lines

//Construct first char string of 28 bytes – end of buffer is filled with blank spaces flexibly with pstring
//but could contruct the buffer with sprintf if I wasn’t changing my sensors so often!

PString str(EEPROMBuffer, sizeof(EEPROMBuffer));
str = CycleTimeStamp;str.print(F(“,”));str.print(Vcc);str.print(F(” “));

Write_i2c_eeprom_page(EEPROM_ADDR, CurrentPageStartAddress, EEPROMBuffer); // whole page is written at once here
CurrentPageStartAddress += EEPromPageSize;

//Construct second char string of 28 bytes to complete the record
str = “,”; str.print(x);str.print(F(“,”));str.print(y);str.print(F(“,”));str.print(z);str.print(F(“,”));
str.print(wRTCtemp);str.print(F(“.”));str.print(fRTCtemp);str.print(F(“,”));str.print(F(” “));

Write_i2c_eeprom_page(EEPROM_ADDR, CurrentPageStartAddress, EEPROMBuffer); // 28 bytes/page is max whole page is written at once here
CurrentPageStartAddress += EEPromPageSize;

// IF full set of sample cycles is complete, run a loop to dump data to the sd card
// BUT only if Vcc is above 2.85 volts so we have enough juice!
if (Cycle==(SamplesPerCycle-1) && Vcc >= 2850){

Serial.print(F(” –Writing to SDcard –“)); delay (10);// this line for debugging only

CurrentPageStartAddress=0; //reset the page counter back to the beginning

file.open(FileName, O_RDWR | O_AT_END);
// open the file for write at end like the Native SD library
//if (!file.open(FileName, O_RDWR | O_AT_END)) {
// error(“L open file fail”);

for (int i = 0; i < SamplesPerCycle; i++) { //loop to read from I2C ee and write to SD card

Read_i2c_eeprom_page(EEPROM_ADDR, CurrentPageStartAddress, EEPROMBuffer, sizeof(EEPROMBuffer) ); //there will be a few blank spaces
CurrentPageStartAddress += EEPromPageSize;

Read_i2c_eeprom_page(EEPROM_ADDR, CurrentPageStartAddress, EEPROMBuffer, sizeof(EEPROMBuffer) );
CurrentPageStartAddress += EEPromPageSize;
file.println(F(” “));

// An application which writes to a file using print(), println() or write() must call sync()
// at the appropriate time to force data and directory information to be written to the SD Card.
// every 8 cycles we have dumped approximately 512 bytes to the card
// note only going to buffer 96 cycles to eeprom (one day at 15 min samples)
// setNextAlarmTime();
Alarmhour = now.hour(); Alarmminute = now.minute()+SampleInterval;
if (Alarmminute > 59) { //error catch – if alarmminute=60 the interrupt never triggers due to rollover!
Alarmminute =0; Alarmhour = Alarmhour+1; if (Alarmhour > 23) {Alarmhour =0;}
RTC.setAlarm1Simple(Alarmhour, Alarmminute);

Serial.print(F(” Alarm Set:”)); Serial.print(now.hour(), DEC); Serial.print(‘:’); Serial.print(now.minute(), DEC);
Serial.print(F(” Sleep:”)); Serial.print(SampleInterval);Serial.println(F(” min.”));
delay(100); //a delay long enought to boot out the serial coms

sleepNow(); //the sleep call is inside the main cycle counter loop

} //samples per cycle loop terminator
} //the main void loop terminator

void createLogFile(void) {
// create a new file, up to 100,000 files allowed
// we will create a new file every time this routine is called
// If we are creating another file after fileInterval, then we must
// close the open file first.
if (file.isOpen()) {
for (uint16_t i = 0; i < 100000; i++) {
FileName[3] = i/10000 + ‘0’;
FileName[4] = i/1000 + ‘0’;
FileName[5] = i/100 + ‘0’;
FileName[6] = i/10 + ‘0’;
FileName[7] = i%10 + ‘0’;
// O_CREAT – create the file if it does not exist
// O_EXCL – fail if the file exists O_WRITE – open for write
if (file.open(FileName, O_CREAT | O_EXCL | O_WRITE)) break;
//if you can open a file with the new name, break out of the loop

// clear the writeError flags generated when we broke the new name loop
file.writeError = 0;

if (!file.isOpen()) error (“diskful?”);
Serial.print(F(“Logging to: “));

// fetch the time
DateTime now = RTC.now();
// set creation date time
if (!file.timestamp(T_CREATE,now.year(),now.month(),now.day(),now.hour(),
now.minute(),now.second() )) {
error(“cr t”);
// set write/modification date time
if (!file.timestamp(T_WRITE,now.year(),now.month(),now.day(),now.hour(),
now.minute(),now.second() )) {
error(“wr t”);
// set access date
if (!file.timestamp(T_ACCESS,now.year(),now.month(),now.day(),now.hour(),
now.minute(),now.second() )) {
error(“ac t”);
//file.open(FileName, O_RDWR | O_AT_END);

// write the file as a header:
file.print(F(“The sample interval for this series is:”)); ;Serial.print(SampleInterval);Serial.println(F(” minutes”));
file.println(F(“YYYY/MM/DD HH:MM:SS, Vcc(mV), X = , Y = , Z = , BMATemp (C) , RTC temp (C)”));

Serial.println(F(“New log file created on the SD card!”));
#endif // ECHO_TO_SERIAL

// write out the header to the file, only upon creating a new file
if (file.writeError) {
// check if error writing
error(“write header”);

// if (!file.sync()) {
// check if error writing
// error(“fsync er”);
// }

void syncTheFile(void) {
/* don’t sync too often – requires 2048 bytes of I/O to SD card.
512 bytes of I/O if using Fat16 library */
/* blink LED to show we are syncing data to the card & updating FAT!
but cant use LED on pin 13, because chipselect */
// digitalWrite(LEDpin, HIGH);
if (!file.sync()) { error(“sync error”);}
// digitalWrite(greenLEDpin, LOW);

// Address is a page address
// But data can be maximum of 28 bytes, because the Wire library has a buffer of 32 bytes
void Write_i2c_eeprom_page( int deviceaddress, unsigned int eeaddress, char* data) {
unsigned char i=0;
unsigned int address;
Wire.write((int)((address) >> 8)); // MSB
Wire.write((int)((address) & 0xFF)); // LSB
Wire.write((byte) data[i]);i++;
} while(data[i]);
delay(10); // data sheet says 5ms for page write

// should not read more than 28 bytes at a time!
void Read_i2c_eeprom_page( int deviceaddress, unsigned int eeaddress,char* data, unsigned int num_chars) {
unsigned char i=0;
Wire.write((int)(eeaddress >> 8)); // MSB
Wire.write((int)(eeaddress & 0xFF)); // LSB
while(Wire.available()) data[i++] = Wire.read();

void sleepNow() {
// can set the unused digital pins to output low – BUT only worth 1-2 µA during sleep
// if you have an LED or something like that on an output pin, you will draw more current.
// for (byte i = 0; i <= number of digital pins; i++)
// {
// pinMode (i, OUTPUT);
// digitalWrite (i, LOW);
// }

cbi(ADCSRA,ADEN); // Switch ADC OFF: worth 334 µA during sleep
attachInterrupt(0,clockTrigger, LOW);
// turn off brown-out enable in software: worth 25 µA during sleep
// BODS must be set to one and BODSE must be set to zero within four clock cycles
// BUT http://learn.adafruit.com/low-power-coin-cell-voltage-logger/other-lessons
// MCUCR = bit (BODS) | bit (BODSE); // turn on brown-out enable select
// MCUCR = bit (BODS); // The BODS bit is automatically cleared after three clock cycles
sbi(ADCSRA,ADEN); // Switch ADC converter back ON
//digitalWrite(13, HIGH); this doesnt work because of conflict with sd card chip select

void clockTrigger() {
clockInterrupt = true; //do something quick, flip a flag, and handle in loop();

void clearClockTrigger()
Wire.beginTransmission(0x68); //Tell devices on the bus we are talking to the DS3231
Wire.write(0x0F); //Tell the device which address we want to read or write
Wire.endTransmission(); //Before you can write to and clear the alarm flag you have to read the flag first!
Wire.requestFrom(0x68,1); // Read one byte
dummyRegister=Wire.read(); // In this example we are not interest in actually using the bye
Wire.beginTransmission(0x68); //Tell devices on the bus we are talking to the DS3231
Wire.write(0x0F); //Tell the device which address we want to read or write
Wire.write(0b00000000); //Write the byte. The last 0 bit resets Alarm 1
clockInterrupt=false; //Finally clear the flag we use to indicate the trigger occurred

// could also use RTC.getTemperature() from the library here as in:
// RTC.convertTemperature(); //convert current temperature into registers
// Serial.print(RTC.getTemperature()); //read registers and display the temperature

float get3231Temp()
//temp registers (11h-12h) get updated automatically every 64s
Wire.requestFrom(DS3231_I2C_ADDRESS, 2);

if(Wire.available()) {
tMSB = Wire.read(); //2’s complement int portion
tLSB = Wire.read(); //fraction portion

temp3231 = ((((short)tMSB << 8 | (short)tLSB) >> 6) / 4.0);
// Allows for readings below freezing – Thanks to Coding Badly
//temp3231 = (temp3231 * 1.8 + 32.0); // Convert Celcius to Fahrenheit
return temp3231;

else {
temp3231 = 255.0; //Use a value of 255 as error flag

return temp3231;
byte read3AxisAcceleration()
for(int j = 0; j < 7;j++)
dataArray[j] = Wire.read();

BMAtemp = dataArray[6];
x = dataArray[1] << 8;
x |= dataArray[0];
x >>= 6;
y = dataArray[3] << 8;
y |= dataArray[2];
y >>= 6;
z = dataArray[5] << 8;
z |= dataArray[4];
z >>= 6;

BMAtempfloat = (BMAtemp*0.5)+24.0;
byte initializeBMA()
Wire.write(0x0F); //set g
Wire.write(0x10); //set bandwith

long readVcc() { //trick to read the Vin using internal 1.1 v as a refrence
long result;
// Read 1.1V reference against AVcc
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
delay(3); // Wait for Vref to settle
ADCSRA |= _BV(ADSC); // Convert
while (bit_is_set(ADCSRA,ADSC));
result = ADCL;
result |= ADCH<<8;
result = 1126400L / result; // Back-calculate AVcc in mV
return result;

int freeRam () {
extern int __heap_start, *__brkval;
int v;
return (int) &v – (__brkval == 0 ? (int) &__heap_start : (int) __brkval);

void error(char *str) {
// always write error messages to the serial monitor but this routine wastes
// everything passed to the string from the original call is in sram!
Serial.print(F(“error in: “));Serial.println(str);
/* this next statement will start an endless loop, basically stopping all
operation upon any error. Change this behavior if you want. */
while (1);