Correct millis() with an RTC

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

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.