Skip to content

Instantly share code, notes, and snippets.

@EvanBalster
Last active July 10, 2023 22:16
Show Gist options
  • Save EvanBalster/4ea8642c582752d03ae654206851c580 to your computer and use it in GitHub Desktop.
Save EvanBalster/4ea8642c582752d03ae654206851c580 to your computer and use it in GitHub Desktop.
Tea timer for circuit playground. RB cycles steeps & green/black, LB starts timer. When idle, lights auto-dim.
#include <Adafruit_CircuitPlayground.h>
const long FrameDT = 16;
unsigned long frame_index = 0;
unsigned long frame_mark = 0;
struct Timer
{
long started;
long length;
Timer()
{
reset();
}
long remaining()
{
return length - (millis()-started);
}
bool done()
{
return started && remaining() <= 0;
}
void begin(long length)
{
started = millis();
this->length = length;
}
void reset()
{
started = 0;
length = 0;
}
};
Timer countdown;
Timer alarm;
long last_moved = 0;
enum TeaType
{
SENCHA = 0,
BLACK = 1,
MAX_TEA_TYPE = 2
};
TeaType teaType = SENCHA;
long resteeps = 0;
struct Accel
{
float x, y, z;
long time;
void readSensor()
{
x = CircuitPlayground.motionX();
y = CircuitPlayground.motionY();
z = CircuitPlayground.motionZ();
time = millis();
}
};
Accel gravity;
static uint16_t NeoPixel_Gamma_x128[256];
static float NeoPixel_BrightnessMod = 1.f;
// the setup function runs once when you press reset or power the board
void setup() {
// initialize digital pin 13 as an output.
//pinMode(13, OUTPUT);
// Buttons
//pinMode(A0, OUTPUT);
//pinMode(4, INPUT); pinMode(5, INPUT);
//pinMode(PA30, OUTPUT);
//digitalWrite(PA30, HIGH);
CircuitPlayground.begin();
// Calculate 16-bit gamma table...
for (unsigned i = 0; i < 255; ++i)
{
NeoPixel_Gamma_x128[i] = uint16_t(pow(i/255.f,2.6f)*32640.f+0.5f);
}
CircuitPlayground.setBrightness(255);
teaType = MAX_TEA_TYPE;
resteeps = 3;
refreshPixels();
}
uint32_t pcg16_state = 0x406832dd;
uint16_t pcg16_random(void) {
uint32_t oldstate = pcg16_state;
pcg16_state = pcg16_state * 747796405U + 1U;
uint16_t value = ((oldstate >> 10U) ^ oldstate) >> 12U;
uint32_t rot = oldstate >> 28U;
return (value >> rot) | (value << ((- rot) & 15));
}
void setPixel(uint16_t n, uint16_t r, uint16_t g, uint16_t b, uint16_t w = 0)
{
// Convert RGB into linear brightness.
r = NeoPixel_Gamma_x128[min(r, uint16_t(255))];
g = NeoPixel_Gamma_x128[min(g, uint16_t(255))];
b = NeoPixel_Gamma_x128[min(b, uint16_t(255))];
w = NeoPixel_Gamma_x128[min(w, uint16_t(255))];
// Brightness scaling.
float brightness = min(NeoPixel_BrightnessMod, 2.0f);
r = uint16_t(r*brightness + .5f);
g = uint16_t(g*brightness + .5f);
b = uint16_t(b*brightness + .5f);
w = uint16_t(w*brightness + .5f);
// Dither randomly within one ULP of brightness (128)
unsigned spinDither = 2*frame_index + 3*n + 3*(n>=5);
unsigned rand = pcg16_random();
r += 2 + 2*(spinDither++&15) + 2*( rand &15);
g += 2 + 2*(spinDither++&15) + 2*((rand>> 4)&15);
b += 2 + 2*(spinDither++&15) + 2*((rand>> 8)&15);
w += 2 + 2*(spinDither++&15) + 2*((rand>>12)&15);
static const uint16_t L_MAX = (128*255), L_MIN = (255);
// Excess RGB overflows into white.
if (r > 32640) {w += (r-L_MAX)/3; r = L_MAX;}
if (g > 32640) {w += (g-L_MAX)/2; g = L_MAX;}
if (b > 32640) {w += (g-L_MAX)/4; b = L_MAX;}
if (w > 32640) w = L_MAX;
CircuitPlayground.setPixelColor(n, uint8_t(r>>7), uint8_t(g>>7), uint8_t(b>>7)); //uint8_t(w>>7)
}
void setPixel(uint16_t n, uint32_t color)
{
setPixel(n,
((color>>16)&255),
((color>>8)&255),
(color&255),
(color>>24));
}
void refreshPixels()
{
// Calculate brightness adjustment.
float brightness = 1.f;
if (alarm.started)
{
float pulse = .5f + .5f * sin(TWO_PI * countdown.remaining()/500.f);
brightness = pulse*pulse*2.0f;
}
else if (countdown.started)
{
float pulse = .5f + .5f * sin(TWO_PI * countdown.remaining()/5000.f);
pulse = pulse*pulse*pulse;
brightness = .07f + .07f * pulse;
}
else
{
brightness = 2.f - (millis() - last_moved) / 10000.f;
if (brightness > 1.f) brightness = 1.f;
if (brightness < 0.f) brightness = 0.f;
brightness *= .1f;
}
NeoPixel_BrightnessMod = brightness;
if (countdown.started)
{
float progress = 1.0f - float(countdown.remaining()) / float(countdown.length);
for (long i = 0; i < 10; ++i)
{
float fill = (progress-.1f*float(i))/.1f;
if (fill < 0.f) fill = 0.f;
if (fill > 1.f) fill = 1.f;
setPixel(i, 192.f * fill, 127.f, 192.f - 192.f*fill);
}
}
else
{
switch (teaType)
{
case SENCHA:
setPixel(0, 0x007f5f);
setPixel(1, 0x2b9348);
setPixel(2, 0x55a630);
setPixel(3, 0xaacc00);
setPixel(4, 0x80b918);
break;
case BLACK:
setPixel(0, 0x03071e);
setPixel(1, 0x370617);
setPixel(2, 0xdc2f02);
setPixel(3, 0xf48c06);
setPixel(4, 0xe85d04);
break;
default:
setPixel(0, 0x7F007F);
setPixel(1, 0x550055);
setPixel(2, 0xBB00BB);
setPixel(3, 0x550055);
setPixel(4, 0x7F007F);
break;
}
for (long i = 0; i < 5; ++i)
{
setPixel(5+i, (resteeps > i) ? 0xfdd85d : 0x6798c0);
}
}
}
PinStatus b_right_prev = LOW, b_left_prev = LOW;
static const long ARP_PATTERN[8] = {440, 550, 660, 1600, 880, 1320, 1100, 880};
long teaPower(long base, long resteeps)
{
long value = base << (resteeps>>1);
if (resteeps & 1) {value = (17*value) / 12;}
return value;
}
// the loop function runs over and over again forever
void loop() {
PinStatus b_left = digitalRead(CPLAY_LEFTBUTTON);
PinStatus b_right = digitalRead(CPLAY_RIGHTBUTTON);
bool p_left = (b_left && !b_left_prev);
bool p_right = (b_right && !b_right_prev);
if (countdown.started)
{
if (p_right) // Cancels the alarm.
{
p_left = false;
p_right = false;
countdown.reset();
alarm.begin(250);
}
refreshPixels();
}
else
{
if (teaType == MAX_TEA_TYPE)
{
// Just started loop
teaType = SENCHA;
resteeps = 0;
gravity.readSensor();
last_moved = millis();
}
if (p_right)
{
resteeps += 1;
if (resteeps > 5)
{
resteeps = 0;
teaType = TeaType((long(teaType)+1) % MAX_TEA_TYPE);
// Beep!
refreshPixels();
tone(A0, 440); delay(50);
tone(A0, 330); delay(50);
tone(A0, 660); delay(50);
tone(A0, 880); delay(100);
noTone(A0);
}
else
{
// Beep!
refreshPixels();
tone(A0, teaPower(275, 5-resteeps)); delay(50);
tone(A0, teaPower(220, 5-resteeps)); delay(50);
noTone(A0);
}
}
refreshPixels();
if (p_left)
{
long baseTime = 60;
switch (teaType)
{
case SENCHA: baseTime = 60; break;
case BLACK: baseTime = 120; break;
}
long upscale = resteeps;
while (upscale > 1) {baseTime *= 2; upscale -= 2;}
if (upscale == 1) {baseTime = (17*baseTime) / 12;}
countdown.begin(1000 * baseTime);
// Beep!
for (int i = 0; i < 10; ++i) setPixel(i, 0x7F00FF00);
tone(A0, 880); delay(50);
tone(A0, 1320); delay(50);
tone(A0, 1760); delay(100);
noTone(A0);
}
}
{
Accel accel;
accel.readSensor();
float dtf = (accel.time - gravity.time) / 1000.f;
float dx = accel.x-gravity.x, dy = accel.y-gravity.y, dz = accel.z-gravity.z;
if (sqrt(dx*dx+dy*dy+dz*dz)/dtf > 150.f)
{
last_moved = millis();
}
float a = pow(.95f, dtf);
gravity.x += a*dx;
gravity.y += a*dy;
gravity.z += a*dz;
gravity.time = accel.time;
}
if (countdown.done())
{
resteeps += 1;
alarm.begin(5000);
countdown.reset();
}
static unsigned long alarm_arp = 0;
long delay_this_loop = FrameDT;
if (alarm.started)
{
if (alarm.done() || p_left || p_right)
{
p_left = false;
p_right = false;
alarm.reset();
noTone(A0);
}
else
{
tone(A0, ARP_PATTERN[alarm_arp&7]);
++alarm_arp;
delay_this_loop = 40;
}
}
else
{
alarm_arp = 0;
if (countdown.started)
{
// tick, tock...
digitalWrite(A0, (countdown.remaining()/1000) & 1);
}
else
{
noTone(A0);
}
}
/*if (alarm.started)
{
digitalWrite(CPLAY_REDLED, alarm.remaining() % 250 < 125);
}
else if (countdown.started)
{
digitalWrite(CPLAY_REDLED, countdown.remaining() % 1000 < 100);
}*/
// Simple framerate regulator
{
unsigned long frame_cur = millis();
unsigned long frame_len = (frame_cur - frame_mark);
if (delay_this_loop > frame_len)
{
//digitalWrite(CPLAY_REDLED, 0);
delay(delay_this_loop - frame_len);
}
//else digitalWrite(CPLAY_REDLED, 1);
++frame_index;
frame_mark = frame_cur;
}
b_right_prev = b_right;
b_left_prev = b_left;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment