Tag Archives: arduino

Adding two OLED displays to your Arduino logger (without a library)

Two I2C 0.96″ OLED screens used simultaneously. Left is split Yellow: 128×16 pixels & Blue: 128×48 pixels while the right screen is mono-white. The CODE ON GITHUB drives each screen in different memory modes to simplify the functions and uses internal eeprom memory to store fonts/bitmaps.

Oceanographic instruments rarely have displays, because they don’t contribute much to profilers being lowered off the side of a boat on long cables. And most of our instruments spend months-to-years in underground darkness, far from any observer.  But a few years ago we built a hand-held flow sensor that needed to provide operator feedback while the diver hunted for the peak discharge point of an underwater spring. That prototype was our first compelling use-case for a display screen, and we used cheap Nokia 5110 LCDs because they were large enough to see in murky water.  While driver libraries were plentiful, I realized that Julian Ilett’s shift-out method could pull font maps from the Arduino’s internal eeprom rather than progmem. This let us add those SPI screens to a codebase that was already near the memory limits of a 328p.

Modifications done to the second display: The only thing you must do for dual screens is change the bus address. Here I’ve also removed the redundant I2C  pullups (R5&R6, 4k7) and bridged out the 662k regulator which isn’t needed on a 3.3v system. These changes usually bring sleep-mode current on 0.96″ OLEDs to about 2μA per screen. (or ~5μA ea. with the 662k regulator left in place)

Now it’s time to work on the next gen, and the 0.96″ OLEDs that we initially ignored have dropped to ~$2.50 each.  Popular graphic libraries for these displays (Adafruit, U8G2, etc)  provide more options than a swiss army knife but require similarly prodigious system resources. Hackaday highlighted the work of David Johnson-Davies who’s been using these screens with ATtiny processors where such extravagant memory allocations aren’t possible. His Tiny Function Plotter leverages the ssd1306‘s vertical access mode to plot a graph with a remarkably simple function.  These OLED screens support two bus addresses, and that set me to work combining that elegant  grapher with my eeprom/fonts method for a two screen combo. Updating Iletts shiftout cascade to I2C would loose some of the memory benefit, but I was already embedding wire.h in the codebuild for other sensors. It’s worth noting that David also posted a full featured plotter for the 1106, but these tiny displays are already at the limits of legibility and I didn’t want to lose any of those precious pixels. Moving the axis labels to the other screen forced me to add some leading lines so the eye could ‘jump the gap’:

My eyes had trouble with single-pixel plots so I made the line 2-pixels thick.

Histogram seems the best underwater visibility mode. Dashed lead-lines toggle off

A little repetition produces a bar graph variant.

The battery indicator at upper left uses the same function as the progress bar in the center. Axis label dashes along the right hand side correspond to the lead lines on the next screen. My goal with this layout was ‘at-a-glance” readability. Horizontal addressing makes it easy to update these elements without refreshing the rest of the screen; so there is no frame buffer.

To drive these screens without a library it helps  to understand the controllers different memory addressing modes. There are plenty of good tutorials out there, but the gist is that you first specify a target area on screen by sending (8-pixel high) row and (single pixel wide) column ranges.  These define upper left  & lower right corners of a modifiable region and the ssd1306 plugs any subsequent data that gets sent into the pixels between those corner points using a horizontal or vertical flow pattern. If you send more data than the square can hold, it jumps back to the upper left starting point and continues to fill over top the previous data. In ALL memory modes: each received byte represents a vertical 8-pixel stripe of pixels , which is perfect for displaying small fonts defined in 6×8-bit blocks. For the taller characters I use a two-pass process that prints the top of the large numbers first, changes eeprom memory offset, and then does a second pass in the next row to create the bottoms of the characters. This is different from typical scaling approaches, but gives the option of creating a three-row (24 pixel high) font with basically the same method, and since the fonts are stored in eeprom there’s no real penalty for those extra bits.

In addition to text & graphs, I need a battery status indicator and some way to tell the operator when they have waited long enough to fill sampling buffers. In the first gen units I used LED pips, but similar to the way vertical addressing made the Tiny Function Plotter so clean, horizontal mode makes it very easy to generate single-row progress bars:

void ssd1306_HorizontalProgressBar
(int8_t rowint percentCompleteint barColumnMin,  int barColumnMax)    {

// Set boundary area for the progress bar
Wire.beginTransmission(ssd1306_address);
Wire.write(ssd1306_commandStream);
Wire.write(ssd1306_SET_ADDRESSING);
Wire.write(ssd1306_ADDRESSING_HORIZONTAL);
Wire.write(ssd1306_SET_PAGE_RANGE);
Wire.write(row); Wire.write(row);    // page  start / end  are the same in this case 
Wire.write(ssd1306_SET_COLUMN_RANGE);
Wire.write(barColumnMin); Wire.write(barColumnMax); // column start / end
Wire.endTransmission();

//determine column for the progress bar transition
int changePoint = ((percentComplete*(barColumnMax-barColumnMin))/100)
+barColumnMin;

//loop through the column range, sending a ‘full’ or ’empty’ byte pattern
for (int col = barColumnMin ; col < barColumnMax+1; col++) {
   Wire.beginTransmission(ssd1306_address);
   Wire.write(ssd1306_oneData);   // send 1 data byte at a time to avoid wire.h limits

        // full indicator = all but top pixel on,  also using the ‘filled’ byte as end caps
        if
(col<changePoint || col<=barColumnMin || col>=barColumnMax)
        { Wire.write(0b11111110);  }
        else   Wire.write(0b10000010);   }    // empty indicator  = edge pixels only

    Wire.endTransmission();
  }
}

I mount screens under epoxy & acrylic for field  units but hot glue holds alignment just fine at the prototyping stage, and it can be undone later.

And just for the fun of it, I added a splash screen bitmap that’s only displayed at startup. Since the processors internal eeprom is already being used to store the font definitions, this 1024 byte graphic gets shuttled from progmem into the 4k EEprom on the RTC modules we use on our loggers. It’s important to note that I’ve wrapped the font & bitmap arrays, and the eeprom transfer steps, within a define at the start of the program:

#define runONCE_addData2EEproms
—progmem functions that only need to execute once —
#endif

Since data stored in eeprom is persistent, you can eliminate those functions (and the memory they require) from the compile by commenting-out that define after it’s been run.

You can update the memory in these screens even if the display is sleeping. So a typical logger could send single line updates to the graph display over the course of a day. There’s a scrolling feature buried in the 1306 that would make this perpetual without any buffer variables on the main system.  Color might make it possible to do this with three separate sensor streams, and the 1106 lets you read back from the screens memory, providing an interesting possibility for ancillary storage. However with Arduino migrating to newer mcu’s I think creative code tricks like that are more likely to emerge from ATtiny users in future. As a logger jockey, the first questions that comes to mind are: can I use these screens as the light source in some interesting sensor arrangement that leverages the way I could vary the output?
What’s the spectra of the output?  Could they be calibrated?   What’s the maximum switch rate?

OLED screens are quite readable through the clear 3440 Plano Stowaway boxes used for our classroom logger.

As a final comment, the code on github is just an exploration of concepts so it’s written with readability in mind rather than efficiency.  It’s just a repository of functions that will be patched into other projects when needed, and as such it will change/grow over time as I explore other dual screen ideasKris Kasprzak has a slick looking oscilloscope example (+more here) , and Larry Banks BitBang library lets you drive as many I2C screens as you have the pins for – or you could try a multiplexer on the I2C bus. These OLEDs will only get larger and cheaper over time. (& it will be a while before we see that with e-paper) An important question for our project is: which screens hold up best when subjected to pressure at depth? This was a real problem with the Nokia LCDs.

 

Pro Mini Classroom Datalogger [2020 update]

2020 update to the Cave Pearl Classroom logger. This is a combination of inexpensive pre-made modules from the open-source Arduino ecosystem, and can usually be assembled by beginners in 1-2 hours.

(Latest Update: Jan 12, 2021)

Covid has thrown a spanner into the works for hands-on learning because even if you have the space to run a ‘socially distanced’ course, your students could still be sent home at any time.  With that in mind, we’ve divided the tutorial from 2019 into separate stages that make it easier to restructure the labs:

1) Component prep:  requires the equipment you  normally have access to in the lab like soldering irons, heat guns, drills, etc.

2) Logger assembly: can be done remotely with scissors, wire strippers & a screwdriver. All connections are made by Dupont connectors or by clamping wires under screw terminals.

The complete tutorial can be run F2F, or if students are ‘distance learning’, the instructor can do the soldering steps (~15-20 minutes per set) and send out part-kits ready for the overall assembly. Even that will be challenging through a zoom window, so you might want to add USB isolators to protect tethered student laptops from accidental shorts. One big challenge of running a course remotely is the extra time it takes to test all the parts before sending them out (Build yourself a ‘breadboard logger’ like the one shown later in this post to do that testing quickly) . During ‘normal’ runs if a student gets a bad component, or accidentally zaps something in one of the labs, then it simply triggers a brief process-of-elimination lesson while they swap in replacement parts from the storage cabinet until things are working. But if students are remote they won’t have that option unless you send two of every part (which might be viable approach if you are pressed for time? )

This is a variation of the logger described in our 2018 paper but we’ve removed the regulator/ voltage divider and added screw terminals + breadboards for the lab exercises.  Bridging the I2C bus over the A2 & A3 pins leaves only two analog inputs on the screw-terminals. However for ~$1 you can add a 15-bit ADS1115 which provides far more analog sensor capability using a fixed internal reference independent of the supply voltage.

The main components:

 (NOTE: complete parts list with supplier links can be found at the end of this post) You don’t need the cable glands if you are using sensors that will work inside the housing (light, temp, acceleration, magnetic,GPS, etc.) Don’t put holes in your housing unless you are sure you need them.

You will need a UART adapter module to program your logger This must support 3.3v output & is easier to use if the pin order matches the ProMini connections. UART modules available with the FT232, CP2102, & CH340 chips will all work if the chip maker supplies drivers for your operating system. Macs work well with the 6-pin SiLabs modules.

The Arduino IDE will NOT be able to communicate with your logger unless the driver for your UART module is installed on your Windows or MAC operating system.

We use FTDI basic UART modules & you can download the driver at the FTDI website.   There’s a basic installation guide at Adafruit , a more detailed one at Sparkfun, and PDF guides from FTDI.

UART chips can only supply ~50mA at 3.3v so if your sensors need more current than that you might run out of power while tethered.

Two FT232 adapters (red) & a CP2102 UART module (blk) with a pin order that matches the ProMini. [ DTR-RX-TX-3v3-CTS-GND ]  I have connected ProMini’s to these UART modules the wrong way round many, many, times, and none have been harmed by the temporary reversal. Also note that FAKE FTDI chips are a common problem with cheap eBay vendors, so it might be safer to buy a  SiLabs CP2102, or CH340 UART from those sources.

Component Prep.  Part 1:   Pro Mini   ( 3.3v 8Mhz )                       (click any image to enlarge)

Install the UART driver & IDE. Solder the UART pins & test ProMini board with the blink sketch:  Set the IDE to (1) TOOLS> Board: Arduino Pro or Pro Mini (2)TOOLS> ATmega328(3.3v, 8mhz) in addition to the (3)TOOLS> COM port to match the # that appears when you plug in the serial adapter.

Remove Limit Resistor to disable power indicator

With a known good Promini: Remove the power LED [in red square].                               SPI bus clock pulses will flash the pin 13 LED – so  leaving the pin13 LED connected will show you when data is being saved to the SD card.

Carefully remove the voltage regulator from the two leg side with snips. Your system voltage will vary over time, but our starter script records that rail voltage without a voltage divider by comparing it to the 328p’s internal bandgap voltage. Lithium AA’s  are recommended because of their flat discharge curve.

Add header pins to the sides of the Pro Mini, but do not solder the two RESET pins.

Bridge the two I2C bus connections with the wire leg of a resistor. Connect A4->A2 & A5->A3.

A4 & A5 I2C bus bridged to side rails

Adding:   DIDR0 = 0x0F;  in Setup disables digital I/O on pins A0-A3 so they can’t interfere with I2C bus.

Lithium AA batteries are preferred when running a 2-cell unregulated system because the slope of an alkaline discharge curve will reach the ProMini’s 2.7v brown-out with >50% of the battery capacity unused. (note that SD cards are safe down to ~1.8v) While the voltage of a ‘brand new’ Lithium AA is usually 1.8v/cell, that upper plateau usually settles at ~1.7 v/cell within an hour or two of starting the logger. That briefly dips below 1.6v/cell during >100mA SD card save events. At temps near 5°C (in my refrigerator) the SD write battery-droop reached about 1.5 v/cell while on the upper plateau. Lithium cells only deliver ~50% of their rated capacity at temps below freezing, but that’s still an improvement. And alkaline batteries leak quite ofteneven when they are not fully discharged. To date, leaking batteries have been the most common reason for data loss on the project.

The MIC5205 regulator is not efficient at low currents, so removing it reduces your sleep current by ~50%. However that modification also forces you to deal with a rail voltage that changes over time. Thermal rise from 15 to 45°C will raise your rail voltage by about 100 millivolts on lithium cells that have been in service for a few months. If your sensor circuit is a voltage divider that is being powered by the same  voltage the ADC is using as a reference then the ADC readings are unaffected by this.  However you will need to compensate for this in your calculations if your analog sensor circuits are not ratio-metric.  Battery thermal mass can also cause hysteresis unless you read your reference resistors under the same conditions. Regulated ProMini’s usually see the rail vary by ~10-20 millivolts over a similar range of temperatures however it’s worth noting the reg/cap combinations on cheap eBay modules can be  subject to other problems such as noise; which can be even more problematic wrt the quality of your data.  Most chip-based I2C sensor modules carry their own regulators (usually a 662k LDO) and use internal bandgap reference voltages.

So making students deal with deal with power supply variation right from the start will save them from making more serious mistakes later  because every component in your logger is a temperature sensor.  

Component Prep. Part 2:     Screw-Terminal Board & SD adapter

Reset terminals repurposed with jumpers under the board

At the UART end of the board: Use a tinned resistor leg to repurpose the RESET terminals: Join RST & GND pins on the digital side and link the pins labeled RST, 5v and Vin for the positive rail.

Label the screw terminal blocks that were connected under the shield with red & black markers to indicate that these are now the power rails connections.

Gently rock the Pro Mini back to front (holding the two short sides) until the pins are fully inserted. Some ST shields have slightly misaligned headers so this insertion can be tricky.

Remove the last three ‘unused’ female headers to make room for the SD adapter

…which fits perfectly into that pocket

Remove the bottom 3 resistors from the SD adapter – leave the top resistor near the C1 label in place!

Connection map for analog side of Nano S-Terminal board

NOTE: The Screw Terminal board we use in this build was designed for the 5v Arduino NANO, so the shield labels don’t match the Pro Mini pins on the ‘analog’ side. (the digital side does match) To avoid confusion may want to tape over those incorrect labels and hand write new labels to match the pattern above. Wire connections in this tutorial will be specified by ProMini pins:  D10-13 are used for the SD card, A4/A2 is the I2C Data line, and A5/A3 is the I2C clock line.

Technically speaking, bridging the I2C bus (A4=data & A5=clock) over top of A2 & A3 subjects those lines to more capacitance and pin leakage. (regardless of whether that channel is selected as input for the ADC p257).  However in practice, the 4K7 pull-up resistors on the RTC module handle that at the 100 kHz default bus speed.

Adding DIDR0 = 0x0F;

in setup disables digital I/O on pins A0-A3 to prevent interference with A0/1 ADC readings and the I2C bus on A2/3. If you want to disable only the digital IO on only A2 & A3 add

bitSet (DIDR0, ADC2D);
bitSet (DIDR0, ADC3D);

to setup.

Component Prep.  Part 3:     RTC Module,  Indicator  LED  &  Plano 3440 Housing

Remove two SMD resistors from the RTC board with the tip of your soldering iron. Note that this module includes 4k7 pullup resistors on SQW, SCL & SDA

This step is optional!  Cutting the Vcc leg on the DS3231 lowers sleep current by 0.1mA

Add 90 degree header pins to the I2C cascade port. Note: Cutting the VCC leg also requires you to ‘enable alarms from the backup battery’ with a registry setting.

Clean flux residue from both the main & cascade header pins with 90% isopropyl alcohol

Use a coin cell to determine the GND leg of a diffused common cathode RGB LED

Solder a 1-2k ohm limit resistor on the common GND. The precise value is not critical.

Add heat shrink, bend & trim the pins for connection to the screw terminal board. Pre-made 5050 modules also work but are not as good in light sensing mode.

Holes in the rear struts of the Plano 3440 Stowaway housing provide logger tie-down points.

Stepped drill bits make clean holes in plastic  housings for different thread diameters. We use glands with waterproof DS18b20 sensors on 1m cables.

Centered holes are ‘slightly’ closer the bottom of the box to allow the gland to rotate without hitting the rim. Inner nuts must also be able to rotate for tightening.

rubber washers are added to both the inside & the outside surfaces. PG7 size washers fit M12 cable glands.

A 3440 Plano Box configured for two external sensors with nylon M12 cable glands for 3-6mm cables. After threading  sensor cables I often seal the outside of the glands with a layer of silicone goop or nail polish.

As with the ProMini, it’s worth testing the RTC modules  & SD adapters before logger assembly. I keep a breadboard version of the logger handy so I can test several at a time. If the RTC temp register reads too high I throw them out because the clock time is corrected based on those internal readings. You could also set the clock at this point if the coin-cell is already in place. Note the 5050 LED module on the breadboard shown above could replace the 5mm LED in this tutorial.

Forcing the RTC to run from the backup coin cell reduces overall current by ~0.1mA, bringing a typical “no-reg & noRTCvcc” build to ~0.15 mA when the logger sleeps between readings. (with well behaved SD cards)  As a rough estimate, Lithium AA’s provide ~7 million milliamp-seconds of power, and your logger will burn about 12,960 mAs/day at 0.15mA. So you should approach a year before you fall off the ‘upper plateau’.  The clock will reset to Jan 1st, 2000 if you disconnect the RTC’s battery with a hard bump, but a couple of drops of hot glue should prevent this. If a reset does occur the time stamps will be wrong, but the logger will continue running the next day once the clock rolls around to the previous hour/minute alarm that was set.  A CR2032 coin cell can power the RTC about four years but if you cut the vcc leg you must set bit six of the DS3231_CONTROL_REG to 1 to enable alarms or the logger will not be able to wake up. (NOTE: our logger code does this by at startup with the enableRTCAlarmsonBackupBattery function, which only has to run once -> the RTC remembers the setting after that)

The soldered components ready for assembly.

Cutting the Vcc leg on the RTC is optional: if you leave the RTC power leg attached you’ll see typical logger sleep sleep currents in the 0.25 mA range, which should still give you 4 months of operation before you trigger a low voltage shutdown. I’m being conservative here because runtime also depends on sensors and other additions you make to the base configuration.

See our RTC page for more detailed information on this DS3231 module.


Assembly Part 1:  The Screw-Terminal Stack

Part 1: This stack is the ‘core’ of your data logger.

It is very easy to get a couple of wires switched around at this stage so work through these instructions slowly & carefully.  Connect the Dupont jumpers to the SD module so that the metal & plastic retainer clips are facing upwards after the logger is assembled. That way you can diagnose connection issues with the tip of a meter probe on the exposed metal and, if necessary, pull out & replace a single bad wire without taking everything apart. The extra wire you trim from the SD module gets re-used.

Add 2-3 layers of double sided foam tape to the sides & center of the screw terminal shield. The tape needs to extend farther the height of the solder points.

Add single wires from the  20cm Dupont cable to the SD adapter.

The ‘metal tabs’ of the Dupont ends are facing the large metal shield over the SD card.

 

 

A layer of double sided foam tape secures the connectors & extends over the solder points.

Red=3v3
Grey=CableSelect [d10]
Orange=MOSI [d11]
Brown=CLocK [d13]
Purple=MISO [d12]
Black =GND

Tape the SD adapter into place on the areas that’s been cleared at the back of  the screw terminal board.

Bend the jumper wires into place, mark the length & trim the 20cm wires so that the final insulation length is length 9cm or less.

Score the wire insulation about 1.5cm back from the wire end but do not remove the insulation.

Gently ‘pull & twist’ the  scored insulation away from the wire to wind the thin strands together.

‘Fold-back’ about half of the stripped wire to provide more copper surface for the screw terminals to bite on to.

 

 

SD Connection pattern:    Grey (CS) to ProMini D10Orange (MOSI) -> pm D11Purple (MISO) ->pm D12,      Brown (CLK) -> pm D13  (NOTE the shield labels say A0-A3 which do not match the D10-13 pattern of the Pro Mini pins)

Bring the red SD wire over to the re-purposed RST power connection

and the black wire to GND on the digital side. At this point you could test the  connections by inserting an SD card & running the CardInfo utility.

 

 

Connect the legs of the indicator LED at: D3=red, D4=GND, D5=green, D6=blue

Use the male ends of the wires you trimmed from the SD module to break out pin connections: Grey to A0, Brown to A1, Orange to D7, Purple to D8.

Add a layer of heavy duty (30Lb) double sided mounting tape to the back of the 2xAA battery holder. The battery holder wires need to be approx. 6inches/15cm long.

