Low Power Logger - Adafruit Featherlogger M0 and LSM9DS0

I'm working on a datalogger to track the movements of birds and I am looking for some guidance. I am able to sample at 50Hz for 18hrs and I would really like to extend the battery beyond 24hrs. I am using a 350mAh battery because weight is a factor, but I could go slightly bigger. Is there anything I could do from a programming standpoint to reduce power consumption? I am using an Adafruit Featherlogger M0 with a LSM9DS0 sensor.

/*
 Connections (USES SPI)
 Note: M0 to 9DS0
 Connect Pin 24 to SCL
 Connect Pin 22 to SDOG and SDOXM (MISO)
 Connect Pin 23 to SDA (MOSI)
 Connect Pin 10 to CSXM
 Connect Pin 09 to CSG
 Connect 3V to 3V3 (could also go to VIN)
 Connect GND to GND
*/

#include <RTCZero.h>
#include <SPI.h>
#include <SdFat.h>
SdFat SD;
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_LSM9DS0.h>

#define LSM9DS0_XM_CS 10
#define LSM9DS0_GYRO_CS 9
//Hardware SPI (Creates a unique Sensor ID)
Adafruit_LSM9DS0 lsm = Adafruit_LSM9DS0(LSM9DS0_XM_CS, LSM9DS0_GYRO_CS, 1000);

#define cardSelect 4  // Set the pin for uSD
#define RED 13 // Red LED Pin #13
#define GREEN 8 // Green LED Pin #8
#define VBATPIN A7    // Bat Volt Pin A7

// Number of samples to buffer before flush called.
#define SamplesPerCycle 10000
// Number of lines of data per file
#define SamplesPerFile 60000

byte hours = 1;
byte minutes = 1;
byte seconds = 1;
byte day = 1;
byte month = 1;
byte year = 17; 
// Variable used to keep track of changes in seconds, minutes, hours, days, months
byte timekeeper = 0;
// delay to adjust sampling rate
// 20ms = ~30Hz
// 11ms = ~50Hz
// 0ms = ~100HZ
byte samplingRateDelay = 11;

RTCZero rtc;
File logfile;
File settingsFile;
char filename[16];    // Array for file name data logged to named in setup 
char settingsFilename[15];    // Array for file name of Settings File to log settings of Deployment
float measuredvbat;   // Variable for battery voltage
unsigned int CurrentCycleCount;  // Num of samples in current cycle, before uSD flush call
unsigned int CurrentFileCount;   // Num of samples in current file
unsigned long milli; //used to store the milliseconds since the program started

void setup() {
  //  Setup RTC and set time
  rtc.begin();
  rtc.setTime(hours, minutes, seconds);
  rtc.setDate(day, month, year);

  strcpy(filename, "ANALOG000.CSV");   
  strcpy(settingsFilename, "InitVal00.TXT");   
  /* Initialise the sensor */
  if(!lsm.begin())
  {
    while(1);
  }
  lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_2G);
  lsm.setupMag(lsm.LSM9DS0_MAGGAIN_2GAUSS);
  lsm.setupGyro(lsm.LSM9DS0_GYROSCALE_245DPS);
  CreateSettingsFile();
  CreateFile();
}  

void loop() {
  CurrentCycleCount += 1; //  Increment samples in current uSD flush cycle
  CurrentFileCount += 1; // Increment samples in current file
  WriteToSD(); // Output to uSD card stream

  //  Code to limit the number writes to the uSD
  if( CurrentCycleCount >= SamplesPerCycle ) {
    logfile.flush(); //is this power hungry?
    CurrentCycleCount = 0;
  }

  // Code to increment files limiting number of lines in each
  if( CurrentFileCount >= SamplesPerFile ) {
    if (logfile.isOpen()) {
      logfile.close();
    }
    CreateFile();
    CurrentFileCount = 0;
  }
}

