Category Archives: Reducing power consumption

The goal: an operating lifespan > 1 Year on AA batteries before hardware optimization, and >2 years with both software & hardware based techniques.

Project Update: Power Tests & Battery issues

3LPowerTestEnds

I’ve been waiting for this red LED to light for quite some time..

The power drain test of the spare TinyDuino based pendulum unit (identical to the ones we deployed back in March) has finally completed.  The unit captured sensor readings every minute from April 23rd to June 9th (about 62000 records), at which point I bumped it up to a 10 second sample frequency to accelerate the test, which continued until Aug 6th.  Three AA Energiser Max alkaline batteries (rated at 2850 mAh each) powered the unit, and given the extremely low current, I would guess that we used at least half of this power before we hit the 2800 mV cutoff I set in the code to prevent unsafe SD card writing. While the HMC5883L compass & BMA250 accelerometer had voltage regulators & level shifters on each shield,  the unit drove the DS18B20 temperature sensor, the AT24C32 eeprom, and the DS3231 RTC with the raw Vcc (including the A4/A5 I2C lines), showing that those components are robust enough to handle a fairly large voltage swing:

3LPowerDrainGraph
Y axis: Vcc (millivolts)         X Axis: # of sensor records recorded

In total almost 390000 sensor records were recorded to the SD card. With 42 eeprom page writes per cycle (and inefficient Ascii character encoding via pString) the 4k AT24C32 on the $2 RTC board was filled almost 9300 times in this test.  This is approximately the same amount of data we would process from one year of operation at a 1.5 minute sample interval…and the design supports six batteries: three more than were used in this test.

This opens up two possible directions for the physical build: I can reduce the housing size so that it only accommodates three batteries, or I can keep the current dimensions, with some reasonable faith that even if we get some pretty bad cells, the loggers will still last for a year.  For now, I think I will take what’s behind curtain number two because another interesting event happened this last weekend….

At least we know the solvent weld is water tight!

At least we know the ABS to PVC solvent weld is tight!

I set up one of the new drip sensors with Duracell batteries straight from the package, and I left it under the rain gutter down-spout to give it something to record from time to time.  A few days later I returned to find the housing bowed out from internal pressure, and found upon opening that one of the batteries had leaked quite badly.  I think the electrolyte in alkaline batteries is potassium hydroxide, and as you can see it corroded the aluminum battery holder significantly. The PVC of the housing was unaffected, and the excess fluid simply pooled in the bottom of the rubber cap.

test

The package said these batteries were good to 2017

Fortunately the divider that monitors battery voltage put the Arduino to sleep as soon as the failing cell brought the supply below the 3.4 volts required by the regulator on the Pro Mini clone used in this build. This occurred within one day of the start of the unit, and the only electronic component to expire was the ADXL345 accelerometer, which probably suffered from too much physical strain on its solder joints, as it was welded to the ABS cap which was strongly convex before I opened the clamp.

So what happened here? The unit was in a shaded area, so I don’t think it failed due to excessive heat.  The batteries were purchased from Amazon, so it seems unlikely that they were counterfeit.  I could not find any scientific surveys or lab tests to determine how often consumer batteries leak like this, but it does reinforce the benefit of testing the units for a couple of days on the surface, before we deploy them.  I wonder if we will see any more of this…

Bench-top Power Test Results : May – June 2014

The part swaps in March left me with a complete “spare” pendulum unit to bring back home. This has proved quite handy to explain the design to people, but I was keen to do a complete power test with the build, as I had set one of the pendulums in the cave to a short 5 minute sample interval, not knowing if it would go the distance.

Until I get my hands on an EEVblog uCurrent (or perhaps a Fried Circuits USB tester..?), there is only one way for me to determine power usage of the whole system, and that’s simply to plug one in and let it run. So on April 23rd,  I set the spare unit up on a bookshelf with 3 AA alkaline batteries, and a sample interval of 1 minute (to give the batteries a recovery period). Since then this test has been interrupted a few times due to random bumps during show and tell sessions. The gap around 20 000 samples was interesting in that the RTC alarm signal kept working, but the unit stopped recording the date/time data when an I2C wire was broken during handling. The Tinycircuits compass board level shifters kept the unit running, despite the loss of the other I2C lines, until I noticed the break and repaired it.

20140609_DryPowerConsumptionTest_graph

Vertical grey lines ~ 2000 records:  approximating one month of samples at 15 minute interval.

These results are as good as I could have hoped for: >61 000 records recorded on the SD card, with the power supply falling from 4380mV down to 3750mV (read with the internal Vcc reading trick, after the shottky diodes on the power module). That’s around 22 months of operation if we were capturing data every 15 minutes, and the units currently installed in the caves have twice as many batteries in them. Even better, this test is still comfortably on the alkaline battery power plateau –  nowhere near the 2800 mV cutoff I set in the code to protect against unsafe SD card writing. I intend to let this test run to completion to see how gracefully the unregulated unit handles the eventual loss of power.  Since I have a 3v button cell backing up the DS3231 RTC, my guess is that once the main power supply falls below that point, the RTC will sleep, and stop sending the wakeup alarm to the logging platform. This would be a fairly graceful fail if it happens that way, but the data could get choppy for a while as Vcc wobbles by 50-80 mV from one reading to the next in the current record. I will just have to see how it goes as I still have quite a few component changes to work through before I start counting chickens. My plan is for the next round of deployments to compare data from several different sensors measuring the same parameter in the same unit.  So there will be a one-of build with three temperature sensors, or three accelerometers, etc., making good use of the excess power available at this stage.

