Skip to content

Instantly share code, notes, and snippets.

@EvanBalster
Last active July 10, 2023 22:16
Show Gist options
  • Select an option

  • Save EvanBalster/4ea8642c582752d03ae654206851c580 to your computer and use it in GitHub Desktop.

Select an option

Save EvanBalster/4ea8642c582752d03ae654206851c580 to your computer and use it in GitHub Desktop.

Revisions

  1. EvanBalster revised this gist Jul 10, 2023. 1 changed file with 185 additions and 79 deletions.
    264 changes: 185 additions & 79 deletions CircuitPlayground_TeaTimer.cpp
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,12 @@
    #include <Adafruit_CircuitPlayground.h>


    const long FrameDT = 16;

    unsigned long frame_index = 0;
    unsigned long frame_mark = 0;


    struct Timer
    {
    long started;
    @@ -44,9 +51,6 @@ TeaType teaType = SENCHA;
    long resteeps = 0;


    const long DT = 8;


    struct Accel
    {
    float x, y, z;
    @@ -63,6 +67,9 @@ struct Accel
    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() {
    @@ -78,44 +85,66 @@ void setup() {

    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)
    {
    float brightness = 1.f;
    if (alarm.started)
    {
    float pulse = .8f + .8f * sin(TWO_PI * countdown.remaining()/500.f);
    brightness = pulse;
    }
    else if (countdown.started)
    {
    float pulse = .5f + .5f * sin(TWO_PI * countdown.remaining()/5000.f);
    pulse = pulse*pulse*pulse;
    brightness = .7f + .3f * pulse;
    }
    else
    {
    brightness = 2.f - (millis() - last_moved) / 10000.f;
    if (brightness > 1.f) brightness = 1.f;
    if (brightness < 0.f) brightness = 0.f;
    }

    r = uint16_t(r*brightness);
    g = uint16_t(g*brightness);
    b = uint16_t(b*brightness);
    w = uint16_t(w*brightness);

    // Excess RGB overflow into white.
    if (r > 255) {w += (r-253)/3; r = 255;}
    if (g > 255) {w += (g-254)/2; g = 255;}
    if (b > 255) {w += (g-252)/4; b = 255;}
    if (w > 255) w = 255;

    CircuitPlayground.setPixelColor(n, Adafruit_CPlay_NeoPixel::gamma32(Adafruit_CPlay_NeoPixel::Color(r, g, b, w)));

    // 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)
    @@ -129,45 +158,68 @@ void setPixel(uint16_t n, uint32_t color)

    void refreshPixels()
    {
    switch (teaType)
    // Calculate brightness adjustment.
    float brightness = 1.f;
    if (alarm.started)
    {
    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;
    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 < 5; ++i)
    for (long i = 0; i < 10; ++i)
    {
    float fill = (progress-.2f*float(i))/.2f;
    float fill = (progress-.1f*float(i))/.1f;
    if (fill < 0.f) fill = 0.f;
    if (fill > 1.f) fill = 1.f;
    setPixel(5+i, 192.f * fill, 127.f, 192.f - 192.f*fill);
    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);
    @@ -180,6 +232,14 @@ 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);
    @@ -188,7 +248,19 @@ void loop() {
    bool p_left = (b_left && !b_left_prev);
    bool p_right = (b_right && !b_right_prev);

    if (!countdown.started)
    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)
    {
    @@ -211,14 +283,17 @@ void loop() {
    refreshPixels();
    tone(A0, 440); delay(50);
    tone(A0, 330); delay(50);
    tone(A0, 220); delay(100);
    tone(A0, 660); delay(50);
    tone(A0, 880); delay(100);
    noTone(A0);
    }
    else
    {
    // Beep!
    refreshPixels();
    tone(A0, 440); delay(50);
    tone(A0, 550); delay(50);
    tone(A0, teaPower(275, 5-resteeps)); delay(50);
    tone(A0, teaPower(220, 5-resteeps)); delay(50);
    noTone(A0);
    }

    }
    @@ -238,21 +313,16 @@ void loop() {
    while (upscale > 1) {baseTime *= 2; upscale -= 2;}
    if (upscale == 1) {baseTime = (17*baseTime) / 12;}

    resteeps += 1;

    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);
    }
    }
    else
    {
    refreshPixels();
    }

    {
    Accel accel;
    @@ -264,11 +334,6 @@ void loop() {
    if (sqrt(dx*dx+dy*dy+dz*dz)/dtf > 150.f)
    {
    last_moved = millis();
    digitalWrite(CPLAY_REDLED, 1);
    }
    else
    {
    digitalWrite(CPLAY_REDLED, 0);
    }

    float a = pow(.95f, dtf);
    @@ -281,19 +346,46 @@ void loop() {

    if (countdown.done())
    {
    resteeps += 1;

    alarm.begin(5000);
    countdown.reset();
    }

    static unsigned long alarm_arp = 0;

    long delay_this_loop = FrameDT;

    if (alarm.started)
    {
    unsigned long arp = alarm.remaining() / 96;
    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;

    tone(A0, ARP_PATTERN[7-(arp&7)]);

    if (alarm.done()) alarm.reset();
    if (countdown.started)
    {
    // tick, tock...
    digitalWrite(A0, (countdown.remaining()/1000) & 1);
    }
    else
    {
    noTone(A0);
    }
    }
    else noTone(A0);

    /*if (alarm.started)
    {
    @@ -304,7 +396,21 @@ void loop() {
    digitalWrite(CPLAY_REDLED, countdown.remaining() % 1000 < 100);
    }*/

    delay(DT);
    // 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;
  2. EvanBalster revised this gist Jul 4, 2023. 1 changed file with 35 additions and 7 deletions.
    42 changes: 35 additions & 7 deletions CircuitPlayground_TeaTimer.cpp
    Original file line number Diff line number Diff line change
    @@ -83,12 +83,12 @@ void setup() {
    refreshPixels();
    }

    void setPixel(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0)
    void setPixel(uint16_t n, uint16_t r, uint16_t g, uint16_t b, uint16_t w = 0)
    {
    float brightness = 1.f;
    if (alarm.started)
    {
    float pulse = .5f + .5f * sin(TWO_PI * countdown.remaining()/500.f);
    float pulse = .8f + .8f * sin(TWO_PI * countdown.remaining()/500.f);
    brightness = pulse;
    }
    else if (countdown.started)
    @@ -104,10 +104,16 @@ void setPixel(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0)
    if (brightness < 0.f) brightness = 0.f;
    }

    r = uint8_t(r*brightness);
    g = uint8_t(g*brightness);
    b = uint8_t(b*brightness);
    w = uint8_t(w*brightness);
    r = uint16_t(r*brightness);
    g = uint16_t(g*brightness);
    b = uint16_t(b*brightness);
    w = uint16_t(w*brightness);

    // Excess RGB overflow into white.
    if (r > 255) {w += (r-253)/3; r = 255;}
    if (g > 255) {w += (g-254)/2; g = 255;}
    if (b > 255) {w += (g-252)/4; b = 255;}
    if (w > 255) w = 255;

    CircuitPlayground.setPixelColor(n, Adafruit_CPlay_NeoPixel::gamma32(Adafruit_CPlay_NeoPixel::Color(r, g, b, w)));
    }
    @@ -200,7 +206,21 @@ void loop() {
    {
    resteeps = 0;
    teaType = TeaType((long(teaType)+1) % MAX_TEA_TYPE);

    // Beep!
    refreshPixels();
    tone(A0, 440); delay(50);
    tone(A0, 330); delay(50);
    tone(A0, 220); delay(100);
    }
    else
    {
    // Beep!
    refreshPixels();
    tone(A0, 440); delay(50);
    tone(A0, 550); delay(50);
    }

    }

    refreshPixels();
    @@ -218,7 +238,15 @@ void loop() {
    while (upscale > 1) {baseTime *= 2; upscale -= 2;}
    if (upscale == 1) {baseTime = (17*baseTime) / 12;}

    resteeps += 1;

    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);
    }
    }
    else
    @@ -233,7 +261,7 @@ void loop() {

    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 > 100.f)
    if (sqrt(dx*dx+dy*dy+dz*dz)/dtf > 150.f)
    {
    last_moved = millis();
    digitalWrite(CPLAY_REDLED, 1);
  3. EvanBalster created this gist Jul 3, 2023.
    283 changes: 283 additions & 0 deletions CircuitPlayground_TeaTimer.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,283 @@
    #include <Adafruit_CircuitPlayground.h>

    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;


    const long DT = 8;


    struct Accel
    {
    float x, y, z;
    long time;

    void readSensor()
    {
    x = CircuitPlayground.motionX();
    y = CircuitPlayground.motionY();
    z = CircuitPlayground.motionZ();
    time = millis();
    }
    };
    Accel gravity;



    // 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();

    teaType = MAX_TEA_TYPE;
    resteeps = 3;
    refreshPixels();
    }

    void setPixel(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0)
    {
    float brightness = 1.f;
    if (alarm.started)
    {
    float pulse = .5f + .5f * sin(TWO_PI * countdown.remaining()/500.f);
    brightness = pulse;
    }
    else if (countdown.started)
    {
    float pulse = .5f + .5f * sin(TWO_PI * countdown.remaining()/5000.f);
    pulse = pulse*pulse*pulse;
    brightness = .7f + .3f * pulse;
    }
    else
    {
    brightness = 2.f - (millis() - last_moved) / 10000.f;
    if (brightness > 1.f) brightness = 1.f;
    if (brightness < 0.f) brightness = 0.f;
    }

    r = uint8_t(r*brightness);
    g = uint8_t(g*brightness);
    b = uint8_t(b*brightness);
    w = uint8_t(w*brightness);

    CircuitPlayground.setPixelColor(n, Adafruit_CPlay_NeoPixel::gamma32(Adafruit_CPlay_NeoPixel::Color(r, g, b, w)));
    }

    void setPixel(uint16_t n, uint32_t color)
    {
    setPixel(n,
    ((color>>16)&255),
    ((color>>8)&255),
    (color&255),
    (color>>24));
    }

    void refreshPixels()
    {
    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;
    }

    if (countdown.started)
    {
    float progress = 1.0f - float(countdown.remaining()) / float(countdown.length);

    for (long i = 0; i < 5; ++i)
    {
    float fill = (progress-.2f*float(i))/.2f;
    if (fill < 0.f) fill = 0.f;
    if (fill > 1.f) fill = 1.f;
    setPixel(5+i, 192.f * fill, 127.f, 192.f - 192.f*fill);
    }
    }
    else
    {
    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};


    // 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 (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);
    }
    }

    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);
    }
    }
    else
    {
    refreshPixels();
    }

    {
    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 > 100.f)
    {
    last_moved = millis();
    digitalWrite(CPLAY_REDLED, 1);
    }
    else
    {
    digitalWrite(CPLAY_REDLED, 0);
    }

    float a = pow(.95f, dtf);
    gravity.x += a*dx;
    gravity.y += a*dy;
    gravity.z += a*dz;
    gravity.time = accel.time;
    }


    if (countdown.done())
    {
    alarm.begin(5000);
    countdown.reset();
    }

    if (alarm.started)
    {
    unsigned long arp = alarm.remaining() / 96;

    tone(A0, ARP_PATTERN[7-(arp&7)]);

    if (alarm.done()) alarm.reset();
    }
    else noTone(A0);

    /*if (alarm.started)
    {
    digitalWrite(CPLAY_REDLED, alarm.remaining() % 250 < 125);
    }
    else if (countdown.started)
    {
    digitalWrite(CPLAY_REDLED, countdown.remaining() % 1000 < 100);
    }*/

    delay(DT);

    b_right_prev = b_right;
    b_left_prev = b_left;
    }