Scoreboard ws2812 with 560 LED's 11 seconds delay in 10 minutes

Hello, trying to give as much info as i can:

I am making a scoreboard for a basketball team with wired controller (matrix). For the countdown i have 228 LED's (ws2812b) connected to a single pin. For the "period", "faults" and "scores" i have another pin connected, but all of that code is not important right now (and not ready yet), so i will only post the code that matters for now that works.

For controlling i use an Elegoo Mega R3 Mega clone

I have read a lot about using delay, millli's, RTC DS 3231, zero error 1 second timing , but for me it's difficult!

With the following code, without a delay of 130ms, the countdown goes too fast! When i set the countdown for 10 minutes and compare it with real time, real time has 11 seconds left when the countdown is at 0.

What i first did, as you can see in the code, was adding a delay of 130ms which works, but the button press (offcourse) would allso have the same delay.

Is there a way i can "fool" the countdown timer to get more accurate without using this delay?
My personal guess is that the Elegoo clone crystal is the big problem. But i (in the end) need 19 pins to controll all functions for the scoreboard. The Mega has the proper amount of pins to handle all of my needs.

Could be that i am missing another board which is more reliable with the same amount of pins, but could not find them.

Code is :

#include <FastLED.h>
#define DATA_PIN     25
#define NUM_LEDS2    228
#define NUM_LEDS_PER_DIGIT2 56
#define DISPLAY_MMSS
#define BRIGHTNESS  20
#define LED_TYPE    WS2812B
#define COLOR_ORDER GRB
#define ROWS 6
#define COLS 5
#include <Bounce2.h>
#include "Adafruit_Keypad.h"
#include <Keypad.h>

uint8_t rowPins[ROWS] = {22, 24, 26, 28, 30, 32};
uint8_t colPins[COLS] = {23, 8, 27, 29, 31};

char keys[ROWS][COLS] = {
  {'1','2','3','4','5'},
  {'6','7','8','9','0'},
  {'a','b','c','d','e'},
  {'f','g','h','i','j'},
  {'k','l','m','n','o'},
  {'p','q','r','s','t'}
};

bool lit[ROWS*COLS] = {0};

CRGB leds2[NUM_LEDS2];

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS);

const uint64_t digits[10] = {
  0b0000000011111111111111111111111100000000111111111111111111111111, // 0
  0b0000000011111111000000000000000000000000111111110000000000000000, // 1
  0b0000000000000000111111111111111111111111111111111111111100000000, // 2
  0b0000000011111111111111110000000011111111111111111111111100000000, // 3
  0b0000000011111111000000000000000011111111111111110000000011111111, // 4
  0b0000000011111111111111110000000011111111000000001111111111111111, // 5
  0b0000000011111111111111111111111111111111000000001111111111111111, // 6
  0b0000000011111111000000000000000000000000111111111111111100000000, // 7
  0b0000000011111111111111111111111111111111111111111111111111111111, // 8
  0b0000000011111111111111110000000011111111111111111111111111111111, // 9
};

// The time at which the counter was (most recently) started
unsigned long startTime;

// Duration is specified in ms. So 1000 = 1 second, 60000 = 1 minute, etc.
unsigned long timerDuration = 0;

// Keep track of elapsed time from previous start/stop cycles
unsigned long cumulativeElapsedTime;

// Keep track of the current state of the device
enum State {Inactive, Active};
State state = State::Inactive;
// Count direction
enum Mode {CountUp, CountDown};
Mode mode = Mode::CountUp;

// FUNCTIONS
// Set the values in the LED strip corresponding to a particular display/value 
void setDigit(int display, int val, CHSV colour){
  
  for(int i=0;i<NUM_LEDS_PER_DIGIT2; i++){
    colour.v = bitRead(digits[val], i) * 255;
    leds2[display*NUM_LEDS_PER_DIGIT2 + i] = colour;
  }
}

void Start(){
  Serial.println(F("Timer activated!"));
  state = State::Active;
  startTime = millis();
}