Attach the battery holder wires to RED>Vin & black>GND.  The breakout wires from A0/1 & D7/8  should be about 12cm long to comfortably reach the breadboard area.

Checking continuity at the ProMini solder point also confirms your pin header & screw terminal connections are good.

Take a moment to check the continuity of the SD module wires. With one probe on the Dupont metal & the other on top of the corresponding ProMini pin – you should read ~1 ohm or less for each connection path. Occasionally you get a bad crimp-end on those multi-wire Dupont ribbons, and it’s easier to replace a bad wire at this stage than it is after the parts are in the housing.

Note: We’ve used the hardware interrupt port at D3 for the red LED channel, but if you have sensors that need that simply shift the LED over by one. Any digital I/O pins can be used for the LED, but 3,5 & 6 have PWM outputs which lets you do multi-color tricks using analogWrite() commands.

Assembly Part 2: Add RTC Module Jumper Wires

Attach Dupont jumper wires to the RTC module using White=I2C Data, Yellow = I2C Clock. Blue is the SQR alarm output line. Nothing is attached to the 32k output pin as cutting the chips Vcc leg disabled it.

Use 20cm M-F jumpers on the input side and shorter 10cm M-F wires on the smaller cascade port.

 

Add a layer of double sided tape to secure the jumper shrouds, and provide housing attachment points for the module.

Add as small piece of 1/16″ heat shrink tubing to reinforce the contact spring. This reduces the chance that the connection will be bumped loose if the logger is dropped.

Write the installation date on the coin cell with a marker. A new coin cell should power the RTC for about 4 years.          To avoid accidentally disconnecting the coin cell battery after the logger is assembled ->

Always check the backup voltage by placing the positive probe tip between the spring plates rather than on the surface of the cell.

Assembly Part 3:    Connect modules inside the housing:

Part 3: This is what you are aiming for.

The final assembly stage can be a bit tricky – sometimes the metal contact flaps under the screw terminals are ‘sticky’ so take some time to loosen  the screws and poke with the sharp end of your tweezers to make sure you can insert the bus wires from the RTC. It’s helpful to mark the wire lengths with a pen before cutting or stripping the RTC connections. When in doubt leave them a bit long. With screw terminals you always have the option of trimming down any wire a later time.

Attach the screw terminal stack onto the upper left corner of the housing, and the 2xAA battery holder in the lower right corner. These parts sb as far from each other as possible.

Add the first mini breadboard against the back so that it’s rear right edge aligns with the second rear support strut on the housing. Connect the long stack jumpers to that bboard to keep them out of the way.

Tape the RTC module into the lower left side of the housing, The blue board should be up against the front edge of the housing  so that you can easily access the nearby screw terminals.

Measure, mark and trim:  Red=3.3v, Yellow=>A3/A5 I2C clock, & White=>A2/A4 I2C data with enough wire to twist & fold the stripped ends under the terminals (~10cm of insulation length)

RTC module power & I2C bus connections:  All ports on the analog side of the Pro Mini / Shield combination are occupied with a wire connection.

Connect the black wire to the re-purposed RST=GND, and the blue alarm wire to  D2, leaving some extra wire length in a loop for strain relief. (5-6cm of insulation length)

Use the other side of the trimmed blue jumper wire  to extend the D9 connection over to the breadboard. You want enough wire length that the pins reach back-most row on the breadboard.

Attach a cable mount to the back of the housing, as close to the bottom of the box as possible so that it does not interfere with closing the housing lid. You can trim those plastic mounts with scissors to make them thinner.

‘Loosely’ tie the long wires to the rear mount. Add another cable mount near the center and attach the 2nd b-board leaving equal side of the 2nd board.

Every year at least one student gets confused about the orientation of the connections inside the breadboard and connects all the jumper wires together in the same row – including the red and black power wires. The resulting short circuit usually kills either the Pro Mini, the UART module, and/or possibly even the USB port on the computer it’s connected to:

Each 5-hole row of spring contacts is connected together by a rail of metal inside the breadboard.

Also note that the internal connectors do not cross the ‘gutter’ depression in the middle, so each side of the breadboard board is its own separate set of connections.


Your Logger is now ready for testing!

A typical sensor configuration with: BMP280 pressure, BH1750 lux & 0.96″ I2C OLED display – connected by short jumper wires made with a crimping tool. The combination shown above averages ~10mA with screen & cpu running, and a sleep current of 0.147 mA with a 1Gb Sandisk SD card. Without the SD, the sleep current on this unit was 37µA; with the sensor modules needing 2-3µA each & the sleeping 0.96″ OLED drawing ~7µA.  A 25µA sleep current from the ProMini clone hints that the MCU might be fake but with a AA power supply it doesn’t really matter. Anything up to 250uA sleep current for a student build with an SD card in tow should be considered good.  Watch out for SD cards that don’t go to sleep properly as they usually pull 30-50mA all the time.

(Note: Most of the time the tests listed below go well, however if you run into trouble at any point read through the steps suggested for Diagnosing Connection Problems at the end of this page.)

1. If you have not already done so, Install the UART driver. The IDE will NOT be able to communicate with your logger unless the driver for your UART module is installed on your Windows or MAC operating system. 

2. Install the Arduino IDE into whatever default directory it wants – we’ve had several issues where students tried to install the IDE into some other custom sub-directory, and then code wouldn’t verify without errors because the IDE could not find the libraries. The programming environment is written in Java, and the IDE installer comes with its own bundled Java runtime so there should be no need for an extra Java installation. However we have seen machines in the past which would not compile known-good code until Java was updated on those machines; but this problem is rare.

If you have not already done so, there are three things you need to set under the IDE>TOOLS menu to enable communication with the logger:

Note: that the “COM’ setting will be different for each computer, so you will have to look for the one that appears on your system AFTER you plug in the UART module.

Using 22AWG solid core jumpers to bridge a set of ‘power rails’ along each side keeps the wiring tidy later on. If you are using several I2C sensors you could do the same thing with those bus lines.

The one that’s easy to forget is choosing the 328P 3.3v 8Mhz clock speed. If you leave the 328p 5v 16mhz (default), the programs will upload OK, but any text displayed on the serial monitor will be random garbled characters because of the clock speed mismatch.  Also be sure to disconnect battery power (by removing one of the AA batteries) whenever you connect your logger to a computer.  There is no power switch on the loggers, which are turned on or off via the battery insertion. Use a screwdriver when removing the batteries so that you don’t accidentally cause a series of disconnect-reconnect voltage spikes which might hurt the SD card.

3. Test the LED – the default blink sketch uses the pin13 LED, but because that pin is shared with the SD card’s clock line it’s recommended that you test the RGB indicator instead by adding commands in setup which set the digital pin 4 act as the ground line for the LED:
     pinMode(4, OUTPUT);   digitalWrite(4, LOW); 
Then change LED_BUILTIN in the blink code example to the number of a pin connected to your led module. (ie: for Red set it to 3, for Green use 5, or Blue use 6)

4. Scan the I2C bus with the scanner from the Arduino playgound. The RTC module has a 4K eeprom at address 0x56 (or 57) and the DS3231 RTC chip should show up at address 0x68.

The address of the eeprom can be changed via solder pads on the board, so it may have a different address. If you don’t see at least these two devices listed in the serial monitor when you run the scan, there is something wrong with your RTC module or the way it’s connected: It’s very common for a beginner to get some of the wire connections switched around during assembly but with the screw terminals this takes only a few moments to fix.

5. Set the RTC time, and check that the time was set – The easiest method would be to use the SetTime / Gettime scripts from our Github repository, but you first you need to download & install this RTC control library  The SetTime script automatically updates the RTC to the moment the code was compiled (just before uploading) so only run SetTime once, and then upload the GetTime sketch to remove SetTime from memory. Otherwise SetTime will keep setting the RTC to the old ‘code compile time’ every time it runs – and one of the quirks of the Arduino environment is that it restarts the processor EVERY TIME you open a serial window. The SetTime script also has a function which enables the alarm(s) while running the RTC from the backup coin cell battery.

Note:  the RTClib by Mr. Alvin that we use has the same name as the Adafruit library for this RTC and this will give you compiler errors with our logger script if somehow the Adafruit library is already installed on your computer. You may have to uninstall the Adafruit library ‘manually’ before installing Alvin’s RTClib. This problem of ‘two different libraries with the same name’ was common back when this project started many years ago, but is rare today.

Typical Cardinfo output on a windows computer when the connections are correct. If you format your SD card on an Apple computer there will also be a long list of ‘invisible’ .trash and .Spotlight files/folders at the root of the SDcard that show up with a CardInfo scan.

6. Check the SD card with Cardinfo
Note that the SDfat library we use to communicate with SD cards works well with smaller cards formatted as fat16, but ‘some’ Apple users find they can not write to cards in that format, requiring the SD cards to be reformatted as fat32 (note that most Apple systems have no problem with the fat16 SDcards). With either OS you should format the micoSD cards with this SDFormatter utility.  With a 15 minute sampling interval, most loggers generate ~ 5Mb of CSV format text files per year. Older, smaller SD cards in the 256-512Mb range often use less power. Note that we apply internal pullup resistors on some of the SD card lines in setup to help the SD cards go into low power sleep modes more reliably.

7. Calibrate your internal voltage reference with CalVref from OpenEnergyMonitor.

This logger uses an advanced code trick to read the positive rail voltage to ~11mv resolution by comparing it to an internal 1.1v bandgap reference inside the processor. That internal ref. can vary by ±10% from one chip to another, and CalVref gives you a numerical constant which usually brings the starter script’s rail=battery readings within ±20mv of actual. An accurate rail reading is more important when you are using ANALOG sensors where the positive rail directly affects the ADC output, but you can skip this procedure if you are only using digital sensors because they use their own internal reference voltages.

Typical CalVref output          (click to enlarge)

Load CalVref while the logger is running from USB power and then measure the voltage between GND and the positive rail with a  voltmeter. (this voltage will vary depending on your computer’s USB output, and the UART adapter you are using) Then type that voltage into the entry line at the top of the serial monitor window & press the enter key. Write down the reference voltage & constant which is then output to the serial monitor window. I write these ‘chip-specific’ numbers inside the logger with a black marker as they are related only to the 328p processor on the ProMini board used to make that particular logger. You then need to change the line #define InternalReferenceConstant 1126400L in our starter code to match the long number returned by CalVref. Alternatively you could just tweak the value of the reference constant ‘by hand’, increasing or decreasing the value till the reported rail readings match what you measure with a voltmeter. After you’ve done this once or twice you can usually reach the correct value with about 10 successive guesses.

8. Find a script to run your on logger. For test runs on a USB tether, the simplest bare-bones logger code is probably Tom Igoe’s 1-pager at the Arduino playground. It’s not really deploy-able because it never sleeps the processor, but it is still a useful ‘1-pager’ for teaching exercises and testing sensor libraries.  In 2016 we posted an extended version of Tom’s code for UNO based loggers that included sleeping the logger with RTC wakeup alarms. Our current logging “Starter Script” has grown since then to ~750 lines, but it should still be understandable once you have a few basic Arduino programming concepts under your belt.


Using the logger for experiments:

It’s important to understand that this logger was designed to be a teaching tool rather than a off the shelf, plug-&-play logger solution.  The process of wrangling your code into shape driving some new sensor combination is a vital part of that learning process.   So perhaps the best piece of advice I can give is:  Test,  Test,  and when you think its working ok: Test it Again

Even for people with years of experience it’s nearly impossible to write code without little bugs and the only way to root them out is with multiple test runs. Even if the script you wrote is ‘perfect’ the processors on the sensor modules are also running code that you don’t have access to. For example, the mcu inside your SD card memory is more powerful, and may have more code on it than the ProMini at the heart of this logger. The only way to catch timing errors that might not get triggered until the 10th or 200th(?) pass is to run your code with a short 1 minute sampling interval until it’s crossed those roll-over thresholds many times. Use “Starting sensor X read” & “Finished sensor X read” print statements liberally during  early USB tethered tests so you can observe the timing of events.

If you see water condensing like this on the lid of your logger then it’s time to examine the o-ring and add some extra sealant (nail polish or silicone) around the exterior of the cable glands. This logger quit after one week and it only lasted that long because of the desiccant pack.

Same thing applies to the sensor hardware in terms of durability, only now you have moisture to deal with. Everything that can be sealed in adhesive lined heat shrink, or potted in epoxy should be, once that hardware has passed your ‘dry’ tests. As a general rule no $10 sensor is going to be rated past IP68 which at best gives you 2-3 weeks of operation in the real world before water works it’s way in because of pressure imbalances caused by daily thermal cycling. You’d be surprised how easily moisture can wick along the air space ‘between the copper strands’ inside wires.

A doubling schedule works well for this:  checking the logfile at 1 hour, 2 hours, 4 hours, overnight, 1 day, 2 days, 4 days, 8, 16, 32… etc. Move to each longer test only when the data  from the previous run is confirmed keeping a close eye on that battery burn down rate: until you get the hang of putting your sensors into low current ‘sleep’ states – getting your first logger builds to run for a couple of months on new batteries should be considered a spectacular success. At every startup watch and wait for the pattern of LED flashes to confirm that the startup went smoothly – its easy to insert a battery or SD card crooked by ‘just enough’ that the unit does not start, and it’s very frustrating to find this out a week later.

Until you’ve deployed a new sensor you don’t really know how long it’s going to last – no matter what the manufacturer says in the data sheet. Even then we usually deploy three of everything, and if we are lucky, we get one complete data set.  Batteries leak, critters love to chew on things, and whenever humans come across something they’ve not seen before they will pick it up – especially if you had to invest a good deal of time securing your logger in exactly the correct position in the stream, on the tree, etc.


One positive aspect of the relatively loose fit of the Plano box lid is that it lets you run sensor tests quickly if you jumper your sensor module with thin 28-30 gauge wires:

A BMP280 pressure sensing module on long wires with crimped male dupont ends in the breadboard.

~1″ square of foam mounting tape with wires spaced evenly

 

Leave the red backing facing up as you fold the tape & wires over the corner edge.

The front corners of the box exert less pressure than the back corner shown here.

The sharp inner edge of the lid would cut the wire insulation if the tape was not there to protect, and even then you can only use this trick a few times.

The tape over the wires has to be replaced every time.

This gives you a chance to do some test runs before you commit to modifying the housing with holes or cable glands. For some indoor experiments this might be all you actually need, though I would still coat the ‘non-sensing’ parts of that dangling breakout with either conformal coating or clear nail polish. My general advice is: Do not put holes in the housing unless you are sure you need them.  The most common failure mode for student loggers used in outdoor environments is from moisture seeping into the through the cable gland. Natural heating and cooling cycles creates pressure differences between the inside and outside of the logger that drives this vapor exchange.  Moisture then condenses when temperatures fall at night, collecting on leftover flux residue to corrode contacts. An outer layer of self-fusing rubber mastic tape is often used by electricians doing out-door installations – even when using expensive cable glands with soft rubber ‘cages‘.

After 1-2 minutes of kneading to mix the epoxy you have ~ 1 minute to work the putty into place. (it will become rock-hard within ~10 minutes). Be sure to leave yourself enough extra wire/space inside the housing so that you can open and close the lid easily without disconnecting anything after the putty hardens. This seal is not strong enough for underwater deployments, but it should easily withstand exposure to rain-storm events. HOT GLUE also works to seal pass-through ports with smaller wires & cables. Both pass-through methods can be helped by a layer of silicon caulking, nail polish, or conformal coating applied to the outside edges.

For a classroom project you could simply drill small a hole through the lid and stick the sensor/module on top of the housing, sealing the hole with double-sided tape. Thicker pass-throughs can be also be sealed reasonably well with plumbers epoxy putty which is non-conductive, and adheres quite well to  metal, glass & plastic surfaces->  This putty is also a quick way to make custom mounting brackets, or even threaded fittings if you wrap it around a bolt (which you carefully remove before the putty hardens completely)

No matter which pass-through method you are using: Silica gel desiccant packs are important for any outdoor deployments and 5-10 gram packets are a good size for this logger.

Don’t subject these loggers to a lot of bashing around by deploying them in a rough surf-wash zone, or swaying freely in the wind off the end of a tree branch. Solid core wires are pretty good if you cut them to exactly the right length , but longer beadboard jumpers are very easy to bump loose, so once you have your prototype working, it’s usually best to re-connect the sensors directly to the screw terminals before deploying a logger where it could get knocked around. In a pinch you can secure breadboard pins with a small drop of hot glue to keep them from wiggling.  Also remember that there are six ‘unused’ screw terminals on the back of the shield and these can be use to connect wires together securely. 

2019 Logger mounted on a south-facing window. The top surface was covered with  white label-maker tape to act as a diffuser. 

[Click HERE] to read about the many types of sensors can be added to this logger The transparent enclosure makes it easy to do light-based experiments. Grounding the indicator LED through a digital pin allows it to be used as both a status indicator, and as a light sensorThe code we are using for this is from the Arduino playground. This polarity reversal technique relies on the very tiny parasitic capacitance inside the LED. (~50-300pF) This technique works better when the LED is connected directly to input pins because breadboards can add random capacitance at the same scale. Another thing to watch out for is moisture condensation inside your logger housing: this provides an alternate discharge path for the reverse-charge on the LED, which effectively shorts out the light level reading. And finally the timing of any reverse bias decay is affected by the starting voltage, so if you want to use the technique in a rigorous analytical way you should leave the regulator on your logger when you assemble it.

We have integrated this LED sensor technique into the starter script on GitHub. I’ve tweaked the playground version with port commands so the loop execution takes about 100 clock cycles instead of the default of about 400 clock cycles.  The faster version was used to generate the following light exposure graph with a generic 5mm RGB LED, with a 4k7Ω limiter on the common ground.

Red, Green & Blue channel readings from the indicator LED  (from a regulated logger) over the course of one day (logger photo above)  The yellow line is from an LDR sensor the same unit, that was over-sampled to 16-bit resolution. The LED 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. Note how the RED signal changes before/after Blue & Green at sunrise & sunset.  LED’s work well with natural full-spectrum light, but their limited frequency bands can give you trouble with the odd spectral distribution of indoor light sources. The peak response of LED’s is usually 30–50nm lower than their peak emission wavelength So the blue channel is actually recording in the near-UV range, the Green channel is responding at ~ 420nm (blue) and the red channel is actually responding to a wide band of yellow-green light. 

You can read more about LED based sensing techniques in the post about our leaf testing experiments which used two LEDs for a transmission-based variant of the NDVI ratio.

While the LED sensor idea is fun to work with, it’s a relatively slow method that can keep the logger running for many seconds when light levels are low. Figuring out how to take those light readings only during the day is a good coding exercise for students.

Note: VERY FEW light sensors can withstand exposure to direct sunlight. PTFE is an excellent light diffusing material which available in different sheet thicknesses.  The ‘divot’ on the lid of the Plano box is just a bit larger than 55mm x 130mm x 3mm (depth). The “teflon” tape that plumbers use to seal threaded joints can also be used. PTFE introduces fewer absorbance artifacts than other DIY diffusers like ping-pong balls, or hot melt glue. Most light sensors like the TSL2561 need 3-5mm of that PTFE sheeting to prevent the sensors from saturating in full sun. LED’s don’t saturate that badly, but you still lose all the useful detail in your data at peak brightness > 80,000 lux unless you add a diffusion layer to attenuate.

Full sun exposure can also cook your logger. Internal temps above 80°C may cause batteries to leak or damage the SD card.  So if you are leaving the logger in full sun, add a bit of reflective film or some aluminum foil around the outside to protect the electronics. Of course if you have a light sensor you’ll need to leave an un-covered ‘window area’ for it to take a reading. 

The RTC has a built-in temperature register which automatically gets saved with our starter script however that record only resolves 0.25°C, so we’ve also added support for the DS18b20 temperature sensor to the base code. A genuine DS18b20 (yes, fake sensors are a thing) draws very little power between readings and you can add many DS18b’s to the same logger.


Addendum: Diagnosing Connection Problems

If you successfully loaded the blink sketch to test the ProMini during your initial assembly, then issues during the testing stage are often due to incomplete connections to the I/O pins.

If you see only “Scanning I2C….. ” but nothing else appears when running the bus scanner, then it means that the ProMini can not establish communication with the RTC module. One common cause of this problem is that the white & yellow wires have been switched around at one end or the other. It’s also easy to not quite remove enough insulation from the wires to provide a good electrical connection under the screw terminals, so undo those connection and check that the wires were stripped, cleaned & wrapped together before being put under the terminals.

Scanner lockup can also happen if one of the I2C devices on the bus is simply not working: usually about 1 in 6 logger builds ends up with some bad component that you have to identify by process of elimination. (These are 99¢ parts from eBay…right?) It only takes a moment to swap in a new RTC board via the black Dupont connector and re-run the scan. If the replacement RTC also does not show up with the I2C scanner then it’s likely that one of the four bus lines does not provide a complete connection between the ProMini & the RTC module.

On this unit I measured 1 ohm of resistance on the I2C clock line between the ProMini A5 pin (on top of the board) and the SCL header pin on the RTC module. So this electrical connection path is good. It’s not unusual for each ‘dry’ connection to add 0.5-1 ohm of resistance to a signal path.

