/* NeoPixel Driver using AVR Hardware and Events - see http://www.technoblogy.com/show?5BQG David Johnson-Davies - www.technoblogy.com - 20th August 2025 AVR128DA28 @ 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 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_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.CHANNEL0 = EVSYS_CHANNEL0_PORTA_PIN5_gc; // Link PA5 ... EVSYS.USERCCLLUT1A = EVSYS_USER_CHANNEL0_gc; // ... to LUT1 EVENTA // Use Events to copy PD4 to PA7 PORTMUX.EVSYSROUTEA = PORTMUX_EVOUTA_ALT1_gc; // EVOUTA on PA7 EVSYS.CHANNEL2 = EVSYS_CHANNEL2_PORTD_PIN4_gc; // Link PD4 ... EVSYS.USEREVSYSEVOUTA = EVSYS_USER_CHANNEL2_gc; // ... and PA7 // Pins PORTD.OUTSET = PIN4_bm; PORTD.DIRSET = PIN4_bm; // PD4 EN output high PORTF.DIRSET = PIN0_bm; // PF0 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? PORTD.OUTSET = PIN4_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 PORTD.OUTCLR = PIN4_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) PORTF.OUTSET = PIN0_bm; // Error light else while (millis() - t < 20); // Wait until 20ms tick } void loop() { Waves(); }