diff --git a/README.md b/README.md index 03621b2..b07c623 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ The following external libraries are included and may not be licensed under the * Adafruit NeoPixel Library * SparkFun Lipo Fuel Gauge Library / Example code + * "Sleepy" class from https://github.com/jcw/jeelib/ Non-Code License = diff --git a/TODO.md b/TODO.md index edd8d77..2d23bfe 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,5 @@ * Implement flashing lights if battery level is too low (use fuelgauge.isAlerting() and setAlertThreshold()) * Need to implement "always on toggle" * Need to implement PIR distance sensor to verify door of safe closed - * Need low power mode implementation(s) * Need to add support for Adafruit Trinket Pro * Add note about adjusting values for distance, use sleep, alert threshold (if using fuel gauge) diff --git a/src/MAX1704.cpp b/src/MAX1704.cpp index 7966cb6..8839be1 100644 --- a/src/MAX1704.cpp +++ b/src/MAX1704.cpp @@ -117,12 +117,44 @@ boolean MAX1704::isSleeping(){ } void MAX1704::sleep(){ - + byte msb = 0; + byte lsb = 0; + + // Get the current config so we don't wipe any previous settings + readFrom(MAX1704_CONFIG,msb,lsb); + + // Set SLEEP config bit to 1 to enable + lsb |= (1<<7); + + // Update config + Wire.beginTransmission(MAX1704_ADDR); + Wire.write(MAX1704_CONFIG); + Wire.write(msb); + Wire.write(lsb); + Wire.endTransmission(); + + // This delay is here to ensure it's fully asleep before moving on + delay(150); } void MAX1704::awake(){ - - + byte msb = 0; + byte lsb = 0; + + // Get the current config so we don't wipe any previous settings + readFrom(MAX1704_CONFIG,msb,lsb); + // Set SLEEP config bit to 0 to disable + lsb &= ~(1<<7); + + // Update config + Wire.beginTransmission(MAX1704_ADDR); + Wire.write(MAX1704_CONFIG); + Wire.write(msb); + Wire.write(lsb); + Wire.endTransmission(); + + // This delay is here to ensure it's fully awake before moving on + delay(150); } void MAX1704::performCommand(byte address, int value){ diff --git a/src/Sleepy.cpp b/src/Sleepy.cpp new file mode 100644 index 0000000..f7c8ed3 --- /dev/null +++ b/src/Sleepy.cpp @@ -0,0 +1,111 @@ +/// @file +/// Sleepy library definitions. +// 2009-02-13 http://opensource.org/licenses/mit-license.php + +#include "Sleepy.h" +#include +#include + +// #define DEBUG_DHT 1 // add code to send info over the serial port of non-zero + +// ATtiny84 has BODS and BODSE for ATtiny84, revision B, and newer, even though +// the iotnx4.h header doesn't list it, so we *can* disable brown-out detection! +// See the ATtiny24/44/84 datasheet reference, section 7.2.1, page 34. +#if (defined(__AVR_ATtiny84__) || defined(__AVR_ATtiny44__)) && \ + !defined(BODSE) && !defined(BODS) +#define BODSE 2 +#define BODS 7 +#endif + +// flag bits sent to the receiver +#define MODE_CHANGE 0x80 // a pin mode was changed +#define DIG_CHANGE 0x40 // a digital output was changed +#define PWM_CHANGE 0x30 // an analog (pwm) value was changed on port 2..3 +#define ANA_MASK 0x0F // an analog read was requested on port 1..4 + +/// @fn static void Sleepy::powerDown (); +/// Take the ATmega into the deepest possible power down state. Getting out of +/// this state requires setting up the watchdog beforehand, or making sure that +/// suitable interrupts will occur once powered down. +/// Disables the Brown Out Detector (BOD), the A/D converter (ADC), and other +/// peripheral functions such as TWI, SPI, and UART before sleeping, and +/// restores their previous state when back up. + +// ISR(WDT_vect) { Sleepy::watchdogEvent(); } + +static volatile byte watchdogCounter; + +void Sleepy::watchdogInterrupts (char mode) { + // correct for the fact that WDP3 is *not* in bit position 3! + if (mode & bit(3)) + mode ^= bit(3) | bit(WDP3); + // pre-calculate the WDTCSR value, can't do it inside the timed sequence + // we only generate interrupts, no reset + byte wdtcsr = mode >= 0 ? bit(WDIE) | mode : 0; + MCUSR &= ~(1<= 16) { + char wdp = 0; // wdp 0..9 corresponds to roughly 16..8192 ms + // calc wdp as log2(msleft/16), i.e. loop & inc while next value is ok + for (word m = msleft; m >= 32; m >>= 1) + if (++wdp >= 9) + break; + watchdogCounter = 0; + watchdogInterrupts(wdp); + powerDown(); + watchdogInterrupts(-1); // off + // when interrupted, our best guess is that half the time has passed + word halfms = 8 << wdp; + msleft -= halfms; + if (watchdogCounter == 0) { + ok = 0; // lost some time, but got interrupted + break; + } + msleft -= halfms; + } + // adjust the milli ticks, since we will have missed several +#if defined(__AVR_ATtiny84__) || defined(__AVR_ATtiny85__) || defined (__AVR_ATtiny44__) || defined (__AVR_ATtiny45__) + extern volatile unsigned long millis_timer_millis; + millis_timer_millis += msecs - msleft; +#else + extern volatile unsigned long timer0_millis; + timer0_millis += msecs - msleft; +#endif + return ok; // true if we lost approx the time planned +} + +void Sleepy::watchdogEvent() { + ++watchdogCounter; +} + diff --git a/src/Sleepy.h b/src/Sleepy.h new file mode 100644 index 0000000..cb82598 --- /dev/null +++ b/src/Sleepy.h @@ -0,0 +1,61 @@ +// 2009-02-13 http://opensource.org/licenses/mit-license.php + +#ifndef Sleepy_h +#define Sleepy_h + +/// @file +/// Ports library definitions. + +#if ARDUINO >= 100 +#include // Arduino 1.0 +#else +#include // Arduino 0022 +#endif +#include +#include +//#include + +// tweak this to switch ATtiny84 etc to new Arduino 1.0+ conventions +// see http://arduino.cc/forum/index.php/topic,51984.msg371307.html#msg371307 +// and http://forum.jeelabs.net/node/1567 +#if ARDUINO >= 100 +#define WRITE_RESULT size_t +#else +#define WRITE_RESULT void +#endif + +/// Low-power utility code using the Watchdog Timer (WDT). Requires a WDT +/// interrupt handler, e.g. EMPTY_INTERRUPT(WDT_vect); +class Sleepy { +public: + /// start the watchdog timer (or disable it if mode < 0) + /// @param mode Enable watchdog trigger after "16 << mode" milliseconds + /// (mode 0..9), or disable it (mode < 0). + /// @note If you use this function, you MUST included a definition of a WDT + /// interrupt handler in your code. The simplest is to include this line: + /// + /// ISR(WDT_vect) { Sleepy::watchdogEvent(); } + /// + /// This will get called when the watchdog fires. + static void watchdogInterrupts (char mode); + + /// enter low-power mode, wake up with watchdog, INT0/1, or pin-change + static void powerDown (); + + /// Spend some time in low-power mode, the timing is only approximate. + /// @param msecs Number of milliseconds to sleep, in range 0..65535. + /// @returns 1 if all went normally, or 0 if some other interrupt occurred + /// @note If you use this function, you MUST included a definition of a WDT + /// interrupt handler in your code. The simplest is to include this line: + /// + /// ISR(WDT_vect) { Sleepy::watchdogEvent(); } + /// + /// This will get called when the watchdog fires. + static byte loseSomeTime (word msecs); + + /// This must be called from your watchdog interrupt code. + static void watchdogEvent(); +}; + +#endif + diff --git a/src/src.ino b/src/src.ino index 3c5ef7d..55d88b9 100644 --- a/src/src.ino +++ b/src/src.ino @@ -2,6 +2,7 @@ #include "Arduino.h" #include "Wire.h" #include "MAX1704.h" +#include "Sleepy.h" #include "Adafruit_NeoPixel.h" #ifdef __AVR__ #include @@ -9,6 +10,8 @@ // Various defines / pins / etc #define DEBUG true +#define USE_SLEEP false +#define SLEEP_INTERVAL 1000 // miliseconds #define BUTTON_CAP_TOGGLE 9 #define POT_BRIGHT_PIN A0 #define POT_COLOR_PIN A1 @@ -30,6 +33,7 @@ bool max_found = false; // Various function definitions void hsvToRgb(int h, double s, double v); // See end of file for implementation +ISR(WDT_vect) { Sleepy::watchdogEvent(); } // Setup the watchdog -- For sleepy void setup() { Serial.begin(9600); @@ -37,7 +41,7 @@ void setup() { // Setup serial and wait for console if (DEBUG) { - while(!Serial); + //while(!Serial); Serial.println("Starting up..."); } @@ -124,6 +128,20 @@ void loop() { Serial.println("----------"); delay(250); } + + // Sleep mode enabled + if (USE_SLEEP && !always_on) { // Don't enable sleep if always on toggle is set + if (DEBUG) { + Serial.println("SLEEPING!"); + } + fuelGauge.sleep(); + neopix.setBrightness(0); + neopix.show(); + delay(250); // Be sure that everything is settled before going to sleep + Sleepy::loseSomeTime(SLEEP_INTERVAL); + fuelGauge.awake(); + neopix.setBrightness(brightness); + } } // Convert degrees on a circle to color values