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'. If the color is not specified, BLACK is used which is a simple way to create a gap in our strip.The example given in the sketch
fill( 3, GREEN ); sets the 3 pixels following the 5 specified in the preceding slide animation to a GREEN colour.
void slide( uint16_t len, int32_t col = 0, int32_t bcol = BLACK ) - creates the effect of the single colour, or sequence of colours, given in the first parameter, 'sliding' along the background colour or sequence specified by "col". Specifying a colour of -1 means that the colour sequence given in the "Colours" array will be used. In the example used in the sketch, the first 5 colours given in the Colours array appears to slide down a BLACK background.
slide( 5, -1, BLACK );
void bop( uint16_t len, int32_t col = 0, uint32_t bcol = BLACK ) - gives the impression of a single colour "bopping" along a single colour, or sequence background given by the bcol parameter. In the given example
bop( 7 , -1, BLACK ); - the last 7 pixels are set to the 'Colours' array, Each of these pixels appears to get blanked and then restored in a repeated sequence.
The animations are designed to be run inside of a loop with a delay statement as the last instruction. This delay serves 2 functions: First it signals to the strip that we have reached the end of the pixel stream, causing the pixels to be displayed. The minimum delay() of 1 equals 1 mSec which is much greater than the 50 nSecs required to signal end of stream. Second, it provides the timing sequence of the animations.
Cautions and Limitations:
- the Tiny85 does not have the most spacious flash memory. This mostly affects us when specifying the Colours array for longer strips. In my experimentation as I developed the sketch I tried to attach a 144 pixel strip. Expanding the array size exceeded the amount of flash memory. The sketch as given only uses 884 bytes (10%) of flash, and 73 bytes ( 14% ) of RAM. Other than this consideration there should be no limit on strip length other than the amount of power that you can provide to it.
- The use of the USI peripheral limits us to a single strip. We could do something using the overflow interrupt but I think it would make more sense to change to a total bit bang approach as I did with the Tiny10. I actually played with this when I first started this project. It involved cranking the Tiny85 up to it's maximum frequency of 20MHz which required an external crystal (not recommended for a breadboard project). If you are interested in seeing this discussed in another project here on Hack-a-Day just let me know in the comments.
Please nJoy