Would like advice on feasible low power targets

Status
Not open for further replies.

linuxgeek

Well-known member
I want to be able to make wearable devices that run off as small of a battery as possible, without too much effort on my part.

I only need to sample at 20 - 100Hz from i2c sensors and store to SPI flash.

I may think about adding a couple of ADC channels, and possibly another SPI sensor.

What would be the easiest ways to get the teensy down to more manageable levels, since the teensy has rather high power usage compared to everything else.

Remove portions of the code? Lower the MHz? Use some sort of sleep mode btwn samples?

I haven't followed the Low Power/Snooze library development. Is it possible to integrate that w/o being overly complicated? Or is intended for advanced users?

Instead of 20ma, I'm hoping for like 5ma.
 
I've been playing with this some myself. There is a library called Snooze you can use that helps a lot. https://forum.pjrc.com/threads/4141...e-library-fun-and-results?p=130211#post130211 Is where I posted my results. If the chips your using don't pull a lot of current I think 5 ma is doable. Much better is possible if you can use deepSleep to replace any delays. Hmmm the flash pull a lot of current? You may want to store as much in the teensy as you can and dump it to flash infrequently.
 
Last edited:
Ok. Is it feasible to sample continuously with intervals between 10ms to 50msec, and using snooze in between those intervals? Are the sampling intervals reliable still? Can the interrupt still run while in snooze and wake it up to get the sample and then go back into snooze?

How does DMA fit into this? Can DMA run while in snooze and is using DMA helpful with power usage?

I'm ok with just having everything enabled (USB,etc) just at power on, then once it starts, turns off all unnessary peripherals.
 
I want to be able to make wearable devices that run off as small of a battery as possible, without too much effort on my part.

I only need to sample at 20 - 100Hz from i2c sensors and store to SPI flash.

I may think about adding a couple of ADC channels, and possibly another SPI sensor.

What would be the easiest ways to get the teensy down to more manageable levels, since the teensy has rather high power usage compared to everything else.

Remove portions of the code? Lower the MHz? Use some sort of sleep mode btwn samples?

I haven't followed the Low Power/Snooze library development. Is it possible to integrate that w/o being overly complicated? Or is intended for advanced users?

Instead of 20ma, I'm hoping for like 5ma.
do you need USB in your final product?
 
I'm was thinking he was not going to use USB while it's being worn. He could do like I do and in setup don't use any of the snooze low power functions and handle using the data that was stored in loop before dropping the MHz to 2. Requires a reset or power cycle but works. There is an excellent "all things low power" thread here I forgot to link, but he can search for it. Snooze is easy to use I can send a copy of what I did for another example if anyone wants. I even managed to get it to work
 
Last edited:
I'm was thinking he was not going to use USB while it's being worn. He could do like I do and in setup don't use any of the snooze low power functions and handle using the data that was stored in loop before dropping the MHz to 2. Requires a reset or power cycle but works. There is an excellent "all things low power" thread here I forgot to link, but he can search for it.
Or if you don't need much horse power you can compile it at 16, 8, 4, or 2 MHz and not have the hassle of having to reconfigure any of the I2C stuff on the teensy like you would using Snooze's REDUCED_CPU_BLOCK but have no USB.

20 - 100 Hz update rate is probably doable at 2 MHz, that is if I2C works at 2 MHz CPU speed, I never tried that?
 
Good news.

Would it be not too hard to have USB just when I first turn it on. Then when it starts sampling it can disable. Or does it need to be compiled out?

Is there a minimum that SPI flash would be expected to work?

I'll try i2c at the lower clock speeds.

I assume there is a minimum clock speed to have USB when it turns on. What is that?
 
Would it be not too hard to have USB just when I first turn it on. Then when it starts sampling it can disable. Or does it need to be compiled out?
You can do that but your power budget would be dominated by the CPU speed and not the USB. Disabling the USB saves you couple of milliamps I believe.

Is there a minimum that SPI flash would be expected to work?
Probably would work even at 2 MHz CPU and BUS. The SPI clock would have to be less than F_BUS.

I assume there is a minimum clock speed to have USB when it turns on. What is that?
Yes, 24 MHz.
 
When I used the feather OLED it worked ok without changing anything I2C wise inside the reduced cpu block running at 2 Mhz.

Here is the last code I ran with the OLED warts and all. It starts at 96 with usb serial working and after I dump or reset the eeprom in setup just logs slowly and with deepSleep in loop.

Code:
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <EEPROM.h>
#include <Bounce.h>
#include <Snooze.h>
SnoozeTimer timer;
SnoozeBlock config_teensy32(timer);
SnoozeBlock dummyConfig;
#define OLED_RESET 9
Adafruit_SSD1306 display(OLED_RESET);

