Here's a sample code that will drive a Neopixel strip WITHOUT disabling interrupts and is non-blocking. It's PWM based and the 16 MHz Atmega328P timers is barely enough to satisfy the timing requirement. I originally developed this for a 32 MHz XMEGA CPU but since it's PWM based, it's very easy to port to any MCU with fast enough timers. The algorithm actually works best with a CPU that supports nested interrupts and one-shot timers, and even better if you have timers that support DMA (like most higher end Cortex M). This is tested on a Nano but for a Mega, you might need to change the pins where OC1B is connected. I think TIMER1 is going to be the same for both Mega and Nano.
Neopixel pin is at PB2 or D10 in Arduino
#define NUMLEDS 8
void neoPixelUpdate(void);
// NeoPixel variables, 8 LEDs at 3 bytes (24 bits) each
volatile uint16_t NeoPixelBitCounter = 0;
volatile uint8_t neopixelData[NUMLEDS*3];
volatile uint8_t neoCnt01 = 0;
volatile uint8_t neoCnt02 = 0;
volatile uint8_t updateFinish = 0;
volatile uint8_t tmpVar = 0;
volatile uint8_t rcvdChar = 0;
volatile uint8_t uartFlag = 0;
uint8_t ledCounter = 0;
// NeoPixel update ISR
ISR(TIMER1_COMPA_vect)
{
/*******************************************
set PWM setting for the next cycle
*******************************************/
// clear interrupt flags
TIFR1 = 0x27;
// limit "delay" between fall and rise of PWM signal
ICR1 = 200;
OCR1A = 15;
// the magic happens from here
tmpVar = neopixelData[neoCnt01];
// set the duty cycle for the next cycle based on NeoPixel data, 0.4 usec for low, 0.85 usec for high
OCR1B = ( tmpVar & 0x80 ? 13 : 6 );
// update variables
neopixelData[neoCnt01] = tmpVar << 1;
neoCnt02++;
if(neoCnt02 & 0x08)
{
neoCnt02 = 0;
neoCnt01++;
}
NeoPixelBitCounter++;
if(NeoPixelBitCounter > (NUMLEDS*24))
{
// stop TIMER1 and set flag
TCCR1B = 0x00;
updateFinish = 1;
}
}
ISR(USART_RX_vect)
{
rcvdChar = UDR0;
uartFlag = 1;
}
void setup()
{
// disable global interrupts for now
asm("CLI");
// NeoPixel output on PORTB2 (OC1B pin)
PORTB = 0x00;
DDRB |= (1<<PORTB2);
// UART initialization, 115200-8-n-1
UCSR0B = 0x00;
UBRR0H = 0;
UBRR0L = 0;
UCSR0A = 1<<U2X0 | 0<<MPCM0;
UCSR0B = 1<<RXCIE0 | 0<<TXCIE0 | 0<<UDRIE0 | 1<<RXEN0 | 1<<TXEN0 | 0<<UCSZ02;
UCSR0C = 0<<UMSEL01 | 0<<UMSEL00 | 0<<UPM01 | 0<<UPM00 | 0<<USBS0 | 1<<UCSZ01 | 1<<UCSZ00 | 0<<UCPOL0;
UBRR0H = 0;
UBRR0L = 16;
// stop TIMER1
TCCR1A = 0x00;
TCCR1B = 0x00;
// reset counter 1
TCNT1 = 0;
// set TIMER1 TOP value temporarily
ICR1 = 16000 - 1;
// set duty cycles temporarily
OCR1A = 8000 - 1;
OCR1B = 8000 - 1;
// enable interrupt on OCR1A compare
TIMSK1 = 0<<OCIE1B | 1<<OCIE1A | 0<<TOIE1;
// enable global interrupts
asm("SEI");
ledCounter = 1;
updateFinish = 1;
}
void loop()
{
// this is called as soon as the Neopixel is updated
if(updateFinish)
{
updateFinish = 0;
// fill NeoPixel array with color values
for(uint8_t count=0; count<(NUMLEDS*3); count+=3)
{
// alternate color animation
if(ledCounter)
{
neopixelData[count+0] = 0xFF;
neopixelData[count+1] = 0x00;
neopixelData[count+2] = 0x00;
}
else
{
neopixelData[count+0] = 0x00;
neopixelData[count+1] = 0x00;
neopixelData[count+2] = 0xFF;
}
}
if(ledCounter) {
ledCounter = 0;
}
else {
ledCounter = 1;
}
// add some delay or the animation is too fast
for(uint32_t count=0; count<0x8FFFF; count++) {
asm("NOP");
}
// start Neopixel display
neoPixelUpdate();
}
// detect input on serial interface, and echo back
if(uartFlag)
{
uartFlag = 0;
UDR0 = rcvdChar;
while((UCSR0A & 1<<TXC0) == 0);
UCSR0A |= 1<<TXC0;
}
}
void neoPixelUpdate(void)
{
// set initial TIMER1 configuration
TCCR1A = 0<<COM1A1 | 0<<COM1A0 | 1<<COM1B1 | 0<<COM1B0 | 1<<WGM11 | 0<<WGM10;
// stop TIMER1
TCCR1B = 0x00;
// clear flags
TIFR1 = 0x27;
// reset counter to zero
TCNT1 = 0;
// create initial TOP value
ICR1 = 65000;
// set initial duty cycles
OCR1A = 32500;
OCR1B = 0;
// reset NeoPixel variables
NeoPixelBitCounter = 0;
neoCnt01 = 0;
neoCnt02 = 0;
// start TIMER1 at 16 MHz
TCCR1B = 1<<WGM13 | 1<<WGM12 | 0<<CS12 | 0<<CS11 | 1<<CS10;
return;
}
The only thing required on the setup stage is just the TIMSK1 part and setting PB2 as output