Initial implementation of deep sleep
This commit is contained in:
parent
b0f29a2ae7
commit
7a69f68eb4
|
@ -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
|
||||
=
|
||||
|
|
1
TODO.md
1
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)
|
||||
|
|
|
@ -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){
|
||||
|
|
111
src/Sleepy.cpp
Normal file
111
src/Sleepy.cpp
Normal file
|
@ -0,0 +1,111 @@
|
|||
/// @file
|
||||
/// Sleepy library definitions.
|
||||
// 2009-02-13 <jc@wippler.nl> http://opensource.org/licenses/mit-license.php
|
||||
|
||||
#include "Sleepy.h"
|
||||
#include <avr/sleep.h>
|
||||
#include <util/atomic.h>
|
||||
|
||||
// #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<<WDRF);
|
||||
ATOMIC_BLOCK(ATOMIC_FORCEON) {
|
||||
#ifndef WDTCSR
|
||||
#define WDTCSR WDTCR
|
||||
#endif
|
||||
WDTCSR |= (1<<WDCE) | (1<<WDE); // timed sequence
|
||||
WDTCSR = wdtcsr;
|
||||
}
|
||||
}
|
||||
|
||||
/// @see http://www.nongnu.org/avr-libc/user-manual/group__avr__sleep.html
|
||||
void Sleepy::powerDown () {
|
||||
byte adcsraSave = ADCSRA;
|
||||
ADCSRA &= ~ bit(ADEN); // disable the ADC
|
||||
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
||||
ATOMIC_BLOCK(ATOMIC_FORCEON) {
|
||||
sleep_enable();
|
||||
// sleep_bod_disable(); // can't use this - not in my avr-libc version!
|
||||
#ifdef BODSE
|
||||
MCUCR = MCUCR | bit(BODSE) | bit(BODS); // timed sequence
|
||||
MCUCR = (MCUCR & ~ bit(BODSE)) | bit(BODS);
|
||||
#endif
|
||||
}
|
||||
sleep_cpu();
|
||||
sleep_disable();
|
||||
// re-enable what we disabled
|
||||
ADCSRA = adcsraSave;
|
||||
}
|
||||
|
||||
byte Sleepy::loseSomeTime (word msecs) {
|
||||
byte ok = 1;
|
||||
word msleft = msecs;
|
||||
// only slow down for periods longer than the watchdog granularity
|
||||
while (msleft >= 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;
|
||||
}
|
||||
|
61
src/Sleepy.h
Normal file
61
src/Sleepy.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
// 2009-02-13 <jc@wippler.nl> http://opensource.org/licenses/mit-license.php
|
||||
|
||||
#ifndef Sleepy_h
|
||||
#define Sleepy_h
|
||||
|
||||
/// @file
|
||||
/// Ports library definitions.
|
||||
|
||||
#if ARDUINO >= 100
|
||||
#include <Arduino.h> // Arduino 1.0
|
||||
#else
|
||||
#include <WProgram.h> // Arduino 0022
|
||||
#endif
|
||||
#include <stdint.h>
|
||||
#include <avr/pgmspace.h>
|
||||
//#include <util/delay.h>
|
||||
|
||||
// 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
|
||||
|
20
src/src.ino
20
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 <avr/power.h>
|
||||
|
@ -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
|
||||
|
|
Reference in a new issue