Precision of delayMicroseconds()

I use two OLIMEXINO 85 to implement a 2 channel remote control and control 2 digital servos. In general, it works already, but the servos are vibrating all the time.

A digital servo needs a pulse of 1.5 ms for neutral position. A shorter or longer pulse turns it left or right.

I tried two ways...

digitalWrite(Srv1, HIGH);
delayMicroseconds(1500);
digitalWrite(Srv1, LOW);

tic = micros() + 1500;
digitalWrite(Srv2, HIGH);
do {
} while (tic > micros());
digitalWrite(Srv2, LOW);

... but I can see on my oscilloscope that both pulses are jittering a little bit, roughly estimated +/- 5 micro seconds.

Is there a way to create more precise pulse lengths?

Is there a way to create more precise pulse lengths?

micros() has a granularity of 4 so you may well be out of luck, but 4 microseconds in 1.5 milliseconds is not much of a variation.

Have you tried the Servo library writeMicroseconds() method ?

External Crystal/Oscillator.

delayMicroseconds() is a blocking loop that is better than 0.5usec accurate, but it assumes no interrupts. SO if you code has plenty of them delayMicroseconds run out of sync.

If you want really accurate timing you must use the internal timers.

Finally your code contains a possible bug

  • adding time is no good idea as it might overflow causing unwanted effects
digitalWrite(Srv1, HIGH);
delayMicroseconds(1500);
digitalWrite(Srv1, LOW);

tic = micros();
digitalWrite(Srv2, HIGH);
while (micros() - tic < 1500);
digitalWrite(Srv2, LOW);

Thank you very much everybody for your hints.

I have no idea about internal timers and Servo Libraries, but it sounds very promising and I will try to find more informations about this.

Suppose you only send the servo a pulse when you want it to move?

If it has to be continuous, that's what PWM is for.

GoForSmoke:
Suppose you only send the servo a pulse when you want it to move?

If it has to be continuous, that's what PWM is for.

You are right, but I am afraid that the resolution is too low (1:255). I need pulses with HIGH levels from 1000...2000 microseconds and LOW levels 30000 microseconds. 30000 / 255 = 117, so I have only 8 possible values between 1000 and 2000.

I tried already "#include <Servo.h>" but compilation causes many errors. There are many "if"s in the source code depending on the hardware. Perhaps my OLIMEXINO 85 does not match.

Do the pulses have to be continually sent or the servo returns to zero the way a solenoid will?

Johnny010:
External Crystal/Oscillator.

No. That pertains only to long term stability.

If you point the servo in some direction and your code doesn't change the direction, will it stay where you pointed it?

If so then the solution is entirely simple and you don't need to jump through any timing hoops.

GoForSmoke:
If you point the servo in some direction and your code doesn't change the direction, will it stay where you pointed it?

If so then the solution is entirely simple and you don't need to jump through any timing hoops.

Indeed, this i a good idea. Only create pulses if there is a change. I will try and report.

I tried already "#include <Servo.h>" but compilation causes many errors.

Please post an example program that exhibits the errors. It need only be about 10 lines long, if that.

I need pulses with HIGH levels from 1000...2000 microseconds

That's exactly what the Servo writeMicroseconds() method gives you.

This servo library is made for the tinys like your ATtiny85:

https://forum.arduino.cc

UKHeliBob:
Please post an example program that exhibits the errors. It need only be about 10 lines long, if that.

It's not about my code, it is only a program with "#include <Servo.h>" and nothing else.

The result (directory path shortened):

...\libraries\Servo\src\avr\Servo.cpp: In function 'void __vector_3()':
...\libraries\Servo\src\avr\Servo.cpp:82:44: error: cannot convert 'volatile uint8_t* {aka volatile unsigned char*}' to 'volatile uint16_t* {aka volatile unsigned int*}' for argument '2' to 'void handle_interrupts(timer16_Sequence_t, volatile uint16_t*, volatile uint16_t*)'
   handle_interrupts(_timer1, &TCNT1, &OCR1A);
                                            ^
...\libraries\Servo\src\avr\Servo.cpp: In function 'void initISR(timer16_Sequence_t)':
...\libraries\Servo\src\avr\Servo.cpp:128:5: error: 'TCCR1A' was not declared in this scope
     TCCR1A = 0;             // normal counting mode
     ^
...\libraries\Servo\src\avr\Servo.cpp:129:5: error: 'TCCR1B' was not declared in this scope
     TCCR1B = _BV(CS11);     // set prescaler of 8
     ^
...\libraries\Servo\src\avr\Servo.cpp:136:5: error: 'TIFR1' was not declared in this scope
     TIFR1 |= _BV(OCF1A);     // clear any pending interrupts;
     ^
...\libraries\Servo\src\avr\Servo.cpp:137:5: error: 'TIMSK1' was not declared in this scope
     TIMSK1 |=  _BV(OCIE1A) ; // enable the output compare interrupt
     ^

It looks like the ATtiny85 does not have some/all of the registers used by the library.

UKHeliBob:
That's exactly what the Servo writeMicroseconds() method gives you.

Yes, theoretically. Simple program:

void loop() {

  digitalWrite(Srv1, HIGH);
  delayMicroseconds(2000);
  digitalWrite(Srv1, LOW);
  delay(30);
}

This jitters. The pulse length is not always the same, it is getting shorter and longer several times per second.

I've seen those kind of errors before, usually when I had my library files in the wrong folder.

Before writing to the servo you might want to check in the current value is even close to the previous, make a dead zone just wider than a wobble and have that only where the servo gets written to.

Some day I should get a cheap servo just to dink with it.

Plenz:
... can see on my oscilloscope that both pulses are jittering a little bit, roughly estimated +/- 5 micro seconds.

As Rob pointed out an interrupt will lengthen the delay. The timer 0 interrupt, the one that keeps the millis clock updated, takes roughly 5us to execute. It is invoked about once every millisecond.

You really need to use a timer that keeps ticking regardless of interrupts. Micros() does that but only has a 4us resolution. You haven't said how precise the delays need to be. But you're probably going to need to directly use a hardware timer.

One other possible approach is temporarily disabling timer 0 when calling delayMicros(). But whether you can get away with that depends on what else you're doing.

Tiny85 needs a different servo library (or a servo library that checks for the 'x5 series and adjusts accordingly), cause it's timers are wierd (it's timer1 isn't the normal 16-bit one, but a crazy one that can be clocked off the PLL)

Erni linked to one which supposedly works.

GoForSmoke:
Before writing to the servo you might want to check in the current value is even close to the previous, make a dead zone just wider than a wobble and have that only where the servo gets written to.

It works theoretically if I create a pulse only if there is a change in the pulse width.

Unfortunately, if there are no pulses, the servo switches its power off. The things which the servo shall move may have forces which can turn the servo. So there is a constant stream of pulses necessary to keep the servo on its position.