Timer problem with Neopixel Ring 60

Hi together,

I built a clock with a Neopixel Ring with 60 LEDs and an Arduino DUE. As far as I know the Neopixel routine "strip.show()" disables interrupts and thus the timer is not precise anymore.

Due to the high frequency of updating the LEDs the timer drifts within minutes to a delay of a couple of seconds. The high frequency is needed for an effect that shows the running seconds on all pixels.

Is there a way to use a second timer on the Arduino DUE? My first approach is to use a Real Time Clock but I am afraid that the correction between the internal clock and the RTC leads to an poor behavior of the effect for the seconds because this effect depends on the milliseconds of the timer.

Does anybody have a good advise to solve this issue?

Thank you :slight_smile:
Benjamin

"the high frequency of updating the LEDs..."
Once every 1000ms (1/sec) is "high frequency"?

No it's much faster. The effect shows the seconds but it's kind of a fading of all pixels. The whole effect repeats every second.

Try to use a DS3231 RTC. It´s much more efficient and stable

Sounds like my clock project (which I have done no work on in several months!)

Here's my code, maybe you could try it. Also you could post your own code?

//Colour Clock
//PaulRB
//Jul 2014
//Hardware: Arduino Micro Pro, DS3231 RTC, 1m 60 x RGB LED WS2812B 

#include <Adafruit_NeoPixel.h>

#define LEDStripPin 10
#define RTCSQWPin 7

Adafruit_NeoPixel strip = Adafruit_NeoPixel(60, LEDStripPin, NEO_GRB + NEO_KHZ800);

#include <Wire.h>

//#include <LiquidCrystal_I2C.h>
//LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address

//RTC Data
#define RTCadrs 0x68
#define RTCrequestTime 0x00
#define RTCrequestTemp 0x11
#define RTCsetControl 0x0E


void setup() {
  
  Wire.begin();
  Serial.begin(38400);
  //setRTCtime(0x52, 0x21, 0x03, 0x07, 0x14);
  setRTCcontrol(B0000000); // enable 1Hz output
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
  
  pinMode(RTCSQWPin, INPUT_PULLUP);
  //lcd.begin(16,2);               // initialize the lcd 
}

byte LastRTCSQW, CurrRTCSQW;
byte secHand, minHand, hourHand;

void loop() {
  
  char buff[80];
  byte RTCsecs,RTCmins,RTChours,RTCdow,RTCday,RTCmonth,RTCyear;
  
  LastRTCSQW = CurrRTCSQW;
  CurrRTCSQW = digitalRead(RTCSQWPin);
  
  if (LastRTCSQW == LOW && CurrRTCSQW == HIGH) {
    
    //get current date & time from RTC
    Wire.beginTransmission(RTCadrs);
    Wire.write(byte(RTCrequestTime));
    Wire.endTransmission();
    Wire.requestFrom(RTCadrs, 3);
    RTCsecs = Wire.read();
    RTCmins = Wire.read();
    RTChours = Wire.read();
    //RTCdow = Wire.read();
    //RTCday = Wire.read();
    //RTCmonth = Wire.read();
    //RTCyear = Wire.read();
    
    //sprintf (buff, "Date: %02x/%02x/20%02x Time: %02x:%02x:%02xGMT", RTCday, RTCmonth, RTCyear, RTChours, RTCmins, RTCsecs);
    //sprintf (buff, "Time: %02x:%02x:%02x", RTChours, RTCmins, RTCsecs);
    //lcd.home();
    //lcd.print (buff);

    byte secHandNew = bcdToDec(RTCsecs);
    byte minHandNew = bcdToDec(RTCmins);
    byte hourHandNew = (bcdToDec(RTChours) % 12) * 5 + minHandNew / 12;
    for (int i=1; i<256; i+=2) {
  
      if (hourHand != hourHandNew) {
        strip.setPixelColor(hourHand, strip.Color(255-i, 255-i, 0));
        strip.setPixelColor(hourHandNew, strip.Color(i, i, 0));
      }
  
      if (minHand != minHandNew) {
        strip.setPixelColor(minHand, strip.Color(255-i, 0, 255-i));
        strip.setPixelColor(minHandNew, strip.Color(i, 0, i));
      }
  
      if (secHand != secHandNew) {
        if (secHand == hourHand) {
          strip.setPixelColor(hourHand, strip.Color(i, i, 0));
        }
        else if (secHand == minHand) {
          strip.setPixelColor(minHand, strip.Color(i, 0, i));
        }
        else {
          strip.setPixelColor(secHand, strip.Color(0, 255-i, 255-i));
        }
        if (secHandNew == hourHand) {
          strip.setPixelColor(secHandNew, strip.Color(i, i, i));
        }
        else if (secHandNew == minHand) {
          strip.setPixelColor(secHandNew, strip.Color(i, i, i));
        }
        else {
          strip.setPixelColor(secHandNew, strip.Color(0, i, i));
        }
      }
  
      strip.show();
      //delay(1);
    }
    hourHand = hourHandNew;
    minHand = minHandNew;
    secHand = secHandNew;
  }
  
  LastRTCSQW = digitalRead(RTCSQWPin);
}

