/ Buzzing_Mindfulness_Bracelet / Buzzing_Mindfulness_Bracelet.ino
Buzzing_Mindfulness_Bracelet.ino
  1  // SPDX-FileCopyrightText: 2018 Mikey Sklar for Adafruit Industries
  2  //
  3  // SPDX-License-Identifier: MIT
  4  
  5  // Mindfulness Bracelet sketch for Adafruit/Arduino Gemma.  Briefly runs
  6  // vibrating motor (connected through transistor) at regular intervals.
  7  // This code is not beginner-friendly, it does a lot of esoteric low-level
  8  // hardware shenanigans in order to conserve battery power.
  9  
 10  const uint32_t           // These may be the only lines you need to edit...
 11    onTime   =  2 * 1000L, // Vibration motor run time, in milliseconds
 12    interval = 60 * 1000L; // Time between reminders, in milliseconds
 13                           // It gets progressively geekier from here...
 14  
 15  // Additional power savings can optionally be realized by disabling the
 16  // power-on LED, either by desoldering or by cutting the trace from 3Vo
 17  // on the component side of the board.
 18  
 19  // This sketch spends nearly all its time in a low-power sleep state...
 20  #include <avr/power.h>
 21  #include <avr/sleep.h>
 22  
 23  // The chip's 'watchdog timer' (WDT) is used to wake up the CPU when needed.
 24  // WDT runs on its own 128 KHz clock source independent of main CPU clock.
 25  // Uncalibrated -- it's "128 KHz-ish" -- thus not reliable for extended
 26  // timekeeping.  To compensate, immediately at startup the WDT is run for
 27  // one maximum-duration cycle (about 8 seconds...ish) while keeping the CPU
 28  // awake, the actual elapsed time is noted and used as a point of reference
 29  // when calculating sleep times.  Still quite sloppy -- the WDT only has a
 30  // max resolution down to 16 ms -- this may drift up to 30 seconds per hour,
 31  // but is an improvement over the 'raw' WDT clock and is adequate for this
 32  // casual, non-medical, non-Mars-landing application.  Alternatives would
 33  // require keeping the CPU awake, draining the battery much quicker.
 34  
 35  uint16_t          maxSleepInterval;  // Actual ms in '8-ish sec' WDT interval
 36  volatile uint32_t sleepTime     = 1; // Total milliseconds remaining in sleep
 37  volatile uint16_t sleepInterval = 1; // ms to subtract in current WDT cycle
 38  volatile uint8_t  tablePos      = 0; // Index into WDT configuration table
 39  
 40  void setup() {
 41  
 42    // Unused pins can be set to INPUT w/pullup -- most power-efficient state
 43    pinMode(0, INPUT_PULLUP);
 44    pinMode(2, INPUT_PULLUP);
 45  
 46    // LED shenanigans.  Rather that setting pin 1 to an output and using
 47    // digitalWrite() to turn the LED on or off, the internal pull-up resistor
 48    // (about 10K) is enabled or disabled, dimly lighting the LED with much
 49    // less current.
 50    pinMode(1, INPUT);               // LED off to start
 51  
 52    // AVR peripherals that are NEVER used by the sketch are disabled to save
 53    // tiny bits of power.  Some have side-effects, don't do this willy-nilly.
 54    // If using analogWrite() to for different motor levels, timer 0 and/or 1
 55    // must be enabled -- for power efficiency they could be turned off in the
 56    // ubersleep() function and re-enabled on wake.
 57    power_adc_disable();             // Knocks out analogRead()
 58    power_timer1_disable();          // May knock out analogWrite()
 59    power_usi_disable();             // Knocks out TinyWire library
 60    DIDR0 = _BV(AIN1D) | _BV(AIN0D); // Digital input disable on analog pins
 61    // Timer 0 isn't disabled yet...it's needed for one thing first...
 62  
 63    // The aforementioned watchdog timer calibration...
 64    uint32_t t = millis();                       // Save start time
 65    noInterrupts();                              // Timing-critical...
 66    MCUSR &= ~_BV(WDRF);                         // Watchdog reset flag
 67    WDTCR  =  _BV(WDCE) | _BV(WDE);              // WDT change enable
 68    WDTCR  =  _BV(WDIE) | _BV(WDP3) | _BV(WDP0); // 8192-ish ms interval
 69    interrupts();
 70    while(sleepTime);                            // Wait for WDT
 71    maxSleepInterval  = millis() - t;            // Actual ms elapsed
 72    maxSleepInterval += 64;                      // Egyptian constant
 73    power_timer0_disable();  // Knocks out millis(), delay(), analogWrite()
 74  }
 75  
 76  const uint32_t offTime = interval - onTime; // Duration motor is off, ms
 77  
 78  void loop() {
 79    pinMode(1, INPUT);        // LED off
 80    ubersleep(offTime);       // Delay while off
 81  }
 82  
 83  // WDT timer operates only in specific intervals based on a prescaler.
 84  // CPU wakes on each interval, prescaler is adjusted as needed to pick off
 85  // the longest setting possible on each pass, until requested milliseconds
 86  // have elapsed.
 87  const uint8_t cfg[] PROGMEM = { // WDT config bits for different intervals
 88    _BV(WDIE) | _BV(WDP3) |                         _BV(WDP0), // ~8192 ms
 89    _BV(WDIE) | _BV(WDP3)                                    , // ~4096 ms
 90    _BV(WDIE) |             _BV(WDP2) | _BV(WDP1) | _BV(WDP0), // ~2048 ms
 91    _BV(WDIE) |             _BV(WDP2) | _BV(WDP1)            , // ~1024 ms
 92    _BV(WDIE) |             _BV(WDP2) |             _BV(WDP0), //  ~512 ms
 93    _BV(WDIE) |             _BV(WDP2)                        , //  ~256 ms
 94    _BV(WDIE) |                         _BV(WDP1) | _BV(WDP0), //  ~128 ms
 95    _BV(WDIE) |                         _BV(WDP1)            , //   ~64 ms
 96    _BV(WDIE) |                                     _BV(WDP0), //   ~32 ms
 97    _BV(WDIE)                                                  //   ~16 ms
 98  }; // Remember, WDT clock is uncalibrated, times are "ish"
 99  
