Millis/micros Inaccuracy and active compensation

Hi,

Context: I am using 2 shift registers to control 16 LEDs with 4 arduino outputs. All 16 LEDs flash at different rates. I am forced to use the micros function (delays aren’t acceptable).

Problem:
I have a question regarding the inaccuracy of the micros function. I noticed the existence of the “>” symbol in this line of code: if((state[k] == HIGH) && (currentMicros - previousMicros[k] >= onTime[k])). It takes time for the computer to execute the lines of code below this one. By the time it gets to this if statement in the next loop iteration, more time has passed than is needed. For example, if an LED needs to be turned off after 100 milliseconds, the millis number will be about 138 milliseconds when the arduino gets to this if statement (this number is arbitrary and changes) since, again, it takes time to run through the bottom lines of code. This leads to it being 38 milliseconds too late to turn it off, and if that’s true, then it will be 64 milliseconds late to flash it on after that. This problem compounds itself. To get around this problem, I have tried using the ‘subtraction[k]’ variables to subtract the amount of time from the variables ‘onTime[k]’. For example, if it turns the LED on 38 milliseconds too late, actively compensate by subtracting that value from the next time the LED is off. I have trouble writing the code for this active compensation part. I have tried for several days to get it to work, but I never did. Any help would be greatly appreciated.

With regards,
Gabe

const char dataPin = 2;
const char clockPin = 4;
const char latchPin = 3;
const char clearPin = 5;

boolean state[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
unsigned long previousMicros[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
unsigned long subtraction[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

unsigned long offTime[16] = {100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600}; //units in seconds; can't be less than 1 (not a float data type)
unsigned long onTime[16] = {100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600}; //units in seconds; can't be less than 1 (not a float data type)

boolean stateChange = 0;

/////

void setup() {
  pinMode(dataPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(latchPin, OUTPUT);
  pinMode(clearPin, OUTPUT);

  digitalWrite(clearPin, HIGH);   //it only clears if it's 'LOW', otherwise it needs to be 'HIGH'
  
  Serial.begin(9600);

  for(char i=0; i<16; i++) {
     offTime[i] = offTime[i]*1000;
     onTime[i] = onTime[i]*1000;
  }
}

/////

void loop() {
  for(char k=0; k<16; k++) {
    unsigned long currentMicros = micros();
  
    if((state[k] == HIGH) && (currentMicros - previousMicros[k] >= onTime[k])) {
       subtraction[k] = (currentMicros - previousMicros[k]) - onTime[k]; //ppf: beginning of experimental active compensation
       //offTime[k] = offTime[k] - subtraction[k];
       Serial.println(subtraction[k]);
      
       state[k] = LOW;
       previousMicros[k] = currentMicros;
       stateChange = 1;
    }
    else if ((state[k] == LOW) && (currentMicros - previousMicros[k] >= offTime[k])) {
       subtraction[k] = (currentMicros - previousMicros[k]) - offTime[k]; //ppf: experimental active compensation
       //onTime[k] = onTime[k] - subtraction[k];
      
     state[k] = HIGH;
     previousMicros[k] = currentMicros;
       stateChange = 1;
    }
  }
  //everything above sets the state values
  //everything below writes the LED states to the shift registers
  
  if(stateChange == 1) { //only updates the shift register when there is a state change, otherwise all states are the same
   Clear();
  
   for(char i=15; i>=0; i--) {
     if(state[i] == 0) {
       digitalWrite(clockPin, HIGH);
       digitalWrite(clockPin, LOW);
     }
     if(state[i] == 1) {
       digitalWrite(dataPin, HIGH);
       digitalWrite(clockPin, HIGH);
       digitalWrite(dataPin, LOW);
       digitalWrite(clockPin, LOW);
     }
   }
   Latch();
   stateChange = 0;
  }
}

//////////

void Latch() {
  digitalWrite(latchPin, HIGH);
  digitalWrite(latchPin, LOW);
}

void Clear() {
  digitalWrite(clearPin, LOW);     //don't change
  digitalWrite(clearPin, HIGH);   //don't change
}
  Serial.begin(9600);
...
       Serial.println(subtraction[k]);
...

Higher baud rate will help. 250000 is a good choice.

And/or, get rid of the println calls.

Do bother with that fiddly subtraction.

Change lines like this…

       previousMicros[k] = currentMicros;

…to this…

       previousMicros[k] += onTime[k];

If you use something like this, you don't need active compensation.

const unsigned long interval = 100;

void loop() {
  static unsigned long prevMicros = micros();
  if (micros() - prevMicros >= interval) {
    // Do something
    prevMicros += interval;
  }
}

If you find that driving the shift registers takes too much time, consider using the SPI bus to drive them (up to 8 MHz).

Pieter

I suspect you will gain just a bit in performance without incurring any side-effects by changing this…

  for(char k=0; k<16; k++) {
    unsigned long currentMicros = micros();

…to this…

  unsigned long currentMicros = micros();
  for(char k=0; k<16; k++) {

Bear in mind that if you take @PieterP's / my mile-marker advice you may have to use a while loop instead of an if statement.

This line unsigned long currentMicros = micros(); is inside the for loop because there are 16 LEDs. Every for loop iteration has a different micros number because it takes time to execute the code. I don't understand why it'd be better to include this line before the for loop.

gabrielfrancis3:
This line unsigned long currentMicros = micros(); is inside the for loop because there are 16 LEDs.

I understand why you did what you did.

Every for loop iteration has a different micros number because it takes time to execute the code.

Yup.

I don't understand why it'd be better to include this line before the for loop.

Inside the loop just offsets the mark. Doing that "feels good" but has no practical value. Sit down with a pen and paper and work through what happens with just two LEDs.

gabrielfrancis3:
For example, if an LED needs to be turned off after 100 milliseconds, the millis number will be about 138 milliseconds when the arduino gets to this if statement

38 milliseconds late?!? That's 38,000 microseconds! That's 608,000 instruction cycles!!! That is practically FOREVER in processor time. That tends to indicate that your sketch needs to be fixed to reduce the cycle time. If loop can't do everything necessary in 16000 instruction cycles (1 millisecond) you have something coded wrong. :\