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
|
* Adafruit NeoPixel Library
|
||||||
* SparkFun Lipo Fuel Gauge Library / Example code
|
* SparkFun Lipo Fuel Gauge Library / Example code
|
||||||
|
* "Sleepy" class from https://github.com/jcw/jeelib/
|
||||||
|
|
||||||
Non-Code License
|
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())
|
* Implement flashing lights if battery level is too low (use fuelgauge.isAlerting() and setAlertThreshold())
|
||||||
* Need to implement "always on toggle"
|
* Need to implement "always on toggle"
|
||||||
* Need to implement PIR distance sensor to verify door of safe closed
|
* 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
|
* Need to add support for Adafruit Trinket Pro
|
||||||
* Add note about adjusting values for distance, use sleep, alert threshold (if using fuel gauge)
|
* 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(){
|
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(){
|
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){
|
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 "Arduino.h"
|
||||||
#include "Wire.h"
|
#include "Wire.h"
|
||||||
#include "MAX1704.h"
|
#include "MAX1704.h"
|
||||||
|
#include "Sleepy.h"
|
||||||
#include "Adafruit_NeoPixel.h"
|
#include "Adafruit_NeoPixel.h"
|
||||||
#ifdef __AVR__
|
#ifdef __AVR__
|
||||||
#include <avr/power.h>
|
#include <avr/power.h>
|
||||||
|
@ -9,6 +10,8 @@
|
||||||
|
|
||||||
// Various defines / pins / etc
|
// Various defines / pins / etc
|
||||||
#define DEBUG true
|
#define DEBUG true
|
||||||
|
#define USE_SLEEP false
|
||||||
|
#define SLEEP_INTERVAL 1000 // miliseconds
|
||||||
#define BUTTON_CAP_TOGGLE 9
|
#define BUTTON_CAP_TOGGLE 9
|
||||||
#define POT_BRIGHT_PIN A0
|
#define POT_BRIGHT_PIN A0
|
||||||
#define POT_COLOR_PIN A1
|
#define POT_COLOR_PIN A1
|
||||||
|
@ -30,6 +33,7 @@ bool max_found = false;
|
||||||
|
|
||||||
// Various function definitions
|
// Various function definitions
|
||||||
void hsvToRgb(int h, double s, double v); // See end of file for implementation
|
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() {
|
void setup() {
|
||||||
Serial.begin(9600);
|
Serial.begin(9600);
|
||||||
|
@ -37,7 +41,7 @@ void setup() {
|
||||||
|
|
||||||
// Setup serial and wait for console
|
// Setup serial and wait for console
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
while(!Serial);
|
//while(!Serial);
|
||||||
Serial.println("Starting up...");
|
Serial.println("Starting up...");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,6 +128,20 @@ void loop() {
|
||||||
Serial.println("----------");
|
Serial.println("----------");
|
||||||
delay(250);
|
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
|
// Convert degrees on a circle to color values
|
||||||
|
|
Reference in a new issue