void Stop() {
  Serial.println(F("Timer stopped"));
  // Add the length of time elapsed since the timer was last started to the total time elapsed
  cumulativeElapsedTime += (millis() - startTime);
  state = State::Inactive;
}

void Reset(){
  Serial.println(F("Timer reset"));
  cumulativeElapsedTime = 0;
  state = State::Inactive;
}

// This function runs once when the program first starts
void setup() {
  // Initialise a serial connection, used only for debugging
  Serial.begin(115200);
  Serial.println(__FILE__ __DATE__);
// Initialise the LED strip
  FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds2, NUM_LEDS2);
  FastLED.setBrightness(  BRIGHTNESS );      // Set overall brightness
}

// This function runs over and over
void loop() {
  
  // Grab the current timestamp
  unsigned long currentTime = millis();

  // Calculate the value to be displayed
  static long timeValue = 0;
  // The colour hue in which the time will be displayed
  int timeHue = 100;

  // What to do next depends on the current state of the device
  if(state == State::Active) {

    if(mode == Mode::CountDown) {
      // The time remaining is the total game duration, less the time spent during the
      // current period of play, less the time elapsed during any previous sessions
      // or other deductions
      timeValue = timerDuration - (currentTime - startTime) - cumulativeElapsedTime;
     
      // Map colour hue from green -> red based on fraction of time remaining
    if(timeValue <= 31000){  
      timeHue = 0;
     
      // Countdown has reached zero
      if(timeValue <= 0) {
        timeValue = 0;
        state = State::Inactive;
      }
      }
    }
    else if(mode == Mode::CountUp) {
      // Time is however long since we started counting, plus any previous existing time
      timeValue = (currentTime - startTime) + cumulativeElapsedTime;
     
      // Constant colour
    
      timeHue = 0;
    }
  }
  else if(state == State::Inactive){

    // Cycle colour hue while paused (BPM, from_value, to_value)
    if(timeValue >31000){
      timeHue = 100;
    }
    else if(timeValue <=31000){
    timeHue = 0;      
    }
  }
     
  char key = keypad.getKey();

  if (key=='m'){
    mode = Mode::CountDown;
      if (state==Inactive){
  Start();
    }
  }
  if (key=='o'){
    if (mode = Mode::CountDown){
      if (state==Active){
  Stop();
      }
    }
  }
  if (key=='n'){
    if (mode = Mode::CountDown){
      if (state==Inactive){
        timerDuration = 0;
        timeValue = 0;
    Reset();
      }
    }
  }
  if (key=='h'){
    timerDuration+=10000;
    timeValue = timerDuration - cumulativeElapsedTime;
  }
  if (key=='i'){
    timerDuration+=1000;
    timeValue = timerDuration - cumulativeElapsedTime;
  }
  if (key=='k'){
    timerDuration+=600000;
    timeValue = timerDuration - cumulativeElapsedTime;
  }
    if (key=='l'){
    timerDuration+=60000;
    timeValue = timerDuration - cumulativeElapsedTime;
  }
  if (key=='p'){
    timerDuration+=1200000;
    timeValue = timerDuration - cumulativeElapsedTime;
  }
  if (key=='q'){
    timerDuration+=900000;
    timeValue = timerDuration - cumulativeElapsedTime;
  }
  if (key=='r'){
    timerDuration+=600000;
    timeValue = timerDuration - cumulativeElapsedTime;
  }
  if (key=='s'){
    timerDuration+=300000;
    timeValue = timerDuration - cumulativeElapsedTime;
  }
  if (key=='t'){
    timerDuration+=120000;
    timeValue = timerDuration - cumulativeElapsedTime;
  }

  // Display as mm:ss
  #ifdef DISPLAY_MMSS
    // Use modulo to calculate "remainder" seconds
    int seconds = (timeValue / 1000) % 60;
    int minutes = timeValue / 60000;
    // Units
    setDigit(3, seconds%10, CHSV(timeHue, 255, 255));
    // Tens
    setDigit(2,(seconds/10)%10, CHSV(timeHue, 255, 255));
    // Hundreds
    setDigit(1, minutes%10, CHSV(timeHue, 255, 255));
    // Thousands
    setDigit(0,(minutes/10)%10, CHSV(timeHue, 255, 255));
  // Display in seconds
  #else
    // Units
    setDigit(3, (timeValue / 1000) % 10, CHSV(timeHue, 255, 255));
    // Tens
    setDigit(2, (timeValue / 10000) % 10, CHSV(timeHue, 255, 255));
    // Hundreds
    setDigit(1, (timeValue / 100000) % 10, CHSV(timeHue, 255, 255));
    // Thousands
    setDigit(0, (timeValue / 1000000) % 10, CHSV(timeHue, 255, 255));
  #endif

  // Send the updated values to the LED strip
   delay(130);
   FastLED.show();

}
  
    