To diagnose: Unplug any power sources to the logger. Set a multi-meter to measure resistance and put one probe lead on the topmost point of the promini header pins, and the other probe on the corresponding header pin of the RTC module. If there is a continuous electrical connection between the two points then the meter should read one ohm or less. Higher resistances mean that you don’t have a good electrical path between those points even if they look connected:

1) the ground (black) wire should provide a continuous path from the ground pin on the digital side of the Promini board to the GND pin on the RTC module
2) the positive power (red) wire should provide a continuous path from the Promini positive rail pin (the one with the bundle of 4 red wires) to the VCC pin on the RTC
3) A4 (I2C data) near the 328P chip on the Promini must connect all the way through the screw terminal board and through the white Dupont wires to the SDA post on the RTC
4) A5 (I2C clock) nearest the UART end on the Promini must connect through through the yellow Dupont wire to the SCL header on the RTC .

You occasionally get a bad Dupont wire where the silver metal end is not in contact with the  copper wire inside because the crimp ‘wings’ did not fold properly. With a pair of tweezers, you can ‘gently’ lift the little plastic tab on the black shrouds holding the female Dupont ends in place, and then replace any single bad wire. Be careful not to break the little black tab or you will have to replace the entire shroud.

Everyone uses short male-to-male Dupont jumper wires when they are creating test circuits because they are so convenient. But pre-made jumper wires are usually too long and so they get knocked around when you close the lid of the logger: so before you deploy take the time to convert your flexible-wire test circuits into solid core jumpers:

Flexible wires get used during the initial testing stages (when the lid of the logger is open).

Then the circuit gets re-done with solid core wires

Running wires ‘under’ the modules  makes it easier to close the lid  without disturbing the connections.

Also look at the little jumpers used to bridge the A4>A2 and A5>A3. If you have a ‘cold’ solder join, or an accidental bridge connection to something else, it could stop the bus from working. Re-melt each connection point one at a time, holding the iron long enough to make sure the solder melts into a nice ‘liquid flow’ shape for each solder point.

The connection diagnosis procedures described above also apply to the connections for the SD adapter board. Sometimes you end up with an adapter that has a defective spring contact inside the SD module, but the only way to figure that out is to swap it with another one.

Here a jumper wire from the ProMini pin is by-passing a bad connection on the screw terminal board.  This is also how you would break out A6 & A7 if you need them.

Sometimes those screw terminal boards have a poor connection inside the black female headers below the ProMini. It’s also possible to accidentally over-tighten a terminal and ‘crack’ the solder connection below the board – or there may simply be a cold solder joint on one of the terminal posts. If you have only one bad connection, you can jumper from the ProMini header pins on top, down to the other wires under the corresponding screw terminal. If you accidentally strip the threads on a screw terminal, you can use this same approach but move that set of wires over to one of the three ‘unused’ screw terminals at the far end of the board. (beside the SD card adapter) If you’ve gotten through all of the above steps and still have not fixed the problem, then it might be time to simply rebuild the logger with a different screw terminal adapter board.

 

If you do accidentally kill the ProMini by shorting a pin, etc, you can carefully lever it up away from the screw terminal shield and replace it without having to rebuild the whole logger.

I recommend that you build two loggers at a time, because that lets you determine whether problems are code related (which will affect both machines the same way) or hardware related. (which will only affect one of your two units) At any given time I usually have 2-3 units running overnight tests so that I can compare the effect of two different code/hardware changes the next morning.  As a general rule you want to run a new build for at least a week before deploying to get beyond any ‘infant mortality’, and reach the good part of the bathtub curve.

 


An I2C OLED is quite readable through the lid of the housing. I often use Griemans text-only SSD1306Ascii library because it has a low memory footprint and sleeps well. While few loggers need live output when they are deployed, it’s often helpful to view diagnostic messages on battery power during testing. Adding two OLED displays to your Arduino logger let’s you view text & graphic output at the same time.

Addendum:  A note about I2C sensors
The I²C bus is slow, so topology (star, daisy-chain, etc.) doesn’t matter much, but capacitance does. Both length & number of sensors increase capacitance. If you find that the devices work when you switch to a slower speed (e.g. 50 kHz), then this is probably your issue, and you need to minimize bus length and/or maybe decrease the combined resistance of the pull-ups to 2 kΩ or less. The DS3231 RTC module has 4k7 ohm pull-up resistors on the SDA & SCL lines & the Pro Mini adds internal 50k pull ups when the wire library is enabled. Typical I2C sensor modules usually add another set of 10k pullups so your ‘net pullup resistance’ on the I2C bus wires is usually:  50k // 4k7 // 10k = ~3k. With a 3.3v rail that means the devices draw 3.3v / 3k = 1 mA during communication which is fairly normal ( 3mA is max allowed) for total wire lengths below 1m. It’s common for pre-packaged sensors to arrive with housings at the end of about 1m of wire. If each sensor also adds another set of 10k pullups, the resistance generally compensates for the extra wire length, so the combination still works OK. But that depends on the cable too. A very bad cable might not even get to 0.5 meters and a very good cable (little capacitance to ground, no crosstalk between the wires) can go up to 6 meters.

For most sensor types there will be some options that draw much less power than others, and it’s always worth a look at the data sheet to make sure you are using one that will run longer.  The best chip based sensors automatically go into low current modes whenever the bus has been inactive, but more often you need to ‘manually’ put the sensors to sleep via specific commands. So it’s also important to check if your sensor library supports those ‘goto sleep’ & ‘wake up’ commands –  many common Arduino libraries do not.


Addendum:  The importance of moisture protection

I was noodling around in the garden recently and installed a few loggers without desiccants because it was only a short experiment. It rained immediately afterward and I noticed a small amount of moisture condensed inside the plano-box housing. While this didn’t prevent the logger from functioning, it completely disrupted the LED light sensors because the increased humidity provided an alternate discharge path for the reverse bias charge  on the LED’s:

Green channel data from a 5mm diffused RGB LED used as light level sensor. This logger was under some leaf cover, so there was considerable variability from the dappled light crossing over the sensor. An arbitrary cutoff of 200,000 was set in the code at low light levels.

After examining the O-ring I decided to add a little silicone to the channel holding the o-ring to improve the seal:

Gently pry the O-ring loose and apply sealant in the groove before replacing.

Bead only needs to be 3-4mm in diameter.

Close the housing & let the sealant set for a few days. The improved seal is especially visible at the corners

If you already have your logger assembled, try to find a silicone sealant that does not off-gas acetic acid (smells like vinegar) which could harm your circuits. If you are simply preparing empty boxes before assembly, then any regular bathroom sealant will do provided you give it about a week to finish curing.

Attach a mounting base to the lid so that a dessicant pack can be secured above the battery holder without interfering with any breadboard jumpers. Use a desiccant pack with color indicator beads, so you can check whether they are still working simply by looking through the transparent lid.

 


Addendum:  If you want to leave the original regulator in place

It’s worth mentioning that an unregulated build will run for many months – even on 2x regular alkaline batteries which reach the system cutoff (at 2750mv) more quickly. The key deciding factor is whether a sensor you want to use requires tighter voltage regulation (like the LED method discussed previously). The DS18b20 has a nominal low voltage limit of 3v.  So if your project is making heavy use of those then there are only a few of modifications to the tutorial shown above to leave the ProMini’s default MIC5205 regulator in place:

Use straight header pins on the RTC modules cascade port to leave space for the battery holder.

Only bridge the unused RST terminals to the rail connections Leave the Vin terminal separate for the raw battery input.

 

Add a 10/3.3 Meg voltage divider to read the raw battery voltage on A0

Of course you will need more space for the extra batteries. You could go with a 3xAA holder but that leaves about 50% or your alkaline battery capacity unused.

An alternative would be to add a better regulator to some kind of intermediate battery connector. The the photo on the right shows two ceramic 105’s stabilizing an MCP1702-3302E/TO, while the 10/3.3M ohm divider provides a third output  line so the ADC channel can monitor the raw battery voltage. This is the simplest way to retro-fit a unit that was built without a reg, with the added benefit that the new regulator is far more efficient than the original MIC5205 on the ProMini. It’s worth noting that even on a regulated logger you can monitor the rail voltage to determine when the main batteries are depleted because the regulators output will fall if the batteries reach a point below the minimum dropout voltage. If the rail falls under load by more than ~40mv, then it’s probably time to shut down the logger. With the regulator in place you probably don’t need the USB isolator, as the reg. itself cant pass more current than a USB port.


Addendum:  Things to keep in mind when ordering parts

When a finished module arrives at your doorstep for less than you’d pay for any of its sub-components – it’s because you are doing the quality control.

My advice is to order at least 5-6 of each of the core components (Promini, RTC, SD module, screw terminal board, etc) with the expectation that about 10% of any cheap eBay modules will be DOA or have some other problem. I build in batches of six, and one logger typically ends up with a bad part somewhere. Having replacement bits on hand is your #1 way to diagnose and fix these issues. Bad parts tend to come “in bunches”, so if you scale up to ordering in quantities of 10’s & 20’s then spread those orders to a few different suppliers so you don’t end up with all your parts from the same flakey grey market production run. Order from different vendors in different odd-number quantities (11, 21, 9, etc.) because that will be the only way you can distinguish which supplier, sent which parts, because nothing on the package will be written in English.

The other thing I can’t stress enough is CLEAN ALL THE PARTS as soon as they arrive. Leftover flux is very hygroscopic, and solder points will start to corrode the moment your logger gets exposed to atmospheric moisture. I usually give everything about 10 minutes in a cheap sonic bath with 90% isopropyl alcohol, rinse with water, and then dry the parts out in front of a strong fan for an hour. Clean parts that can’t take the sonic vibration (RTC modules, humidity sensors, accelerometers, etc) by hand with a cotton swab. Then store parts in a sealed container with desiccant packs till you need them.  I also coat the non-sensing/non-contact surfaces with a layer of MG Chemicals 422B Silicone Conformal Coating and let that dry for a day before assembling the loggers.  One hint that you may have moisture issues is that the sensors seem to run fine during indoor tests  but start to act strangely when you deploy the unit outside.

Used nut containers make excellent “dry storage” once the parts have been cleaned – but any air-tight container will do.

Another insight I can offer is that the quality of a sensor component is often related to the current it draws – if your ‘cheap module’ is pulling significantly more power than the data sheet indicates, then theres a good chance it’s a junk part. Usually if the sleep current is near spec, then the sensor is probably going to work. It is much easier to check low currents with a µCurrent or a Current Ranger. (I prefer the CR for it’s auto-ranging features) Sensors which automatically go into low current sleep modes take time – so you might need to watch the  current for several seconds before they enter their quiescent states. A common reason for a short operating lifespan on a logger is an SD card that refuses to go into sleep mode. If there is an SD card connected to your logger you must initialize it (with sd.begin in setup) or it may ‘stay awake’ causing a constant 30-40mA drain and/or may even cause the logger to freeze up. Also with SD cards, if the freshly formatted throughput drops below its rated write speeds when tested with H2testw, then find another card to use. I avoid cards bigger than 2Gb because they usually draw too much current, and it’s rare to need that much space for a logger.

TransparentSinglePixl
Bill of Materials: ~$22.00
Plano 3440-10 Waterproof Stowaway Box
Sometimes cheaper at Amazon. $4.96 at Walmart and there are a selection of larger size boxes in the series. 6″ Husky storage bins are an alternate option.
$5.00
Pro Mini Style clone 3.3v 8mHz
Get the ones with A6 & A7 broken out at the back edge of the board. Just make sure its the 3.3v version because you can’t direct-connect the SD cards to a 5v board.
$2.20
‘Pre-assembled’ Nano V1.O Screw Terminal Expansion Board
by Deek Robot, Keyes, & Gravitech (CHECK: some of them have the GND terminals interconnected)  You will also need to have a few small flat head screw drivers to tighten those terminals down.  Since this shield is was originally designed for an Arduino Nano many of the labels on ST board will not agree with the pins on the ‘analog side’ of the ProMini.
$1.85
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!  
$1.25
CR2032 lithium battery  $0.40
SPI Mini SD card Module for Arduino AVR
Buy the ones with four ‘separate’ pull-up resistors so that you can remove three of them.
$0.50
SD card 256mb -to-1Gb 
 Test used cards from eBay before putting them in service. Older Nokia 256 & 512mb cards have lower write currents in the 50-75mA range. This is less than half the current draw on most cards 1gb or larger. I tend to avoid older cards labeled as ‘TransFlash’ because they seem to have more controller artifacts during saves. Small 128mb & 256mb cards under the name Cloudisk have appeared on eBay, and so far they seem to be working ok.
$2.00
Small White 170 Tie-Points Prototype Breadboard
These mini breadboards for inside the logger are also available in other colors.
$0.60
20cm Dupont 2.54mm M2F 40wire ribbon cable
Dupont connector hook-up wires might be expected to add an ohm or two of resistance and carry at most 100mA reliably with their thin 28-30 gauge wires.  Each 40-wire cable will let you make at least 2 loggers.
$1.55
10cm Dupont 2.54mm M2F ribbon cable
Sometimes these 10cm cables are harder to find, so you can just use the longer 20cm wires in a pinch.  It’s usually also helpful to have a few Male-to-Male 10cm cables for interconnections on the breadboard.
$1.00
2×1.5V AA Battery Batteries Holder w Wire Leads
If you are running an unregulated system on 2 lithium batteries, then you can use a 2x AA battery holder. If you need to keep the regulator in place to stabilize the rail voltage for particularly picky sensors, use alkaline batteries and a 4xAA battery holder. Watch out for ‘cheap’ battery holders with weak plastic at the connection ends which will slowly bend out of shape until the batteries pop out in warmer climate.
$0.50
5mm Common cathode RGB LED  $0.10
M12 Nylon Cable Glands (pack of 20 pcs) 
You will also need some extra rubber washers.
$0.70 /2pcs
3.3V FT232 UART Module (get at least 2-3 modules – they are easy kill with a brief short)
 *jumper the pads on your UART module to 3.3v output before using it!* You will also need a few USB 2.0 A Male to Mini B cables. You may need to install drivers from the FTDI website depending on your OS. These boards can only supply ~50mA which can be tricky if your sensors need more for sustained periods. If you are running the class via distance learning it’s probably a good idea to also get some CP2102 (c231932) UART boards and send your students one of each type. If they are unable to get the drivers working for the FT232, they have a second option. You may have to hunt around for non-FTDI chip boards with the same pin order as the ProMini [ DTR-RX-TX-3v3-CTS/gnd-GND ]  The DTR pin is critical for uploading code, while the CTS (clear to send) is an input pin for the FTDI chip only and CTS is not used by the ProMini (so it’s usually just tied to ground).  So many UART adapters only have 5 connections and you have to cross the wires over each other to get the connections sorted out.  Watch out for 6-pin UART modules that put a (+)ive power connection in the same physical alignment as the GND connection on the ProMini  –  those boards can create a short circuit unless you re-route the wires. It’s also worth knowing that UARTs can communicate directly to serial sensors like GPS modules for testing.
$2.75
3M Double-side Foam Tape, LEDs, header pins, 3/4 inch zip Tie Mounts, etc…
I use 30lb ‘outdoor’ or VHB (high bond) foam tape, each logger takes ~25cm length
$1.00
Some extra tools you may need 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 you can get the Yihua 936 soldering iron alone for about $25. While the Yihua is a so-so iron, replacement handles and soldering tips cost very little, and that’s very important in a classroom situation where you can count on replacing at least 1-2 tips per student, per course, because they let them run dry till they oxidize and won’t hold solder any more.  Smaller hand-held heat-shrink guns are available for ~$15, $10 80Watt-AC &  $5 USB soldering irons are quite useable.
$15.00 – $50.00
SYB-46 270 breadboards (used ONLY for soldering header pins )
Soldering the header pins on the pro-mini is MUCH easier if you use a scrap breadboard to hold everything in place while you work. I use white plastic breadboards that only have one power rail on the side since I won’t mistake them for my regular breadboards. I also write ‘for soldering only’ on them with a black marker.
$1.30
SN-01BM Crimp Plier Tool 2.0mm 2.54mm 28-20 AWG Crimper Dupont JST
I use my crimping pliers almost as often as my soldering iron –  usually to add male pins to component lead wires for connection on a breadboard. But making good crimp ends takes some practice.  But once you get the hang of it,  Jumper wires that you make yourself are always better quality than the cheap premade ones.
$16.00
Micro SD TF Flash Memory Card Reader
Get several, as these things are lost easily. My preferred model at the moment is the SanDisk MobileMate SD+ SDDR-103 or 104 which can usually be found on the ‘bay for ~$6.
$1.00
Side Shear Flush Wire Cutters & Precision Wire Stripper AWG 30-20
HAKKO is the brand name I use most often for these, but there are much cheaper versions.
$5-10
Dt380 Multimeter
Dirt Cheap & good enough for most classroom uses.
$3.50
Syba SY-ACC65018 Precision Screwdriver Set
A good precision screwdriver set makes it so much easier to work with the screw terminal boards. But there are many cheaper options.
$12.00
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 mother-ship to keep the open source hardware movement going…
$1.00

.. and the required lithium AA batteries are also somewhat expensive, so a realistic estimate is about $25-30 for each logger when you add a couple of sensors. Expect parts from low-end suppliers to take 4-6 weeks to arrive and always order at least 50% more than you actually need so you have replacement spares. If you’re pressed for time everything on this list is also available from trusted first-tier suppliers like Sparkfun, Adafruit, Pololu, etc – but you will pay 5-10x as much, with an additional $10-15 shipping charge unless you pass the minimum order level. Amazon is now in a kind of weird grey zone between the two as many vendors that sell on eBay, are also selling on Amazon for 2-3x the price. 


Addendum:  Using a more advanced processor

Moteino MEGA based Cave Pearl Logger

After you’ve built a few ProMini based loggers, you might want to try a processor upgrade. The 1284p CPU has twice the speed & 4x the memory, but delivers comparable sleep current & operating life.

 

 

 


Addendum:  Low Temp. effects on 2x Lithium powered logger

2x LithiumAA millivolts (blue-left) vs RTC Temp °C (orange-right) on cells that have been in service for 5 months. We will leave this unit running over winter to see how DS18b20 on that logger handles it if/when the cells fall below the DS18’s 3V minimum, and then rise back up again. (click image to enlarge)

A crop of these loggers have been running in our back-yard garden since mid-summer with various sensor combinations. Winter is finally reaching us so we can now observe how the cold affects an unregulated 2x Lithium AA supply.  This ‘student build’ sleeps at ~170uA and has been running for five months. The battery curve was virtually flat above 15°C but it is now being quite strongly affected. Peak loads from the SD card are in the range of about 150mA and the unit is running with a 5 minute sampling interval.

 

How to make Resistive Sensor Readings with DIGITAL I/O pins

AN685 figure 8: The RC rise-time response of the circuit allows microcontroller timers to be used to determine the relative resistance of the NTC element.

For more than a year we’ve been oversampling the Arduino’s humble ADC to get >16bit ambient temperature readings from a 20¢ thermistor. That pin-toggling method is simple and delivers solid results, but it requires the main CPU to stay awake long enough to capture multiple readings for the decimation step. (~200 miliseconds @ 250 kHz ADC clock) While that only burns 100 mAs per day with a typical 15 minute sample interval, a read through Thermistors in Single Supply Temperature Sensing Circuits hinted that I could ditch the ADC and read those sensors with a pin-interrupt method that would let me sleep the cpu during the process. An additional benefit is that the sensors would draw no power unless they were actively being read.

Given how easy it is drop a trend-line on your data, I’ve never understood why engineers dislike non-linear sensors so much. If you leave out the linearizing resistor you end up with this simple relationship.

The resolution of time based methods depends on the speed of the clock, and Timer1 can be set with a prescalar = 1; ticking in step with the 8 mHz oscillator on our Pro Mini based data loggers. The input capture unit can save Timer1’s counter value as soon as the voltage on pin D8 passes a high/low threshold. This under appreciated feature of the 328p is more precise than typical interrupt handling, and people often use it to measure the time between rising edges of two inputs to determine the pulse width/frequency of incoming signals.

You are not limited to one sensor here – you can line them up like ducks on as many driver pins as you have available.  As long as they share that common connection you just read each one sequentially with the other pins in input mode. Since they are all being compared to the same reference resistor, you’ll see better cohort consistency than you would by using multiple series resistors.