void setRTCtime(byte mins, byte hours, byte date, byte month, byte year) {
  
  Wire.beginTransmission(RTCadrs);
  Wire.write(byte(RTCrequestTime));
  Wire.write(byte(0));
  Wire.write(mins);
  Wire.write(hours);
  Wire.write(byte(0));
  Wire.write(date);
  Wire.write(month);
  Wire.write(year);
  Wire.endTransmission();

}

void setRTCcontrol(byte control) {
  
  Wire.beginTransmission(RTCadrs);
  Wire.write(byte(RTCsetControl));
  Wire.write(control);
  Wire.endTransmission();

}



byte bcdToDec(byte bcd) {
  return (bcd & 0x0F) + ((bcd & 0xF0) >> 1) + ((bcd & 0xF0) >> 3);
}

Notice I have the square-wave output from the RTC module set to 1Hz and connect it to an input pin. The sketch polls that pin to synchronise with the RTC.

Paul

BenJourno:
My first approach is to use a Real Time Clock

Correct and actually, only approach to actually use as a clock. You want a clock, you use a clock. :grinning:

BenJourno:
I am afraid that the correction between the internal clock and the RTC leads to an poor behaviour of the effect for the seconds because this effect depends on the milliseconds of the timer.

What milliseconds of what timer?

If you use an RTC, you are not using millis() to determine the time. You would either poll the RTC 1 Hz output to detect the event, or you would frequently poll the RTC for the seconds value.

Paul__B:
What milliseconds of what timer?

If you use an RTC, you are not using millis() to determine the time. You would either poll the RTC 1 Hz output to detect the event, or you would frequently poll the RTC for the seconds value.

My effect is a fade of all LEDs that runs arround the 60 pixel-circle every second.
That means I need the recent millis() for every update-step of all LEDs. When the last LED is reached it starts again at the first LED.
The problem when I use the external RTC to synchronize with my internal millis() is that I will have a jump at this moment because my internal timer runs slower than the actual time.
Let's say the internal timer is at 990ms when the external (correct running) RTC is at 1000ms. The synchronizing will lead to an time step of 10ms. The effect won't run fluidly but will have a negative delay between the last and the first LED.

I have made many of these clocks, the only way to do this is with a RTC like the DS3231 as already mentioned.
Either use the 1HZ o/p from the RTC (attached to an interrupt pin) or just read the RTC every ~100ms then update the pixel display.

Attached is some code that runs a 60 Neopixel ring clock and it fades between seconds. Maybe you can get ideas or adapt it to your needs. It uses an DS3231 RTC but if you strip out the temperature and pressure code it should work with any RTC that can also generate a 1Hz pulse.
I really should upload a video of the effect but the clock body is not perfected yet.

NEO_Clock_v8.ino (20.9 KB)

BenJourno:
The problem when I use the external RTC to synchronize with my internal millis() is that I will have a jump at this moment because my internal timer runs slower than the actual time.
Let's say the internal timer is at 990ms when the external (correct running) RTC is at 1000ms. The synchronising will lead to an time step of 10ms. The effect won't run fluidly but will have a negative delay between the last and the first LED.

Then you need to use a Phase Locked Loop to synchronise them. It may be easiest to forget using millis(). Instead, use a "busy waiting" loop (which could using more-or-less isochronous code, perform all your other tasks) to delay between NeoPixel updates with a repetition count that you correct each second from the RTC.

Paul__B:
Then you need to use a Phase Locked Loop to synchronise them. It may be easiest to forget using millis(). Instead, use a "busy waiting" loop (which could using more-or-less isochronous code, perform all your other tasks) to delay between NeoPixel updates with a repetition count that you correct each second from the RTC.

That means I have to calculate t_diff in the moment the RTC is changing the second and add t_diff to the loop timing? That sounds like a good idea.

Thanks for this solution. I will share my code when I'm finished with it.

BenJourno:
That means I have to calculate t_diff in the moment the RTC is changing the second and add t_diff to the loop timing? That sounds like a good idea.

Whoah there!

It isn't quite that simple.

A PLL is an example of a servo loop requiring PID control. If you merely correct by the whole measured error over one cycle, it will hunt badly. Probably the easiest solution - considering your RTC pulses are dead regular, will be to zero the sweep on those pulses whilst noting just the direction of the error and use this to increment or decrement the timing variable by one only (or not to alter it if the error is in fact zero).

Whilst this may take some time to "lock", once it has done so it will be extremely stable and the error should then not be noticeable. To reduce the lock-in time, if you can include a "test" function to print out that value, you can then use it to calibrate the default at start-up (or include code to detect stability and then write the value to EEPROM as a default).

From the sketch I posted...

  uint32_t t2 = millis();                             // Get the end time
  // If it took to long then decrease delay a bit
  if((t2 - t1) > 900){
    delayTime--;

This part of doTime() calculate the time the function is running and adjust the seconds led fade delay to ensure it all happens in about 900ms. Once per minute I push the delay time up by 1 so it will always hover around the 900ms value.