So with the delay at the end, it works perfect. But the buttons allso having the same delay, which is a bit of a problem to say the least for a basketball game.

I am thinking of using a RTC module to solve this, but all i understand, is that it is used in combination with a actual clock. Am i missing something here to only use it for a countdown?

I am really triggered to learn, but i am doing this by try and error....
Can someone give me a hint to what to change or where to start?

I am not looking for a direct solution, want to learn, but if it gets too technical a little help would be great.

Thanks!

That's about a 2% error. More than I expect.

If the millis() are off, that's almost all it CAN be... If there are no bugs in the code.

You can just make a 10 minute timer (maybe turn-on an LED, etc.), and if there is just one delay(), you can time-it to see if it's about 11 seconds off. ...When you put delay() in a loop the other code takes time and that creates errors. But one delay() should be as-accurate as the "crystal" and it should match millis() exactly.

I think the official Uno & Mega use a "resonator" instead of a crystal, and it's not as accurate as a crystal. There's a chance that the clone uses an internal processor-clock that's more inaccurate.

You could probably make millis() "percentage" correction every time you read it. i.e. Make your own version of the millis() function where you read it and make an adjustment before returning the result.

And then you have to hope that the error is constant and doesn't drift...

Looking at the board picture it uses a crystal or a ceramic resonator (hard to tell from the picture) it has something connected to the osc pins. Even with a ceramic resonator I wouldn't expect worse the .5% error.

I expect that somewhere in you convoluted timing calculation there is an error. A typical problem is not taking into account the code execution time, but that usually causes the time to be too long.

I second the suggestion to make a simple test case to determine if there is a hardware problem

1 Like

An agreeable sentiment, but even a crystal drifts a bit with temperature changes,

There’s no substitute for a rock solid clock.

You could run a DS3231, using the 1Hz output as an interrupt. Then by counting those ticks from that stable, corrected clock, you’ll have a pretty darn good countdown timer,

The magic is because the Dallas DS clock chips have what’s called a TCXO
LINK
You don’t need the RTC, you only want the stable crystal.

You will likely need an external clock source such as an RTC. FastLED disables interrupts while sending data to the LEDs, disabling the millis interrupt. There is an attempt to compensate by adding to the millis count, but it is quite inaccurate.

Do not update the LEDs every loop, do it only when the pattern changes.

< edit >
I had another idea, if the onboard oscillator is accurate enough. Set up one of the 16-bit timers to generate an interrupt at 10mS intervals, and use that to generate your own counter similar to millis. That will allow you to drive around 330 LEDs before risking losing an interrupt, and give you better resolution than the 1Hz square wave off an RTC.

And this is the cause. All bit-banged methods deal with this issue, and on an AVR there ared no other methods available. RTC clock is the easiest solution to implement.

I don't think so.
Disabling interrupts by FastLED and similar libraries should cause the counter to be too long rather than too fast.
Inaccuracy of the built-in or external oscillator is a much more likely cause.