Using 328p timers is described in detail at Nick Gammons Timers & Counters page, and I realized that I could tweak the method from ‘Timing an interval using the input capture unit’ (Reply #12) so that it recorded only the initial rise of an RC circuit.  This is essentially the same idea as his capacitance measuring method except that I’m using D8’s external pin change threshold rather than a level set through the comparator. That should be somewhere around 0.6*Vcc, but the actual level doesn’t matter so long as it’s consistent between the two consecutive reference & sensor readings. It’s also worth noting here that the method  doesn’t require an ICU peripheral – any pin that supports a rising/ falling interrupt can be used. (see addendum for details) It’s just that the ICU makes the method more precise which is important with small capacitor values. (Note that AVRs have a Schmitt triggers on  the digital GPIO pins. This is not necessarily true for other digital chips. For pins without a Schmitt trigger, this method may not give consistent results)

Using Nicks code as my guide, here is how I setup the Timer1 with ICU:

#include <avr/sleep.h>              //  to sleep the processor
#include <avr/power.h>            //  for peripherals shutdown
#include <LowPower.h>            //  https://github.com/rocketscream/Low-Power

float referencePullupResistance=10351.6;   // a 1% metfilm measured with a DVM
volatile boolean triggered;
volatile unsigned long overflowCount;
volatile unsigned long finishTime;

 

ISR (TIMER1_OVF_vect) {  // triggers when T1 overflows: every 65536 system clock ticks
overflowCount++;
}

 

ISR (TIMER1_CAPT_vect) {     // transfers Timer1 when D8 reaches the threshold
sleep_disable();
unsigned int timer1CounterValue = ICR1;       // Input Capture register (datasheet p117)
unsigned long overflowCopy = overflowCount;

if ((TIFR1 & bit (TOV1)) && timer1CounterValue < 256){    // 256 is an arbitrary low value
overflowCopy++;    // if “just missed” an overflow
}

if (triggered){ return; }  // multiple trigger error catch

finishTime = (overflowCopy << 16) + timer1CounterValue;
triggered = true;
TIMSK1 = 0;  // all 4 interrupts controlled by this register are disabled by setting zero
}

 

void prepareForInterrupts() {
noInterrupts ();
triggered = false;                   // reset for the  do{ … }while(!triggered);  loop
overflowCount = 0;               // reset overflow counter
TCCR1A = 0; TCCR1B = 0;     // reset the two (16-bit) Timer 1 registers
TCNT1 = 0;                             // we are not preloading the timer for match/compare
bitSet(TCCR1B,CS10);           // set prescaler to 1x system clock (F_CPU)
bitSet(TCCR1B,ICES1);          // Input Capture Edge Select ICES1: =1 for rising edge
// or use bitClear(TCCR1B,ICES1); to record falling edge

// Clearing Timer/Counter Interrupt Flag Register  bits  by writing 1
bitSet(TIFR1,ICF1);         // Input Capture Flag 1
bitSet(TIFR1,TOV1);       // Timer/Counter Overflow Flag

bitSet(TIMSK1,TOIE1);   // interrupt on Timer 1 overflow
bitSet(TIMSK1,ICIE1);    // Enable input capture unit
interrupts ();
}

With the interrupt vectors ready, take the first reading with pins D8 & D9 in INPUT mode and D7 HIGH. This charges the capacitor through the reference resistor:

//========== read 10k reference resistor on D7 ===========

power_timer0_disable();    // otherwise Timer0 generates interrupts every 1us

pinMode(7, INPUT);digitalWrite(7,LOW);     // our reference
pinMode(9, INPUT);digitalWrite(9,LOW);     // the thermistor
pinMode(8,OUTPUT);digitalWrite(8,LOW);  // ground & drain the cap through 300Ω
LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_ON);  //overkill: 5T is only 0.15ms

pinMode(8,INPUT);digitalWrite(8, LOW);    // Now pin D8 is listening

set_sleep_mode (SLEEP_MODE_IDLE);  // leaves Timer1 running
prepareForInterrupts ();
noInterrupts ();
sleep_enable();
DDRD |= (1 << DDD7);          // Pin D7 to OUTPUT
PORTD |= (1 << PORTD7);    // Pin D7 HIGH -> charging the cap through 10k ref

do{
interrupts ();
sleep_cpu ();                //sleep until D8 reaches the threshold voltage
noInterrupts ();
}while(!triggered);      //trapped here till TIMER1_CAPT_vect changes value of triggered

sleep_disable();           // redundant here but belt&suspenders right?
interrupts ();
unsigned long elapsedTimeReff=finishTime;   // this is the reference reading

Now discharge and then repeat the process a second time with D7 & D8 in INPUT mode, and D9 HIGH to charge the capacitor through the thermistor:

//==========read the NTC thermistor on D9 ===========

pinMode(7, INPUT);digitalWrite(7,LOW);     // our reference
pinMode(9, INPUT);digitalWrite(9,LOW);     // the thermistor
pinMode(8,OUTPUT);digitalWrite(8,LOW);  // ground & drain the cap through 300Ω
LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_ON);

pinMode(8,INPUT);digitalWrite(8, LOW);    // Now pin D8 is listening

set_sleep_mode (SLEEP_MODE_IDLE);
prepareForInterrupts ();
noInterrupts ();
sleep_enable();
DDRB |= (1 << DDB1);          // Pin D9 to OUTPUT
PORTB |= (1 << PORTB1);    // set D9 HIGH -> charging through 10k NTC thermistor

do{
interrupts ();
sleep_cpu ();
noInterrupts ();
}while(!triggered);

sleep_disable();
interrupts ();
unsigned long elapsedTimeSensor=finishTime;   //this is your sensor reading

Now you can determine the resistance of the NTC thermistor via the ratio:

unsigned long resistanceof10kNTC=
(elapsedTimeSensor * (unsigned long) referencePullupResistance) / elapsedTimeReff;

pinMode(9, INPUT);digitalWrite(9,LOW);pinMode(7, INPUT);digitalWrite(7,LOW);
pinMode(8,OUTPUT);digitalWrite(8,LOW);  //discharge the capacitor when you are done

The integrating capacitor does a fantastic job of smoothing the readings and getting the resistance directly eliminates 1/2 of the calculations you’d normally do with a thermistor. To figure out your constants, you need to know the resistance at three different temperatures. These should be evenly spaced and at least 10 degrees apart with the idea that your calibration covers the range you expect to use the sensor for. I usually put loggers in the refrigerator & freezer to get points with enough separation from normal room temp with the thermistor taped to the surface of a si7051.  Then plug those values into the thermistor calculator provided by Stanford Research Systems.

Just for comparison I ran a few head-to-head trials against my older dithering/oversampling method:

°Celcius vs time [1 minute interval]  si7051 reference [0.01°C 14-bit resolution, 10.8 msec/read ] vs. Pin-toggle Oversampled 5k NTC vs. ICUtiming 10k NTC.   I’ve artificially separated these curves for visual comparison, and the 5K was not in direct contact with the si7051 ±0.1 °C accuracy reference, while the 10k NTC was taped to the surface of chip – so some of the 5ks offset is an artifact. The Timer1 ratios delver better resolution than 16-bit (equivalent) oversampling in 1/10 the time.

There are a couple of things to keep in mind with this method:

si7051 reference temp (C) vs 10k NTC temp with with a ceramic 106 capacitor. If your Ulong calculations overflow at low temperatures like the graph above, switch to doing the division before the multiplication or use larger ‘long-long’ variables. Also keep in mind that the Arduino will default to 16bit calculations unless you set/cast everything to longer ints Or you could make you life easy and save the raw elapsedTimeReff & elapsedTimeSensor values and do the calculations later in Excel. Whenever you see a sudden discontinuity where the result of a calculation suddenly takes a big jump to larger or smaller values – then you should suspect a variable type/cast error.

1) Because my Timer1 numbers were small with a 104 cap I did the multiplication before the division. But keep in mind that this method can easily generate values that over-run your variables during calculationUlong MAX is 4,294,967,295  so the elapsedTimeSensor reading must be below 429,496 or the multiplication overflows with a 10,000 ohm reference. Dividing that by our 8mHz clock gives about 50 milliseconds. The pin interrupt threshold is reached after about one rise-time constant so you can use an RC rise time calculator to figure out your capacitors upper size limit. But keep in mind that’s one RC at the maximum resistance you expect from your NTC – that’s the resistance at the coldest temperature you expect to measure as opposed to its nominal rating.  (But it’s kind of a chicken&egg thing with an unknown thermistor right?  See if you can find a manufacturers table of values on the web, or simply try a 0.1uF and see if it works).  Once you have some constants in hand Ametherm’s Steinhart & Hart page lets you check the actual temperature at which your particular therm will reach a given resistance.  Variable over-runs are easy to spot because the problems appear & then disappear whenever some temperature threshold is crossed. I tried to get around this on a few large-capacitor test runs by casting everything to float variables, but that lead to other calculation errors.

(Note: Integer arithmetic on the Arduino defaults to 16 bit & never promotes to higher bit calculations, unless you cast one of the numbers to a high-bit integer first. After casting the Arduino supports 64-bit “long long” int64_t & uint64_t integers for large number calculations but they do gobble up lots of program memory space – typically adding 1 to 3k to the compiled size. Also Arduino’s printing function can not handle 64 bit numbers, so you have to slice them into smaller pieces before using any .print functions

2) This method works with any kind of resistive sensor, but if you have one that goes below ~200 ohms (like a photoresistor in full sunlight) then the capacitor charging could draw more power than you can safely supply from the D9 pin.  In those cases add a ~300Ω resistor in series with your sensor to limit the current, and subtract that value from the output of the final calculation. At higher currents you’ll also have voltage drop across the mosfets controlling the I/O pins (~40Ω on a 3.3v Pro Mini), so make sure the calibration includes the ends of your range.

There are a host of things that might affect the readings because every component has  temperature, aging, and other coefficients, but for the accuracy level I’m after many of those factors are absorbed into the S&H coefficients. Even if you pay top dollar for reference resistors it doesn’t necessarily mean they are “Low TC”. That’s why expensive resistors have a temperature compensation curve in the datasheet. What you’re talking about in quality references is usually low long-term drift @ a certain fixed temperature (normally around 20 ~ 25°C) so ‘real world’ temps up at 40°C are going to cause accelerated drift.

The ratio-metric nature of the method means it’s almost independent of value of the capacitor, so you can get away with a cheap ceramic cap even though it’s value changes dramatically with temperature. (& also with DC bias ) In my tests thermal variation of a Y5V causes a delta in the reference resistor count that’s about 1/3 the size of the delta in the NTC thermistor.  Last year I also found that the main system clock was variable to the tune of about 0.05% over a 40°C range, but that shouldn’t be a problem if the reference and the sensor readings are taken immediately after one another. Ditto for variations in your supply. None of it matters unless it affects the system ‘between’ the two counts you are using to make the ratio.

The significant digits from your final calculation depend on the RC rise time, so switching to 100k thermistors increases the resolution, as would processors with higher clock speeds.  You can shut down more peripherals with the PRR to use less power during the readings as long as you leave Timer1 running with SLEEP_MODE_IDLE.  I’ve also found that cycling the capacitor through an initial charge/discharge cycle (through the 300Ω on D8) improved the consistency of the readings. That capacitor shakedown might be an artifact of the ceramics I was using but you should take every step you can to eliminating errors that might arise from the pre-existing conditions. I’ve also noticed that the read order matters, though the effect is small.

Code consistency is always important with time based calibrations no matter how stable your reference is. Using a smaller integrating capacitor makes it more likely that your calibration constants will be affected by variations in code execution time, so using a larger 1uF (105) capacitor may be a safer option. This method bakes a heap of small systemic errors into those NTC calibration constants, and this approach works because most of those errors are thermal variations too. However code-dependant variations mess with the fit of the thermistor equation as they tend to be independent of temperature, so make sure the deployment uses exactly the same code that you calibrated with.

Will this method replace our pin-toggled oversampling?  Perhaps not for something as simple as a thermistor since that method has already proven itself in the real world, and I don’t really have anything better to do with A6 & A7. And oversampling still has the advantage of being simultaneously available on all the analog inputs, while the ICU is a limited resource.  Given the high resolution that’s potentially available with the Timer1/ICU combination, I might save this method for sensors with less dynamic range.  I already have some ideas there and, of course, lots more testing to do before I figure out if there are other problems related to this new method.  I still haven’t determined what the long-term drift is for the Pro Mini’s oscillator, and the jitter seen in the WDT experiment taught me to be cautious about counting those chickens.


Addendum:   Using the processors built in pull-up resistors

After a few successful runs, I realized that I could use the internal pull-up resistor on D8 as my reference; bringing it down to only three components. Measuring the resistance of the internal pull-up is simply a matter of enabling it and then measuring the current that flows between that pin and ground. I ran several tests, and the Pro Mini’s the internal pullups were all close to 36,000 ohms, so my reference would become 36k + the 300Ω resistor needed on D8 to safely discharge of the capacitor between cycles. I just have to set the port manipulation differently before the central do-while loop:

PORTB |= (1 << PORTB0);     //  enable pullup on D8 to start charging the capacitor

A comparison of the two approaches:

°Celcius vs time: (Post-calibration, 0.1uF X7R ceramic cap)  si7051 reference [±0.01°C] vs. thermistor ICU ratio w 10k reference resistor charging the capacitor vs. the same thermistor reading calibrated using the rise time through pin D8’s internal pull-up resistor. The reported ‘resistance’ value of the NTC themistor was more than 1k different between the two methods, with the 10k met-film reference providing values closer to the rated spec. However the Steinhart-Hart equation constants from the calibration were also quite different, so the net result was indistinguishable between the two references in the room-temperatures range.

The internal pull-up probably isn’t a real resistor. More likely its a very thin semiconductor channel that simply acts like one, and I found the base-line temperature variance to be about 200 ohms over my 40°C calibration range. And because you are charging the capacitor through the reference and through the thermistor, the heat that generates necessarily changes those values during the process.  However when you run a calibration, those factors get absorbed into the S&H coefficients provided you let the system equilibrate during the run.

As might be expected, all chip operation time affects the resistance of the internal pull-up, so the execution pattern of the code used for your calibration must exactly match your deployment code or the calibration constants will give you an offset error proportional to the variance of the internal pull-up caused by the processors run-time. Discharging the capacitor through D8, also generates some internal heating so those (~30-60ms) sleep intervals also have to be consistent.  In data logging applications you can read that reference immediately after a long cool-down period of processor sleep and use the PRR to reduce self-heating while the sample is taken.

Another issue was lag because that pull-up is embedded with the rest of the chip in a lump of epoxy. This was a pretty small, with a maximum effect less than ±0.02°C/minute and I didn’t see that until temperatures fell below -5 Celsius.  Still, for situations where temperature is changing quickly I’d stick with the external reference, and physically attach it to the thermistor so they stay in sync.


Addendum:   What if your processor doesn’t have an Input Capture Unit?

With a 10k / 0.1uF combination, I was seeing Timer1 counts of about 5600 which is pretty close to one 63.2% R * C time constant for the pair.  That combination limits you to 4 significant figures and takes about 2x 0.7msec per reading on average.  Bumping the integrating capacitor up to 1uF (ceramic 105) multiplies your time by a factor of 10 – for another significant figure and an average of ~15msec per paired set readings.  Alternatively, a 1uF or greater capacitor allows you to record the rise times with micros()  (which counts 8x slower than timer1) and still get acceptable results. (even with the extra interrupts that leaving Timer0 running causes…) So the rise-time method could be implemented on processors that lack an input capture unit – provided that they have Schmitt triggers on the digital inputs like the AVR which registers a cmos high transition at ~0.6 * Vcc.

void d3isr() {
triggered = true;
}

pinMode(7, INPUT);digitalWrite(7,LOW);     // reference resistor
pinMode(9, INPUT);digitalWrite(9,LOW);     // the thermistor
pinMode(3,OUTPUT);digitalWrite(3,LOW);  // ground & drain the cap through 300Ω
LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_ON);  // 5T is only 1.5ms w 10k

pinMode(3,INPUT);digitalWrite(3, LOW);    // Now pin D3 is listening

triggered = false;
set_sleep_mode (SLEEP_MODE_IDLE);  // leave micros timer0 running for micros()
unsigned long startTime = micros();
noInterrupts ();
attachInterrupt(1, d3isr, RISING); // using pin D3 here instead of D8
DDRD |= (1 << DDD7);          // Pin D7 to OUTPUT
PORTD |= (1 << PORTD7);    // Pin D7 HIGH -> charging the cap through 10k ref
sleep_enable();

do{
interrupts ();
sleep_cpu ();
noInterrupts ();
}while(!triggered);

sleep_disable();
detachInterrupt(1);
interrupts ();
unsigned long D7riseTime = micros()-startTime;

Then repeat the pattern shown earlier for the thermistor reading & calculation. I’d probably bump it up to a ceramic 106 for the micros method just for some extra wiggle room. The method doesn’t really care what value of capacitor you use, but you have to leave more time for the discharge step as the size of your capacitor increases. Note that I’m switching between relatively slow digital writes (~5 µs each) outside the timing loop, and direct port manipulation (~200 ns each) inside the timed sequences to reduce that source of error.

Addendum 20191020:

After running more tests of this technique, I’m starting to appreciate that even on regulated systems, you always have about 10-30 counts of jitter in the Timer1 counts, even with the 4x input capture filter enabled.  I suspect this is due to the Shimitt triggers on the digital pins also being subject to noise/temp/etc. A smaller 104 integrating capacitor you make your readings 10x faster, but the fixed jitter error is a correspondingly larger percentage of that total reading (100nF typically sees raw counts in the 3000 range for the 10k reference). By the time you’ve over-sampled 104 capacitor readings up to a bit-depth equivalent of the single-pass readings with the 105 ceramic capacitor ( raw counts in the 60,000 range for the same 10k ref), you’ve spent about the same amount of run-time getting to that result. (Keep in mind that even with a pair of single-pass readings using the 10k/104 capacitor combination; raw counts of ~3500 yield a jitter-limited thermistor resolution of about 0.01C)

So, as a general rule of thumb, if your raw Timer1 counts are in the 20,000-60,000 range, you get beautiful smooth results no matter what you did to get there. This translates into about 2.5 – 7.5 milliseconds per read, and this seems to be a kind of ‘sweet-spot’ for ICU based timing methods because the system’s timing jitter error is insignificant at that point. With 5 significant figures in the raw data, the graphs are so smooth they make data from the si7051 reference I’m using look like a scratchy mess.

Another thing to watch out for is boards using temperature compensated oscillators for their system clock. ICU methods work better with the crappy ceramic oscillators on clone boards because their poor thermal behavior just gets rolled into the thermistors S&H constants during calibration. However better quality boards like the 8mhz Ultra from Rocket Scream have compensation circuits that kick in around 20C, which puts a weird discontinuity into the system behavior which can not be gracefully absorbed by the thermistor constants.  So the net result is that you get worse results from your calibration with boards using temperature compensation on their oscillators.

The thermistor constants also neatly absorb the tempco and even offset errors in the reference resistor. So if you are calibrating a thermistor for a given logger, and it will never be removed from that machine, you can set your reference resistor in the code to some arbitrary perfect value like 10,000 ohms, and just let the calibration push any offset between the real reference and your arbitrary value into the S&H constants. This lets you standardize the code across multiple builds if you are not worried about  ‘interchangeability’.

And finally, this method is working well on unregulated systems with significant battery supply variations as I test the loggers down to -15C in my freezer.  In addition to battery droop, those cheap ceramic caps have wicked tempcos, so the raw readings from the reference resistor are varying dramatically during these tests, but the ‘Ratio/Relationship’ of the NTC to this reference is remaining stable over a 30-40C range, with errors in the ±0.1°C range, relative to the reference. (Note: si7051 itself has ±0.13°C, so the net is probably around ±0.25°C)

Addendum 20201011:

Re: the temperature coefficient of resistance (TCR) of your reference resistor:

“Using a thin film resistor at ±10 ppm/°C would result in a 100 ppm (0.01%) error if the ambient changes by only 10°C. If the temperature of operation is not close to the midpoint of the temperature range used to quantify the TCR at ±10 ppm/°C, it would result in a much larger error over higher temperature ranges. A foil resistor would only change 0.0002% to 0.002% over that same 10°C span, depending upon which model is used (0.2 ppm/°C to 2 ppm/°C.) And for larger temperature spans, it would be even more important to use a resistor with an inherently low TCR.”

During calibration, I usually bake the reference resistor’s tempco into the thermistor constants by simply assuming the resistor is a ‘perfect 10k ohm’ during all calculations. (this also makes the code more portable between units)  However this does nothing to correct long-term drift in your reference.  If you want to tackle that problem with a something like a $10 Vishay Z-foil resistor (with life stability of ± 0.005 %) then it’s probably also worth adding Plastic film capacitors which have much better thermal coefficients: Polyphenylene sulfide (PPS ±1.5%) or Polypropylene (CBB or PP ±2.5%)A quick browse around the Bay shows those are often available for less than $1 each, and the aging rate (% change/decade hour) for both of those dielectrics is listed as negligible. The trade off is that they are huge in comparison to ceramics, so you are not going to just sneak one in between the pins on your pro-mini. Be sure to check the rated voltage – and don’t order them if they are rated >100v as the common 650v film caps are too big to be practical on small logger builds.  For the coup de grâce you could correct away the system clock variation by comparing it to the RTC

Addendum 2020-03-31:  Small capacitors make this method sensitive to a noisy rail

After a reasonable number of builds I have finally identified one primary cause of timing jitter with this technique: a noisy regulator. To get sleep current down I replace the stock MIC5205’s on clone ProMini boards with MCP1700’s and I noticed a few from a recent batch of loggers were producing noisy curves on my calibration runs. One of them was extreme, creating >0.5°C of variation in the record:

ICU based Thermistor readings VS si7051 reference. Sensors in physical contact with each other. Y axis = Celsius

But in the same batch, I had others thermistors with less noise than the si7051’s I was using as a reference. All were using small 104 ceramic capacitors for the integration, producing relatively low counts (~3500 clock ticks) on the 10k reference resistor.

For the unit shown above I removed the regulator, and re-ran the calibration using 2x Lithium AA batteries in series to supply the rail voltage. No other hardware was changed:

Same unit, after regulator was removed. Samples taken at 1 minute interval on both runs.

