Timing The Loop Without Using Serial Monitor

Hi Guys,

Here's a question that I think I know the answer to but would like to confirm if there is another solution

I have a test program running that is multiplexing a couple of LEDs and I need to be able to measure the actual time for one loop to occur because I need to insert a another specific time based action into the loop without affecting the overall speed, at least too drastically. To say it another way, I want to find how much free time I have available to do something.

I could use serial monitor to print out millis() or micros() but as we all know, that will slow the program loop down dramatically so the results would be meaningless.

As far as I know, the only other way I can do it is to measure the pulsed output to one of my LEDs using an oscilloscope.

Am I correct, or is there another way? For example, could I read the values internally and store them in EEPROM memory, then retrieve them after the test is done? Would that be possible? Would it also cause too much error?

Here is a piece of reference code - it is not the final code that will go into my project, just a simulator for testing some principles. If anyone has an oscilloscope and wouldn't mind measuring this through a standard Uno I would be very grateful.

// Arduino Uno Mutliplex test with attempt to crossfade
// Cannot use PWM or analog output from Arduino
// Cannot use delay()

const int Anode1 = 2;             // Anode for LED pair 1
const int Anode2 = 3;             // Anode for LED pair 2
const int Cathode1a = 4;          // Fpr LED1a
const int Cathode1b = 5;          // For LED1b
const int Cathode2a = 6;          // For LED2a
const int Cathode2b = 7;          // For LED2b
const int Butt1 = 8;              // Button for LED pair 1
const int Butt2 = 9;              // Button for LED pair 2

const uint16_t tube_delay = 5000; // multiplexing delay in microseconds: adjust as required

long tube_start = 0;              // multiplex start time used in calculation
int display_status = 1;           // which anode to display now

int state1a = HIGH, state1b = LOW, state2a = HIGH, state2b = LOW; // Initial LED conditions
int reading1, reading2;               // For button presses
int previous1 = LOW, previous2 = LOW; // Default state of each button

// Button Variables
long prevtime1 = 0, prevtime2 = 0;  // debounce start time used in calculation
long debounce = 200;                // debounce value: adjust as required

void setup() {
  pinMode (Anode1, OUTPUT);
  pinMode (Anode2, OUTPUT);

  pinMode (Cathode1a, OUTPUT);
  pinMode (Cathode1b, OUTPUT);
  pinMode (Cathode2a, OUTPUT);
  pinMode (Cathode2b, OUTPUT);
  pinMode (Butt1, INPUT);
  pinMode (Butt2, INPUT);
} // Setup End

void loop() {
  // For button 1
  reading1 = digitalRead(Butt1);  // Butt1 state will be LOW unless pressed
  
  // Check for button press on button 1
  if (reading1 == HIGH && previous1 == LOW && millis() - prevtime1 > debounce) {
    if (state1a == HIGH){         // If Butt1 has been pressed
      state1a = LOW;              // LED1a off
      state1b = HIGH;             // LED1b on
    } else {                      // else
      state1a = HIGH;             // LED1a on
      state1b = LOW;              // LED1b off
    }
    prevtime1 = millis();         // Reset the debounce timer
  }
  previous1 = reading1;           // Resets Butt1 state to LOW when it is released

  // For button 2
  reading2 = digitalRead(Butt2);  // Butt2 state will be LOW unless pressed
  
  // Check for button press on button 2
  if (reading2 == HIGH && previous2 == LOW && millis() - prevtime2 > debounce) {
    if (state2a == HIGH){         // If Butt2 has been pressed
      state2a = LOW;              // LED2a off
      state2b = HIGH;             // LED2b on
    } else {                      // else
      state2a = HIGH;             // LED2a on
      state2b = LOW;              // LED2b off
    }
    prevtime2 = millis();         // Reset the debounce timer
  }
  previous2 = reading2;           // Resets Butt2 state to LOW when it is released

  DisplayOutput();                // Calls display routine
} // Loop End