FastLED adds a compensating amount to the millis timer to attempt to correct for the missed interrupts, but apparently this is slightly too much.

Try this code, using timer1 to drive a counter as a substitute for millis. Note I have changed two lines of code where you had

#include <FastLED.h>
#define DATA_PIN     25
#define NUM_LEDS2    228
#define NUM_LEDS_PER_DIGIT2 56
#define DISPLAY_MMSS
#define BRIGHTNESS  20
#define LED_TYPE    WS2812B
#define COLOR_ORDER GRB
#define ROWS 6
#define COLS 5
#include <Bounce2.h>
//#include "Adafruit_Keypad.h" // ###### commented out, not needed? ######
#include <Keypad.h>

uint8_t rowPins[ROWS] = {22, 24, 26, 28, 30, 32};
uint8_t colPins[COLS] = {23, 8, 27, 29, 31};

char keys[ROWS][COLS] = {
  {'1', '2', '3', '4', '5'},
  {'6', '7', '8', '9', '0'},
  {'a', 'b', 'c', 'd', 'e'},
  {'f', 'g', 'h', 'i', 'j'},
  {'k', 'l', 'm', 'n', 'o'},
  {'p', 'q', 'r', 's', 't'}
};

bool lit[ROWS * COLS] = {0};

CRGB leds2[NUM_LEDS2];

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS);

const uint64_t digits[10] = {
  0b0000000011111111111111111111111100000000111111111111111111111111, // 0
  0b0000000011111111000000000000000000000000111111110000000000000000, // 1
  0b0000000000000000111111111111111111111111111111111111111100000000, // 2
  0b0000000011111111111111110000000011111111111111111111111100000000, // 3
  0b0000000011111111000000000000000011111111111111110000000011111111, // 4
  0b0000000011111111111111110000000011111111000000001111111111111111, // 5
  0b0000000011111111111111111111111111111111000000001111111111111111, // 6
  0b0000000011111111000000000000000000000000111111111111111100000000, // 7
  0b0000000011111111111111111111111111111111111111111111111111111111, // 8
  0b0000000011111111111111110000000011111111111111111111111111111111, // 9
};

// The time at which the counter was (most recently) started
unsigned long startTime;

// Duration is specified in ms. So 1000 = 1 second, 60000 = 1 minute, etc.
unsigned long timerDuration = 0;

// Keep track of elapsed time from previous start/stop cycles
unsigned long cumulativeElapsedTime;

// Keep track of the current state of the device
enum State {Inactive, Active};
State state = State::Inactive;
// Count direction
enum Mode {CountUp, CountDown};
Mode mode = Mode::CountUp;

// FUNCTIONS
// Set the values in the LED strip corresponding to a particular display/value
void setDigit(int display, int val, CHSV colour) {

  for (int i = 0; i < NUM_LEDS_PER_DIGIT2; i++) {
    colour.v = bitRead(digits[val], i) * 255;
    leds2[display * NUM_LEDS_PER_DIGIT2 + i] = colour;
  }
}

void Start() {
  Serial.println(F("Timer activated!"));
  state = State::Active;
  startTime = millis();
}

void Stop() {
  Serial.println(F("Timer stopped"));
  // Add the length of time elapsed since the timer was last started to the total time elapsed
  cumulativeElapsedTime += (millis() - startTime);
  state = State::Inactive;
}

void Reset() {
  Serial.println(F("Timer reset"));
  cumulativeElapsedTime = 0;
  state = State::Inactive;
}

//replace millis with mymillis counter
#define millis mymillis
volatile uint32_t mymillisCounter = 0;

uint32_t mymillis() {
  noInterrupts();
  uint32_t mytimer = mymillisCounter;
  interrupts();
  return mytimer;
}

ISR (TIMER1_COMPA_vect) {
  //interrupt occurs at 10mS intervals
  mymillisCounter += 10;
}