In hindsight, I should have guessed a bad regulator was going to be a problem, as few other issues can cause that much variation in the brief interval between the reference resistor & thermistor readings. Reg. noise/ripple translates instantaneously into a variation in the Schmitt trigger point on the input pin – which affects the ICU’s timer count. It’s possible that this issue could be eliminated with more smoothing so I will try a few caps across the rails on the less problematic units. (~1000µF/108 Tantalums can be found for 50¢ on eBay but I will start with a 10µF/106 & work up from there)

Addendum 2020-04-05:  106 ceramic across rails increased ICU reading noise (w bad reg)

Adding a cheap 106 (Y5V ceramic) across the rails more than doubled the noise in the readings of the NTC with this ICU technique.  This is interesting, as it goes completely against what I was expecting to happen.  Possibly that 10µF cap was just badly sized for this job or had some other interaction via inductance effects that actually accentuated the noise? I probably need a smaller, faster cap for the job.

Changing the sampling capacitor from a 104 (0.1µF) , to a 105 (1µF) dramatically reduced the problem. Not surprising as the rail noise from the regulator is relatively consistent, while the reference timer counts change from ~3500 with a 104 capacitor to ~60,000 with the larger 105. So the jitter is still there, but it is proportionally much smaller. I’m currently re-running the thermistor calibration with that larger capacitor. If the gods are kind, the S&H thermistor constants will be the same no matter what sampling capacitor is used.

It’s worth noting that this issue only appeared with the most recent crop of crappy eBay regulators. But if you are sourcing parts from dodgy vendors, you’d best go with a 105 sampling capacitor right from the start to smooth out that kind of noise

Addendum 2020-04-06:  Re-use old S&H constants after changing the sample capacitor?

After discovering the reg. issue, I re-ran a few thermistor calibrations once the sampling capacitor had been changed from a 104 to a 105:  This reveals that the thermistor constants obtained with a 104 sampling capacitor, still work, but it depends on the tolerance you are aiming for: with the older 104 cap constants drifting over a 40°C range by about ±0.3 Celsius. The extra resolution provided by the larger 105 cap is only useful if you have the accuracy to utilize it (ie: It doesn’t matter if the third decimal point is distinguishable of the whole number is wrong) I generally aim for a maximum of ±0.1°C over that range, so for our research loggers that’s a complete do-over on the calibration. From now on I will only use 105 caps (or larger) with this ICU technique on regulated systems. The battery-only units were smooth as silk with smaller 104 caps because the rail had zero noise.

Addendum 2020-05-21: Using the ADS1115 in Continuous Mode for Burst Sampling

For single resistive sensors, it’s hard to beat this ICU method for elegance & efficiency. However there’s still one sensor situation that forces me to go to an external ADC module: Differential readings on bridge sensors.  In those cases I use an ADS1115 module, which can also generate interrupt alerts for other tricks like ‘event’ triggered sampling.

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

Dupont jumper variant of the “fully soldered’ Classroom Data Logger from the Cave Pearl project: This version uses dupont jumpers to reduce assembly time to about 1 hour

Note: An updated version of the classroom logger was released in 2020:  CLICK THIS LINK to view the newest build tutorial.

It’s only been a couple of weeks since the release of the 2019 EDU logger, and we’re already getting feedback saying all the soldering that we added to that tutorial creates a resource bottleneck which could prevent some instructors 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 that design was to give students their first opportunity to practice soldering skills that are needed when repairing kit in the field. 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 new version that combines the Dupont jumper approach we used in 2016, with this flat-box layout. In the following video, I assemble one of these ‘minimum builds’ in about one hour.  To put that in perspective, the fully soldered version takes 2 – 2.5 hours for someone with experience.

Note: After you’ve seen the video to get a sense of where you are headed, it’s usually much better to work from the photos (below) when assembling your logger. Youtube videos make it look easier than it actually is when you are just starting out. So the first one you build could take several hours as you figure out what you are doing, the second will take half as long, and the third one you make usually takes less than two hours. With some practice you can easily make 4-5 of these things a day.

This variation of the basic 3-component logger is optimized for quick assembly, so the soldering has been reduced to adding header pins and bridging the A4/A5 I2C bus to the outer terminals.  An instructor could easily do that ahead of time with about 15 minutes of prep per unit, leaving only the solder-less steps for their students. After the header pins are in place, connections to the central Pro Mini are made by simply twisting stripped wire ends together and clamping them under screw terminals.

You should use Lithium AA batteries with a 2-cell unregulated supply because the slope of an alkaline batteries discharge would bring the system down to  ProMini’s brown-out of ~2.7v when only 40% of the battery capacity has been used. (note the SD card safe down to 1.8v) While the voltage of a ‘brand new’ Lithium AA is usually 1.8v/cell, that upper plateau provides a sleeping logger voltage of ~1.76 v/cell once the batteries settle, and that briefly dips to about 1.625v/cell under load during data saves. At low temps of about 5°C (in my refrigerator) those SD card voltage dips go down to about 1.525 v/cell. Unloaded readings of 1.5v on a Li AA = battery is dead.

This time reduction involves a few trade-offs, and bringing the I2C bus over to A2 & A3 leaves only two analog ports readily accessible ( although A6 & A7 are still available if you add some jumpers). Removing the regulator & battery voltage divider adds ~30% more operating life, but it also forces you to deal with a changing rail voltage as the Lithium AA batteries wear down. The daily variation is usually quite small, but for quantitative comparisons on monthly scales you will need to correct for the change in rail voltage over time if your sensor circuits are not ratio-metric. To do this voltage compensation multiply your raw sensor readings by the the ratio of (3300mv) / (current rail voltage).  Here 3300mv is just an arbitrary comparison point, which you could replace with any rail voltage reading from the data saved by your logger. Batteries have a lot of mass, so thermal lag in battery voltage can also cause hysteresis for analog temperature sensors unless you read the reference under the same conditions.

 (NOTE: complete parts list with supplier links are located at the end of this post)

Pro Mini Prep:

Solder the UART pins & test ProMini board with the blink sketch:  Set the IDE to (1) TOOLS> Board: Arduino Pro or Pro Mini (2)TOOLS> ATmega328(3.3v, 8mhz) in addition to the (3)TOOLS> COM port to match the # that appears when you plug in the serial adapter board.

Once you know you have a working Promini board: Remove the power LED [in red]. Removing the pin13 LED [yellow square above] is optional. Leaving the pin13 LED in place lets you know when data is being saved to the SD card because the SPI bus SClocK signal flashes the LED.

Remove the voltage regulator with snips. Your system voltage will vary over time, but our starter script records that rail voltage without a voltage divider.

Add pin headers to the sides & Serial input end of the Pro Mini.

Bridge the two I2C bus connections for side access with the leg of a resistor. Connect A4->A2 & A5->A3.

Adding DIDR0 = 0x0F; in Setup disables digital I/O on pins A0-A3 so they don’t interfere with I2C bus coms.

NOTE: The Screw Terminal board we use in this build was designed for the 5v Arduino NANO, so the shield labels don’t match the actual 3.3v Pro Mini pins on the ‘analog’ side. (the digital side does match) To avoid confusion may want to tape over those incorrect labels and hand write new labels to match the pattern above. Wire connections in this tutorial will be specified by ProMini pin labels:  D10-13 are used for the SD card, A4=A2 is the I2C Data line, and A5=A3 is the I2C clock line, A0 & A1 are not used.

Technically speaking, bridging the I2C bus (A4=data & A5=clock) over to A2 & A3 subjects those lines to the pin capacitance and input leakage of those analog pins (regardless of whether that channel is selected as input for the ADC p257). But in practice, the 4K7 pull-up resistors on the RTC module can easily handle that at the 100 kHz default bus speed. Adding DIDR0 = 0x0F; in setup disables digital I/O on pins A0-A3 to prevent interference with the I2C bus. If you want to disable the IO on A2 & A3 ‘only’ add bitSet (DIDR0, ADC2D); bitSet (DIDR0, ADC3D); to your code.

Also note: On the UART adapter in the picture above, the USB to TTL adapter pins are in the reverse order to the Pro Mini board. This is a fairly common issue with clones and if the blink sketch never uploads flip the adapter around and try again. I have connected 3.3v ProMinis to UART modules the wrong way round many, many times, and not one of them has been harmed by the temporary reversal.

Screw-Terminal Component Stack:

Add 3 layers of double sided tape so the tape is thicker than the solder pins.

Align RX&TX corner pins before inserting. The GND points on the screw terminal board may be interconnected (via the back-plane) & must match the ProMini’s GND pins.

Gently rock the Pro Mini back to front (holding the two short sides) until the pins are fully inserted. Some ST shields have mis-aligned headers so this insertion can be tricky.

Remove the last three ‘unused’ pin headers to make room for the SD adapter

Note: Screw-terminal board labels do not match the ProMini pins on the ‘analog’ side

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

Separate Dupont Cable wires & click them into a 6-pin shroud.

Cable Color Pattern:     Black =GND,   Purple=MISO,   Brown=CLocK,   Orange=MOSI,   Grey=CSelect,      and   Red=3v3

 

 

 

Use foam tape to attach SD module to the Screw Terminal board. Metal tabs should be visible on top surface.

Measure, cut & strip the 4 SPI bus wires (NOTE the ‘Nano’ ST board labels say A0-A3 which does not match the D10-13 Pro Mini pins on this side of the board)

Grey (CS) to ProMini D10Orange (MOSI) -> pm D11,      Purple (MISO) ->pm D12,        Brown (CLK) -> pm D13    

 

 

Add three jumper wires to the red power line from the SD module, one with a male end pin. I often add Dupont ends with a crimping tool, rather than using a pre-made jumper.

Strip & twist the 4 red power wires together & add heat-shrink for strain relief. Bundling wires like this is easier if you make the stripped area a bit longer.

A short red jumper goes to RAW(pm)=VIN(st) to recruit the orphan capacitor.

Long red jumper bridges power to ‘unused’ screws on other side of the Terminal board, and the wire with the Dupont male end will go to the breadboard.

Add 2 extra wires to the black GND wire from the SD module (1 with a male Dupont end ). Jumper one black wire across the Promini to an unused terminal beside the red power wire.

The GND bundle completes Pro Mini / ST board / SD module stack. The ‘pinned’ Red & Black ‘pinned’ jumpers shown here are about 1 inch too short to reach the breadboard easily… make yours longer.

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

I always try to make my Dupont connectors so that the metal & plastic retainer clips are accessible (in this case facing upwards) after the logger is assembled. That way you can diagnose bad wire connection with the tip of a meter probe, and if necessary, pull out & replace a single bad wire in the Dupont connector without taking everything apart.

RTC module:

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

The DS3231 modules often have flux residue – clean this off with 90% isopropyl.

 

Cable: Blk(gnd), Red(vcc), White(sda), Yellow(scl), Blue(sqw).  Shroud retainer clips face up & there is no wire on 32K output.

First tape layer

Next two tape layers

OPTIONAL: adding header pins to the cascade port provides a convenient attachment point for I2C sensors later.

Optional: After removing the two SMD resistors on the module, you can clip the Vcc leg on the RTC chip which forces the clock to run entirely from the backup coin-cell battery. This reduces the loggers overall power use by 0.09mA bringing a “no-reg & noRTCvcc” build below 0.1mA when the logger sleeps between sensor readings (this should run for more than 2 years on fresh lithium AA cells) . But the risk is that if you bump the RTC backup battery loose, that disconnection resets the clock time to Jan 1st, 2000. (note: while the time stamps will be wrong after that kind of reset, the logger will continue running after the next hour/min alignment occurs with the ‘old’ alarm time)   A couple of pieces of soft 1.6mm heat shrink tubing under the spring makes the negative coin-cell connection stronger, an a touch of hot melt glue will secure the battery on the top edges.  A CR2032 can power the RTC about four years but you have to set bit six of the DS3231_CONTROL_REG to 1 to enable alarms when running from the coin-cell. (our starter code does this by default) This modification also disables the 32.768 kHz output pin on the RTC.  Visit our RTC page for more detailed information on this clock module.

Final Assembly:
(Note: references here are to pin numbers/labels on the ProMini which do not match ST board labels on the analog side)

Attach the Pro Mini stack & RTC to housing with the double-sided tape.

Trim white & yellow I2C wires from the RTC & add 1 extra wire with dupont ends for each I2C line to bring the bus over to the breadboard

Attach yellow SCL line from the RTC beside the red 3v rail (ie to A3=A5 on the ProMini) then the white SDA line from the RTC to A2=A4.

The four extra jumper wires with male Dupont ends on Vcc, GND, & both I2C lines. These get patched over to the breadboard so you can add I2C sensors.

Each wire must be plugged into its own separate vertical column on the breadboard. Add a 2nd layer of foam tape to the bottom of the bread-board before attaching.

The RTC power line joins that short red jumper on RAW(pm)=VIN(st) at the end of the screw-terminal board.

Some of the box bottoms have slight bowing. If any component doesn’t stick well enough: add another layer of foam tape.

Attach the RTC’s black ground wire to GND & the blue SQW alarm line to ProMini pin D2

Attach 2xAA battery holder with 2 layers of foam tape. Trim wires to length. Use black 30lb Mounting Tape for extra strength.

Battery wires join the black & red jumpers from other side of the terminal board. All six of the ‘unused’ screw terminals we clipped earlier can be used to make secure ‘dry wire’ connections in this way.

Connections complete except the indicator. 5050 LED modules often come with pre-installed limit resistors which make them safer for the classroom. But you can use a raw 5mm LED if you only light the LED  with the INPUT_PULLUP command.

A ‘common cathode’ RGB LED module on pins  Red=D6, Green=D5,   Blue=D4, &  GND=D3.

Your Logger is ready for testing!

(Note: Most of the time the tests listed below go well, however if you run into trouble at any point read through the steps suggested for Diagnosing Connection Problems at the end of this page.)

Install the Arduino IDE into whatever default directory it wants to be in – we’ve had several issues where students tried to install the IDE into some other custom sub-directory, and then code will not verify without errors because it doesn’t know where to look for libraries. If you have not already done so, there are three things you need to set under the IDE>TOOLS menu to enable communication with the logger:

Note: that the “COM’ setting will be different for each computer, so you will have to look for the one that appears on your system AFTER you plug in the UART module.

The one that’s easy to forget is choosing the 328P 3.3v 8Mhz clock speed. If you leave the 328p 5v 16mhz (default), the programs will upload OK, but any text displayed on the serial monitor will just be a random bunch of garbled characters because of the clock speed mismatch.  Also be sure to disconnect battery power (by removing one of the AA batteries) whenever you connect your logger to a computer.

1. Test the LED – Edit the default blink sketch, adding commands in setup which set the digital pin 3 connected to the ground line of the LED to “OUTPUT” and “LOW”
      pinMode(3, OUTPUT);   digitalWrite(3, LOW); 
Since we removed the ‘default’ indicator led on the Pro Mini board, you will also need to change LED_BUILTIN variable in the blink code example to one of the pins connected to one of the color channels on your led module. (in this example change LED_BUILTIN to either 4, 5, or 6)

2. Scan the I2C bus with the scanner from the Arduino playgound. The RTC module has a 4K eeprom at address 0x56 (or 57) and the DS3231 RTC should show up at address 0x68.

The address of the eeprom can be changed via solder pads on the board, so sometimes it moves around. If you don’t see at least these two devices listed in the serial monitor when you run the scan, there is something wrong with your RTC module or the way it’s connected: It’s very common for a beginner to get at least one set of wire connections switched around during the assembly. With the screw terminals this takes only a few moments to fix. Also have a spare RTC module on hand in case you get a defective module…which is fairly common.

NOTE: Some sensors really need the stability provided by the on-board voltage regulator. Here is an alternative arrangement of parts for the classroom logger that leaves the 3.3v regulator in place on the ProMini and powers the logger from 4xAAA alkaline batteries (NOTE: regulated builds also leave out the short red jumper that was used to recruit the orphan capacitor after the reg was clipped.  The RTC & added sensors now get connected ONLY to the Pro-Mini’s regulated 3.3v rail )   We’ve designed the Cave Pearl Logger for maximum flexibility, so you can easily change components and positions like this to suit the needs of your design / experiment.

3. Set the RTC time, and check that the time was set – The easiest method would be to use the SetTime / Gettime scripts from our Github repository, but first you need to download & install this RTC control library  The SetTime script, automatically updates the RTC to the time the code was compiled (just before uploading) so you only run SetTime once, and then immediately upload the GetTime sketch to get the SetTime code out of memory after it’s done it’s job. Otherwise SetTime will reset the RTC to the ‘code compile time’  every time the Arduino restarts (and the Arduino restarts EVERY TIME you open the serial window…) 

There are dozens of other good Arduino libraries you could use to control the DS3231, and there is also a script over at TronixLabs.com that lets you set the clock without installing a library. [ in 24-hour time, & year with two digits eg: setDS3231time(30,42,21,4,26,11,14);  ] The trick with Tronix’s “manual” method is to change the parameters in the line: setDS3231time(second, minute, hour, dayOfWeek, dayOfMonth, month, year);  to about 2-3 minutes in the future, and then upload that code until about 20 seconds before your computers clock reaches that time (this compensates for delay caused by the compilers processing & upload time). Open the serial window immediately after the upload finishes, and when you see the time being displayed (and it’s not too far off actual…) upload the examples>blink sketch to remove the clock setting program from memory.

Another option would be to try setting the clock’s time using one of the serial window input utilities from Github.

4. Check the SD card is working with CardinfoChanging chipSelect = 4; in that code 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 because they get formatted as fat32. Most loggers only generate 5 Kb of CSV format data per year when they are running, so you don’t need a big SD card. In fact older, smaller SD cards in the 256-512mb range often use less power if it’s a good name brand like Sandisk or Nokia.

Zip-Tie Mounting Bases are an easy way to add attachment points inside your logger to secure sensor cables, or desiccant packs. These adhesive cable tie mounts come in many varieties, and cost about 10¢ each at most hardware stores.

5. Optional: If you are running with no regulator & using analog sensors: Calibrate your internal voltage reference with CalVref from OpenEnergyMonitor.

This logger uses an advanced code trick to read the positive rail voltage to ~11mv resolution by comparing it to an internal 1.1v reference  inside the processor. That internal ref. can vary by ±10% from one chip to another, and CalVref gives you a constant which will make the rail=battery voltage reading more accurate. Load CalVref while the logger is running from USB power and then measure the voltage between GND and the positive rail with a good quality voltmeter. (this voltage will vary depending on your computer’s USB output, and the UART adapter you are using) Then type that voltage into the serial monitor window entry line & hit enter. Write down the reference voltage & constant which is output to the serial monitor window. I usually write these ‘chip-specific’ numbers inside the logger with a black sharpie. You will need to add that info to the core data logger code later by changing the line #define InternalReferenceConstant 1126400L to match the long number returned from CalVref. Alternatively you could just tweak the value of the constant ‘by hand’, increasing or decreasing the value till the reported rail readings match what you see with a voltmeter. After you’ve done this once or twice you can usually get within 15mv of actual with about 10 trials.

This calibration brings the starter script’s battery readings within ±15mv of actual but you can skip the CalVref procedure if you are only using digital sensors, as the script will still produce reasonably good battery readings with the default 1126400L value. Increasing the rail reading accuracy is more important when you are using ANALOG sensors which use that rail voltage to drive your sensors – so the +ive rail directly affects their output if you are not reading a ratiometric circuit.

6. Find a script to run your on logger. For test runs on a USB tether, the simplest bare-bones logger code is probably Tom Igoe’s 1-pager at the Arduino playground. It’s not really deploy-able because it never sleeps the processor, but it is still useful for teaching exercises and testing sensors after you set chipSelect = 10;  In 2016 we posted an extended version of Tom’s code for UNO based loggers that included sleeping the logger with RTC wakeup alarms. Our latest logging “Starter Script” has grown in complexity to ~750 lines, but it should still be understandable once you have a few basic Arduino programming concepts under your belt.

After 1-2 minutes of kneading to mix the epoxy you have ~ 60 seconds to work the putty into place. (it will become rock-hard within ~5-10 minutes). Be sure to leave yourself enough extra wire/space inside the housing so that you can open and close the lid easily without disconnecting anything after the putty hardens. This seal is not strong enough for underwater deployments, but it should easily withstand exposure to rain-storm events. This putty is also a quick way to make custom mounting brackets, or even threaded fittings if you wrap it around a bolt (which you carefully remove before the putty hardens completely)

In the previous tutorial we attached external sensors with a cable gland passing through the housing and epoxying them into a pvc cap.  Cheap cable glands are notoriously unreliable and I always add a second rubber oring on the inside – and often over-seal the outside with  conformal coating. But for a simpler classroom project you could simply drill small a hole through the lid and stick the sensor/module on top of the housing, seal the hole with double-sided tape. Thicker pass-through wires can be sealed reasonably well with plumbers epoxy putty from the hardware store. This putty is non-conductive, and adheres quite well to both metal & plastic surfaces. 

Remember that breadboard connections are very easy to bump loose, so once you have your prototype circuits  working, its usually best to re-connect the sensors directly to the screw terminals before deploying a logger where it could get knocked about. In a pinch you can secure breadboard pins with a ‘tiny’ drop of hot glue to keep them from wiggling around.

There is no power switch on the loggers, which are turned on or off via the battery insertion. Use a screwdriver, or some other tool, when removing the batteries so that you don’t accidentally cause a series of disconnect-reconnect voltage spikes which might hurt the SD card.

