/* IR NeoPixel Controller - see http://www.technoblogy.com/show?5H4W David Johnson-Davies - www.technoblogy.com - 2nd June 2026 AVR64DD14 @ 24 MHz (internal oscillator; BOD disabled; PF6 input) CC BY 4.0 Licensed under a Creative Commons Attribution 4.0 International license: http://creativecommons.org/licenses/by/4.0/ */ #include #define RGB // Options: RGB, GRB, RGBW, GRBW // NeoPixel data ********************************************** const int NumPixels = 9; // Number of LEDs #if defined(RGB) || defined(GRB) const int Channels = 3; #else const int Channels = 4; #endif union { uint8_t col[NumPixels][Channels]; uint8_t out[NumPixels*Channels]; } Buffer; // Display buffer volatile int BufPtr; enum colour { RED, GRN, BLU, WHI }; // Channel numbers enum control { HUE, SATN, VALUE }; // HSV numbers #if defined(RGB) || defined(RGBW) // Order of NeoPixels uint8_t Order[] = { RED, GRN, BLU, WHI }; // White optional #else uint8_t Order[] = { GRN, RED, BLU, WHI }; // White optional #endif // Save state in EEPROM ********************************************** const int Version = 2; struct { uint8_t flag = NumPixels; // Check if changed uint8_t Control[NumPixels+1][3]; // Control values uint8_t LED = 1; // 1 to 9 or 0 = all bool Animate = false; // Changing display bool On = true; // Switch LEDs on/off } State; volatile int SaveInterval = 10; // Save every 10 secs // IR Remote ********************************************** // Values for Adafruit IR remote const uint16_t IRAddress = 0xbf00; enum keycode { VOLM = 0x00, PLAY = 0x01, VOLP = 0x02, SETUP = 0x04, UP = 0x05, STOP = 0x06, LEFT = 0x08, ENTER = 0x09, RIGHT = 0x0a, ZERO = 0x0c, DOWN = 0x0d, RETURN = 0x0e, ONE = 0x10, TWO = 0x11, THREE = 0x12, FOUR = 0x14, FIVE = 0x15, SIX = 0x16, SEVEN = 0x18, EIGHT = 0x19, NINE = 0x1a }; const int IRpin = PIN_PF6; volatile uint8_t Ticks; volatile int8_t NextBit; // Next bit to receive uint8_t KeyNumber (uint8_t keycode) { // Convert key to number uint8_t base = keycode - 16; uint8_t jump = base / 4; return base - jump + 1; } void Error (bool on) { if (on) PORTC.OUTSET = PIN2_bm; else PORTC.OUTCLR = PIN2_bm; } void ProcessCode (uint32_t code, bool repeat) { uint16_t address = code & 0xffff; uint8_t key = code >> 16 & 0xff; uint8_t id = code >> 24 & 0xff; if (address != IRAddress) return; // Check for remote uint8_t n = State.LED; switch (key) { case UP: if (State.Control[n][VALUE] <= 253) State.Control[n][VALUE]+=2; else Error(true); break; // UP increases brightness case DOWN: if (State.Control[n][VALUE] >= 2) State.Control[n][VALUE]-=2; else Error(true); break; // DOWN reduces brightness case RIGHT: State.Control[n][HUE] = State.Control[n][HUE] + 1; break; // RIGHT increases hue case LEFT: State.Control[n][HUE] = State.Control[n][HUE] - 1; break; // LEFT reduces hue case VOLP: if (State.Control[n][SATN] <= 253) State.Control[n][SATN]+=2; else Error(true); break; // VOLP increases saturation case VOLM: if (State.Control[n][SATN] >= 2) State.Control[n][SATN]-=2; else Error(true); break; // VOLM reduces saturation case STOP: if (!repeat) State.On = !State.On; break; // STOP toggles all on/off case ZERO: State.LED = 0; break; // 0 controls all LEDs case ONE ... NINE: State.LED = KeyNumber(key); break; // 1 to 9 selects an LED case PLAY: if (!repeat) State.Animate = !State.Animate; break; // Toggle animation } } void ConfigureIRReceiver () { // Configure Timer/Counter TCB0 to time in units of 112.5us TCB0.CCMP = 2700-1; // 112.5us TCB0.CTRLA = TCB_CLKSEL_DIV1_gc | TCB_ENABLE_bm; // Divide timer by 1 TCB0.CTRLB = 0; // Periodic Interrupt mode TCB0.INTCTRL = TCB_CAPT_bm; // Overflow interrupt // Set up pin-change interrupt on IRpin PORTF.PIN6CTRL = PORT_PULLUPEN_bm | PORT_ISC_FALLING_gc;// Interrupt falling edge NextBit = -1; // Wait for AGC start pulse } // Timer/Counter TCB interrupt - counts in units of 112.5us ISR(TCB0_INT_vect) { TCB0.INTFLAGS = TCB_CAPT_bm; // Clear interrupt flag if (Ticks < 255) Ticks++; } // Counts in units of 112.5us const int ZeroBit = 10; // 10 * 112.5us = 1.125ms const int OneBit = 20; // 20 * 112.5us = 2.25ms const int RepeatPulse = 100; // 100 * 112.5us = 11.25ms const int AGCPulse = 120; // 120 * 112.5us = 13.5ms // Interrupt service routine - called on every falling edge of IRpin ISR (PORTF_PORT_vect) { static uint32_t RecdData; // Waiting for AGC pulse and gap int Time = Ticks; if (NextBit == -1) { // Waiting for AGC pulse if ((Time > AGCPulse-10) && (Time <= AGCPulse+10)) { // Got AGC pulse RecdData = 0; NextBit = 0; } else if ((Time > RepeatPulse-10) && (Time <= RepeatPulse+10)) { ProcessCode(RecdData, true); // Repeat code } } else { // Data bit if ((Time > OneBit+5) || (Time == 255)) NextBit = -1; // Fail - restart else { if (Time > OneBit-5) { // Bit = '1' RecdData = RecdData | ((unsigned long) 1<1.0) ? 1.0 : v; } // Triangle function float tri (float m) { return 2.0 * fabsf(fract(m + 0.5) - 0.5); } float hsv (int k, float h, float s, float v) { #if defined(RGB) || defined(GRB) // 3 channels return clamp(2.0 - (3.0 * tri(h - k/3.0))) * v * s + v * (1 - s); #else // 4 channels if (k == 3) return v * (1 - s); // White channel else return clamp(2.0 - (3.0 * tri(h - k/3.0))) * v * s; #endif } // Setup and main loop ********************************************** void ResetState () { for (int n=0; n SaveInterval*1000) { EEPROM.put(0, State); // Save settings Start = millis(); } if (millis()-t >= 25) Error(true); else Error (false); // Error light while (millis() - t < 20); // Wait until 20ms tick } void loop() { Refresh(); }