part two:
// ONE-TIME INITIALIZATION -------------------------------------------------
void setup() {
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000L)
clock_prescale_set(clock_div_1); // Allow 16 MHz Trinket too
#endif
#ifdef POWER_PIN
pinMode(POWER_PIN, OUTPUT);
digitalWrite(POWER_PIN, LOW); // Power-on LED strip
#endif
strip.begin(); // Allocate NeoPixel buffer
strip.clear(); // Make sure strip is clear
strip.show(); // before measuring battery
#ifdef BATT_LVL_PIN
// Battery monitoring code does some low-level Gemma-specific stuff...
int i, prev;
uint8_t count;
uint16_t mV;
pinMode(BATT_LVL_PIN, INPUT); // No pullup
// Select AREF (PB0) voltage reference + Bandgap (1.8V) input
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2); // AREF, Bandgap input
ADCSRA = _BV(ADEN) | // Enable ADC
_BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); // 1/128 prescale (125 KHz)
delay(1); // Allow 1 ms settling time as per datasheet
// Bandgap readings may still be settling. Repeated readings are
// taken until four concurrent readings stabilize within 5 mV.
for(prev=9999, count=0; count<4; ) {
for(ADCSRA |= _BV(ADSC); ADCSRA & _BV(ADSC); ); // Start, await ADC conv
i = ADC; // Result
mV = i ? (1100L * 1023 / i) : 0; // Scale to millivolts
if(abs((int)mV - prev) <= 5) count++; // +1 stable reading
else count = 0; // too much change, start over
prev = mV;
}
ADCSRA = 0; // ADC off
mV *= 2; // Because 50% resistor voltage divider (to work w/3.3V MCU)
uint8_t lvl = (mV >= BATT_MAX_MV) ? NUM_LEDS : // Full (or nearly)
(mV <= BATT_MIN_MV) ? 1 : // Drained
1 + ((mV - BATT_MIN_MV) * NUM_LEDS + (NUM_LEDS / 2)) /
(BATT_MAX_MV - BATT_MIN_MV + 1); // # LEDs lit (1-NUM_LEDS)
for(uint8_t i=0; i<lvl; i++) { // Each LED to batt level
uint8_t g = (i * 5 + 2) / NUM_LEDS; // Red to green
strip.setPixelColor(i, 4-g, g, 0);
strip.show(); // Animate a bit
delay(250 / NUM_LEDS);
}
delay(1500); // Hold last state a moment
strip.clear(); // Then clear strip
strip.show();
randomSeed(mV);
#else
randomSeed(analogRead(2));
#endif // BATT_LVL_PIN
// Assign random starting colors to waves
for(uint8_t w=0; w<N_WAVES; w++) {
wave[w].hue = wave[w].hueTarget = 90 + random(90);
wave[w].r = h2rgb(wave[w].hue - 30);
wave[w].g = h2rgb(wave[w].hue);
wave[w].b = h2rgb(wave[w].hue + 30);
}
// Configure motion pin for change detect & interrupt
pinMode(MOTION_PIN, INPUT_PULLUP);
PCMSK = _BV(MOTION_PIN); // Pin change interrupt mask
GIFR = 0xFF; // Clear interrupt flags
// Interrupt is not actually enabled yet, that's in sleep function...
sleep(); // Sleep until motion is detected
}
// MAIN LOOP ---------------------------------------------------------------
uint32_t prevFrameTime = 0L; // Used for animation timing
uint8_t brightness = 0; // Current wave height
boolean rampingUp = false; // If true, brightness is increasing
void loop() {
uint32_t t;
uint16_t r, g, b, m, n;
uint8_t i, x, w, d1, d2, y;
// Pause until suitable interval since prior frame has elapsed.
// This is preferable to delay(), as the time to render each frame
// can vary.
while(((t = micros()) - prevFrameTime) < (1000000L / FPS));
// Immediately show results calculated on -prior- pass,
// so frame-to-frame timing is consistent. Then render next frame.
strip.show();
prevFrameTime = t; // Save frame update time for next pass
if(GIFR & _BV(PCIF)) { // Pin change detected?
rampingUp = true; // Set brightness-ramping flag
GIFR = 0xFF; // Clear interrupt masks
}
// Okay, here's where the animation starts to happen...
// First, check the 'rampingUp' flag. If set, this indicates that
// the vibration switch was activated recently, and the LEDs should
// increase in brightness. If clear, the LEDs ramp down.
if(rampingUp) {
// But it's not just a straight shot that it ramps up. This is a
// low-pass filter...it makes the brightness value decelerate as it
// approaches a target (200 in this case). 207 is used here because
// integers round down on division and we'd never reach the target;
// it's an ersatz ceil() function: ((199*7)+200+7)/8 = 200;
brightness = ((brightness * 7) + 207) / 8;
// Once max brightness is reached, switch off the rampingUp flag.
if(brightness >= 200) rampingUp = false;
} else {
// If not ramping up, we're ramping down. This too uses a low-pass
// filter so it eases out, but with different constants so it's a
// slower fade. Also, no addition here because we want it rounding
// down toward zero...
if(!(brightness = (brightness * 15) / 16)) { // Hit zero?
sleep(); // Turn off animation
return; // Start over at top of loop() on wake
}
}
// Wave positions and colors are updated...
for(w=0; w<N_WAVES; w++) {
wave[w].center += wave[w].speed; // Move wave; wraps around ends, is OK!
if(wave[w].hue == wave[w].hueTarget) { // Hue not currently changing?
// There's a tiny random chance of picking a new hue...
if(!random(FPS * 4)) {
// Within 1/3 color wheel
wave[w].hueTarget = random(wave[w].hue - 30, wave[w].hue + 30);
}
} else { // This wave's hue is currently shifting...
if(wave[w].hue < wave[w].hueTarget) wave[w].hue++; // Move up or
else wave[w].hue--; // down as needed
if(wave[w].hue == wave[w].hueTarget) { // Reached destination?
wave[w].hue = 90 + wave[w].hue % 90; // Clamp to 90-180 range
wave[w].hueTarget = wave[w].hue; // Copy to target
}
wave[w].r = h2rgb(wave[w].hue - 30);
wave[w].g = h2rgb(wave[w].hue);
wave[w].b = h2rgb(wave[w].hue + 30);
}
}