// This function runs once when the program first starts
void setup() {
  //initialize timer1 to generate 10mS interrupt
  //  (16000000MHz / 256) / 625 = 100Hz
  //FastLED disables interrupts for approximately 6.84mS with 228 LEDs
  noInterrupts();
  TCCR1A = 0; //clear timer1 control registers
  TCCR1B = 0;
  TCCR1B = 1 << WGM12 | 1 << CS12; //CTC Mode, prescaler 256
  OCR1A  = 624; //count to 625
  TIMSK1 = 1 << OCIE1A; //enable Timer1 Interrupt
  TCNT1 = 0; //zero counter
  interrupts();
  // Initialise a serial connection, used only for debugging
  Serial.begin(115200);
  Serial.println(__FILE__ __DATE__);
  // Initialise the LED strip
  FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds2, NUM_LEDS2);
  FastLED.setBrightness(  BRIGHTNESS );      // Set overall brightness
}

// This function runs over and over
void loop() {

  // Grab the current timestamp
  unsigned long currentTime = millis();
  Serial.println(currentTime);
  
  // Calculate the value to be displayed
  static long timeValue = 0;
  // The colour hue in which the time will be displayed
  int timeHue = 100;

  // What to do next depends on the current state of the device
  if (state == State::Active) {

    if (mode == Mode::CountDown) {
      // The time remaining is the total game duration, less the time spent during the
      // current period of play, less the time elapsed during any previous sessions
      // or other deductions
      timeValue = timerDuration - (currentTime - startTime) - cumulativeElapsedTime;

      // Map colour hue from green -> red based on fraction of time remaining
      if (timeValue <= 31000) {
        timeHue = 0;

        // Countdown has reached zero
        if (timeValue <= 0) {
          timeValue = 0;
          state = State::Inactive;
        }
      }
    }
    else if (mode == Mode::CountUp) {
      // Time is however long since we started counting, plus any previous existing time
      timeValue = (currentTime - startTime) + cumulativeElapsedTime;

      // Constant colour

      timeHue = 0;
    }
  }
  else if (state == State::Inactive) {

    // Cycle colour hue while paused (BPM, from_value, to_value)
    if (timeValue > 31000) {
      timeHue = 100;
    }
    else if (timeValue <= 31000) {
      timeHue = 0;
    }
  }

  char key = keypad.getKey();

  if (key == 'm') {
    mode = Mode::CountDown;
    if (state == Inactive) {
      Start();
    }
  }
  if (key == 'o') {
    if (mode == Mode::CountDown) { // ###### replaced = with == ######
      if (state == Active) {
        Stop();
      }
    }
  }
  if (key == 'n') {
    if (mode == Mode::CountDown) { // ###### replaced = with == ######
      if (state == Inactive) {
        timerDuration = 0;
        timeValue = 0;
        Reset();
      }
    }
  }
  if (key == 'h') {
    timerDuration += 10000;
    timeValue = timerDuration - cumulativeElapsedTime;
  }
  if (key == 'i') {
    timerDuration += 1000;
    timeValue = timerDuration - cumulativeElapsedTime;
  }
  if (key == 'k') {
    timerDuration += 600000;
    timeValue = timerDuration - cumulativeElapsedTime;
  }
  if (key == 'l') {
    timerDuration += 60000;
    timeValue = timerDuration - cumulativeElapsedTime;
  }
  if (key == 'p') {
    timerDuration += 1200000;
    timeValue = timerDuration - cumulativeElapsedTime;
  }
  if (key == 'q') {
    timerDuration += 900000;
    timeValue = timerDuration - cumulativeElapsedTime;
  }
  if (key == 'r') {
    timerDuration += 600000;
    timeValue = timerDuration - cumulativeElapsedTime;
  }
  if (key == 's') {
    timerDuration += 300000;
    timeValue = timerDuration - cumulativeElapsedTime;
  }
  if (key == 't') {
    timerDuration += 120000;
    timeValue = timerDuration - cumulativeElapsedTime;
  }

  // Display as mm:ss
#ifdef DISPLAY_MMSS
  // Use modulo to calculate "remainder" seconds
  int seconds = (timeValue / 1000) % 60;
  int minutes = timeValue / 60000;
  // Units
  setDigit(3, seconds % 10, CHSV(timeHue, 255, 255));
  // Tens
  setDigit(2, (seconds / 10) % 10, CHSV(timeHue, 255, 255));
  // Hundreds
  setDigit(1, minutes % 10, CHSV(timeHue, 255, 255));
  // Thousands
  setDigit(0, (minutes / 10) % 10, CHSV(timeHue, 255, 255));
  // Display in seconds
#else
  // Units
  setDigit(3, (timeValue / 1000) % 10, CHSV(timeHue, 255, 255));
  // Tens
  setDigit(2, (timeValue / 10000) % 10, CHSV(timeHue, 255, 255));
  // Hundreds
  setDigit(1, (timeValue / 100000) % 10, CHSV(timeHue, 255, 255));
  // Thousands
  setDigit(0, (timeValue / 1000000) % 10, CHSV(timeHue, 255, 255));
#endif

  // Send the updated values to the LED strip
  delay(130);
  FastLED.show();

}

