Topics

► Games

► Sound & Music

► Watches & Clocks

► Wireless

► GPS

► Power Supplies

► Computers

► Graphics

► Lighting

► Thermometers

► Educational

► Wearables

► Test Equipment

► Tutorials

► Libraries

► PCB-Based Projects

By processor

AVR ATtiny

► ATtiny10

► ATtiny2313

► ATtiny84

► ATtiny841

► ATtiny85

► ATtiny861

► ATtiny88

AVR ATmega

► ATmega328

► ATmega1284

AVR 0, 1, and 2-series

► ATmega1608

► ATmega4808

► ATmega4809

► ATtiny1604

► ATtiny1614

► ATtiny202

► ATtiny3216

► ATtiny3224

► ATtiny3227

► ATtiny402

► ATtiny404

► ATtiny414

► ATtiny814

AVR DA/DB/DD-series

► AVR128DA28

► AVR128DA32

► AVR128DA48

► AVR128DB28

► AVR128DB48

► AVR64DD14

► AVR32DD28

ARM

► ATSAMD21

► RP2040

► RA4M1

About me

  • About me
  • Twitter
  • Mastodon

Feeds

RSS feed

A NeoPixel Driver using AVR Hardware [2]

20th August 2025

My previous article described a driver for NeoPixel (WS2812) LED displays using the SPI peripheral on an AVR processor in conjunction with a Timer/Counter and the Configurable Custom Logic (CCL).

This article shows how to go one step further, and eliminate most of the interconnections between the pins on that circuit by taking advantage of an additional feature of recent AVR processors, the Event System:

NeoPixelDriver2.jpg

NeoPixel Driver using the hardware peripherals and Event System in an AVR128DA28.

1st November 2025: Added a Postscript explaining how to eliminate the wire between PA1 and PA6 by using the CCL.

Introduction

The article A NeoPixel Driver using AVR Hardware [1] described a program for AVR processors that created the stream of pulses needed by the NeoPixel protocol, using the SPI peripheral in conjunction with a Timer/Counter Type A and the Configurable Custom Logic. It used the following circuit based on an AVR128DA28:

NeoPixelDriver3.gif

Circuit of the NeoPixel Driver using an AV128DA28 showing
the wires that can be replaced by the Event System.

We can go one step further, and use another feature of the AVR processors, the Event System, to replace the wires shown in red in the above circuit by internal connections defined using event channels. This not only saves wires on the circuit board, but also frees up some of the pins so they could be used for other functions.

The Event System

Terminology

The Event System avoids using the terms "input" or "output" because this could get confusing. For example, the input to the Event System could be the output from a Timer/Counter. It therefore uses the terms "event generator" for the signal initiating the event, and "event user" for the signal or signals that will be changed by the event.

Event generator

In our application the signal coming from an I/O port input is the event generator. You can usually use any I/O port input as an event generator.

There can only be one event generator; it wouldn't make sense to have more than one because there would be a conflict if the two inputs had different logic levels.

Event users

There is one event user available per port. In the AVR128DA28 the available pins are PA2 or PA7 for EVOUTA, PC2 for EVOUTC, and PD2 or PD7 for EVOUTD. Where there are two alternatives for a port the output on bit 2 is the default, and you select the alternative output on bit 7 using the PORTMUX EVSYSROUTEA register.

Channels

Each patch lead, linking an event generator with one or more event users, is called a "channel". Depending on the processor there can be between 6 and 10 channels, numbered from CHANNEL1 to CHANNEL10.

In the DA, DB, and DD series there are constraints on which channels you can use with event generators in each port, as shown in the following table:

Ports Channels
PORTA and PORTB CHANNEL0 and CHANNEL1
PORTC and PORTD CHANNEL2 and CHANNEL3
PORTE and PORTF CHANNEL4 and CHANNEL5
PORTG CHANNEL6 and CHANNEL7

So you can use at most two I/O pins from PORTA and PORTB, two from PORTC and PORTD, and so on.