void DisplayOutput(){                       // Actually gives outputs to pins
  if (micros()-tube_start >= tube_delay) {  // If time has passed for next display update to occur
    switch (display_status) {               // Increment through each case
        
      case 1:                               // Clears anode 2, sets anode 1
        digitalWrite(Anode2, LOW);          // Turn off previous anode    
        CathodeOutput1();                   // Get cathode state for LED 1
        digitalWrite(Anode1, HIGH);         // Turn on this anode
        tube_start += tube_delay;           // increment the delay value and
        display_status++;                   // increment to next anode
      break;                                // Ignore below cases, we are done here
      
      case 2:                               // Clears anode 1, set anode 2
        digitalWrite(Anode1, LOW);          // Turn off previous anode    
        CathodeOutput2();                   // Get cathode state for LED 2
        digitalWrite(Anode2, HIGH);         // Turn on this anode
        tube_start += tube_delay;           // increment the delay value
        display_status = 1;                 // All done, return to first case
      break;                                // Ignore below cases, we are done here
    }
  }
}  //DisplayOutput End

void CathodeOutput1(){                      // Set required cathode1 selection on or off
  digitalWrite(Cathode1a, state1a);
  digitalWrite(Cathode1b, state1b);
}
void CathodeOutput2(){                      // Set required cathode2 selection on or off
  digitalWrite(Cathode2a, state2a);
  digitalWrite(Cathode2b, state2b);
}

Time something like 1000 or 10000 and print the total time afterwards.

Writing to EEPROM takes 3.3mS, so I'd no to that.
Using a pulsed output and oscilloscope would be best. Or a logic analyzer for the same measurement.
I was able to get 45 bytes refreshed at 20 KHz rate using SPI transfer - watching on a logic analyzer showed disruptions which I was able to determine were coming from the millis/micros interrupt. Turning off all interrupts while I blasted out 45 bytes 325 times in a row let me keep the 20 KHz rate - the 45 transfers took 47.8uS (17 clocks per SPI transfer), leaving 2.2uS to check an input pin and set a pointer to the next 45 byte block in the 14,625 byte array (Atmega1284P!) holding the data to go out.

MarkT,
Sorry, just to clarify - do you mean you could count the timing into a variable, then start Serial.begin from within the loop only after a specific number of readings have been taken?

I had always assumed Serial.begin was always placed in Setup, but sounds cool if it can be started from within Loop. I will give it a try.

CrossRoads,
Thanks - very valuable information.

Dumb question - can you take your timing marks and print them after you are done measuring?
Or if you are after free time - measure busy time and figure out the free time?

Vaclav,
On the first point, I think that's what MarkT was suggesting,
On the second point, how would you go about measuring the busy time?

archiebald:
Vaclav,
On the first point, I think that's what MarkT was suggesting,
On the second point, how would you go about measuring the busy time?

I do not get your second question, maybe show in your code the "busy time" .

Can yu do somting liek this

loop()
{
start time = millis();

time whatever process

end time = millis();

Serial.print( end time - start time );

... rest of the loop process

}

Okay Vaclav, I got it when you clarified it thanks.

Just for feedback info I managed to get a little pocket DSO* to measure the speed. Using digital write on pin 10, the loop was measured at around 30kHz which improved to around 62kHz by changing everything to direct port addressing. I still have couple of digitalRead statements in there - not sure if they slow things down at all.

Just for giggles, here is the code with port addressing - if anyone has any comments I'd be happy to hear them.

// Arduino Uno Mutliplex test with attempt to crossfade

const int Butt1 = 8;                  // Button for LED pair 1  PB0
const int Butt2 = 9;                  // Button for LED pair 2  PB1

const uint16_t tube_delay = 5000;     // multiplexing delay in microseconds: adjust as required

long tube_start = 0;                  // multiplex start time used in calculation
int display_status = 1;               // which anode to display now

