Timing is off with a non-blocking delay

Hi,

I want to make a pulse train. So I don't to block other processes, I'm not using "delay" but a non-blocking timer. I'm doing this: http://arduino.cc/en/Tutorial/BlinkWithoutDelay in other words.

To check my circuit is producing the pulse at the required frequency, I've hooked up an oscilloscope and measured the pulse rate. I've found that it's always less than what I am expecting. It looks like other operations being conducted by the program are somehow throwing off the timing. The Arduino (Mega) isn't working flat out (I have plenty of spare cycles). So I can subtract a constant from my desired inter-pulse-interval, in order to get the desired pulse rate. The problem is that this has to be tweaked depending on what else the circuit is doing and it's hard to do precisely. Does anyone know of a solution? I'm surprised I'm having this problem, I must say. I imagine the Arduino is just executing operations in a different order to what I am expecting or there's some over-head of which I'm unaware.

As an example, the following code should yield 5 kHz but actually yields 4.86 kHz

const int ledPin =  22;      // the number of output pulse pin
unsigned long previous = 0;  // will store last time the pulse happend
long interval = 200;         // interval (micro secs) at which to pulse (so should produce 5 kHz)

void setup() {
  pinMode(ledPin, OUTPUT);      
}

unsigned long cur; //The current time in microseconds
void loop()
{
  cur=micros();
  if(cur - previous > interval) {
    previous = cur;  
    makePulse(ledPin);
  }
}


void makePulse(int pin){
  digitalWrite(pin,HIGH);
  delayMicroseconds(1); 
  digitalWrite(pin,LOW);
}

Thanks!

You probably have a rounding error using micros().

http://arduino.cc/en/Reference/Micros

On 16 MHz Arduino boards (e.g. Duemilanove and Nano), this function has a resolution of four microseconds (i.e. the value returned is always a multiple of four).

For closer precision, you might need to set up a timer and interrupt.

200 micros is a multiple of 4 but you have some commands and a 1 usec delay, maybe that’s what’s throwing you off? Perhaps change interval to be ( 200 - process micros ) will do?

if(cur - previous >= interval)

if(cur - previous >= interval)

You have to adjust your delay form 200 to 193 to take make up for the time the instructions take.

Note:
If you add new instructions you will have to adjust the delay.

AWOL, you're right that >= is correct but it's still off (probably due to precision issues). However, this isn't the solution the problem I'm having in real code examples.

The point is that other stuff in the loop will cause a change in the pulse rate. Say, for instance, I add a command to the loop that prints a single character to the serial port on each pass then my frequency drops hugely (to 3.9 kHz). Even if I set up the loop to print a serial character on only every thousandth pass through the loop, I still see a small drop in pulse rate. This also happens for other stuff, not just serial output. The point is that the delay I ask for and what I get are two different things.

I will look up interrupts. I have yet to learn about those. Hopefully the answer is there.

You have to adjust your delay form 200 to 193 to take make up for the time the instructions take.

Explain, please.

LarryD:

if(cur - previous >= interval)

You have to adjust your delay form 200 to 193 to take make up for the time the instructions take.

Note:
If you add new instructions you will have to adjust the delay.

Yes, that is what I said I was doing. I say I can "fix" it by subtracting a constant. What I'm asking is how do I avoid needing to do this in the first place. I want to get the correct pulse rate from the get-go. The subtraction is a bit of a hack and, what's worse, it can be thrown off by the user's actions which might cause more or less code to run depending on the state of the system.

Also, I don't understand why I even have this issue since the "extra" code isn't placed between the current variable and the "if" test.

This page looks useful: http://www.instructables.com/id/Arduino-Timer-Interrupts/
And this: Arduino Playground - HomePage

One correction already mentioned by AWOL…

void loop()
{
unsigned long cur; //The current time in microseconds
cur=micros();
if(cur - previous >= interval) {
previous += interval;
makePulse(ledPin);
}
}

Making cur local is a “general improvement”; it is not required.

LarryD:
You have to adjust your delay form 200 to 193 to take make up for the time the instructions take.

Incorrect. See the post above.

Say, for instance, I add a command to the loop that prints a single character to the serial port on each pass then my frequency drops hugely (to 3.9 kHz).

Yes, I imagine it does.
Now try Serial.begin (115200); instead of Serial.begin (38400); :wink:

Have you considered using: tone(22,5000); ?
This will produce a square wave however, not a pulse train.

Maybe i'm wrong but delayMicroseconds will stop micros() from being updated

So your frequency actually is not 1/200us (5Khz) but 1/201us, 4.975Khz, maybe it's part of the error...

guix:
Maybe i'm wrong but delayMicroseconds will stop micros() from being updated

You are (now) wrong. I recall that in previous versions of the core interrupts were disabled in delayMicroseconds. That is no longer the case.

Thanks, this is a real improvement. Much more accurate!

LarryD:
Have you considered using: tone(22,5000); ?
This will produce a square wave however, not a pulse train.

Yes, thought of tone. That's the first thing I did, actually. It doesn't work for my application, though.

raacampbell:

[quote author=Coding Badly link=topic=178210.msg1321400#msg1321400 date=1374178064]

One correction already mentioned by AWOL…

void loop()
{
unsigned long cur; //The current time in microseconds
cur=micros();
if(cur - previous >= interval) {
previous += interval;
makePulse(ledPin);
}
}

Making cur local is a “general improvement”; it is not required.

Thanks, this is a real improvement. Much more accurate!
[/quote]

Hmmm… There is one downside. I’m using a pot to set the pulse rate. When the pot is “at zero” there are no pulses. Pulse rate should increase increases as pot resistance alters. With the timing code as above, there is some weird behavior as the pot is dialled upwards. I initially see a burst of really high frequency pulses (they’re coming in at the Arduino’s maximum rate). I think I can find a solution, but a solution is necessary because this pulse rate is being turned into motion…

OK, good luck finding that solution.
Hope it gets less weird.

Boy, I learn something every day, I guess that's a good thing.
Thank you.

Two other things…

unsigned long interval = 200; // interval (micro secs) at which to pulse (so should produce 5 kHz)

You are welcome.