//capture sensor settings, sampling rate, associated log files and battery status
void CreateSettingsFile(void)
{  
  if (!SD.begin(cardSelect)) {
    error(2);     // 2 red flashes means no card or init failed.
  }
  //increments the value of the ## in filename (Max is 99)
  for (uint8_t i = 0; i < 100; i++) {
    settingsFilename[7] = '0' + i/10;
    settingsFilename[8] = '0' + i%10;
    if (! SD.exists(settingsFilename)) {
      break;
    }
  }  
  settingsFile = SD.open(settingsFilename, FILE_WRITE);
  writeDeploymentDetails();
  if( ! settingsFile ) {
    error(3);
  }
  settingsFile.close();
}

// Create new Log file 
void CreateFile()
{
  if (!SD.begin(cardSelect)) {
    error(2);     // 2 red flashes means no card or init failed.
  }
  //increments the value of the ### in filename (Max is 999)
  for (uint8_t i = 0; i < 1000; i++) {
    filename[6] = '0' + i/100;
    filename[7] = '0' + (i%100)/10;
    filename[8] = '0' + i%10;
    if (! SD.exists(filename)) {
      break;
    }
  }  
  //log the newly created file name to the settings file
  settingsFile = SD.open(settingsFilename, FILE_WRITE);
  settingsFile.print(filename); 
  // Was giving 5V bat voltage, not sure why
  settingsFile.print(" Battery Voltage: "); settingsFile.println(BatteryVoltage ()); 
  settingsFile.close();
  delay(10);
  logfile = SD.open(filename, FILE_WRITE);
  writeHeader();
  if( ! logfile ) {
    error(3);
  }
}

// Write data header to file of uSD.
void writeHeader() {
  logfile.println("YYYY-MM-DD hh:mm:ss,Timestamp(Ms),ACCELX,ACCELY,ACCELZ,MAGX,MAGY,MAGZ,GYRX,GYRY,GYRZ,TEMP");
}

// Print data and time followed by battery voltage to SD card
void WriteToSD() {

  sensors_event_t accel, mag, gyro, temp;
  lsm.getEvent(&accel, &mag, &gyro, &temp);   

  // Formatting for file output yyyy-mm-dd hh:mm:ss 
  timekeeper = rtc.getSeconds();
  if(seconds != timekeeper) {
    seconds = timekeeper;
    timekeeper = rtc.getMinutes();
    if(minutes != timekeeper){
      minutes = timekeeper;
      blink(RED,3);
      timekeeper = rtc.getHours();
      if(hours != timekeeper){
        hours = timekeeper;
        timekeeper = rtc.getDay();
        if(day != timekeeper){
          day = timekeeper;
          timekeeper = rtc.getMonth();
          if(month != timekeeper){
            month = timekeeper;
          }
        }
      }
    }
  }
  //How do I get all this to happen in fewer print calls
  //Is a StringBuffer a better option?
  logfile.print("20");
  logfile.print(year);   
  logfile.print("-");
  logfile.print(month);
  logfile.print("-");
  logfile.print(day);
  logfile.print(" ");
  logfile.print(hours);
  logfile.print(":");
  if(minutes < 10)
    logfile.print('0');      
  logfile.print(minutes);
  logfile.print(":");
  if(seconds < 10)
    logfile.print('0');      
  logfile.print(seconds); logfile.print(",");
  // sensor timestamp
  logfile.print(accel.timestamp); logfile.print(",");
  // print accelleration data to the log file: 
  logfile.print(accel.acceleration.x); logfile.print(",");
  logfile.print(accel.acceleration.y); logfile.print(",");
  logfile.print(accel.acceleration.z); logfile.print(","); 
  // print magnetometer data to the log file:
  logfile.print(mag.magnetic.x); logfile.print(",");
  logfile.print(mag.magnetic.y); logfile.print(",");
  logfile.print(mag.magnetic.z); logfile.print(",");
  // print gyroscopic data to the log file:  
  logfile.print(gyro.gyro.x); logfile.print(",");
  logfile.print(gyro.gyro.y); logfile.print(",");
  logfile.print(gyro.gyro.z); logfile.print(",");
  // print temperature data
  logfile.println(temp.temperature);

  delay(samplingRateDelay);

}