#define LOGO16_GLCD_HEIGHT 16
#define LOGO16_GLCD_WIDTH  16

#if (SSD1306_LCDHEIGHT != 32)
#error("Height incorrect, please fix Adafruit_SSD1306.h!");
#endif
struct mVdata { // data in mv format
  uint16_t Vcc_mv;
  uint16_t Bat_mv;
  uint16_t Led_mv;
};
struct ADCdata { // as read Data
  uint16_t VccADC;
  uint16_t BatADC;
  uint16_t LedADC;
};
struct EEPROMdata { //main set of data makes eeprom read write easy
  uint32_t Compact3ADC;
  uint32_t ElaspedT;
};
const int milisPerDay = 86400000;
const int milisPerHour = 3600000;
const int milisPerMin = 60000;

struct ADCdata RawADC;
struct mVdata dispData;
struct EEPROMdata StoreSet;
int eepromAdr = 0;
#define BUTTON_C 8
elapsedMillis OnTime;
int saves = 0;
Bounce bouncer_C = Bounce( BUTTON_C, 5 );
void saveXmV( uint16_t Delta_mV = 10);
boolean checkWipe(void);
boolean checkDump(void);
void wipeeEEPROM(void);
void dumpall(void);
boolean serialCheckYnWtimeout(uint32_t timeout = 30000);
void setup()   {
  Serial.begin(9600);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 128x32)
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.print("Serial?");
  display.display();
  while (!Serial && (OnTime < 15000));
  if (OnTime < 15000) {
    display.print(" Y"); display.display();
    if (checkDump()) {
      dumpall();
      for (int cd = 60 ; cd > 0; cd = cd - 1) {
        delay (1000);
        display.clearDisplay();
        display.setTextSize(2);
        display.setTextColor(WHITE);
        display.setCursor(0, 0);
        display.print(cd);
        display.display();
      }
    }
    if (checkWipe()) {
      wipeeEEPROM();
    }
  }
  else {
    display.print(" N"); display.display();
    delay(5000);
  }
  pinMode(BUTTON_C, INPUT_PULLUP);
  pinMode(LED_BUILTIN, OUTPUT);
  analogReadResolution(10); // 10-bit resolution 0 to 1023
  analogReadAveraging(32); // 4,8,16, or 32 samples.

}

void loop() {
  REDUCED_CPU_BLOCK(dummyConfig) {
    int value = 0;    int digit = 0;
    RawADC.VccADC = analogRead(A9);
    RawADC.BatADC = analogRead(A7);
    dispData.Vcc_mv = ((RawADC.VccADC * 6600) / 1023);
    dispData.Bat_mv = ((RawADC.BatADC * 6600) / 1023);
    digitalWrite (LED_BUILTIN, HIGH);
    timer.setTimer(20); //led on
    Snooze.deepSleep( config_teensy32 );
    RawADC.LedADC = analogRead(A0);
    dispData.Led_mv = ((RawADC.LedADC * 3300) / 1023);
    digitalWrite (LED_BUILTIN, LOW);
    bouncer_C .update ( );
    value = bouncer_C.read();
    if ( value == LOW) {
      display.clearDisplay();
      display.setTextSize(1);
      display.setTextColor(WHITE);
      display.setCursor(0, 0);
      display.print( dispData.Vcc_mv);
      display.print( " ");
      display.println( dispData.Bat_mv);
      display.println( dispData.Led_mv);
      digit = OnTime / milisPerDay;
      display.print(digit);
      display.print("D ");
      digit = (OnTime % milisPerDay) / milisPerHour;
      if (digit < 10)display.print("0");
      display.print(digit);
      display.print(":");
      digit = (OnTime % milisPerHour) / milisPerMin;
      if (digit < 10)display.print("0");
      display.print(digit);
      display.print(":");
      digit = (OnTime % milisPerMin) / 1000;
      if (digit < 10)display.print("0");
      display.print(digit);
      display.print(" EE=");
      display.println(saves);
      display.display();
    }
    else {
      display.clearDisplay();
      display.display();
      timer.setTimer(1980); // display off
      Snooze.deepSleep( config_teensy32 );
    }
    saveXmV(25);
  }
}