100  void ubersleep(uint32_t ms) {
101    if(ms == 0) return;
102    tablePos      = 0;                   // Reset WDT config stuff to
103    sleepInterval = maxSleepInterval;    // longest interval to start
104    configWDT(ms);                       // Set up for requested time
105    set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Deepest sleep mode
106    sleep_enable();
107    while(sleepTime && (tablePos < sizeof(cfg))) sleep_mode();
108    noInterrupts();                      // WDT off (timing critical)...
109    MCUSR &= ~_BV(WDRF);
110    WDTCR  = 0;
111    interrupts();
112  }
113  
114  static void configWDT(uint32_t newTime) {
115    sleepTime = newTime; // Total sleep time remaining (ms)
116    // Find next longest WDT interval that fits within remaining time...
117    while(sleepInterval > newTime) {
118      sleepInterval /= 2;                          // Each is 1/2 previous
119      if(++tablePos >= sizeof(cfg)) return;        // No shorter intervals
120    }
121    uint8_t bits = pgm_read_byte(&cfg[tablePos]);  // WDT config bits for time
122    noInterrupts();                                // Timing-critical...
123    MCUSR &= ~_BV(WDRF);
124    WDTCR  =  _BV(WDCE) | _BV(WDE);                // WDT change enable
125    WDTCR  =  bits;                                // Interrupt + prescale
126    interrupts();
127  }
128  
129  ISR(WDT_vect) { // Watchdog timeout interrupt
130    configWDT(sleepTime - sleepInterval); // Subtract, setup next cycle...
131  }