Addendum 2014-06-12

I just reviewed the DS3231 data sheet again, and the DS3231 is designed to keep running from Vcc well after the main supply falls below the voltage of the rtc backup battery.  The actual “Power-Fail” voltage”, where a low VCC stops the SQW alarm,  doesn’t kick in until somewhere between 2.45v min – 2.7v max. All below the 2.8 volts minimum for safe SD card writing. So it looks like the system clock wont give me a graceful power out behavior unless I somehow lower the RTC’s input voltage below the rest of the system ( by .2 to .3v …perhaps with a Shottky diode?). Interestingly, you can go the other way if you need to: Bit 6 (BBSQW) of the DS3231’s control register 0Eh, can be set to 1 to force the wakeup alarms to continue when running the RTC from the back up battery alone. Not useful here but perhaps on another project. 

Addendum 2014-06-12

 The power drain test with this unit was finally completed on Aug 06, 2014, recording 390000 records with 3 alkaline AA batteries.

Addendum 2014-09-21

The real world results were dramatically different from this bench testing, due to excessive sleep current being drawn by counterfeit SD cards in the deployed units.

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

The next steps…

Well, now that we have working ‘betas’ in the field, it is time for me to get back to the workbench. It’s clear from the November test runs that power management is going to be my next priority.  But I approach this problem differently from someone with more technical ability, who could simply dig into datasheets, rooting around for micro-controllers that are especially optimized for low power consumption.

One of my primary design goals is to make this thing easy for anyone (including me!) to build (plumbing parts, standard Arduino, AA batteries, etc) but the practical limitations of fieldwork are also affecting the design.  I currently envision a service cycle where we dive to retrieve the units, bring them to the surface, swap the batteries and retrieve the data, and then immediately return them to the sample locations in the caves.  I would love to come up with a system that does not break the watertight seal around the electronics, like the optical system of the HOBO Waterproof Shuttle, or the external screw contacts of the ReefNet Sensus; but it is going to be a quite a while before my Kung Fu is that strong.  And it might be non trivial to put this kind of kind of transmission window in the current pvc housing.

Taking the laptop apart to dry it out after getting rained on while  tethered to an RCM9

This is me taking a laptop apart to dry it out after the rain hit us – while tethered to an Aanderaa RCM.

More typically, one would download data from the units by simply connecting the motherboards to a USB port cable once they were on dry land. But it’s pretty common to get caught in downpour, so just taking your laptop into the field for this procedure can be risky. (In fact it’s not to much of a stretch to say that is how we kill many of our machines.) Now multiply this fiddle factor by the 5-10 units we might have deployed in a given cave and you see where potential bottlenecks could arise on the real world side of things.

So I am still planning to use SD cards as the data storage device, even though I know that card writing is overwhelmingly the biggest power user in the entire system.  Somehow I need to buffer the data with a low power system, and only write to the cards once per day.

The sensor read cycles are only generating about 60 characters of data at the moment, so one strategy is to try buffering to the arduinos internal 1k of eeprom memory, which would give me about 15 sample cycles before I had to do a save.  This approach has the benefit that it would need no extra hardware, which means no change whatever in the current physical build of the units if the internal eeprom is robust enough for all that traffic without becoming corrupted.  Unfortunately, the routines for reading and writing data to EEPROM are kind of primitive, limited to a single byte at a time. And I will have to work on my chops before I am comfortable using pointer variables to directly access memory. But perhaps there are easier ways to write data to an eeprom?

If I add a larger external eeprom chip to the SPI lines, I would have 16-32k to play with; more than enough to do a whole days worth of reading before I have to fire up the SD card. I have also found several DS3231 real time clock shields that already include I2C eeprom memory, so if the libraries are out there, they might be pretty easy to implement and many of these chips support page writes to simplify the code…or perhaps not. And many of the “combo” shields I’ve come across seem to have rechargeable (LIR2032) cells on them, so I am concerned that there is the power hog of a charging circuit in there somewhere.

Alternatively, some RTC’s have 1-2K of internal eeprom themselves, so perhaps I could find an RTC based on those chips.  I would not want to give up the accuracy of the DS3231 though.

And while I am sorting all that out the Sparkfun power management tutorial shows that there is still room for other power reductions that can be achieved through software alone. The problem of burden voltage on multimeters, means I will need to buy better equipment to even measure differences down in µA territory. I am still kicking myself for not getting my hands on one of the EEVblog uCurrents before he ran his spectacularly successful kickstarter, which of course means that I wont be able to get my hands on one for months while he deals with the resulting backlog.

