Skip to content

Instantly share code, notes, and snippets.

@t413
Last active November 2, 2017 19:10
Show Gist options
  • Save t413/6463677f4828409934d34309b4ce84ba to your computer and use it in GitHub Desktop.
Save t413/6463677f4828409934d34309b4ce84ba to your computer and use it in GitHub Desktop.
#include <FastLED.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_LEDBackpack.h>
#include <Encoder.h>
#include <avr/sleep.h>
#include <avr/power.h>
#define PXLS_CNT 10+3
#define encoderBtnPin 3
#define encoderPinA 4
#define encoderPinB 5
#define LED_PIN 6
Encoder encoder(encoderPinA, encoderPinB);
#define ENC_SCALE 3
#define EASTER_EGG_ENC 133
CRGB pixels[PXLS_CNT];
Adafruit_AlphaNum4 alpha4;
char displayBuffer[4] = {' ', ' ', ' ', ' '};
void segprint(String s) {
s.toUpperCase();
for (uint8_t i=0; i < 4 && i < s.length(); i++)
displayBuffer[i] = s.charAt(i);
}
void segprint(int num) {
sprintf(displayBuffer + 1, "%3i", num);
}
const uint8_t segChars[] = {
0b00111111, // 0
0b00000110, // 1
0b01011011, // 2
0b01001111, // 3
0b01100110, // 4
0b01101101, // 5
0b01111101, // 6
0b00000111, // 7
0b01111111, // 8
0b01101111, // 9
0b00000000, // :
0b00000000, // ;
0b00000000, // <
0b01001000, // =
0b00000000, // >
0b00000011, // ?
0b00111011, // @
0b01110111, // A
0b01111100, // b
0b00111001, // C
0b01011110, // d
0b01111001, // E
0b01110001, // F
0b00111101, // G
0b01110110, // H
0b00110000, // I
0b00011110, // J
0b01110000, // K
0b00111000, // L
0b00110110, // M
0b00110110, // N
0b00111111, // O
0b01110011, // P
0b00111111, // Q
0b01110011, // R
0b01101101, // S
0b00000111, // T
0b00111110, // U
0b00110000, // V
0b00110110, // W
0b00000000, // X
0b00000000, // Y
0b00001001 // Z
};
void alpha4Write() {
for (uint8_t i=0; i < 4; i++) {
char c = displayBuffer[i];
uint8_t d = segChars[constrain(c - '0', 0, sizeof(segChars))];
if (c == ' ') d = 0;
alpha4.writeDigitRaw((i < 2)? i : i + 1, d);
}
}
int8_t brightness = 3;
void setup() {
alpha4.begin(0x70);
pinMode(encoderBtnPin, INPUT); digitalWrite(encoderBtnPin, HIGH);
pinMode(13, OUTPUT); //onboard led
randomSeed(analogRead(2));
LEDS.addLeds<WS2812,LED_PIN,GRB>(pixels, PXLS_CNT);
setBrightness(brightness);
encoder.write(137 * ENC_SCALE);
}
void setBrightness(uint8_t level) { //sets leds and characters to value 0-7
LEDS.setBrightness(map(level, 0, 7, 5, 220));
alpha4.setBrightness(level * 3 / 2);
}
void loop() {
uint8_t baseColor = 105;
uint8_t lastBtn = 0;
uint32_t btnDown = 0;
int16_t btnDownEnc = 0;
int16_t oldEncPos = 0;
uint8_t displayOverride = 0, overrideClick = 0; //1=brightness 2=off display
uint32_t pulse_begin = 0;
uint16_t pulse_length = 400, recover_len = 1000;
uint8_t clrs[] = {0,0,0,0,0,0,0,0, 32,192,110,60,160,105,000,64,224,32 }; //half set, half random
for (uint8_t i=0; i < 8; i++) clrs[i] = random(0, 255);
uint16_t vccMeasured = 0;
while(1) {
const uint32_t current = millis();
displayBuffer[0] = displayBuffer[1] = displayBuffer[2] = displayBuffer[3] = ' ';
int16_t encoderPos = encoder.read() / ENC_SCALE;
/// --------------------- ///
/// -- Button Handling -- ///
/// --------------------- ///
const uint8_t btn = !digitalRead(encoderBtnPin);
digitalWrite(13, btn);
if (btn && !lastBtn) { //mark the state!
if (current - btnDown < 300) { /// -- double click
encoderPos += (random(0, 3) > 0)? random(8, 120) : -random(8, 40);
encoder.write(encoderPos * ENC_SCALE);
overrideClick = true;
pulse_begin = 0;
}
btnDown = current;
btnDownEnc = encoderPos;
}
if (!btn && !btnDown) { //intentional stub, do nothing!
} else if (!btn && lastBtn && (current - btnDown) < 1000) { /// -- short press (on release)
if (overrideClick) {
overrideClick = false;
} else if (btnDownEnc == EASTER_EGG_ENC) {
easterEgg();
btnDownEnc = encoderPos = EASTER_EGG_ENC + 1;
displayOverride = 1; //restores more state later
} else {
recover_len = (current > (pulse_begin + 3*pulse_length + recover_len))? 1000 : 2000;
pulse_begin = current;
}
} else if (btn && (current - btnDown) > 700 && (displayOverride != 1)) { /// -- long hold (immediate action)
//animate thre reset-counting up/down to reality C147
encoderPos = map(constrain(current - btnDown, 700, 1100), 700, 1100, btnDownEnc, 137);
encoder.write(encoderPos * ENC_SCALE);
if (current - btnDown > 1500) { //animation for turning off
displayOverride = 2;
String off("off ");
segprint(off.substring(0, map(current - btnDown, 1300, 2500, 0, 3)));
if (current - btnDown > 2500) {
LEDS.setBrightness(0); FastLED.show();
while (!digitalRead(encoderBtnPin)) delay(10); //wait for button release
while (true) { //sleep loop, requires a longer press wakeup
doSleep();
uint32_t start = millis();
while (!digitalRead(encoderBtnPin)) delay(1); //wait for button release
if (millis() - start > 20)
break;
}
if (vccMeasured = readVcc() < 3500) { //low battery warning!
segprint("bATT");
delay(750);
segprint(vccMeasured); //show voltage readout
delay(2000);
}
return; //restarts the while loop
}
}
} else if (btn && !displayOverride && btnDownEnc != encoderPos) { /// -- button down *while turning*
displayOverride = 1;
} else if (btn && (displayOverride == 1)) {
brightness = constrain(3 + ((int)encoderPos - (int)btnDownEnc), -2, 7);
setBrightness((brightness >= 0)? brightness : 3);
if (brightness == -1) { //show 'bATT'
segprint("BATT");
} else if (brightness == -2) {
if (!vccMeasured) vccMeasured = readVcc();
segprint(vccMeasured); //show voltage readout
} else {
segprint(brightness);
segprint("B");
}
}
if (!btn && displayOverride) { //finished updating brightness
if (displayOverride != 2)
encoder.write(((encoderPos = btnDownEnc)) * ENC_SCALE); //restore encoder position
displayOverride = oldEncPos = vccMeasured = 0;
}
lastBtn = btn;
/// ---------------------- ///
/// -- Encoder Handling -- ///
/// ---------------------- ///
bool renderSeg = displayOverride;
if (oldEncPos != encoderPos && !displayOverride) {
renderSeg = true;
oldEncPos = encoderPos;
char dimensionLetter = (encoderPos / 10 - 1) % 10 + 'A';
baseColor = clrs[(encoderPos / 10) % sizeof(clrs)]; //105 + (dimensionLetter - 12) * 75;
displayBuffer[0] = dimensionLetter;
segprint(encoderPos);
}
if (renderSeg) {
alpha4.clear();
alpha4Write();
alpha4.writeDisplay();
}
#define cap8b(a) (constrain((a), 0, 255))
/// --------------- ///
/// -- LED Magic -- ///
/// --------------- ///
int16_t spotlight_cycle = (current >> 4) & 0xFF;
int16_t pulse = 0, recovery = 0; //quadwave8((current >> 2)) / 4;
//button press effects
if (pulse_begin && current < (pulse_begin + pulse_length)) {
pulse = cap8b(map(current - pulse_begin, 0, pulse_length, 0, 240));
} else if (pulse_begin && current < (pulse_begin + recover_len)) { //button press effect recovery
recovery = cap8b(map(current - pulse_begin, pulse_length, recover_len, 200, 0));
}
for (uint8_t w = 0; w < 3; w++) { //front three
int16_t spotlight_sin = quadwave8(spotlight_cycle + 255/w) / 2 + 128;
pixels[w] = CHSV(baseColor, 255 - pulse, cap8b(spotlight_sin + pulse - recovery/2));
}
for (uint8_t w = 3; w < PXLS_CNT; w++) {
int16_t spotlight_sin = quadwave8(spotlight_cycle + w*32) / 2 + 128;
pixels[w] = CHSV(baseColor, 255 - pulse, cap8b(spotlight_sin - recovery));
}
FastLED.show();
}
}
void easterEgg() {
uint32_t start = millis(), current = 0;
while ((current = millis()) < (start + 1000) || digitalRead(encoderBtnPin)) {
const int16_t cycle = (current >> 2) & 0xFF;
int16_t encoderPos = encoder.read() / ENC_SCALE;
if (millis() & 1) {
const uint8_t scycle = current >> 6;
alpha4.clear();
alpha4.writeDigitRaw((encoderPos % 4 < 2)? encoderPos % 4 : encoderPos % 4 + 1, (1 << (scycle % 6)) );
alpha4.writeDisplay();
}
#define roll8b(a) ((a) & 0xff)
for (int16_t w = 0; w < 3; w++) { //front three
pixels[w] = CHSV(roll8b(cycle + w * 82), 255, 255);
}
for (int16_t w = 3; w < PXLS_CNT; w++) {
pixels[w] = CHSV(roll8b(cycle + w * 8), 255, 255);
}
FastLED.show();
}
while (!digitalRead(encoderBtnPin))
delay(10); //wait for button release
}
void woken() { }
void doSleep() {
alpha4.clear();
alpha4.setBrightness(0);
alpha4.writeDisplay();
FastLED.show();
pinMode(13, INPUT); //onboard led
LEDS.setBrightness(0);
for (uint8_t w = 0; w < PXLS_CNT; w++) pixels[w] = 0;
FastLED.show();
attachInterrupt(digitalPinToInterrupt(encoderBtnPin), woken, FALLING);
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Set sleep mode.
sleep_enable(); // Enable sleep mode.
sleep_mode(); // Enter sleep mode.
detachInterrupt(digitalPinToInterrupt(encoderBtnPin));
setup();
}
long readVcc() {
long result;
// Read 1.1V reference against AVcc
#if defined(__AVR_AT90USB1286__) || defined(__AVR_ATmega32U4__)
ADMUX = _BV(REFS0) | 0b011110;
#else
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif
delay(2); // Wait for Vref to settle
ADCSRA |= _BV(ADSC); // Convert
while (bit_is_set(ADCSRA,ADSC));
result = ADCL;
result |= ADCH<<8;
result = 1126400L / result; // Back-calculate AVcc in mV 1126400L = (1023L * 1000 * 1.1)
return result;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment