/ 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 }