/* Light Lab - see http://www.technoblogy.com/show?5DCO David Johnson-Davies - www.technoblogy.com - 14th January 2026 AVR128DB28 @ 24 MHz, Bootloader Serial Port: "USART0 (default pins)" CC BY 4.0 Licensed under a Creative Commons Attribution 4.0 International license: http://creativecommons.org/licenses/by/4.0/ */ #include #include #include // Fixed-point arithmetic ********************************************** typedef int16_t Fixed; // Fixed point, 8.8 bits // Fixed-point constants const Fixed zero = 0x000; const Fixed sixth = 0x02b; const Fixed quarter = 0x040; const Fixed third = 0x055; const Fixed half = 0x080; const Fixed twothirds = 0x0ab; const Fixed one = 0x100; Fixed Gamma[257]; // gamma lookup table // Pattern typedefs ********************************************** const int PATTERNS = 22; typedef Fixed (*fn_ptr_type)(uint8_t n, Fixed g, Fixed t, Fixed x, Fixed a, Fixed b, Fixed c); typedef const struct { fn_ptr_type patfn; const char *title1, *title2; const char *label1, *label2, *label3; bool twoway; } pattern_t; // Save state in EEPROM ********************************************** const int LEDTYPES = 3; enum rgb { red, green, blue }; typedef const struct { const char *label; bool neopixel; uint8_t order[3]; } led_type_t; led_type_t LEDtype[LEDTYPES] = { { "DotStar ", false, { blue, green, red }}, { "Neo GRB", true, { green, red, blue }}, { "Neo RGB", true, { red, green, blue }} }; struct { uint8_t flag = 0; // Check for blank EEPROM int leds = 20; // Number of LEDs: 20 to 160 uint8_t led_type = 0; // Index in array LEDtype[LEDTYPES] bool neopixel = true; // Type: false = DotStar, true = NeoPixel uint8_t bright = 0; // Global brightness: 0 (min) to 4 (max) uint8_t pattern = 1; // Current pattern } State; // NeoPixel (WS2812) Driver ********************************************** const int MaxLEDs = 160; union { uint8_t col[MaxLEDs][3]; uint8_t out[MaxLEDs*3]; } Buffer; volatile int BufPtr; void ConfigureNeoPixel () { // Configure Timer/Counter Type A to generate two waveforms PORTMUX.TCAROUTEA = PORTMUX_TCA0_PORTF_gc; // WO0, WO1 to PF0, PF1 TCA0.SINGLE.CTRLD = 0; // Normal mode PORTF.DIRSET = PIN0_bm | PIN1_bm; // WO0 and WO1 outputs TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV1_gc; // Clock divided by 1 TCA0.SINGLE.CTRLB = TCA_SINGLE_WGMODE_SINGLESLOPE_gc // Single-slope PWM ... | TCA_SINGLE_CMP0EN_bm | TCA_SINGLE_CMP1EN_bm; // waveform on WO0 and WO1 TCA0.SINGLE.PER = 30-1; // Period is 1250ns TCA0.SINGLE.CMP0 = 8-1; // WO0 (PF0) is 333.33ns TCA0.SINGLE.CMP1 = 17-1; // WO1 (PF1) is 708.33ns // Configure SPI0 in client mode PORTA.DIRSET = PIN5_bm; // MISO output SPI0.CTRLB = SPI_BUFEN_bm | SPI_BUFWR_bm // Buffer mode ... | SPI_MODE_0_gc; // transfer mode 0 SPI0.CTRLA = SPI_ENABLE_bm; // Enable in client mode // Use CCL to make a gate between TCA0 WO0, TCA0 WO1, and MISO CCL.LUT1CTRLB = CCL_INSEL0_TCA0_gc | CCL_INSEL1_TCA0_gc;// TCA0 WO0 and WO1 CCL.LUT1CTRLC = CCL_INSEL2_EVENTA_gc; // LUT1 EVENTA CCL.TRUTH1 = 0b11001010; CCL.LUT1CTRLA = CCL_ENABLE_bm; // Enable, no output on PC3 CCL.CTRLA = CCL_ENABLE_bm; // Enable CCL last // Use Events to copy the MISO output to LUT1 IN2 EVSYS.CHANNEL0 = EVSYS_CHANNEL0_PORTA_PIN5_gc; // Link PA5 ... EVSYS.USERCCLLUT1A = EVSYS_USER_CHANNEL0_gc; // ... to LUT1 EVENTA // Use Events to copy PD6 to PA7 (SS) PORTMUX.EVSYSROUTEA = PORTMUX_EVOUTA_ALT1_gc; // EVOUTA on PA7 EVSYS.CHANNEL2 = EVSYS_CHANNEL2_PORTD_PIN6_gc; // Link PD6 ... EVSYS.USEREVSYSEVOUTA = EVSYS_USER_CHANNEL2_gc; // ... and PA7 // Use Events to move LUT1 OUT to PC2 EVSYS.CHANNEL1 = EVSYS_CHANNEL1_CCL_LUT1_gc; // Link LUT1 OUT ... EVSYS.USEREVSYSEVOUTC = EVSYS_USER_CHANNEL1_gc; // ... to PC2 // Pins PORTD.OUTSET = PIN6_bm; PORTD.DIRSET = PIN6_bm; // PD6 EN output high } ISR(SPI0_INT_vect) { if (BufPtr < State.leds*3) { // More data to write? SPI0.DATA = Buffer.out[BufPtr++]; // Output byte, clears flag } else if (SPI0.INTFLAGS & SPI_TXCIF_bm) { // Shift register empty? PORTD.OUTSET = PIN6_bm; // Take EN high to stop SPI TCA0.SINGLE.CMP0 = 0; // WO0 constant low signal TCA0.SINGLE.CMP1 = 0; // WO1 constant low signal SPI0.INTFLAGS = SPI_TXCIF_bm; // Clear flag SPI0.INTCTRL = 0; // Disable interrupts } else { SPI0.INTFLAGS = SPI_DREIF_bm; // Clear flag SPI0.INTCTRL = SPI0.INTCTRL &~SPI_DREIE_bm; // Disable DREIF interrupt } } void StartNeoPixel () { BufPtr = 0; SPI0.DATA = Buffer.out[BufPtr++]; // Initial data SPI0.DATA = Buffer.out[BufPtr++]; // Initial data SPI0.INTCTRL = SPI_DREIE_bm | SPI_TXCIE_bm; // Enable interrupts TCA0.SINGLE.CTRLA &= ~TCA_SINGLE_ENABLE_bm; // Stop Timer/Counter TCA0.SINGLE.CMP0 = 8-1; // WO0 (PA0) is 333.33ns TCA0.SINGLE.CMP1 = 17-1; // WO1 (PA1) is 708.33ns TCA0.SINGLE.CNT = 0; // Clear Timer/Counter PORTD.OUTCLR = PIN6_bm; // EN low to start output TCA0.SINGLE.CTRLA |= TCA_SINGLE_ENABLE_bm; // Start Timer/Counter } // DotStar (APA102 or SK9822) Driver ********************************************** void ConfigureDotStar () { pinMode(PIN_PC3, OUTPUT); // SPI1 SS pin SPI.swap(SPI1_SWAP_DEFAULT); SPI.begin(); } void StartDotStar () { for (int i=0; i<4; i++) SPI.transfer(0); // Start frame } void TransferDotStar (int p, uint8_t bright) { SPI.transfer(bright + 0xE0); // Brightness 31 max SPI.transfer(Buffer.col[p][0]); SPI.transfer(Buffer.col[p][1]); SPI.transfer(Buffer.col[p][2]); } void EndDotStar () { for (int i=0; i<8; i++) { SPI.transfer(0xff); // End frame at least LEDs/2 bits of '1' } } // I2C OLED display ********************************************** int const address = 60; int const commands = 0x00; int const onecommand = 0x80; int const data = 0x40; int const onedata = 0xC0; // Character set - stored in program memory const uint8_t CharMap[][6] PROGMEM = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00 }, { 0x00, 0x07, 0x00, 0x07, 0x00, 0x00 }, { 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00 }, { 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00 }, { 0x23, 0x13, 0x08, 0x64, 0x62, 0x00 }, { 0x36, 0x49, 0x56, 0x20, 0x50, 0x00 }, { 0x00, 0x08, 0x07, 0x03, 0x00, 0x00 }, { 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00 }, { 0x00, 0x41, 0x22, 0x1C, 0x00, 0x00 }, { 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, 0x00 }, { 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00 }, { 0x00, 0x80, 0x70, 0x30, 0x00, 0x00 }, { 0x08, 0x08, 0x08, 0x08, 0x08, 0x00 }, { 0x00, 0x00, 0x60, 0x60, 0x00, 0x00 }, { 0x20, 0x10, 0x08, 0x04, 0x02, 0x00 }, { 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00 }, { 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00 }, { 0x72, 0x49, 0x49, 0x49, 0x46, 0x00 }, { 0x21, 0x41, 0x49, 0x4D, 0x33, 0x00 }, { 0x18, 0x14, 0x12, 0x7F, 0x10, 0x00 }, { 0x27, 0x45, 0x45, 0x45, 0x39, 0x00 }, { 0x3C, 0x4A, 0x49, 0x49, 0x31, 0x00 }, { 0x41, 0x21, 0x11, 0x09, 0x07, 0x00 }, { 0x36, 0x49, 0x49, 0x49, 0x36, 0x00 }, { 0x46, 0x49, 0x49, 0x29, 0x1E, 0x00 }, { 0x00, 0x36, 0x36, 0x00, 0x00, 0x00 }, { 0x00, 0x56, 0x36, 0x00, 0x00, 0x00 }, { 0x00, 0x08, 0x14, 0x22, 0x41, 0x00 }, { 0x14, 0x14, 0x14, 0x14, 0x14, 0x00 }, { 0x00, 0x41, 0x22, 0x14, 0x08, 0x00 }, { 0x02, 0x01, 0x59, 0x09, 0x06, 0x00 }, { 0x3E, 0x41, 0x5D, 0x59, 0x4E, 0x00 }, { 0x7C, 0x12, 0x11, 0x12, 0x7C, 0x00 }, { 0x7F, 0x49, 0x49, 0x49, 0x36, 0x00 }, { 0x3E, 0x41, 0x41, 0x41, 0x22, 0x00 }, { 0x7F, 0x41, 0x41, 0x41, 0x3E, 0x00 }, { 0x7F, 0x49, 0x49, 0x49, 0x41, 0x00 }, { 0x7F, 0x09, 0x09, 0x09, 0x01, 0x00 }, { 0x3E, 0x41, 0x41, 0x51, 0x73, 0x00 }, { 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00 }, { 0x00, 0x41, 0x7F, 0x41, 0x00, 0x00 }, { 0x20, 0x40, 0x41, 0x3F, 0x01, 0x00 }, { 0x7F, 0x08, 0x14, 0x22, 0x41, 0x00 }, { 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00 }, { 0x7F, 0x02, 0x1C, 0x02, 0x7F, 0x00 }, { 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00 }, { 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00 }, { 0x7F, 0x09, 0x09, 0x09, 0x06, 0x00 }, { 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00 }, { 0x7F, 0x09, 0x19, 0x29, 0x46, 0x00 }, { 0x26, 0x49, 0x49, 0x49, 0x32, 0x00 }, { 0x03, 0x01, 0x7F, 0x01, 0x03, 0x00 }, { 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00 }, { 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00 }, { 0x3F, 0x40, 0x38, 0x40, 0x3F, 0x00 }, { 0x63, 0x14, 0x08, 0x14, 0x63, 0x00 }, { 0x03, 0x04, 0x78, 0x04, 0x03, 0x00 }, { 0x61, 0x59, 0x49, 0x4D, 0x43, 0x00 }, { 0x00, 0x7F, 0x41, 0x41, 0x41, 0x00 }, { 0x02, 0x04, 0x08, 0x10, 0x20, 0x00 }, { 0x00, 0x41, 0x41, 0x41, 0x7F, 0x00 }, { 0x04, 0x02, 0x01, 0x02, 0x04, 0x00 }, { 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0 }, { 0x00, 0x03, 0x07, 0x08, 0x00, 0x00 }, { 0x20, 0x54, 0x54, 0x78, 0x40, 0x00 }, { 0x7F, 0x28, 0x44, 0x44, 0x38, 0x00 }, { 0x38, 0x44, 0x44, 0x44, 0x28, 0x00 }, { 0x38, 0x44, 0x44, 0x28, 0x7F, 0x00 }, { 0x38, 0x54, 0x54, 0x54, 0x18, 0x00 }, { 0x00, 0x08, 0x7E, 0x09, 0x02, 0x00 }, { 0x18, 0xA4, 0xA4, 0x9C, 0x78, 0x00 }, { 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00 }, { 0x00, 0x44, 0x7D, 0x40, 0x00, 0x00 }, { 0x20, 0x40, 0x40, 0x3D, 0x00, 0x00 }, { 0x7F, 0x10, 0x28, 0x44, 0x00, 0x00 }, { 0x00, 0x41, 0x7F, 0x40, 0x00, 0x00 }, { 0x7C, 0x04, 0x78, 0x04, 0x78, 0x00 }, { 0x7C, 0x08, 0x04, 0x04, 0x78, 0x00 }, { 0x38, 0x44, 0x44, 0x44, 0x38, 0x00 }, { 0xFC, 0x18, 0x24, 0x24, 0x18, 0x00 }, { 0x18, 0x24, 0x24, 0x18, 0xFC, 0x00 }, { 0x7C, 0x08, 0x04, 0x04, 0x08, 0x00 }, { 0x48, 0x54, 0x54, 0x54, 0x24, 0x00 }, { 0x04, 0x04, 0x3F, 0x44, 0x24, 0x00 }, { 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00 }, { 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00 }, { 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00 }, { 0x44, 0x28, 0x10, 0x28, 0x44, 0x00 }, { 0x4C, 0x90, 0x90, 0x90, 0x7C, 0x00 }, { 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00 }, { 0x00, 0x08, 0x36, 0x41, 0x00, 0x00 }, { 0x00, 0x00, 0x77, 0x00, 0x00, 0x00 }, { 0x00, 0x41, 0x36, 0x08, 0x00, 0x00 }, { 0x02, 0x01, 0x02, 0x04, 0x02, 0x00 }, { 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F }, // 7F }; void InitDisplay () { Wire.beginTransmission(address); Wire.write(commands); Wire.write(0x20); Wire.write(0x00); // Horizontal addressing mode Wire.write(0xA1); // Flip horizontal Wire.write(0xC8); // Flip vertical Wire.write(0xD3); Wire.write(0); // Reset scroll Wire.endTransmission(); } bool Display; void DisplayOn (bool on) { Display = on; Wire.beginTransmission(address); Wire.write(commands); Wire.write(0xAE + on); Wire.endTransmission(); } void DisplayArea (int row1, int row2, int col1, int col2) { Wire.beginTransmission(address); Wire.write(commands); Wire.write(0x21); Wire.write(col1); Wire.write(col2); // Column range Wire.write(0x22); Wire.write(row1); Wire.write(row2); // Page range Wire.endTransmission(); } void ClearDisplay () { DisplayArea(0, 7, 0, 127); for (int p = 0 ; p < 8; p++) { for (int q = 0 ; q < 8; q++) { Wire.beginTransmission(address); Wire.write(data); for (int i = 0 ; i < 16; i++) Wire.write(0); Wire.endTransmission(); } } } // Plots a character; line = 0 to 7; column = 0 to 20 void PlotChar (uint8_t c, int line, int column) { column = column*6; DisplayArea(line, line, column, column+5); Wire.beginTransmission(address); Wire.write(data); for (uint8_t col = 0 ; col < 6; col++) { Wire.write(pgm_read_byte(&CharMap[c-32][col])); } Wire.endTransmission(); } // Plot an integer from -999 to 999 in a field 4 wide. void PlotInt (int n, int line, int column) { bool lead = false, sign = false; for (int d=100; d>0; d = d/10) { char j = (abs(n)/d) % 10; if (j!=0 || lead || d==1) { if (!sign) PlotChar((n < 0) ? '-' : ' ', line, column++); sign = true; PlotChar(j + '0', line, column++); lead = true; } else PlotChar(' ', line, column++); } } // Draw scroll bar for pattern n (0 to PATTERNS-1) void PlotScrollBar (int n) { int width = 62/PATTERNS; int from = width*n, to = width*(n+1)+1; DisplayArea(0, 7, 126, 127); Wire.beginTransmission(address); Wire.write(data); int len = to - from + 1; uint64_t bar; if (n == 64) bar = (uint64_t)~0; else bar = (((uint64_t)1<> (uint64_t)8; } Wire.endTransmission(); } // Controls ********************************************** uint8_t control[3]; const int Samples = 4; const int Up = 4; const int Down = 5; void ButtonsOn () { // PD4 (Up), PD5 (Down) inputs with pullups PORTD.PIN4CTRL = PORT_PULLUPEN_bm | PORT_ISC_FALLING_gc; PORTD.PIN5CTRL = PORT_PULLUPEN_bm | PORT_ISC_FALLING_gc; } int PatternChange = 0; ISR(PORTD_PORT_vect) { bool displayChange = false; uint8_t flags = PORTD.INTFLAGS; if (flags & 1<> 3; } // Reads the next control into control[i] and displays percentage void ReadControls (pattern_t p) { static uint8_t i; i = (i + 1) % 3; int v = Average(i); if (abs(v - control[i]) > 2) { uint16_t percent = (v*101+50)/256; if (i == 0 && p.twoway) percent = percent - 50; PlotInt(percent, 7, i*8-i/2); PlotChar('%', 7, 4+i*8-i/2); control[i] = v; } } void InitialControls (pattern_t p) { for (int i=0; i<3; i++) { uint16_t percent = control[i]*101/256; if (i == 0 && p.twoway) percent = percent - 50; PlotInt(percent, 7, i*8-i/2); PlotChar('%', 7, 4+i*8-i/2); } } // Settings screen ********************************************** const int LEDLENGTHS = 14; const int LEDlength[LEDLENGTHS] = {20, 30, 36, 40, 50, 60, 64, 72, 100, 120, 128, 144, 150, MaxLEDs}; // Add more if you want const char *BrightOption[5] = {" 6%", "12%", "25%", "50%", "Max"}; void DoSettings () { int val, v; ClearDisplay(); PrintString((char*)"Settings", 0, (22-8)/2); PrintString((char*)"Press UP to exit", 4, 2); Labels((char*)"Type", (char*)"LEDs", (char*)"Bright"); do { val = Average(0); v = (val*LEDTYPES)/256; // Option number PrintString((char*)LEDtype[v].label, 7, 0); State.neopixel = LEDtype[v].neopixel; State.led_type = v; val = Average(1); v = (val*LEDLENGTHS)/256; // Option number PlotInt(LEDlength[v], 7, 9); State.leds = LEDlength[v]; val = Average(2); v = (val*5)/256; // Option number PrintString((char*)BrightOption[v], 7, 17); State.bright = v; } while ((PORTD.IN & 1<181 || x<-181 || y>181 || y<-181) return ((int32_t)x * (int32_t)y) >> 8; else return (x * y) >> 8; } Fixed fdiv (Fixed x, Fixed y) { if (x>127 || x<-127) return ((int32_t)x << 8) / y; else return (x << 8) / y; } Fixed fract (Fixed a) { if (a >= 0) return a & 0xff; else return 0x100 - (a & 0xff); } Fixed clamp (Fixed v, Fixed lo, Fixed hi) { return (vhi) ? hi : v; } Fixed d (Fixed m, Fixed n) { return abs(fract(m - n + half) - half); } Fixed tri (Fixed x) { return 2 * abs(half - (fract (half + x))); } Fixed squ (Fixed x) { return fmul(x, x); } Fixed hsv (Fixed g, Fixed h, Fixed s, Fixed v) { return fmul(2 * (one - 3 * fmul(d(g, h), s)), v); } bool tstbit (uint64_t pat, int k) { return ((pat>>k) & (uint64_t)1); } uint64_t setbit (uint64_t pat, int k) { return pat | (uint64_t)1<> n % (b * 12 / one + 4)) & 1) * c; } Fixed snakes_pattern (uint8_t n, Fixed g, Fixed t, Fixed x, Fixed a, Fixed b, Fixed c) { const int patlen = (State.leds + 7)/8; static uint8_t pat[MaxLEDs/8]; static uint8_t ledshift = 0; static Fixed t0; if (n == 0 && g == 0 && (t*20)/one != t0) { t0 = (t*20)/one; ledshift = (ledshift + 1) % State.leds; uint8_t previousbit = random(2); for (int m=0; m> 7; pat[m] = (pat[m] << 1) | previousbit; previousbit = topbit; } } uint8_t on = (pat[n/8]>>(n & 7)) & 1; Fixed hue = ((n - ledshift + State.leds)%6)*sixth; return hsv(g, hue+b, c, on * one); } Fixed fireflies_pattern (uint8_t n, Fixed g, Fixed t, Fixed x, Fixed a, Fixed b, Fixed c) { static uint64_t pat = 0x0101010101010101; // 8 fireflies static Fixed t0; static uint8_t fly; if (n == 0 && g == zero) { fly = 0; if ((t*20)/one != t0) { t0 = (t*20)/one; int i = -1; for (int m=1; m<=8; m++) { i++; while (!tstbit(pat, i)) i++; // Skip unlit int k = (i + random(2) * 2 - 1); // next or previous if (k >= 0 && k <= 63 && !tstbit(pat, k)) { pat = setbit(pat, k); pat = clrbit(pat, i); i=k; } } } } uint8_t on = tstbit(pat, n % 64); if (on && (g == 0)) fly++; return hsv(g, fly % 6 * sixth + b, c, on * one); } Fixed snowstorm_pattern (uint8_t n, Fixed g, Fixed t, Fixed x, Fixed a, Fixed b, Fixed c) { bool on = (t % one > ((n + t / one) * 317 + 37) % one); return hsv(g, n * 317 + 37 + b, c, on * one); } Fixed matrix_circles_pattern (uint8_t n, Fixed g, Fixed t, Fixed x, Fixed a, Fixed b, Fixed c) { uint8_t nx = n/8, ny = n%8; return hsv(g, squ(nx * half / 4 - 112) + squ(ny * half / 4 - 112) + t, b, c); } Fixed matrix_message_pattern (uint8_t n, Fixed g, Fixed t, Fixed x, Fixed a, Fixed b, Fixed c) { uint8_t nx = n/8, ny = n%8; const char message[] = "Happy Birthday Zoe! "; const uint8_t len = strlen(message); uint8_t let = (abs(t/4)+nx)/6%len; bool on = (pgm_read_byte(&CharMap[message[let]-32][(abs(t/4)+nx)%6])>>(7 - ny)) & 1; return hsv(g, let*b*6/len, c, on * one); } #pragma GCC diagnostic pop // Setup and main loop ********************************************** void PatternChanged () { if (PatternChange != 0) { State.pattern = (State.pattern + PatternChange + PATTERNS) % PATTERNS; UpdateDisplay(State.pattern, Pattern[State.pattern]); // Update display EEPROM.put(0, State); // Save state to EEPROM PatternChange = 0; } } void setup () { if (EEPROM.read(0) == 0) EEPROM.get(0, State); // Only read if valid data analogRead(ADC_VDDIO2DIV10); // Read supply voltage PORTD.PIN4CTRL = PORT_PULLUPEN_bm; PORTD.PIN5CTRL = PORT_PULLUPEN_bm; PORTD.DIRSET = PIN7_bm; // PD7 Error output low for (int n=0; n<=256; n++) Gamma[n] = pow((float)n/256.0, 2.2) * 255; if (State.neopixel) ConfigureNeoPixel(); else ConfigureDotStar(); Wire.begin(); delay(500); InitDisplay(); ClearDisplay(); DisplayOn(true); PrintString((char*)"Light Lab", 2, (22-9)/2); // Welcome screen delay(1500); if ((PORTD.IN & 1<= 25) PORTD.OUTSET = PIN7_bm; else PORTD.OUTCLR = PIN7_bm; // Error light while (millis() - timer < 20); // Wait until 20ms tick return time; } void loop () { refresh(); }