Thank you for the info. It is a new for me.
Could you please to point where the compensation code residues in the library?
By the way, FastLED contains drivers for many board's architectures, is the compensation applicable to the all boards or to AVR only?

Searching through the source files, the code appears to be near the beginning of the src/platforms/avr/clockless_trinket.h file for AVR processors. The code defines MS_COUNTER to be the external reference to the actual millis counter in one of the other header files.

2 Likes

If you loose 11 seconds in 10 minutes, do you loose 21 seconds in 20 minutes? That would be a constant loss... if 22 seconds were lost, that would be a linear loss.

If constant loss, use "61000" milliseconds for one minute.
If linear loss, use "60000*2%*minutes)"

First of all, thanks everyone for the input. Lots of googling to do for me now!

I edited my starting post, because i made a mistake in what i meant and what you read. I apologise for that :flushed: .
My code is working with the delay at the very end of it. Time is accurate, but without it, it goes too fast.

David_2018, if i upload your version, but delete the delay at the end, a few things happen:
if i start the timer, let it run down to zero, it works, timer is off about 1 second in 10 minutes, so very nice!
If i reset and start again, after 10 times of resetting, setting time and starting again, the timer adds 1 second to the total i set. It does not matter if i reset via the button. After 20 times starting, resetting etc, there is another second added.

If i put back the delay, the code allso works, but without above problems, and offcourse the delay in button-press is back again.

I understand the change you made with = and ==, thanks for the correction.
The timer1 part is what i have been reading about alot, but had no idea how and where to implement it in code. Again, googling is my friend i guess.

It has something to do with delay function and timer1, but i would have to understand the coding part first, to change it.

I found a couple of problems.

First was a mistake I made, I used a #define to replace millis() with mymillis(), but neglected to notice that your start() and stop() functions came before this, so they were still using the actual millis() function. Best solution is to just replace millis() with mymillis() throughout the sketch and remove the #define.

Second, you use a variable cumulativeElapsedTime to save the elapsed time when the timer is stopped, then later when the timer is restarted use this to continue the count. When you reset the timer, this value is set to 0, but if the timer runs down to 0 and stops, this value is not set to 0. If you then restart the timer without resetting, instead of starting with the full time duration, the previous cumulativeElapsedTime is subtracted from the duration immediately.

I replaced the millis (should have seen that myself too, so not only your mistake). This solves about all of my issues. Really great work, thank yoy

Funny thing is, that it allso fixes what i explained before :

This doesn't happen anymore, so i guess i have to digg a little more into the timer1 part??

For what you mean by the second problem :
I understand what you mean, but it is not what actually happens.....
If i do not reset after counter goes to 0,and hit start again, it starts at the last entered time. If i add time with buttons (so without reset), it adds to the previous set time (is what i want).

