/* NeoPixel Driver using AVR Hardware and Events - see http://www.technoblogy.com/show?5CC0 David Johnson-Davies - www.technoblogy.com - 26th August 2025 AVR64DD14 @ 24 MHz (internal oscillator; BOD disabled) CC BY 4.0 Licensed under a Creative Commons Attribution 4.0 International license: http://creativecommons.org/licenses/by/4.0/ */ const int NumPixels = 20; union { uint8_t col[NumPixels][3]; uint8_t out[NumPixels*3]; } Buffer; volatile int BufPtr; enum colour { GRN, RED, BLU }; void setup() { // Configure Timer/Counter Type A to generate two waveforms PORTMUX.TCAROUTEA = PORTMUX_TCA0_PORTA_gc; // Clear routing TCA0.SINGLE.CTRLD = 0; // Normal mode PORTA.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 (PA0) is 333.33ns TCA0.SINGLE.CMP1 = 17-1; // WO1 (PA1) is 708.33ns // Configure SPI0 in client mode PORTMUX.SPIROUTEA = PORTMUX_SPI0_ALT4_gc; // PD4, PD5, PD6, PD7 PORTD.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_OUTEN_bm | CCL_ENABLE_bm; // Enable, output on PC3 CCL.CTRLA = CCL_ENABLE_bm; // Enable CCL last // Use Events to copy the MISO output to LUT1 IN2 EVSYS.CHANNEL2 = EVSYS_CHANNEL2_PORTD_PIN5_gc; // Link PD5 ... EVSYS.USERCCLLUT1A = EVSYS_USER_CHANNEL2_gc; // ... to LUT1 EVENTA // Use Events to copy PC1 to PD7 PORTMUX.EVSYSROUTEA = PORTMUX_EVOUTD_ALT1_gc; // EVOUTD on PD7 EVSYS.CHANNEL3 = EVSYS_CHANNEL3_PORTC_PIN1_gc; // Link PC1 ... EVSYS.USEREVSYSEVOUTD = EVSYS_USER_CHANNEL3_gc; // ... and PD7 // Pins PORTC.OUTSET = PIN1_bm; PORTC.DIRSET = PIN1_bm; // PC1 EN output high PORTC.DIRSET = PIN2_bm; // PC2 Error output low } ISR(SPI0_INT_vect) { if (BufPtr < NumPixels*3) { // More data to write? SPI0.DATA = Buffer.out[BufPtr++]; // Output byte, clears flag } else if (SPI0.INTFLAGS & SPI_TXCIF_bm) { // Shift register empty? PORTC.OUTSET = PIN1_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 StartTransfer () { 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 PORTC.OUTCLR = PIN1_bm; // EN low to start output TCA0.SINGLE.CTRLA |= TCA_SINGLE_ENABLE_bm; // Start Timer/Counter } // Demo - Colour Waves ********************************************** int fix (int y) { y = y % 768; if (y >= 256) y = 511 - y; if (y < 0) y = 0; return y; } void Waves () { unsigned long t = millis(); StartTransfer(); // Runs under interrupt for (int p=0; p= 20) PORTC.OUTSET = PIN2_bm; // Error light else while (millis() - t < 20); // Wait until 20ms tick } void loop() { Waves(); }