uint32_t packData(struct ADCdata chunk) { // checked ok
  return ((((chunk.VccADC * 1024) + chunk.BatADC) * 1024) + chunk.LedADC);
}
struct ADCdata unpackData(EEPROMdata EEpackeddata) { // checked ok
  struct ADCdata tempDataChunk; uint32_t packeddata;
  packeddata = EEpackeddata.Compact3ADC;
  tempDataChunk.LedADC = (packeddata % 1024);
  tempDataChunk.BatADC = ((packeddata / 1024) % 1024);
  tempDataChunk.VccADC = ((packeddata / 1024) / 1024);
  return tempDataChunk;
}
struct ADCdata unpackData(uint32_t packeddata) { // checked ok
  struct ADCdata tempDataChunk;
  tempDataChunk.LedADC = (packeddata % 1024);
  tempDataChunk.BatADC = ((packeddata / 1024) % 1024);
  tempDataChunk.VccADC = ((packeddata / 1024) / 1024);
  return tempDataChunk;
}
void saveXmV( uint16_t Delta_mV ) {
  static uint32_t eepromAdr = 0; static uint16_t lastSavemV = 6000;
  if ((dispData.Bat_mv >= (lastSavemV  + Delta_mV)) or (dispData.Bat_mv <= (lastSavemV  - Delta_mV))) {
    lastSavemV = dispData.Bat_mv;
    lastSavemV = lastSavemV / Delta_mV;
    lastSavemV = lastSavemV * Delta_mV;
    StoreSet.ElaspedT = OnTime;
    StoreSet.Compact3ADC = packData(RawADC);
    if (eepromAdr < (E2END - sizeof(StoreSet))) {
      EEPROM.put( eepromAdr, StoreSet );
      eepromAdr = eepromAdr + sizeof(StoreSet);
      saves = saves + 1;
    }
  }
}
void dumpall(void) {// check
  struct mVdata tempDataChunk; struct ADCdata temmpEEChunk;
  eepromAdr = 0;

  while (eepromAdr < E2END) {
    EEPROM.get( eepromAdr, StoreSet );
    temmpEEChunk = unpackData(StoreSet);
    tempDataChunk.Vcc_mv = ((temmpEEChunk.VccADC * 6600) / 1023);
    tempDataChunk.Bat_mv = ((temmpEEChunk.BatADC * 6600) / 1023);
    tempDataChunk.Led_mv = ((temmpEEChunk.LedADC * 3300) / 1023);
    if (StoreSet.ElaspedT >= 1000) {
      Serial.print(tempDataChunk.Vcc_mv);
      Serial.print(",");
      Serial.print(tempDataChunk.Bat_mv);
      Serial.print(",");
      Serial.print(tempDataChunk.Led_mv);
      Serial.print(",");
      Serial.println(StoreSet.ElaspedT);
    }
    else {
      eepromAdr = E2END;
    }
    eepromAdr = eepromAdr + sizeof(StoreSet);
  }
}
boolean serialCheckYnWtimeout(uint32_t timeout) {// checked ok
  // returns true only if y or Y entered within timeout
  elapsedMillis waiting = 0; char c = ' ';
  while (waiting < timeout) {
    if (Serial.available()) {
      waiting = timeout + 1; // got one no more wait needed-
      c = Serial.read();
      if ((c == 'y') or (c == 'Y') )
      {
        return true;
      }
      return false;
    }
  }
  return false;
}
void wipeeEEPROM(void) { // checked ok
  for (int adr = 0; adr < E2END; adr = adr + 1) { //<=E2END *********************
    EEPROM.put( adr, 0);
  }
}
boolean checkWipe(void) {  // checked ok
  // double check wipe returns true to wipe
  Serial.println("Wipe EEPROM y or n ? ");
  if (serialCheckYnWtimeout() == true) {

    Serial.println("Are you sure ? Wipe EEPROM y or n ? ");
    if (serialCheckYnWtimeout() == true) {

      return true;
    }

    return false;
  }

  return false;
}
boolean checkDump(void) {  // checked ok
  // double check wipe returns true to wipe
  Serial.println("Dump EEPROM y or n ? ");
  if (serialCheckYnWtimeout() == true) {
    return true;
  }
  else {
    return false;
  }
}
 
What if I use bluetooth over Serial1? Is there a minimum MHz for Serial1?

Then I could power BT only when I need it to transfer. And the USB would only be used for power/charging battery.

@DaQue Is your EEPROMdata stored on a separate chip or on the teensy directly?
 
What would be the best way to implement sampling using IntervalTimer with the Snooze library?
The IntervalTimer clock is gated when the Teensy is sleeping using Snooze or not.

I'll test this weekend to see the lowest Mhz for i2c, ADC, SPI, and Serial1..
I know Serial1 and ADC work at 2MHz. I pretty sure SPI will just have to use SLOW clock speed. Never tried I2C.
 
Ok, I guess it's all about slowing the MHz down for me then.

Is it possible to build a simple 555 timer circuit at 100Hz, and have that trigger the wake up of the teensy? Can the teensy wake up and go to sleep that fast?
 