start = 10s, goes to end, no reset.
hit 1s button, start again without reset = start at 11s.

start =20s, goes to end, no reset.
start again without reset = 20s starting again.

Do the above with reset = start with respectively 1s or 20s.

I can repeat this as much as i want, but the outcome is allways the predicted outcome (not what happened before as i qouted).

What i want to accomplish, is that the person who is in charge of the controller, can see that the timer was reset or not, by changing the color for reset to blue and stay that way. Or keep being red, if no reset was pressed.

But to be honest, right now i am trying to understand the timer1 part, which intrigues me (and gives me a little headache....) , beats driving my truck i guess :laughing:

P.s. i think most of the helpers here a outside of the EU. So answering-time is a little off i assume.

I don't have much experience with using the counter/timers, but here is my understanding of the code:

  noInterrupts();
  TCCR1A = 0; //clear timer1 control registers
  TCCR1B = 0;

This temporarily disables interrupts while the configuration of timer1 is being changed, then clears the Timer/Counter control registers for timer1.

  TCCR1B = 1 << WGM12 | 1 << CS12; //CTC Mode, prescaler 256

This is setting the Timer/Counter mode to CTC (Count To Compare). There are four bits that control the mode, WGM13, WGM12, WGM11, and WGM10, only WGM12 needs to be set to 1, all others remain at 0.
The Timer/Counter also contains a prescaler, which divides the clock source (the 16MHz oscillator) before feeding it into the Timer/Counter. In this case the prescaler is dividing by 256, to produce 62,500Hz. The prescaler is controlled by bits CS12, CS11, and CS10, which are set to 1, 0, 0 respectively.

  OCR1A  = 624; //count to 625

This sets the Output Compare Register to 624. (note this is 1 less than the actual count of 625).
In CTC mode, the counter counts the number of clock pulses, starting at 0. When the count matches the contents of the Output Compare Register, an interrupt is generated, and the counter is reset to 0.
625 cycles of the 62,500Hz clock will produce an interrupt every 10mS.

  TIMSK1 = 1 << OCIE1A; //enable Timer1 Interrupt

This enables the timer1 interrupt.

  TCNT1 = 0; //zero counter
  interrupts();

This sets the timer1 counter initial value to 0, then re-enables interrups.

The interrupt service routine is fairly simple, it just adds 10 to a counter at 10mS intervals, producing a milliseconds count that increments in steps of 10.

ISR (TIMER1_COMPA_vect) {
  //interrupt occurs at 10mS intervals
  mymillisCounter += 10;
}

The purpose of doing all of this is to produce a millisecond count, similar to millis(), that has an interrupt interval longer than the time it takes FastLED to update the LED strip. With 228 LEDs, that is 6.84mS. FastLED disables interrupts during this time, but any interrupt that occurs during that time will be processed when interrupts are re-enabled. The problem with millis() is that it needs an interrupt every 1.024mS, so several will be lost during the update of the LEDs (six interrupts will occur, but only one is processed when interrupts are re-enabled).

If you change to 560 LEDs, that will take 16.8mS, making the 10mS interval I'm using too short. In that case, you can increase the count from 625 to 1250, to get a 20mS interval, and change the interrupt service routine to add 20.

I found different sites that explain how it works, but it is easier to understand your way. Now i can actually see what happens in real-time. Makes sense, allthough i will dig a little deeper into the interrupt part to understand it better.

To explain this part of the code:

//#include "Adafruit_Keypad.h" // ###### commented out, not needed? ######

The controller i made, is built with some hardware called NeoKey 5x6 Ortho Snap-apart from Adafruit. It has built-in resistors, diodes, LED's to make it easier to install without soldering etc.
Right now i don't need the inbuilt LED's, but that may happen in the future for visualizing button press.
That's why it's commented out for now.

I will set this topic to solved, even if there is a whole lot left to learn for me.
Thank you all!

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