If you try to use an I/O pin with a channel that doesn't support it you'll get a compile error, such as: 

'EVSYS_CHANNEL2_PORTA_PIN0_gc' was not declared in this scope

Event configurations

The following sections describe the event configuration statements used in the NeoPixel Driver:

Connecting the TCA0 waveform outputs to the look-up table

In the original circuit there were connections between the Timer/Counter waveform outputs WO0 (PA0) and WO1 (PA1), and the LUT1 inputs IN0 (PC0) and IN1 (PC1). These can be eliminated by defining them in the LUT1 configuration as follows:

  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

This replaces the two wires between WO0 and PD0, and WO1 and PD1, by TCA0 events.

Each LUT has two possible event inputs, called EVENTA and EVENTB (nothing to do with the event users EVOUTA and EVOUTB). Here we are specifying that IN2 comes from LUT1 EVENTA instead of from the external input PC2.

Connecting the SPI MISO pin to LUT1

We will use event channel 0 to eliminate the wire between MISO (PA5) and IN2 (PC2). The first half of this connection was defined in the configuration of LUT1 above. The second half is defined as follows:

  EVSYS.CHANNEL0 = EVSYS_CHANNEL0_PORTA_PIN5_gc;          // Link PA5 ...
  EVSYS.USERCCLLUT1A = EVSYS_USER_CHANNEL0_gc;            // ... to LUT1 EVENTA

Connecting PD4 to the SPI SS pin

Finally, we will use event channel 2 to eliminate the wire between SS (PA7) and the EN output PD4:

  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

Connecting the TCA0 WO1 output to the SPI SCK pin

In the original version of this article I I wrote that it was not possible to eliminate the external wire between WO1 (PA1) and SCK (PA6), as PA6 doesn't correspond to an event output. However, there is a way to do it using the CCL; see the Postscript.

The circuit

Here's the final circuit using the Event System to make the internal interconnections in the AVR128DA28 version of the NeoPixel Driver:

NeoPixelDriver2.gif

Simplified circuit of the NeoPixel Driver eliminating external connections
by using the Event System.

The performance is the same as the original circuit in the previous article, but because the pins PC0, PC1, and PC2 are no longer required as the look-up table inputs they could be used for another function.

Resources

Here's the version of the NeoPixel Driver for the AVR128DA28 using the Event System: NeoPixel Driver Program 2.

Further suggestions

After using the Event System to eliminate most of the interconnections between the pins in the AVR128DA28 version of the NeoPixel Driver I realised that it would be possible to fit the same functionality on an AVR family chip with half the number of pins, the AVR64DD14. I'll describe how to do this in the third and final article in this series.

Postscript

Prompted by a comment by Gavin Wahl I decided to have another look at the datasheet, and realised that it's possible to use the CCL to eliminate the final external wire between WO1 (PA1) and SCK (PA6). I've decided to leave it as a postscript as I'm not sure if the saving justifies the extra complexity. Here's the configuration:

Connecting the TCA0 WO1 output to the SPI SCK pin

First we specify the alternate position of the LUT0 output, on PA6:

  PORTMUX.CCLROUTEA = PORTMUX_LUT0_ALT1_gc;               // OUT on PA6

Next we mask IN0 and IN2, and take IN1 from PA2:

  CCL.LUT0CTRLB = CCL_INSEL0_MASK_gc | CCL_INSEL1_IN1_gc; // IN0 masked, IN1 from PA1
  CCL.LUT0CTRLC = CCL_INSEL2_MASK_gc;                     // IN2 masked

The truth table simply copies IN1 to OUT and ignores IN0 and IN2:

  CCL.TRUTH0 = 0b11001100;                                // Just copy IN1 to OUT

Finally we enable the output on PA6 and enable LUT0:

  CCL.LUT0CTRLA = CCL_OUTEN_bm | CCL_ENABLE_bm;           // Enable, output on PA6

We also need to move the statement enabling the CCL to after these statements:

  CCL.CTRLA = CCL_ENABLE_bm;                              // Enable CCL last

blog comments powered by Disqus