Using the logger for experiments:

Logger mounted on a south-facing window and held in place with double sided tape. Here the top surface of the housing was covered with two layers of white label-maker tape to act as a light diffuser. PTFE is another excellent light diffusing material available in different sheet thicknesses.  The ‘divot’ on the lid of the Plano box is just a bit larger than 55mm x 130mm x 3mm (depth). The “teflon” tape that plumbers use to seal threaded joints can also be used in a pinch. PTFE introduces fewer absorbance artifacts than other diy diffusers like ping-pong balls, or hot melt glue. Most light sensors like the TSL2561 need 3-5mm of that PTFE sheeting to prevent the sensors from saturating in full daylight. LED’s don’t saturate that badly, but you still lose all the useful detail in your data at peak brightnesses > 80,000 lux unless you add a diffusion layer to attenuate the light.

Many types of sensors can be added to this logger and the RTC has a built-in temperature register which automatically gets saved with our starter script. The transparent enclosure makes it easy to do light-based experiments. Diodes, LED’s & solar panels are basically the same device. So grounding the indicator LED through a digital pin allows it to be used as both a status light, and as a 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 LED’s that detect those two colors 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 in the first place) has shown the readings from red LED’s can be used as a reasonable proxy for total PAR.  Photoperiod measurements have important implications for plant productivity, as do  measurements of transmittance through the plant canopy. Chlorophyll fluorescence is another potential application, and the response of plants to UV is fascinating.

The original code for using LEDs as 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 random capacitance) . Another thing to watch out for is moisture condensation inside your logger housing: this provides an alternate discharge path for the reverse-charge on the LED, which effectively shorts out the light level reading.

We have integrated this into the starter script which you can download from GITHUB. I’ve tweaked the playground version with port commands so the loop execution takes about 100 clock cycles instead of the default of about 400 clock cycles.  The faster version was used to generate this light exposure graph with a generic 5mm RGB LED, with a 4k7Ω limiter on the common ground which was connected to pin D3:

Red, Green & Blue channel readings over the course of one day 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 LED 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. Note how the RED signal changes more quickly than Blue & Green at sunrise & sunset.  LED’s work well with natural full-spectrum light, but their limited frequency sensing bands can give you trouble with the spectral distribution of  indoor light sources. The peak spectral response of LED’s is usually around 30–50nm lower than their peak emission wavelength So the blue channel is actually recording in the near-UV range, the Green channel is responding at ~ 420nm (blue) and the red channel is actually responding to a wide band of yellow-green light. 

Characterizing light absorption and re-emission is also a primary technique in climate science . 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 these columnated readings, in fact on bright sunny days you usually have to place the LED/sensor beneath a fair thickness of white diffusing material (like PTFE tape) to prevent it from being completely saturated.  Older LEDs that emit less light can sometimes be easier to work with because they are less sensitive, so the discharge time does 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. A light transmission-based variant of the NDVI ratio can be used to determine plant health after adding an IR LED to the logger.

Of course, full sun exposure can also cook your little logger: temps above 80°C may cause batteries to leak or damage the SD card. If you have to leave the logger in full sun, add a bit of reflective film or a layer of aluminum foil around the outside to protect the electronics. Though if use a light sensor you’ll need to leave a little un-covered window for it to take a reading. 

You might also find it handy to add a few holes to the Plano box as tie-down points:

and it’s always a good idea to add desiccant packs inside the box to prevent condensation. If you use a desiccant with color indicator beads, you can check whether they are still good simply by looking through the transparent housing.

If you clipped the RTC leg, your logger should pull less than 0.1mA while sleeping. Back of the envelope for Lithium AA’s is about 7 million milliamp-seconds of power, with your logger burning about 8600 mAs/day at 0.1mA.  So clipping the voltage regulator and cutting the vcc leg on the RTC should easily get you out to about a year before you fall off the “upper plateau” of the lithium burn-down curve, and more likely to ~two years if your sensors don’t use much power (…and you have a well behaved SD card).  With the RTC power leg still attached you’ll see sleep currents in the 0.18-0.22 mA range, so at least 6 months of operation before you come close to triggering the low voltage shutdown (which is embedded in the logger code). I’m being conservative here because it all depends on your sensors and other additions you make to the base configuration. For every type of sensor the market, there will be some brands that draw much less power than others, and it’s always worth a look at the data sheet to make sure you are buying one that will run longer on a given set of batteries. Good sensors automatically go into a low current sleep mode whenever the bus has been inactive for a period of time.

While the LED sensor idea is fun to work with, it’s a very slow method that keeps the logger running for many seconds per reading when light levels are low -> so reading all three color channels will probably cut your operating life in half again. Figuring out how to only take those light readings during the day is a good coding exercise for students that saves quite a bit of power. The RTC’s temperature record is pretty crude, so we’ve also added support for the DS18b20 temperature sensor in the base code. If you have a genuine DS18b20 (yes, crummy fake knock-off sensors are a thing) , these venerable old temperature sensors draw virtually no power between readings.


TransparentSinglePixl
Bill of Materials: $20.50
Plano 3440-10 Waterproof Stowaway Box
Sometimes cheaper at Amazon. $4.96 at Walmart and there are a selection of larger size boxes in the series. 6″ Husky storage bins are an alternate option.
$5.00
Pro Mini Style clone 3.3v 8mHz
Get the ones with A6 & A7 broken out at the back edge of the board. Just make sure its the 3.3v version because you can’t direct-connect the SD cards to a 5v board.
$2.20
‘Pre-assembled’ Nano V1.O Screw Terminal Expansion Board
by Deek Robot, Keyes, & Gravitech (CHECK: some of them have the GND terminals interconnected)  You will also need to have a few small flat head screw drivers to tighten those terminals down.  Since this shield is was originally designed for an Arduino Nano many of the labels on ST board will not agree with the pins on the ‘analog side’ of the ProMini.
$1.85
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!  
$1.25
CR2032 lithium battery  $0.40
SPI Mini SD card Module for Arduino AVR
Buy the ones with four ‘separate’ pull-up resistors so that you can remove three of them.
$0.50
Sandisk or Nokia Micro SD card 256mb-512mb 
 Test used cards from eBay before putting them in service. Older Nokia 256 & 512mb cards have lower write currents in the 50-75mA range. This is less than half the current draw on most cards 1gb or larger.
$2.00
Small White 170 Tie-Points Prototype Breadboard
These mini breadboards for inside the logger are also available in other colors.
$0.60
Dupont 2.54mm F2F 40wire ribbon cable Without Housing
Cheaper if you get the ones with the black plastic shrouds on the ends. Dupont connector hook-up wires might be expected to add an ohm or two of resistance and carry at most 100mA reliably with their thin 28-30 gauge wires. Poorer quality crimped ends can add significant contact resistance. Each 40-wire cable will let you make at least 2 loggers, and you”l need a couple of  6-pin connector shrouds.
$1.55
2×1.5V AA Battery Batteries Holder w Wire Leads
If you are running an unregulated system on 2 lithium batteries, then you can use a 2x AA battery holder. If you need to keep the regulator in place to stabilize the rail voltage for particularly picky sensors, use alkaline batteries and a 4xAA battery holder.
$0.50
5050 LED module (with built-in limit resistors) 
(Alternatively, you can also use cheaper 5mm diffused LEDs with a  4K7 limit resistor on the GND line – that you solder into place on the LED yourself)  
$0.75
3.3V 5V FT232 Module
  *set the UART module to 3.3v before using it!* and you will also need a few USB 2.0 A Male to Mini B cables. You may need to install drivers from the FTDI website depending on your OS. Get at least 2-3 of these as they do wear out and you can kill them with a short circuit. These boards can only supply about 50mA which can be tricky with sensors that draw more than that.
$2.75
3M Dside Mounting Tape, 22awg silicone wireheader pins, etc… $1.00
You might need some extra tools 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 you can get the Yihua 936 soldering iron alone for about $25. While the Yihua is a so-so iron, replacement handles and soldering tips cost very little, and that’s very important in a classroom situation where you can count on replacing at least 1-2 tips per student, per course, because they let them run dry till they oxidize and won’t hold solder any more.  Smaller hand-held heat-shrink guns are also available for ~$15.
$50.00
SYB-46 270 breadboards (used ONLY for soldering header pins )
Soldering the header pins on the pro-mini is MUCH easier if you use a scrap breadboard to hold everything in place while you work. I use white plastic breadboards that they only have one power rail on the side since they do not look like regular breadboards &  I write ‘for soldering only’ on them with a black sharpie to make sure I don’t get the heat-damaged boards mixed up with the good ones.
$1.30
SN-01BM Crimp Plier Tool 2.0mm 2.54mm 28-20 AWG Crimper Dupont JST
I use my crimping pliers almost as often as my soldering iron –  usually to add male pins to component lead wires for connection on a breadboard. But making good crimp ends takes some practice.  Once you get the hang of it,  Jumper wires that you make yourself are always better quality than the cheap premade ones.
$16.00
Micro SD TF Flash Memory Card Reader
Get several, as these things are lost easily. My preferred model at the moment is the SanDisk MobileMate SD+ SDDR-103 or 104 which can usually be found on the ‘bay for ~$6.
$1.00
Side Shear Flush Wire Cutters & Precision Wire Stripper AWG 30-20
HAKKO is the brand name I use most often for these, but there are much cheaper versions.
$5-10
Syba SY-ACC65018 Precision Screwdriver Set
A good precision screwdriver set makes it so much easier to work with the screw terminal boards.
$12.00
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 mother-ship to keep the open source hardware movement going…so more cool stuff like this can happen!
$1.00

When a finished module with 20 sub-components arrives at your doorstep for less than you’d pay for any one of those components individually, then the tradeoff is that you are the quality control.

.. and the required lithium AA batteries are also somewhat expensive, so a realistic estimate is about $25 for each logger before you add sensors & tools.

My advice is to order at least 5-6 of each of the core components (Promini, RTC, SD module, screw terminal board, etc) with the expectation that about 10-20% of any cheap eBay modules will be DOA due to some quality control problem. I build loggers in batches of six, and one unit typically ends up with a bad part somewhere. Having replacement parts on hand is your #1 way to diagnose and fix these issues. I’ve noted over the years that bad parts tend to come “in bunches”, so if you scale up to ordering in quantities of 10’s & 20’s then spread those orders to a few different suppliers so you don’t end up with all your parts coming from the same flakey production run. 

The other thin I can’t stress enough is CLEAN ALL THE PARTS as soon as they arrive. Leftover flux is very hygroscopic, and pretty much guarantees that solder joints will start to corrode the moment your logger gets exposed to any atmospheric moisture. So I usually give everything about 10 minutes in a cheap sonic bath with 90% isopropyl alcohol, rinse with water, and then dry the parts out in front of a strong fan for an hour. Store parts in a sealed container with a dessicant pack till you need them.  I clean parts that can’t take the sonic vibration (like RTC modules, humidity sensors, accelerometers, etc) by hand with cotton swabs. Then I coat everything I can with a layer of MG Chemicals 422B Silicone Conformal Coating and let that dry for a day before assembling the loggers.  One hint that you have moisture issues is that the sensors seem to run fine in the house but start to act weird when you deploy the unit outside.

Used nut containers make excellent “dry storage” once the parts have been cleaned – but any air-tight container will do.

Another insight I can offer is that the quality of a sensor component is often related to the current it draws – if your ‘cheap module’ is pulling much more power than the data sheet for that sensor indicates, then it’s probably a junk part. If the sleep current is on spec, then your part is probably going to work OK. It is much easier to check low current devices with at Ucurrent or a Current Ranger.  Keep in mind that sensors which automatically go into low current sleep modes usually take some time to do so after the bus has gone quiet – so you might need to watch the sleep current for 30 seconds or more before they fall down into their quiescent state. With SD cards, if the throughput drops below the typical write speed for that model with H2testw, then you should just find another card to use with your logger.

I strongly recommend that you build at least two loggers at a time, because that lets you isolate whether problems you run into during testing are code related (which will always affect both machines the same way) or hardware related. (which will usually only affect one of your two units) At any given time I usually have 2-3 units running overnight tests so that I can compare the effect of two different code/hardware changes the next morning.  As a general rule you want to run a new build for at least a week before deploying it to make sure you’ve made it past any ‘infant mortality’, and moved onto the good part of the bathtub curve.


Addendum: Diagnosing Connection Problems

If you loaded the blink sketch to test the Prominin during your initial assembly, you know that part of the logger is working, so issues during the testing stage are due to incomplete connections from the outside to the Promini I/O ports. Before you start your troubleshooting it’s worth remembering that after you’ve built a few of these units you can simply rebuild the whole logger in about a hour. So it really doesn’t make sense to spend much more time than that – especially when a few five-minute part swaps are likely to fix the problem. The  components are only worth a couple of bucks each, and your time is more valuable than that!

This is the connection pattern inside the white breadboard. Take care that you don’t connect the breadboard jumper wires together by accidentally plugging them in to the same row or – the I2C bus will stop working. NEVER CONNECT the GND & 3.3v wires or the short circuit will likely kill your logger, and possibly even the computer they are connected to . . .

If you see only “Scanning I2C….. ” but nothing else appears when running the bus scanner, then it means that the ProMini can not establish communication with the RTC module. The most common cause of this problem is that the white & yellow wires were switched around at one end or the other. It’s also easy to not quite remove enough insulation from the wires to provide a good electrical connection under the screw terminals, so undo those connection and check that the wires were stripped, cleaned & wrapped together ‘clean’ before being put under the  terminals. Those screws need to be clamped down relatively tight on the thin Dupont wires and if you are not careful, you might have accidentally cut away some of the thin copper wires inside the Dupont cable when you stripped the insulation.

Scanner lockup can also happen if one of the I2C devices on the bus is simply not working: usually about 1 in 6 logger builds ends up with some bad component that you have to identify by process of elimination. (These are 99¢ parts from eBay…right?) It only takes a moment to swap in a new RTC board via the black Dupont connector and re-run the scan. If the replacement RTC also does not show up with the I2C scanner then it’s likely that one of the four bus lines does not have a complete connection between the ProMini & the RTC module.

On this unit I measured 1 ohm of resistance on the I2C clock line between the ProMini A5 pin (on top of the board) and the SCL header pin on the RTC module. So this electrical connection path is good. It’s not unusual for each ‘dry’ connection to add 0.5-1 ohm of resistance to a signal path.

To diagnose this: set a multi-meter to measure resistance and put one probe lead on the topmost point of the promini header pins, and the other probe on the corresponding header pin of the RTC module. If there is a continuous electrical connection between the two points then the meter should read one ohm or less. Higher resistances mean that you don’t have a good electrical path between those points even if they look connected:

1) the ground (black) wire should provide a continuous path from the ground pin on the digital side of the Promini board to the GND pin on the RTC module
2) the positive power (red) wire should provide a continuous path from the Promini positive rail pin (the one with the bundle of 4 red wires) to the VCC pin on the RTC
3) A4 (I2C data) near the 328P chip on the Promini must connect all the way through the screw terminal board and through the white Dupont wires to the SDA post on the RTC
4) A5 (I2C clock) beside A4 on the Promini must connect through through the yellow Dupont wire to the SCL header pin on the RTC .

For ‘single-wire’ connections under the screw terminal with those thin dupont wires, strip enough insulation that you can ‘fold-back’ some extra wire to give the terminal more wire to bite onto.

You occasionally get a bad Dupont wire where the silver metal end is not in contact with the  copper wire inside because the crimp ‘wings’ folded over plastic insulation. With a pair of tweezers, you can ‘gently’ lift the little plastic tab on the black shroud holding the female Dupont ends in place, and then replace any single bad wire. Be careful not to break the little black tab or you will have to replace the entire shroud.

Also look at the little jumpers used to bridge the A4>A2 and A5>A3. If you have a ‘cold’ solder join, or an accidental bridge connection to something else, it could stop the bus from working. Remelt each connection point one at a time, holding the iron long enough to make sure the solder melts into a nice ‘liquid flow’ shape for each solder point.

A note about I2C sensors: The I²C bus is slow, so topology (star, daisy-chain, etc.) doesn’t matter much, but capacitance does. Length and number of devices increase capacitance. If you find that the devices work when you switch to a slower speed (e.g. 50 kHz), then this is probably your issue, and you need to minimise bus length and/or maybe decrease the combined resistance of the pull-ups to 2 kΩ or less. The DS3231 RTC module has 4k7 ohm pull-up resistors on the SDA & SCL lines & the Pro Mini adds internal 50k pull ups when the wire library is enabled. Typical I2C sensor modules usually add another 10k so your ‘net pullup resistance’ on the I2C bus wires is usually:  50k // 4k7 // 10k = ~3k. With a 3.3v rail that means the devices draw 3.3v / 3k = 1 mA during communication which is fairly normal ( 3mA is max) for total wire lengths below 1m. It’s  common for pre-packaged sensors to arrive with housings at the end of about 1m of wire. If each sensor also adds another set of 10k pullups, the resistance generally compensates for the extra wire length, so the combination still works OK. But that depends on the cable too. A very bad cable might not even get to 0.5 meters and a very good cable (little capacitance to ground, no crosstalk between the wires) can go up to 6 meters.

The connection diagnosis procedures described above also apply to the connections for the SD adapter board. Sometimes you end up with an adapter that has a defective spring contact inside the SD module, but the only way to figure that out is to swap it with another one.

Here a jumper wire from the Promini pins is by-passing a bad connection.  This is also how you would break out A6 & A7 connections if you need them.

Sometimes those screw terminal boards have a a poor connection inside the black female headers below the Promini. It’s also possible to accidentally over-tighten a terminal and ‘crack’ the solder connection below the board – or there may simply be a cold solder joint on one of the terminal posts. If you have only one bad connection, you can jumper from the Promini header pins on top, down to the other wires under the corresponding screw terminal. If you accidentally strip the threads on a screw terminal, you can use this same approach but move that set of wires over to one of the three ‘unused’ screw terminals at the far end of the board. (beside the SD card adapter) If you’ve gotten through all of the above steps and still have not fixed the problem, then it might be time to simply rebuild the logger with a different screw terminal adapter board.

 

Also note: The environment is written in Java, and the IDE installer comes with its own bundled Java runtime, so there should be no need for an extra Java installation. However we have seen machines in the past which would not compile working code due until Java was updated on those machines; but this problem is rare. More likely some permission limitation meant that you’ve ended up installing the IDE in a directory other than the default, and now it can’t find its libraries.


Addendum 2020-02-02:  Refining the build

The ‘solderless’ classroom build includes a breadboard for flexibility, but if you are building a logger for a single sensor application you can leave out the breadboard, and add some under board jumpers to avoid those finicky multi-wire bundles:

Using the tined leg of a resistor to add some under-board connections to give you more 3.3v & GND attachment points. Clean any residual solder flux thoroughly!

To repurpose those  screw terminals, leave the corresponding pins un-soldered so they can be  removed.

 

Here, we are repurposing the TX, RX, & RST terminals as GND connections. DO not repurpose TX & RX if you need serial comms with NEMA sensors like GPS.

Use a Black marker to label the repurposed GND points. NOTE: The GND-labeled terminals can not be repurposed because they are bridged via the back plane of the screw terminal board itself.

Label the connections with red. NOTE: ONLY join the Vraw & Vcc like this if you have removed the regulator. If you are keeping the reg. in your build then Vraw must remain separate, & you can only repurpose the reset pin for one extra 3.3v rail.

In a no-regulator build, you now have three (+)ive rail connections. This is handy if you accidentally strip one of the terminals by over-tightening.

Adding header pins to the RTC module’s cascade port provides a more convenient attachment point for I2C sensors, and removing the breadboard leaves room for a desiccant pack:

Example of a typical “dedicated sensor” build  without the breadboard, and with the under-board jumpers to provide extra (+)ive & GND terminals. This photo also shows a mosfet controlling the SD ground line, and a thermistor is tucked under the Promini board – but it’s probably better to tackle those additions when you have a few successful logger builds under your belt.

‘Single shroud’ 40-wire 20cm FF Dupont cables are ~$1 each – 1/3 the cost of the ones without housings.

We deliberately put students through the creation of custom sized Dupont connectors because it’s a useful thing to know about, however single shroud wires also work on the RTC and SD module because you are using the double sided tape to hold everything in place. I also tend to use RAW diffused LEDs as indicators, and simply light the individual channels with INPUT_PULLUP mode which uses the internal pullup resistor to limit current. However a single mistaken OUTPUT-HIGH can kill your Arduino if your not careful with that approach. I also leave one of my battery leads long enough that I can tap in to that line & measure sleep currents later on with a Current Ranger.

MCP1702-3302E/TO dead-bug hanging off a Deans style T-plug connector.

The unit pictured above has had its regulator removed so it can run more efficiently on 2x AA lithium batteries. But if you later find out that your sensors need strict voltage regulation, you can always replace the battery holder with a 4xAA, and add a regulator in-line on a battery connector. In the photo (right) two ceramic 105’s stabilize the MCP1700, while a 10/3.3M ohm divider provides a third output line so the ADC can monitor the raw battery status. This is the simplest way to retro-fit a regulator onto a unit that was built without one.