byte prevstate1, prevstate2;          // Use when crossfading between states
byte state1 = B00010000;              // Only bit 5 is on initially led1b
byte state2 = B01000000;              // Only bit 7 is on initially led2b

int reading1, reading2;               // For button presses
int previous1 = LOW, previous2 = LOW; // Default state of each button

// Button Variables
long prevtime1 = 0, prevtime2 = 0;    // debounce start time used in calculation
long debounce = 200;                  // debounce value: adjust as required

void setup() {
  DDRD |= B11111100;                  // set PORTD (pins 7 to 2) to outputs, leave 1 & 0 alone
  DDRB &= B11111100;                  // set PORTB (pins 9-8) to inputs, leave 13-10 alone
  DDRB |= B00000100;                  // set pin 10 to output, leave others alone
} // Setup End

void loop() {
//  PORTB &= B11111011;               // Turns off pin 10 just for measuring loop cycle

  // For button 1
  reading1 = digitalRead(Butt1);      // Butt1 state will be LOW unless pressed
  
  // Check for button press on button 1
  if (reading1 == HIGH && previous1 == LOW && millis() - prevtime1 > debounce) {
    prevstate1 = state1;                // Remember this state for use in next fade
    state1 ^= B00110000;              // Toggle bits 5 & 4 to alternate LEDs
    prevtime1 = millis();             // Reset the debounce timer
  }
  previous1 = reading1;               // Resets Butt1 state to LOW when it is released

  // For button 2
  reading2 = digitalRead(Butt2);      // Butt2 state will be LOW unless pressed
  
  // Check for button press on button 2
  if (reading2 == HIGH && previous2 == LOW && millis() - prevtime2 > debounce) {
    state2 ^= B11000000;              // Toggle bits 7 & 6 to alternate LEDs
    prevtime2 = millis();             // Reset the debounce timer
  }
  previous2 = reading2;               // Resets Butt2 state to LOW when it is released
//  PORTB |= B00000100;               // Turns on pin 10 just for measuring loop cycle
  DisplayOutput();                    // Calls display routine
} // Loop End

void DisplayOutput(){                       // Gives outputs to pins
  if (micros()-tube_start >= tube_delay) {  // If time has passed for next multiplex step to occur
    switch (display_status) {               // Switch to applicable case this time around
   
      case 1:                               // Clears anode 2, sets anode 1
        PORTD &= B11110111;                 // Turns off Anode2, (pin 3, PD3)
        PORTD = state1;                     // Output the current led status to pins 4 & 5
        PORTD |= B00000100;                 // Turns on Anode1, (pin2, PD2)
        tube_start += tube_delay;           // reset the delay value and
        display_status++;                   // increment anode for next update
      break;                                // Ignore below cases, we are done here
      
      case 2:                               // Clears anode 1, set anode 2
        PORTD &= B11111011;                 // Turns off Anode1, (pin 2, PD2)
        PORTD = state2;                     // Output the current led status to pins 6 & 7
        PORTD |= B00001000;                 // Turns on Anode2, (pin 3,PD3)
        tube_start += tube_delay;           // reset the delay value
        display_status = 1;                 // increment anode for next update
      break;                                // Ignore below cases, we are done here

      // Note, LED condition will remain unchanged until tube_delay has elapsed,
      // there will be many loop cycles before next multiplex changes
    }
  }
}  //DisplayOutput End

Just out of interest, apart from being faster, it compiled into 500bytes less than the digitalWrite version. I will be making the effort to do more "proper" coding from now on!!

  • Yeah, I know most people regard the pocket DSO's as crap but it was cheap enough to slide under the chief finance minister's radar (wifey) and it turned out to be perfect for what I needed in this case - did the job a treat and if you understand the limitations then I would recommend to add one to the tool inventory. Mine was the Sainsmart DSOII 202. Maybe a real oscilloscope will be on the Xmas list for 2016!!