Why not just use Snooze timer functionality and deepSleep ( 10 )?

Oh, it looks like that's what I'm missing. I'll try and read thru the snooze library and how to use deepSleep. That sounds perfect.
Combining that with lower MHz, and it should go down to a trickle.

Thanks!
 
The example code below is my attempt at taking an interrupt driven sampling to use Snooze timer instead.
Does this make sense? And with using Snooze deepSleep(10), do I put it back to sleep once it's done with the processing, to wake up at the next timer trigger? And if so, how would I do that?

If before I had this:
Code:
IntervalTimer samplingTimer;
elapsedMillis elapsedMsec;

void setup() {
  samplingTimer.begin(sample, 1000000/100);
}

void loop() {
  //only check every 500msec for a button press
  if (elapsedMsec > 500) {
    elapsedMsec=0; //reset

    if (isButtonPressed()) {
      samplingTimer.end();
    }
  }
  checkForCommand();   //check for command, such as fetch
}

void sample() {
  //all sampling code would be here
}

boolean isButtonPressed() {
  return false;
}

void checkForCommand() {}

Would I now have this:
Code:
#include <Snooze.h>

int who;
SnoozeTimer timer;
SnoozeBlock config_teensy32(timer);
boolean bFirstSample = true;
elapsedMillis elapsedMsec;

void setup() {
  timer.setTimer(1000/100); // milliseconds
}

void loop() {
     sample(); //this should get done only once each time the teensy wakes up.
     who = Snooze.deepSleep(config_teensy32);
}

void sample() {

     if (bFirstSample) {
       //all sampling code would be here
    bFirstSample = false;
     }

     //only check every 500msec for a button press
     if (elapsedMsec > 500) {
      //reset
      elapsedMsec=0;

      if (isButtonPressed()) {
           endSampling();
      }
     }

     checkForCommand(); //check for command, such as fetch
}

boolean isButtonPressed() {
  return false;
}

void checkForCommand() {}

void endSampling() {}
 
Last edited:
Put
Code:
who = Snooze.deepSleep(config_teensy32);
in loop, then whenever its called it will sleep for 10 milliseconds.
 
ok, so don't put it in setup() ? I edited to reflect your suggestion.

I assume the regular sampling interval won't be as accurate as using an interrupt, since it will potentially be doing different amounts of processing at different samples. Is it worthwhile to try measure time (is that even possible?) elapsed so to maybe sleep for 8msec, and then append time to always get to 10msec?
 
Yes, the timer uses the LPO clock which is not particularly accurate and is the only clock active in deepSleep or hibernate so the jitter might be to much for some sampling applications. If thats the case maybe only sleep when there is nothing to do?
 
LPO is 1kHz, so maybe +-1 msec? That might be ok.

After coming out of sleep is it possible to find out how long the sleep actually was? For example, can it sleep 9msec, then upon waking, check the microseconds elapsed, and add an appropriate # of microseconds to get close to 10msec. Any help if a crystal is added for RTC?

If a 555 timer circuit to trigger a sample was made for 9.5 msec, would the LPO clock reliably allow it to wake at the 10 msec tick.
 
Oh, from your library, can I just use sleep? and can I use intervalTimer to trigger a wakeup while it is in Snooze.sleep(). If so, I didn't see an example of that.
BTW, impressive library!

Code:
/***************************************
 * This shows all the wakeups for sleep
 * Expect IDD of  around 1.2mA (Teensy 3.x)
 * and IDD of around 900uA for (Teensy LC).
 *
 * Sleep is the most flexable and any
 * interrupt can wake the processor.
 *
 * Touch interface does not work in sleep
 * mode.
 ****************************************/
 
LPO is 1kHz, so maybe +-1 msec? That might be ok.
Thats about the jitter I saw.

After coming out of sleep is it possible to find out how long the sleep actually was? For example, can it sleep 9msec, then upon waking, check the microseconds elapsed, and add an appropriate # of microseconds to get close to 10msec. Any help if a crystal is added for RTC?

If a 555 timer circuit to trigger a sample was made for 9.5 msec, would the LPO clock reliably allow it to wake at the 10 msec tick.
systick milli seconds variable is updated so millis will work right after sleeping micros will be off unfortunately.

Oh, from your library, can I just use sleep? and can I use intervalTimer to trigger a wakeup while it is in Snooze.sleep(). If so, I didn't see an example of that.
BTW, impressive library!
Yes, assign a dummy SnoozBlock with no drivers attached like:
Code:
SnoozeBlock dummyBlock;

// in loop
Snooze.sleep(dummyBlock);
 
Status
Not open for further replies.
Back
Top