Low Power Means Putting Things to Sleep!
Now to get a trinket talking to the XBee... I couldn't get software serial to work reliably on the trinket at 57600, so backed all the XBees down to 19200. I created SleepyTrinket based on the code from Nathan Seidle at https://github.com/sparkfun/H2OhNo/tree/master/firmware/WatchDogTest. That code uses pins 3 and 4 for the software serial, but I switched it to 0 and 1, then used pin 2 for the XBee sleep line, which leaves 3 (A3) and 4 (A2) for something analog useful. I cut the current draw on the trinket by 3 mA by breaking off the green power LED. The XBee sleeps when pin 9 is set high if sleep mode is enabled (ATSM1 for hibernate or ATSM2 for doze, plus ATWR).
I first powered the Trinket and the XBee directly on the 3V line from a 3.7 V 100 mAh LiPo, hoping to bypass any regulator losse, but that worried me a little so I switched to the regulated battery input. Either way, with everything awake it used about 60 mA and with everything asleep that fell to about 40 uA! That's about 1 mAh per day while sleeping and 1 mAh per minute while awake.
How quickly can I wake everything up, spit out a serial burst and go back to sleep? It works with delays in the code of 1 s, 500 ms, 100 ms, so 50 ms is probably fast enough with a total time around 200 ms per burst. To split the power consumption between awake and asleep would mean reporting about 300 times a day or about once every 5 seconds.
When I enabled the ADC and started reading a TMP 36 the sleep load came up to about 270 uA, or about 6 mAh per day, which would still get months on a 1300 mAh battery. Still, enabling and disabling the ADC each cycle brings the sleep load back down to about 60 uA. Better stability for the TMP 36 comes from a 2 M resistor and a 104 ceramic cap bridging the signal pin to ground. Based on these measurements, the little 500 mAh LiPo in the background should be good for more than 6 months between recharges. The next step is a little tidier packaging and something at the house end that will listen, repeat the data out to the world, and maybe send an SMS if alarm conditions come up.
A little more testing shows that the accuracy of the temperature measurements from this combo are a little dodgy, so time to track down where the error comes from. (Yes I know that the TMP36 spec says +-2C, but I want better ;-) )
/*
Use with Adafruit Trinket 3V - remove green power LED - compile as trinket 8MHz
about 6 mA awake and 25 uA asleep, 10 mA if the LED is turned on for just the trinket
sleep current goes up to about 1 mA if connected to FTDI cable
2014-02-11
Rick Sellens adapted from:
1-14-2013
Spark Fun Electronics
Nathan Seidle
This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).
https://github.com/sparkfun/H2OhNo/tree/master/firmware/WatchDogTest
*/
#define TRX 1 // Trinket RX pin
#define TTX 0 // Trinket TX pin
#define XBS 2 // XBee sleep pin
#define BLNK 1 // Blinking LED pin
#define TMP 2 // Analog pin number for TMP 36
#define WT 50
#include <avr/sleep.h> //Needed for sleep_mode
#include <SoftwareSerial.h>
SoftwareSerial mySerial(1, 0); // RX, TX
volatile int watchdog_counter;
//This runs each time the watch dog wakes us up from sleep
ISR(WDT_vect) {
watchdog_counter++;
}
void setup()
{
digitalWrite(XBS,LOW); // start with the XBee awake
delay(WT);
mySerial.begin(19200); // flaky at 57600, but seems OK at 9600, 19200, 38400
delay(WT);
mySerial.println("\n\nRWS Sleeper");
pinMode(XBS,OUTPUT);
pinMode(BLNK,OUTPUT);
delay(WT);
watchdog_counter = 0;
//Power down various bits of hardware to lower power usage
set_sleep_mode(SLEEP_MODE_PWR_DOWN); //Power down everything, wake up from WDT
sleep_enable();
ADCSRA |= (1<<ADEN); //Enable ADC, costs ~230uA
ADCSRA &= ~(1<<ADEN); //Disable ADC, saves ~230uA
setup_watchdog(6); //Wake up after 1 sec (6)
}
void loop() {
static int rdg = 0;
static int n = 0;
digitalWrite(XBS,HIGH); // sleep the XBee
sleep_mode(); // sleep the trinket
if(watchdog_counter > 0){
// trinket awake, so take a reading
ADCSRA |= (1<<ADEN); //Enable ADC, costs ~230uA
delay(WT);
rdg += analogRead(TMP);
n++;
ADCSRA &= ~(1<<ADEN); //Disable ADC, saves ~230uA
if(watchdog_counter > 9){ // wake the XBee every 10 counts
digitalWrite(BLNK,HIGH); // LED on
digitalWrite(XBS,LOW); // wake the XBee
delay(WT);
mySerial.print("Awakened... ");
delay(WT);
digitalWrite(BLNK,LOW); // LED off
long int itemp = rdg; // temperature in tenths of a degree C
itemp = (itemp * 3300 / 1024) / n - 500;
mySerial.print(itemp);
mySerial.println(" tenths of a degree C. ZZZ...");
delay(WT);
// rest the accumulator and counters
watchdog_counter = 0;
rdg = 0;
n = 0;
}
}
}
// 0=16ms, 1=32ms, 2=64ms, 3=128ms, 4=250ms, 5=500ms
// 6=1sec, 7=2sec, 8=4sec, 9=8sec
// From: http://interface.khm.de/index.php/lab/experiments/sleep_watchdog_battery/
void setup_watchdog(int timerPrescaler) {
if (timerPrescaler > 9 ) timerPrescaler = 9; //Correct incoming amount if need be
byte bb = timerPrescaler & 7;
if (timerPrescaler > 7) bb |= (1<<5); //Set the special 5th bit if necessary
//This order of commands is important and cannot be combined
MCUSR &= ~(1<<WDRF); //Clear the watch dog reset
WDTCR |= (1<<WDCE) | (1<<WDE); //Set WD_change enable, set WD enable
WDTCR = bb; //Set new watchdog timeout value
WDTCR |= _BV(WDIE); //Set the interrupt enable, this will keep unit from resetting after each int
}