/* Bauble - see http://www.technoblogy.com/show?4O86 David Johnson-Davies - www.technoblogy.com - 4th November 2025 AVR32DD28 @ 24 MHz (internal); BOD Mode: Disabled/Disabled CC BY 4.0 Licensed under a Creative Commons Attribution 4.0 International license: http://creativecommons.org/licenses/by/4.0/ */ #include // Constants const uint32_t SleepAfter = (uint32_t)1*60*60; // 1 hour in seconds const uint32_t WakeEvery = (uint32_t)24*60*60; // 24 hours in seconds; set to 0 for no wake uint8_t Flag __attribute__ ((section (".noinit"))); const float Latitude[16] = // 0.0 = South pole, 1.0 = North pole { 1.0/8.0, 3.0/8.0, 5.0/8.0, 7.0/8.0, 7.0/8.0, 5.0/8.0, 3.0/8.0, 1.0/8.0, 1.0/8.0, 3.0/8.0, 5.0/8.0, 7.0/8.0, 7.0/8.0, 5.0/8.0, 3.0/8.0, 1.0/8.0 }; const float Longitude[16] = // 0.0 to 1.0 = one rotation { 1.0/8.0, 1.0/8.0, 1.0/8.0, 1.0/8.0, 5.0/8.0, 5.0/8.0, 5.0/8.0, 5.0/8.0, 3.0/8.0, 3.0/8.0, 3.0/8.0, 3.0/8.0, 7.0/8.0, 7.0/8.0, 7.0/8.0, 7.0/8.0 }; const float Colour[16] = { 1.0/32.0, 25.0/32.0, 9.0/32.0, 17.0/32.0, 3.0/32.0, 27.0/32.0, 11.0/32.0, 19.0/32.0, 13.0/32.0, 21.0/32.0, 5.0/32.0, 29.0/32.0, 15.0/32.0, 23.0/32.0, 7.0/32.0, 31.0/32.0,}; // PWM on 16 outputs ********************************************** uint8_t Level[16], Buffer[16]; void SetupDisplay () { PORTA.DIRSET = 0xfc; // PA2 to PA7 outputs PORTC.DIRSET = 0x0f; // PC0 to PC3 outputs PORTD.DIRSET = 0xfe; // PD1 to PD7 outputs PORTF.DIRSET = 0x03; // PF0 and PF1 outputs } void SetupInterrupt () { TCB0.CCMP = 1535; // 24MHz/96 = 15625Hz TCB0.CTRLA = TCB_CLKSEL_DIV1_gc | TCB_ENABLE_bm; // Divide timer by 1 TCB0.CTRLB = 0; // Periodic Interrupt TCB0.INTCTRL = TCB_CAPT_bm; // Enable interrupt } // Timer/Counter TCB interrupt - multiplexes the LEDs ISR(TCB0_INT_vect) { TCB0.INTFLAGS = TCB_CAPT_bm; // Clear the interrupt flag PWM(); } PORT_t *port[16] = { &PORTA, &PORTA, &PORTA, &PORTA, &PORTA, &PORTA, &PORTC, &PORTC, &PORTD, &PORTD, &PORTD, &PORTD, &PORTD, &PORTD, &PORTD, &PORTF }; uint8_t pin[16] = { 2, 3, 4, 5, 6, 7, 0, 1, 1, 2, 3, 4, 5, 6, 7, 0 }; void PWM () { static uint8_t ramp; if (ramp == 0) { PORTA.OUTCLR = 0xfc; // All outputs low PORTC.OUTCLR = 0x03; PORTD.OUTCLR = 0xfe; PORTF.OUTCLR = 0x01; for (uint8_t led=0; led<16; led++) { Buffer[led] = Level[led]; } } else { // ramp != 0 for (uint8_t led=0; led<16; led++) { if (Buffer[led] == ramp) port[led]->OUTSET = 1<hi) ? hi : v; } // tri(t) gives triangle wave 0--1 float tri(float t) { return 2.0 * fabsf (.5 - fmodf(.5 + t, 1.0)); } // fract(x) gives the fractional part of x float fract (float x) { return fmodf(x,1.0); } const int Cases = 8; // Number of patterns + 1 void setup () { Flag = (Flag+1) % Cases; set_sleep_mode(SLEEP_MODE_PWR_DOWN); SetupRTC(); SetupDisplay(); SetupInterrupt(); ADC0.CTRLA = 0; // Disable ADC to save power } void loop () { Seconds = SleepAfter; uint64_t start = millis(); do { uint64_t elapsed = millis() - start; float t = (float) elapsed / 1000.0; for (uint8_t i=0; i<16; i++) { float lat = Latitude[i]; float lon = Longitude[i]; float col = Colour[i]; float led = (float)(i + 0.5)/16.0; float value; switch (Flag) { case 0: DoFlash(); Sleep(true); // Sleep break; case 1: value = 1- tri((t/16.0+int(col*4.0))/4.0) * 2.0; // Two Colour fade break; case 2: value = 1 - 0.5*tri(t/16.0 - col) * 8.0; // Two Colour fade 2 break; case 3: value = 1 - tri((t*(1+col*0.1))/5.0) * 8.0; // Sparkle break; case 4: value = 1 - fract((t + sin(t*0.01)*10.0 + fract(sin(col*77.1)*93.5) )*2.0) * 15.0; // 7 Flashy break; case 5: value = (1 - cos(clamp(fmodf(t - 8 * lat, 8), 0, 1) * 2 * pi)) / 2; // Rings break; case 6: value = ((int)(100 + t*10.0) % (int)(30 + led*16) == 0) ? 1 : 0; // Random break; case 7: value = (fract(t/10+col*0.3)-fract(t+col*0.3)) * 8.0; // Pulsate } value = pow(value, 2.2); Level[i] = 255 * clamp(value, 0.0, 1.0); } } while (Seconds != 0); Sleep(WakeEvery == 0); // If WakeEvery is 0 don't wake Seconds = WakeEvery - SleepAfter; do { sleep_enable(); sleep_cpu(); // Stay asleep until timed out } while (Seconds != 0); DisplayOn(); }