In some of my other projects I discussed driving a NEOpixel strip from an ATtiny10 as well as a Milk-V Duo.  I invite you to take a quick look at these projects as many of the basic principles of driving NEOpixels are already explained in these blogs.  I have found that using the SPI bus is the easiest method of lighting up pixels on a strip with the exception of the Tiny10 which has no concept of Serial. It required a total bit-bashing approach.

As usual, this project has a companion YouTube video.  The link should be attached as soon as I have the video published.

I like to play with the ATtiny85 but a quick look at the DataSheet shows that it does not have dedicated I2C or SPI or Serial interfaces.  But it does reveal something called a USI or Universal Serial Bus.  Digging into it reveals that it consists of an 8 bit shift register with some 'hooks' around it to allow implementation of both SPI and I2C with minimal software.  So, seeing as NEOpixels require a minimal SPI, I decided to give it a try - here we see the result.

The key to driving a NEOpixel strip is the timing - we need pulses of approximately 400 or 800 nSecs for Zeros and Ones.  This means that we need to clock the shift register at something close to 2.5 MHz.  The USI has options to use the timer, or to manually bit bash the clock.  For this project I chose the latter for greater flexibility.  This makes the actual clock rate achieved dependent on the system clock frequency. Taking into account the 2 cycles need to bash the clock bit, I found that setting the system clock to 4 MHz gave satisfactory pulse widths. On the analyzer I observed 470 nSecs pulses, although calculation would indicate it is probably closer to 500 nSecs.  Connecting the NEOpixel strip gave the expected pixels.

It should be noted that this project assumes the use of the ATTinyCore boards package which gives many clocking options for use with the Arduino IDE.  Also be aware that you must do a Load Bootloader from the IDE for System Clock changes to take effect.  (we will, of course, have to upload the sketch again).  Spence's github has a wealth of information on this package.

To produce the pulses for a single pixel we load two 3 bit NEObits into the 8 bit shift register, then shift it out 12 times for the 24 bit RGB value to be loaded into the NEOpixel strip.  The 'low' time of the pulses is not very critical - as long as we don't exceed 50 nSecs.  The relevant code looks something like:

    for(x=0;x<12;x++) {
    USIDR = 0x44;  //  preload the data register to 'Zeros'
    if( tmp & 1 ) { USIDR |= 0x06; }  //  low nibble to 'One'
    if( tmp & 2 ) { USIDR |= 0x60; }  // high nibble to 'One'
    Send();                           // Clock out the data
    tmp = tmp >> 2;    //  get the nex 2 bits
       }

the Send() function consists of assembler code defined as an inline function:

#define STB { __asm__("sbi 0x0d,0x1"); } // strobe USICR bit 1 to clock out data

inline void Send() { STB; STB; STB; STB; STB; STB; STB; STB; } // 8 strobes

Note: the "tmp" variable is the 32 bit (uint32_t) colour value. The compiler is not real adept at manipulating 32 bits. A simple tmp >> 2 gives no errors but only rotates 16 of the 32 bits.  Similarly a tmp++ does not work - we need to use tmp = tmp + 1 instead.

To have the pixels display on the strip we need to introduce a delay of more than 50 nSecs.  The strip will retain the data until we send new data.


Animations:

What is the point of driving a NEOpixel strip if we don't make it dance?  I have included some simple animations to do just that.  We want to note that these animations add to each other - ie. one continues right after the other until a final delay of greater than 50 nSecs is encountered, at which time the pixel animations are displayed on the strip.

void fill( uint16_t len, uint32_t col = 0 ) - this routine simply fills the next 'len' pixels with color 'col'. ...

Read more »