You occasionally get a plano box where the plastic ridges of the housing don’t mate well with the red rubber o-ring. In those cases it’s relatively easy to pull the o-ring out of its groove in the lid, and add some gasket compound to the recessed trough before carefully putting the o-ring back in. The additional height then compensates for casting error in the housings, though don’t add to much or you won’t be able to close the lid. One positive aspect of the relatively loose fit on that lid is that it lets you run prototype tests quickly if you jumper to your sensor module with thin 28-30 gauge wires:

This is a BMP280 pressure sensing module. Wires must extend beyond the edge

~1″ square of foam mounting tape with wires spaced evenly

 

Leave the red backing facing up as you fold the tape & wires over the corner edge.

The front corners of the box exert less pressure than the back

The sharp inner edge of the lid would cut the wires insulation if the tape was not there to protect

The tape has to be replaced every time.

This gives you a chance to do overnight run tests before you commit to modifying the housing with holes, putty, or pass-throughs with cable glands. And for indoor sensor deployments this might be all you actually need to do, though I would still coat the exposed solder joints on that dangling breakout module with either conformal coating or clear nail polish to prevent oxidation.


Addendum 2020-07-29:  Better moisture protection

I was noodling around in the garden recently and installed a few loggers without desiccants because it was only a short experiment. It rained immediately afterward and I noticed a small amount of moisture condensed inside the plano-box housing. While this didn’t prevent the logger from functioning, it completely disrupted the LED light sensors because it provided an alternate discharge path for the reverse bias charge:

Green channel data from a 5mm diffused RGB LED used as light level sensor. This logger was under some leaf cover, so there was considerable variability from the dappled light crossing over the sensor. An arbitrary cutoff of 200,000 was set in the code at low light levels.

After examining the O-ring I decided to add a little silicone to the channel holding the red o-ring to improve the seal:

 

Gently pry the O-ring loose and apply sealant in the groove before replacing.

Bead only needs to be 3-4mm in diameter.

Close the housing & let the sealant set for a few days. The improved seal is especially visible at the corners

If you already have your logger assembled, try to find a silicone sealant that does not off-gas acetic acid (smells like vinegar) which could harm your circuits. If you are simply preparing empty boxes before assembly, then any regular bathroom sealant will do provided you give it about a week to finish curing.


Addendum 2020-05-11:  Using a more advanced processor

After you’ve built a few ProMini based loggers, you might want to try a processor upgrade. The 1284p CPU has twice the speed & 4x the memory, but still delivers comparable sleep current / longevity.

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

Note: An updated tutorial was released in 2020:  CLICK THIS LINK to view that version


“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.

PARTS & MATERIALS

TransparentSinglePixl
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.
$5.00
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.
$1.00
M12 IP68 Nylon Cable Gland
Adjustable for 3mm-6mm diameter. You need two for the build. Make sure they include O-rings.
$1.00
3/4″ Schedule 40 PVC Cap
Diameter will depend on the size of your sensor breakout board. Get ones with FLAT ends.
$1.00
White 170 Tie-Points Prototype Breadboard
Available in other colors.
$0.60
Pro Mini Style clone 3.3v 8mHz
Get the ones with A6 & A7 broken out at the back edge of the board.
$2.20
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.  
$1.05
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!  
$1.25
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.
$0.40
SPI Mini SD card Module for Arduino AVR
Buy the ones with four ‘separate’ pull-up resistors so that you can remove them.
$0.50
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.
$2.00
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.
$0.40
Common Cathode Bright RGB LED 5mm 
( & 4k7 limit resistor) 
$0.05
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!
$1.00
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.
$50.00
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.
$2.75
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.
$1.00

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:
 
COMPONENT PREPARATION

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.

rtc1

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, but you could just add straight pin headers: If you don’t want to make your own crimp ends,  4-pin JST XH series connectors have a pitch of 2.5 mm which is effectively identical to the 0.1″ pitch of those Dupont pins. 

The Plano Stowaway housing: (~10 min)

In these photos, I’ve melted threads into the housing for a pass-through with a cable gland, but that is entirely optional. This trick works with many different housing materials, but it takes a bit of practice to figure out what the right temperature is for any given one. The plastic in the plano boxes melts quite easily, so be gentle with the heat gun. Glands much larger than PG7 (12mm) will not fit in the available space in that corner.

These two cable glands have matching thread specifications

Heat only the threads from the metal cable gland with your heat-shrink gun & a pair of pliers

Gently twist the threads into the plastic of the Plano-box & as soon as it’s through blow on the metal until it completely cools

After cooling,  unscrew the metal threads

Cut away excess plastic burs with a sharp knife and thread the plastic cable gland into place.

You may need two washers due to the curvature of the box. The cable gland shown above is an M12 x 1.5 IP68 for 3-6mm dia. cables (also desc. as PG7 12mm)

Strategically placed holes in the clips provide zip-tie locations to secure your logger.

Holes for Zip-tie securing

ASSEMBLING THE LOGGER PLATFORM:  (~30 min)

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:
file.print(YourSensorVariableName);
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. It’s worth noting that even with this precaution, we still see 1-2 units out of 10 loose their time when they have to be transported in airline luggage.

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 on the lower plateau 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. If you are using ratio-metric analog circuits then the voltage fluctuation over time will not affect your readings, but if you need absolute measurements then use the internal voltage reference as Aref.  Digital sensor levels are based on a percentage of Vcc, so they are relatively insensitive to voltage variations.

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 specs on the long term stability of the bandgap ref. and even OpenEnergy no longer uses the internal 1.1vref because v.regulators offer more long-term stability.  ALSO NOTE that genuine Sparkfun ProMini’s have an SJ1 jumper that lets you disconnect the reg simply by de-soldering a pad on top of the board , but clones rarely have that feature.) 

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).  According to Murata the 8Mhz system clock will remain stable: “Unlike RC or LC circuits, ceramic resonators use mechanical resonance. This means it is not basically affected by external circuits or by the fluctuation of the supply voltage.” 

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 the budget for a classroom set of soldering stations. So we’ve worked out a 1 hour “simplified build” of this logger that uses crimped Dupont jumpers to reduce assembly time:

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 a digital pin (usually D3) , rather than directly to GND as shown in the tutorial above. Also note that the technique relies on the very tiny internal capacitance inside the LED (typically 10 to 15 pF) so you need to connect the LED  right to the screw terminals (ie not the breadboard).  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. In this case the clouds & snow acted as a near perfect light diffuser, but in normal weather I’d have placed a thin white teflon sheet over the LED sensor to smooth out the readings. Adding an IR led to the logger lets you create a transmission-based NDVI variant that discriminates the amount of chlorophyll in plant leaves.

These readings are arbitrary time-based units that are completely dependent on your particular hardware combination and system voltage. If you go down the DIY sensor rabbit-hole, you might want to calibrate 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 & direction, and you can calculate solar position.

Addendum 2020-08-18:
We’ve found that the seal on some of the Plano boxes needs a little help, or current leakage via surface moisture shorts out the LED light sensor readings. In the worst cases leakage lets in enough water vapour to chew through your desiccant pack in less than a week. A little bead of silicone under the o-ring corrects that problem:

Gently pry the O-ring loose so we can add some sealant.

Bead only needs to be 3-4mm in diameter.

Close the housing & let the sealant set for a few days. The improved seal is especially visible at the corners

If you already have your logger assembled, try to find a better quality silicone sealant that does not off-gas acetic acid (smells like vinegar) which could harm the circuits. If you are preparing empty boxes before the logger assembly, then any regular bathroom sealant will do provided you give it about a week to finish curing. I usually choose silicone rated for outdoor applications as they have better resistance to breakdown from UV exposure.

Using a DS18B20 Temperature Sensor “without” a dedicated Arduino library

New housings in production

New housings in production. The sensor wells on top will be filled with epoxy to seal the hull.  Note: some of these housings have unnecessary “chunks” of PVC on the outside, as I made them for latch clamps which I am no longer using in the design.

I haven’t had time to post to the blog lately, as I am now in full-on production mode: trying to get five full units running for deployment next week.  Between all the cutting and sanding of the new underwater housings, I have been investigating sensors, and thinking about how to make the entire unit modular enough to allow quick field repairs.  The I2C bus architecture becomes very attractive here, because so many sensors are available for it, and I found a nifty I2C hub from Seed studios, which gives me a standard plug for connectivity. But most of the sensors I found want a steady 3.3 volt supply, and that is not available on the Tiny Duino  (the lack of a 3.3v rail is a weakness of the platform but I was the one who wanted to run with no voltage regulator in the first place..) So I started designing a power supply module around a NCP1402-3.3V Step-Up Breakout from Sparkfun.  I knew this was going to waste 25% of my power for this deployment, but I figured I could use lithium AA’s to make up for the loss, and look around for a more efficient voltage regulator later.

No luck with the voltage regulator even though it seemed to be working fine...

It seemed to be working fine…

Well, a frustrating day or two later and I still had no joy from the 3.3v regulator. I don’t know if it was an inrush current brownout, or transient spikes, or output ripple…but for some reason the tiny stack simply will not run from this regulator – all I get is one little pip out of the on-board led and that’s it.  Grrr! There goes about half of sensors I planned to use with the nice I2C breakout boards I had just purchased, unless I can somehow power the sensors “separately” from the main mcu stack. (And there is no guarantee I won’t have the same kind of gremlins pestering me there…)

Time to make lemonade: I had a few DS18B20 one wire temperature sensors, and they are not too choosy about input voltage, so I figured I would give them a try, while just running the Tiny’s from unregulated AA’s (…again).  DS18B20’s are common as dirt, and almost as cheap, and there are libraries for them everywhere. So it should not take long to get them going…right?

Well what I thought was going to take me 30 minutes has actually taken me a day of digging to sort out, so I am posting the result here, to hopefully save someone else the trouble.  The standard approach is to install a Dallas control library, and a one wire library to run this sensor. Most sources suggested the library written by Miles Burton, and the one wire library over at PJRC.  And after a few grumbles, like finding out that zip file created directories with the wrong names  ( “dallas-temperature-control” from the zip extraction needs to be renamed to “DallasTemperature” for it to work) I did get the test code running…sort of. Now don’t get me wrong, Miles Burton has created a veritable swiss army knife of a library, but a tiny script for this one sensor weighed in at 8,772 bytes. That’s almost a third of the available program memory, and I already knew my data logger script was around 22 k (with accelerometer) , so that was not going to leave much space for the other sensors I want to add.

Logger units with I2C hub for sensor and RTC connections.

Logger units with I2C hub for sensor and RTC connections.

And while I was sorting all that out, I discovered another problem with the DS18b20: Every once and a while it was throwing out a spurious reading of 85 degrees, but it was frustratingly intermittent. More run tests with different settings showed that the error never happened when I had the sensor set to run at 9-bit, but it popped up more frequently as I raised the bit depth. Back to digging through the forums, which revealed that this is a pretty common issue with the DS18b20. The “default” setting of the registers produces the 85, which is what you get if you read the sensor too soon after a reset. If you sift through the datasheet, you find that when you ask the sensor for the full 12 bits of resolution, you need to wait at least 750 ms for the sensor to embed the temperature data in it’s eeprom before you can read it out. So although the sensor only draws 1.5mA during the conversion, and it goes into standby mode right afterwards,  it was going to hold the whole system in limbo for that conversion time, doing some serious damage my the overall power budget.

More googling, and I came across a great little project blog called BitKnitting, where someone managed to use the sensor without a dedicated sensor library. So it was possible! However, they were only using the integer part of the 12 bit register, and I wanted all of the information, as the temperature variations in cave system is often only fractions of a degree C. I found a floating point capture demonstrated over at the Bildr forum. Combining that with a 1 second Watch Dog Timer sleep (to save power while the mcu waits for the sensor’s temperature conversion) produced this little script which weighs in at 5988 bytes. Not much savings on memory, because of the addition of the wdt library, but hopefully much lighter on the projects power budget. I will be weaving this into the main logger code later. I also have a feeling that I can go rooting through those libraries and delete a few functions I am not using to make them lighter, when I have time.

Addendum 2015-02-25

I think this is approaching the largest string I would want to deploy on a dive.

Addendum 2015: I think this is approaching the largest senor string I would want to deploy on a dive.

I recently started making long chains of these sensors, in an attempt to build a poor mans thermistor string. Here is a link to the post about those experiments.

Addendum 

The DS18B20 is only supposed to draw around 1μA when in standby, and not being interrogated. So it hardly seems worth the bother of turning them on and off to me, but there are a few people doing so by powering the units directly from the digital pins of the Arduino.  I might investigate that later if I have not used the pins for something more productive.

Addendum

I have also noticed that this sensor seems to warm itself if you have it doing a continuous reads at 12 bit as the script below does (which has the unit at its maximum 1.5 mA current the entire time). So just be warned that if you are using the ‘waterproof’ models, which are encased in a metal sleeve filled with epoxy, you can’t drive the sensor full tilt without internal heat building up & affecting the readings. 

// adapted from https://github.com/BitKnitting/prototypes/blob/master/SensorNode433/SensorNode433.ino
//  and  http://forum.bildr.org/viewtopic.php?f=17&t=779
//  sleep from http://www.gammon.com.au/forum/?id=11497

#include <avr/sleep.h>
#include <avr/wdt.h>

#include <OneWire.h>
const byte DS18B20_PIN=4;  //sensor data pin
OneWire ds(DS18B20_PIN);
byte addr[8];
float DS18B20float;

void setup() {

Serial.begin(9600);

//Set up Temp sensor – there is only one 1 wire sensor connected
if ( !ds.search(addr)) {
Serial.println(F(“—> ERROR: Did not find the DS18B20  Temperature Sensor!”));
return;
}
else {
Serial.print(F(“DS18B20 ROM address =”));
for(byte i = 0; i < 8; i++) {
Serial.write(‘ ‘);
Serial.print(addr[i], HEX);
}
Serial.println();
}
delay (200);
}

void loop() {

DS18B20float = getTemp();
Serial.print(F(“FLOAT temp in celcius: “));
Serial.println(DS18B20float);
//Note: sensor defaults to a reading of 85 if you read it too soon after a reset!
delay (200);  //just a delay to boot out the coms
}

// watchdog interrupt
ISR (WDT_vect)
{
wdt_disable(); // disable watchdog
} // end of WDT_vect

// this returns the temperature from one DS18S20 in DEG Celsius using 12 bit conversion
float getTemp(){
byte data[2];
ds.reset();
ds.select(addr);
ds.write(0x44); // start conversion, read temperature and store it in the scratchpad

//this next bit creates a 1 second WDT delay during the DS18b20 temp conversion
//The time needed between the CONVERT_T command and the READ_SCRATCHPAD command has to be at least
//750 millisecs (but can be shorter if using a D18B20 type with resolutions < 12 bits)
MCUSR = 0; // clear various “reset” flags
WDTCSR = bit (WDCE) | bit (WDE); // allow changes, disable reset
// set interrupt mode and an interval
WDTCSR = bit (WDIE) | bit (WDP2) | bit (WDP1); //a 1 sec timer
wdt_reset(); // pat the dog
set_sleep_mode (SLEEP_MODE_PWR_DOWN);
sleep_enable();
sleep_cpu ();
sleep_disable(); // cancel sleep after wakeup as a precaution

byte present = ds.reset();  //now we can read the temp sensor data
ds.select(addr);
ds.write(0xBE); // Read Scratchpad
for (int i = 0; i < 2; i++) { // Only read the bytes you need? there is more there
data[i] = ds.read();
}
byte MSB = data[1];
byte LSB = data[0];
float tempRead = ((MSB << 8) | LSB); //using two’s compliment
float TemperatureSum = tempRead / 16; //this converts to C
return TemperatureSum;

}

 

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
#ifdef ECHO_TO_SERIAL
//#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 */
#endif

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))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

#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!

RTC_DS3231 RTC;
byte Alarmhour = 1;
byte Alarmminute = 1;
byte dummyRegister;
byte INTERRUPT_PIN = 2;
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 () {

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

Serial.begin(9600);
Wire.begin();
RTC.begin();
clearClockTrigger(); //stops RTC from holding the interrupt low if system reset
// time for next alarm
RTC.turnOffAlarm(1);

#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());
#endif

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

#ifdef ECHO_TO_SERIAL
Serial.print(F(“Initializing SD card…”));
#endif

// 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”));
error(“0”);}

#ifdef ECHO_TO_SERIAL
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)”));
#endif

// 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”));
error(“1”);
}

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)”));
file.close();

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) {
clearClockTrigger();
}

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
#ifdef ECHO_TO_SERIAL
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(wholeBMAtemp);Serial.print(F(“.”));Serial.print(fracBMAtemp);Serial.print(F(“,”));
Serial.print(wRTCtemp);Serial.print(F(“.”));Serial.print(fRTCtemp);
Serial.print(F(“, Ram:”));Serial.print(freeRam());
delay(40); //short delay to clear com lines
#endif

//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(wholeBMAtemp);str.print(F(“.”));str.print(fracBMAtemp);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){

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

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;
file.write(EEPROMBuffer,sizeof(EEPROMBuffer));

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

countLogs++;
// 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)
if(i==8){syncTheFile;}
if(i==16){syncTheFile;}
if(i==24){syncTheFile;}
if(i==32){syncTheFile;}
if(i==40){syncTheFile;}
if(i==48){syncTheFile;}
if(i==56){syncTheFile;}
if(i==64){syncTheFile;}
if(i==72){syncTheFile;}
if(i==80){syncTheFile;}
if(i==88){syncTheFile;}
}
file.close();
}
// 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);
RTC.turnOnAlarm(1);

#ifdef ECHO_TO_SERIAL
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
#endif

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()) {
file.close();
}
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: “));
Serial.println(FileName);

// 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.sync();
//file.close();
//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)”));
file.close();

#ifdef ECHO_TO_SERIAL
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;
address=eeaddress;
Wire.beginTransmission(deviceaddress);
Wire.write((int)((address) >> 8)); // MSB
Wire.write((int)((address) & 0xFF)); // LSB
do{
Wire.write((byte) data[i]);i++;
} while(data[i]);
Wire.endTransmission();
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.beginTransmission(deviceaddress);
Wire.write((int)(eeaddress >> 8)); // MSB
Wire.write((int)(eeaddress & 0xFF)); // LSB
Wire.endTransmission();
Wire.requestFrom(deviceaddress,(num_chars-1));
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
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
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
sleep_mode();
//HERE AFTER WAKING UP
sleep_disable();
detachInterrupt(0);
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
Wire.endTransmission();
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.beginTransmission(DS3231_I2C_ADDRESS);
Wire.write(0x11);
Wire.endTransmission();
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()
{
Wire.beginTransmission(BMA250);
Wire.write(0x02);
Wire.endTransmission();
Wire.requestFrom(BMA250,7);
for(int j = 0; j < 7;j++)
{
dataArray[j] = Wire.read();
}
if(!bitRead(dataArray[0],0)){return(0);}

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.beginTransmission(BMA250);
Wire.write(0x0F); //set g
Wire.write(GSEL);
Wire.endTransmission();
Wire.beginTransmission(BMA250);
Wire.write(0x10); //set bandwith
Wire.write(BW);
Wire.endTransmission();
return(0);
}

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);
}

Updated SRAM buffering data logger code.

I de-soldered the power led, and removed the Bat charging circuit from the RTC board

I de-soldered the power led, and removed the battery charging circuit from the RTC board.

Before I post the results of the eeprom buffering experiment, I will just add the updated version of the code that buffers 12 sensor read cycles of data in the internal SRAM before writing to a small 128mb Sandisk SD card.  In my power drain tests, using 2xAA batteries as the power supply, this code was completing ~1700 accelerometer/RTC read cycles per 8mv drop on the power supply! If I take a sample every 15 minutes, this projects out to a core data logger unit that uses power on par with the natural self-discharge rate of the batteries!

Updates from the last version:
This script automatically creates new data files at an interval determined by comparing the fileInterval to the countlog (thanks adafruit!) There is also a nice #ifdef …#endif trick to control echoing to serial by simply commenting out the ECHO_TO_SERIAL def at the beginning of the code. And finally an error subroutine, which hangs the system in a while(1); if something goes wrong. I use somewhat cryptic error codes, as every character passed to the error routine eats into my precious SRAM budget.

The current data output looks like this on the SD card:

The sample interval for this series is: 1 minute
MM/DD/YY HH:MM:SS Cycle# = Toffset ,Vcc(mV), X = Y = Z = ,BMATemp, RTCtemp
2/27/2014 13:53 time offset: 0   2972   10  -5 228   28     25
2/27/2014 13:53 time offset: 1   2964      9  -5 227   27.5  25
2/27/2014 13:53 time offset: 2   2964      9  -4 227   27.5  24
2/27/2014 13:53 time offset: 3   2964      8  -3 227   26.5  24
2/27/2014 13:53 time offset: 4   2964      9  -4 227   26     24
2/27/2014 13:53 time offset: 5   2964      9  -4 228   26     24
2/27/2014 13:53 time offset: 6   2964    10  -3 228   26.5  24
2/27/2014 13:53 time offset: 7   2964    10  -4 228   25.5  24
2/27/2014 13:53 time offset: 8   2964    10  -4 228   25.5  24
2/27/2014 13:53 time offset: 9   2964      9  -4 228   25.5  24
2/27/2014 13:53 time offset: 10 2964    10  -4 227   25.5  24
2/27/2014 13:53 time offset: 11 2964     10  -4 228   25.5  24
2/27/2014 14:05 time offset: 0  2964        8 -3 228    25.5  24
…etc
(Note: Excel switched to d/m/y here! I always use Y/M/D!)