And finally, it would not hurt to get a ‘real’ voltage divider in the units, so I know what Vcc actually is, rather than relying on the twitchy internal VCC reading trick to monitor the power supply.

I definitely have my work cut out for me…

Switching to the low power TinyDuino platform.

After sending the first gen housings out for some real world testing, I returned my attention to the guts of my data loggers.  While the Uno & Adafruit data logging shield was dead easy to get up and running, the whole unit would wring out 6AA’s in about a day, and I needed a heck of allot more run-time than that.

So I started combing through the forums at the Arduino playground for any power management threads I could find. A myopia inducing week of screen reading later, I had developed a rough list of strategies to pursue, even though I had no idea how much difference each one would make. These fell into two basic categories:

Lower the voltage & clock frequency,  then get rid of any circuits you don’t need:
– like indicator leds, voltage regulators, usb interfaces, etc

and once you have that sorted:

Turn off parts of the processor you are not using with software:
– and if possible put the whole chip to “sleep”

And although I did not find this mentioned anywhere, I also penciled in a note to research different battery chemistries, as I knew that on year long time scales, self discharge was going to be a real issue.

So I started with the hardware, and dug into the stunning array of “bare bones” clones that had spun off since Banzi et.al gave their little project to the world. I whittled the list down to a handful of low power units including: the Pro Mini,  the Solarbotics Ardweeny, the Rocket Scream Mini Ultra, Modern Device Jeenodes, RFduinos, and a few others that seemed to be pushing the limits of low power design. Some of these designs were so minimal, they were barely more than a row of breadboard pins soldered onto the raw Atmel chip itself. So, for a while, I flirted with the idea of just sticking a chip on a mini breadboard and going from there.

I was comfortable with the idea of using a separate FTDI breakout board to program the unit, but some of the low power designs had non standard processor chips, and I knew that was not going to work, because I was still cutting my teeth on “Coding for Dummies” tutorials. And on a purely practical level, the mini breadboard I used with an Ardweeny started getting ugly once I wired in the SD card shield, the accelerometer, the FTDI chip, the battery connectors, etc.  I was not sure the resulting octopus would survive the bashing around these sensors were likely to see dangling from the side of a cave diver, no matter how tough my housings were.

Then, I got wind of a spectacularly successful Kickstarter campaign  for a new, ultra small Arduino board that was being made in Ohio, and everything just fell into place.  While the new TinyDuino system was a bit expensive, it delivered almost everything on my power management wish list and they also had an accelerometer, an SD card shield, with several protoboards.  But rather than describe the effect of all this with words, perhaps a picture would convey all this a little better:

Alpha VS Beta

My digital innards went from an alpha kludge to a running beta in one fell swoop!

So although I had some misgivings about their fragile looking stack connectors, adopting the Tinyduino meant that I could now start chewing on the gnarlier bits of software based power management.

The hardware selection had essentially just been one long process of elimination, but I knew right from the start that coding would put me on a much steeper learning curve. AVR assembly language still seemed like quasi magical incantation, so I just started gathering every example of “sleep code” I could find, like a squirrel collecting nuts for winter.  I figured I would just try to  cut & paste from simple examples like the Nightingale code until I was successfully shutting down the cpu, and waking it with the internal watchdog timer on eight second cycles, to check if it was time to read data.

But I kept finding comments in the forums about how sloppy the Atmel internal clocks could get, affected by everything from temperature variations and input voltage, to “animal spirits”.  With so many iterations needed to extend those “overflow interrupt” blips out to a real world sampling cycle of perhaps 15 minutes, or even an hour, I worried that even tiny timing errors would eventually accumulate into sizable ones. (And recently I have heard rumors of people locking up their Arduinos because of watchdog timer conflicts with the default bootloader)

So I started investigating the use of an external real time clock (RTC) to let the little logger sleep for extended, and hopefully more precise, times between wake cycles.  There were tantalizing clues out there that this could make a system run for years, but I could not  follow the discussions far enough into the technical brambles to really understand them. And if I had thought that the machine code for sleeping was hairy, trying to pick a suitable RTC by looking at libraries and data sheets, seemed like it might convert an already steep learning curve into a straight vertical line.

Fortunately, it was then that I found an excellent post on power saving techniques which had clear examples of how to both sleep, and wake up the cpu with alarm interrupts. I was back in the game, and for the first time I had a sense of what all these things were actually contributing to my overall power budget. Combining his I2C primer  with the DSS circuits post on the Effects of varying I2C pull-Up resistors, and a few more tweaks, I finally managed to get a chronodot RTC (on the left in the picture above) to wake my sleeping data logger at any time interval I wanted.

Whew! My little project was finally looking like it might go the distance, and I was thanking the Gods for mysterious Über-tecks on the other side of the world, like Nick Gammon, who had put their mild obsessive compulsive disorders to such great effect bailing out newbies like myself.

<—Click here to continue reading—>