// blink out an error code
void error(uint8_t errno) {
  while(1) {
    uint8_t i;
    for (i=0; i<errno; i++) {
      digitalWrite(13, HIGH);
      delay(100);
      digitalWrite(13, LOW);
      delay(100);
    }
    for (i=errno; i<10; i++) {
      delay(200);
    }
  }
}

// blink out an error code, Red on pin #13 or Green on pin #8
void blink(uint8_t LED, uint8_t flashes) {
  uint8_t i;
  for (i=0; i<flashes; i++) {
    digitalWrite(LED, HIGH);
    delay(100);
    digitalWrite(LED, LOW);
    delay(100);
  }
}

// Measure battery voltage using divider on Feather M0
float BatteryVoltage () {
  measuredvbat = analogRead(VBATPIN);   //Measure the battery voltage at pin A7
  measuredvbat *= 2;    // we divided by 2, so multiply back
  measuredvbat *= 3.3;  // Multiply by 3.3V, our reference voltage
  measuredvbat /= 1024; // convert to voltage
  return measuredvbat;
}

// Writes to a TXT file about the setting
void writeDeploymentDetails(void)
{
  settingsFile.print("Init. Time: ");
  settingsFile.print("20");
  settingsFile.print(year);
  settingsFile.print("/");
    settingsFile.print(month);
  settingsFile.print("/");
    settingsFile.print(day);
  settingsFile.print(" ");
  settingsFile.print(hours);
  settingsFile.print(":");
  if(minutes < 10)
    settingsFile.print('0');
  settingsFile.print(minutes);
  settingsFile.print(":");
  if(seconds < 10)
    settingsFile.print('0');
  settingsFile.println(seconds);  
  settingsFile.print("Sampling Rate Delay: ");
  settingsFile.println(samplingRateDelay);
  settingsFile.print("Num of Samples before Flush: "); 
  settingsFile.println(SamplesPerCycle);
  settingsFile.print("Num of Samples per CSV: ");
  settingsFile.println(SamplesPerFile);
  settingsFile.println(F("Log Files and Bat.Volt:"));
}

Any thoughts or guidance would be greatly appreciated.

Check out :Power Management | Adafruit Feather M0 Adalogger | Adafruit Learning System

If you can avoid flushing you will save around 25% in your case.

Thank you for feedback. I have set the Flush Rate as a variable and I'm currently flushing every 2 minutes. I could probably bump that up significantly (perhaps to once per hour or 2). Is there a good way to determine power consumption of individual functions?

IlPinguino:
Is there a good way to determine power consumption of individual functions?

Try to turn the function off, measure consumption with and without the function. The same goes for any part of hardware - I don't know what you are measuring but do you need 50Hz? What if you measured with much lower frequency?

Is there a way to turn of certain hardware components in the SAMD21 such as I2C or UART? My project is only using SPI.

Do you have the current power consumption of the Featherlogger and the Sensor in all states? in running and sleeping modes?

It's very likely you should be able to reduce the average consumption well under 10mA by just using MCU and sensor sleeping. Even if you need to wake-up 50 times/seconds, put the MCU to sleep instead of "delaying".

Disabling peripherals will save only a few uAs, which will not significantly extend the battery life. You should have a current avg consumption of 15-18mA (11mA MCU + 5-7mA sensor), so reducing few uAs doesn't help, you need to first cut a few mAs :slight_smile:

I am looking into the Energy Saving library and using sleep mode on the M0. My current consumption is ~13.675mA and I'm able to log at 50Hz for 18hrs. I have an 11ms delay in the code that limits it to 50Hz. I would love to sleep during that delay, but I wasn't sure if that was too short of an interval.

I appreciate the thoughts.

Yes, just sleep during this time... 50*11ms is same as sleeping 51% of your cycle... and if you dig more, you'll be able to sleep while sensor is collecting data as well.