Only one time stamp is recorded per 12 cycles, as that’s just too many characters to buffer in the limited SRAM (no unix time yet!). So I will have to re-constitute the full time stamp in post. If you use this code, keep a very close eye on the freemem, as you sensors will generate different data, and every single byte/character you are buffering to SRAM matters. My system gets wobbly whenever the freemem goes down near 550…

Addendum: The code shown below has been posted to the projects GitHub. You can now download it HERE.

// 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 which does not use the RTCLIB!
// BMA250_I2C_Sketch.pde -BMA250 Accelerometer using I2C from http://www.dsscircuits.com/accelerometer-bma250.html
// combined with internal voltage reading trick //forum.arduino.cc/index.php/topic,15629.0.html
// floats to string conversion: http://dereenigne.org/arduino/arduino-float-to-string

// 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

// new name routine from https://github.com/adafruit/Light-and-Temp-logger
// about 12 bytes per data cycle! 681 freeram with 10 cycles!

#include <SD.h> //a memory hog – takes 512 bytes of ram just to run!
#include <Wire.h>
#include <SPI.h> // not used here, but needed to prevent a RTClib compile error
#include <avr/sleep.h>
#include <RTClib.h>

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

#define DS3231_I2C_ADDRESS 104 //for the RTC temp reading function

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

#define SampleInterval 1 // power-down time in minutes before interupt triggers the next sample
#define SamplesPerCycle 12 //# of sample cycles before writing to the sd card
unsigned int countLogs = 0; // how many records written to each file
unsigned int fileInterval = 96; // #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
#ifdef ECHO_TO_SERIAL
//#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 */
#endif

File logfile;
char filename[] = “LOGGER00.CSV”; //the first file name

RTC_DS3231 RTC;
byte Alarmhour = 1;
byte Alarmminute = 1;
byte dummyRegister;
byte INTERRUPT_PIN = 2;
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;

const byte chipSelect = 10; //sd card chip select

uint8_t dataArray[16]; //variables for accellerometer reading
int8_t BMAtemp; //why does the bma temp read out as an interger? Temp is in units of 0.5 degrees C
//8 bits given in two’s complement representation
float BMAtempfloat;//float BMATempHolder;
//char BMATempHolder[ ]= “00.00”;
//components for holding bma temp as two intergers – we have no negative temps in our application
uint8_t wholeBMAtemp[SamplesPerCycle],fracBMAtemp[SamplesPerCycle];

int x,y,z; //these guys range to negative values
int xAcc[SamplesPerCycle],yAcc[SamplesPerCycle],zAcc[SamplesPerCycle];

uint8_t wRTCtemp[SamplesPerCycle],fRTCtemp[SamplesPerCycle]; //components for holding RTC temp as two intergers
int temp3231;
int Vcc[SamplesPerCycle];//the supply voltage via 1.1 internal band gap

byte ledpin = 13; //led indicator pin not used in this code

void setup () {

pinMode(INTERRUPT_PIN, INPUT);
digitalWrite(INTERRUPT_PIN, HIGH);//pull up the interrupt pin
pinMode(13, OUTPUT); // initialize the LED pin as an output.
digitalWrite(13, HIGH); // turn the LED on to warn against SD card removal does this work?

Serial.begin(9600);
Wire.begin();
RTC.begin();
clearClockTrigger(); //stops RTC from holding the interrupt low if system reset
// time for next alarm
RTC.turnOffAlarm(1);

#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());
#endif

DateTime now = RTC.now();
DateTime compiled = DateTime(__DATE__, __TIME__);
if (now.unixtime() < compiled.unixtime()) {
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__));
}

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;}
}

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

delay(1000); //delay to prevent power stutters from writing header to the sd card

//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

// initialize the SD card
Serial.print(“Initializing SD card…”);
// make sure that the default chip select pin is set to
// output, even if you don’t use it:
pinMode(10, OUTPUT);

// see if the card is present and can be initialized:
if (!SD.begin(chipSelect)) {
Serial.println(“Card failed, or not present”);
// don’t do anything more:
return;
}
Serial.println(“card initialized.”);

// create a new file
for (uint8_t i = 0; i < 100; i++) {
filename[6] = i/10 + ‘0’;
filename[7] = i%10 + ‘0’;
if (! SD.exists(filename)) {
// only open a new file if it doesn’t exist
logfile = SD.open(filename, FILE_WRITE);
break; // leave the loop!
}
}

if (! logfile) {
Serial.println(F(“Error creating logger file!”));error(“1”);
}

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

// if (logfile.writeError || !logfile.sync()) {
// Serial.println(F(“Error writing header to logger file!”));error(“2”);
// }

#ifdef ECHO_TO_SERIAL
Serial.print(“Logging to: “);
Serial.println(filename);
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)”));
#endif

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){

// create a new file
for (uint8_t i = 0; i < 100; i++) {
filename[6] = i/10 + ‘0’;
filename[7] = i%10 + ‘0’;
if (! SD.exists(filename)) {
// only open a new file if it doesn’t exist
logfile = SD.open(filename, FILE_WRITE);
break; // leave the loop!
}
}

if (! logfile) {
Serial.println(F(“Error creating logger file!”));error(“1”);
}

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

// if (logfile.writeError || !logfile.sync()) {
// Serial.println(F(“Error writing header to logger file!”));error(“2”);
// }

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

countLogs = 0; // reset our counter to zero

}

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

if (clockInterrupt) {
clearClockTrigger();
}

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

if(Cycle==0){ //timestamp for each cycle only gets set once
sprintf(CycleTimeStamp, “%04d/%02d/%02d %02d:%02d:%02d”, now.year(), now.month(), now.day(), now.hour(), now.minute(), now.second());
}

xAcc[Cycle]=x;yAcc[Cycle]=y;zAcc[Cycle]=z; //BMAtemp_[Cycle] = BMAtempfloat;
wholeBMAtemp[Cycle] = (int)BMAtempfloat; fracBMAtemp[Cycle]= (BMAtempfloat – wholeBMAtemp[Cycle]) * 100; // Float split into 2 intergers
//can use sprintf(BMATempHolder, “%2d.%2d”, wholeBMAtemp[Cycle], fracBMAtemp[Cycle]) if we need to recompose that float
Vcc[Cycle] = (readVcc());
if (Vcc[Cycle] < 2800){Serial.println(F(“Voltage too LOW”));error (“L”);} //the hangs the system when the voltage is too low.

RTCTempfloat= get3231Temp();
wRTCtemp[Cycle] = (int)RTCTempfloat; fRTCtemp[Cycle]= (RTCTempfloat – wRTCtemp[Cycle]) * 100; // Float split into 2 intergers

//main serial line output loop – which can be commented out for deployment
#ifdef ECHO_TO_SERIAL
Serial.print(CycleTimeStamp); Serial.print(F(” Cycle “)); Serial.print(Cycle);Serial.print(F(“,”)); Serial.print(Vcc[Cycle]); Serial.print(F(“,”));
Serial.print(xAcc[Cycle]); Serial.print(F(“,”));Serial.print(yAcc[Cycle]); Serial.print(F(“,”)); ;Serial.print(zAcc[Cycle]); Serial.print(F(“,”));
Serial.print(wholeBMAtemp[Cycle]);Serial.print(F(“.”));Serial.print(fracBMAtemp[Cycle]);Serial.print(F(“,”));
Serial.print(wRTCtemp[Cycle]);Serial.print(F(“.”));Serial.print(fRTCtemp[Cycle]);
Serial.print(F(“, Ram:”));Serial.print(freeRam());
delay(50); //short delay to clear com lines
#endif

// Once each full set of cycles is complete, dump data to the sd card
// but if Vcc below 2.85 volts, dont write to the sd card
if (Cycle==(SamplesPerCycle-1) && Vcc[Cycle] >= 2850){
Serial.print(F(” –write data –“)); delay (50);// this line for debugging only

File logfile = SD.open(filename, FILE_WRITE);

if (logfile) { // if the file is available, write to it:

for (int i = 0; i < SamplesPerCycle; i++) { //loop to dump out one line of data per cycle
logfile.print(CycleTimeStamp);
logfile.print(F(“,time offset:,”));logfile.print(i);logfile.print(F(“,”));logfile.print(Vcc[i]); logfile.print(F(“,”));
logfile.print(xAcc[i]); logfile.print(F(“,”));logfile.print(yAcc[i]); logfile.print(“,”);logfile.print(zAcc[i]); logfile.print(F(“,”));
logfile.print(wholeBMAtemp[i]);logfile.print(F(“.”));logfile.print(fracBMAtemp[i]);logfile.print(F(“,”));
logfile.print(wRTCtemp[i]);logfile.print(F(“.”));logfile.print(fRTCtemp[i]);logfile.println(F(“,”));
// do I need to add a delay line here for sd card communications? could I buffer this better to save power?
countLogs++;
}
logfile.close();
}
else { //if the file isn’t open, pop up an error:
Serial.println(F(“Error opening datalog.txt file”));
}
}

// 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);
RTC.turnOnAlarm(1);

//print lines commented out for deployment
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
}

}

void sleepNow() {
// set the unused digital pins to output low – 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 <= A5; i++)
// {
// pinMode (i, OUTPUT);
// digitalWrite (i, LOW);
// }

cbi(ADCSRA,ADEN); // Switch ADC OFF: worth 334 µA during sleep
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
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
MCUCR = bit (BODS) | bit (BODSE); // turn on brown-out enable select
MCUCR = bit (BODS); // The BODS bit is automatically cleared after three clock cycles
sleep_mode();
//HERE AFTER WAKING UP
sleep_disable();
detachInterrupt(0);
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
Wire.endTransmission();
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.beginTransmission(DS3231_I2C_ADDRESS);
Wire.write(0x11);
Wire.endTransmission();
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 to error flag that we did not get temp data from the ds3231
}

return temp3231;
}
byte read3AxisAcceleration()
{
Wire.beginTransmission(BMA250);
Wire.write(0x02);
Wire.endTransmission();
Wire.requestFrom(BMA250,7);
for(int j = 0; j < 7;j++)
{
dataArray[j] = Wire.read();
}
if(!bitRead(dataArray[0],0)){return(0);}

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.beginTransmission(BMA250);
Wire.write(0x0F); //set g
Wire.write(GSEL);
Wire.endTransmission();
Wire.beginTransmission(BMA250);
Wire.write(0x10); //set bandwith
Wire.write(BW);
Wire.endTransmission();
return(0);
}

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. */
// red LED indicates error
//digitalWrite(redLEDpin, HIGH);
while (1);
}

Leaner meaner code for the Cave Pearls.

The new terminal block protoshield makes the RTC hookup easy!

The new  TinyDuino terminal block protoshield makes the RTC hookup easy!

I have spent some time making that rough code more efficient, so that I can buffer more sensor read cycles to the Arduino’s very limited SRAM before having to burn away battery power writing data to the SD cards. There were several great references out there to guide me, and a few helpful people took time to email me suggestions, some of which have been implemented here:

Eliminate unnecessary cruft:

I stopped dumping all the data into a text string, which got rid of the commas, spaces, etc that I had been buffering for nothing. I guess this is a pretty common newbie mistake.

And instead of buffering the entire date & time stamp, I simply record it once at the beginning of a sample cycle and then use a one byte record/offset number, as I can re-calculate the actual time for each sample from this offset number. This saved a huge amount of free ram! (Thanks Mike!)

Use the smallest variable types possible.

I converted the floating point temperature readings into sets of two integers (the part before the decimal place, and the part after the decimal place) and any integers that were storing values less than 256, are now bytes, or uint8_t, which cut them down by half. The single time stamp per cycle is now stored in a well defined “char” variable. One person suggested that with some fancy bit management I could probably cut those variables in half again, but I have not quite wrapped my head around bit shifting & two’s complement yet. (Thanks Ken!)

Write better code:

The crude code duplication of the earlier versions has been replaced with array variables and for loops, which you control via the SampleInterval and SamplesPerCycle defines at the beginning of the script. These cascade through to the array definitions. I should mention here that I tried this approach before when I had the data packed into strings and it simply did not work. The freeRam routine showed me that every cycle bled away another byte or two till the system crashed. But AFTER I got rid of all the strings in the script, the loops are stable. I suspect there is a bug in the way the AVR compiler handles string data.

The net result of these changes is that each record went from about 60 characters down to 12 bytes of data, so this script can easily buffer 10 cycles or more of data and still leave 680 bytes of free ram. A nice safe margin away from the 512 bytes that I have to leave available for the SD.h buffer.

I expect to sample the sensors between 50-100 times per day, but for power management, I only want to write to the SD card once per day. So now I need to look at a second level of buffering, with eeprom memory…

// 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 which does not use the RTCLIB!
// BMA250_I2C_Sketch.pde -BMA250 Accelerometer using I2C from http://www.dsscircuits.com/accelerometer-bma250.html
// combined with internal voltage reading trick //forum.arduino.cc/index.php/topic,15629.0.html
// floats to string conversion: http://dereenigne.org/arduino/arduino-float-to-string// 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

#include <SD.h> //a memory hog – takes 512 bytes of ram just to run!
#include <Wire.h>
#include <SPI.h> // not used here, but needed to prevent a RTClib compile error
#include <avr/sleep.h>
#include <RTClib.h>

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

#define DS3231_I2C_ADDRESS 104 //for the RTC temp reading function

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

#define SampleInterval 1
// power-down time in minutes before interupt triggers the next sample

#define SamplesPerCycle 10
//# of sample cycles before writing to the sd card

RTC_DS3231 RTC;
byte Alarmhour = 1;
byte Alarmminute = 1;
byte dummyRegister;
byte INTERRUPT_PIN = 2;
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;

const byte chipSelect = 10; //sd card chip select

uint8_t dataArray[16]; //variables for accellerometer reading
int8_t BMAtemp; //Temp is in units of 0.5 degrees C
//8 bits given in two’s complement representation
float BMAtempfloat;//float BMATempHolder;
//char BMATempHolder[ ]= “00.00”;
//components for holding BMA250 temp as two integers – we have no negative temps in our application
uint8_t wholeBMAtemp[SamplesPerCycle],fracBMAtemp[SamplesPerCycle];

int x,y,z; //these guys range to negative values
int xAcc[SamplesPerCycle],yAcc[SamplesPerCycle],zAcc[SamplesPerCycle];

uint8_t wRTCtemp[SamplesPerCycle],fRTCtemp[SamplesPerCycle]; //components for holding RTC temp as two intergers
int temp3231;
int Vcc[SamplesPerCycle];//the supply voltage via 1.1 internal band gap

byte ledpin = 13; //led indicator pin not used in this code

void setup () {

pinMode(INTERRUPT_PIN, INPUT);
digitalWrite(INTERRUPT_PIN, HIGH);//pull up the interrupt pin
pinMode(13, OUTPUT); // initialize the LED pin as an output.
digitalWrite(13, HIGH); // turn the LED on to warn against SD card removal

Serial.begin(9600);
Wire.begin();
RTC.begin();
DateTime now = RTC.now();
DateTime compiled = DateTime(__DATE__, __TIME__);
if (now.unixtime() < compiled.unixtime()) { //only update rtc if needed
Serial.println(F(“RTC is older than compile time! Updating”));
RTC.adjust(DateTime(__DATE__, __TIME__));
}
clearClockTrigger(); //stops RTC from holding the interrupt low if system reset
// time for next alarm
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;}
}

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…”));
if (!SD.begin(chipSelect)) { // see if the card is present and can be initialized:
Serial.println(F(“Card failed, or not present”)); // don’t do anything more:
return;
}
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)”));

File dataFile = SD.open(“datalog.txt”, FILE_WRITE); //PRINT THE DATA FILE HEADER
if (dataFile) { // if the file is available, write to it:
dataFile.print(F(“The sample interval for this series is: “));dataFile.print(SampleInterval);dataFile.println(F(” minutes.”));
dataFile.print(F(“A full timestamp will be generated every”));dataFile.print(SamplesPerCycle);dataFile.println(F(” samples.”));
dataFile.println(F(“YYYY/MM/DD HH:MM:SS, Cycle# = Time offset, Vcc(mV), X = , Y = , Z = , BMATemp (C) , RTC temp (C)”));
dataFile.close();
}
else { //if the file isn’t open, pop up an error:
Serial.println(F(“Error opening datalog.txt file!”));
}
digitalWrite(13, LOW);
}

void loop () {

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

if (clockInterrupt) {
clearClockTrigger();
}

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

if(Cycle==0){   //timestamp for each cycle only gets set on first pass
sprintf(CycleTimeStamp, “%04d/%02d/%02d %02d:%02d:%02d”, now.year(), now.month(), now.day(), now.hour(), now.minute(), now.second());
}

xAcc[Cycle]=x;yAcc[Cycle]=y;zAcc[Cycle]=z; //BMAtemp_[Cycle] = BMAtempfloat;
wholeBMAtemp[Cycle] = (int)BMAtempfloat; fracBMAtemp[Cycle]= (BMAtempfloat – wholeBMAtemp[Cycle]) * 100; // Float split into 2 integers
//you can use sprintf(BMATempHolder, “%2d.%2d”, wholeBMAtemp[Cycle], fracBMAtemp[Cycle]) if we need to recompose that float later
Vcc[Cycle] = (readVcc()); RTCTempfloat= get3231Temp();
wRTCtemp[Cycle] = (int)RTCTempfloat; fRTCtemp[Cycle]= (RTCTempfloat – wRTCtemp[Cycle]) * 100; // Float split into 2 integers

//main serial line output loop – which can be commented out for deployment
Serial.print(CycleTimeStamp); Serial.print(F(” Cycle “)); Serial.print(Cycle);Serial.print(“,”); Serial.print(Vcc[Cycle]); Serial.print(“,”);
Serial.print(xAcc[Cycle]); Serial.print(“,”);Serial.print(yAcc[Cycle]); Serial.print(“,”); ;Serial.print(zAcc[Cycle]); Serial.print(“,”);
Serial.print(wholeBMAtemp[Cycle]);Serial.print(“.”);Serial.print(fracBMAtemp[Cycle]);Serial.print(“,”);
Serial.print(wRTCtemp[Cycle]);Serial.print(“.”);Serial.print(fRTCtemp[Cycle]);
Serial.print(F(“, Ram:”));Serial.print(freeRam());
delay(50); //short delay to clear com lines

// dump data to the sd card on the last cycle but only if Vcc is above 2.85 volts
if (Cycle==(SamplesPerCycle-1) && Vcc[Cycle] >= 2850){
Serial.print(F(” –write data –“)); delay (50);// this line for debugging only

File dataFile = SD.open(“datalog.txt”, FILE_WRITE);

if (dataFile) { // if the file is available, write to it:

for (int i = 0; i < SamplesPerCycle; i++) { //loop to dump out one line of data per cycle
if (i=0){dataFile.print(CycleTimeStamp);}
dataFile.print(F(“,offset:”));dataFile.print(i);dataFile.print(“,”);dataFile.print(Vcc[i]); dataFile.print(“,”);
dataFile.print(xAcc[i]); dataFile.print(“,”);dataFile.print(yAcc[i]); dataFile.print(“,”);dataFile.print(zAcc[i]); dataFile.print(“,”);
dataFile.print(wholeBMAtemp[i]);dataFile.print(“.”);dataFile.print(fracBMAtemp[Cycle]);dataFile.print(“,”);
dataFile.print(wRTCtemp[i]);dataFile.print(“.”);dataFile.println(fRTCtemp[i]);

}
dataFile.close();
}
else { //if the file isn’t open, pop up an error:
Serial.println(F(“Error opening datalog.txt file”));
}
}

// 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);
RTC.turnOnAlarm(1);

//print lines commented out for deployment
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
}

}
void sleepNow() {
// set the unused digital pins to output low – but only worth 1-2 µA during sleep
// so not sure if its worth doing this or not.
// for (byte i = 3; i <= 13; i++)
// {
// pinMode (i, OUTPUT);
// digitalWrite (i, LOW);
// }

cbi(ADCSRA,ADEN); // Switch ADC OFF: worth 334 µA during sleep
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
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
MCUCR = bit (BODS) | bit (BODSE); // turn on brown-out enable select
MCUCR = bit (BODS); // The BODS bit is automatically cleared after three clock cycles
sleep_mode();
//HERE AFTER WAKING UP
sleep_disable();
detachInterrupt(0);
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
Wire.endTransmission();
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.beginTransmission(DS3231_I2C_ADDRESS);
Wire.write(0x11);
Wire.endTransmission();
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 to error flag that we did not get temp data from the ds3231
}

return temp3231;
}
byte read3AxisAcceleration()
{
Wire.beginTransmission(BMA250);
Wire.write(0x02);
Wire.endTransmission();
Wire.requestFrom(BMA250,7);
for(int j = 0; j < 7;j++)
{
dataArray[j] = Wire.read();
}
if(!bitRead(dataArray[0],0)){return(0);}

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.beginTransmission(BMA250);
Wire.write(0x0F); //set g
Wire.write(GSEL);
Wire.endTransmission();
Wire.beginTransmission(BMA250);
Wire.write(0x10); //set bandwith
Wire.write(BW);
Wire.endTransmission();
return(0);
}

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);
}