[SOLVED] How to multitask with servos?

Hello,

I was working on servo code developing and testing. I have two models for testing and I just have to choose one and implement in a project just to left a gate arm that is cardboard fabric so there’s not any heavy load to the servo.

Until I was testing a function for multitasking, using millis function.

But I noticed even adding anything along with calling the function to drive the servo actually affects the behavior of the servo and it functions differently!

I tried the Servo library but the example code uses delay function, and trying without delay function, the servo works so fast and can’t take full sweeps as I have to use delay function.

And of course delay function isn’t optimal for multitasking code.

So, what to do then?

This is my testing code, there are a lot of commented functions in the loop function, which is my testing work.

My last testing work, as it’s obvious, I’m working on the servo_toggle function. And I have commented everything in that function and only put two commands.

One is servo_pulse … and the second line is Serial.print. Just to test how the function would behave if I added something, and my guess is right. It works differently!

#define SERVO1 3
#define SERVO2 13
#define IR_1 A0
#define POT A1
#define NICE_SPEED 200000

uint16_t pot_val,map_val;

uint32_t servo_st;
uint32_t servo_cr;
uint32_t servo_left = 1000;
uint32_t servo_center = 1750;
uint32_t servo_right = 2600;
uint16_t servo_speed = 1000;
byte servo_state;
byte servo_delay,toggle_state,toggle_lock;

void servo_radar(uint8_t pin, uint32_t servo_speed);
void servo_sweep(uint8_t pin);
void servo_pulse(uint8_t pin, uint16_t pwm);
void servo_gate_ir(uint8_t inpin, uint8_t servono);
void servo_toggle(uint8_t servono);

void setup() {
  Serial.begin(9600);
  pinMode(IR_1,INPUT);
  pinMode(POT,INPUT);
  pinMode(SERVO1,OUTPUT);
  pinMode(SERVO2,OUTPUT);
  servo_st = millis();
}

void loop() {
//servo_radar(SERVO2,NICE_SPEED);
//servo_radar(SERVO1,NICE_SPEED);
//servo_sweep(SERVO1);

/*pot_val = analogRead(POT);
Serial.println(pot_val);
map_val = map(pot_val,0,1023,500,2000);
servo_pulse(SERVO1,map_val);*/

//if()servo_pulse(SERVO1,500);
//servo_gate_ir(IR_1,SERVO1);
//servo_pulse(SERVO1,1000);
servo_toggle(SERVO1);
}

void servo_gate_ir(uint8_t inpin, uint8_t servono){
  if(!digitalRead(inpin)){
    servo_pulse(servono,500);
  }
  else{
    servo_pulse(servono,1400);
  }
}

void servo_toggle(uint8_t servono){
  servo_pulse(servono,servo_left); 
  Serial.print("hi");
  
  /*if(!toggle_state){ 
     servo_pulse(servono,servo_left); 
  }
  else{
    servo_pulse(servono,servo_right); 
  }
*/
  /*servo_cr = millis();
  if(servo_cr - servo_st >= 1000){
    toggle_state ^= 1;
    servo_st = millis();
  }*/
}

void servo_radar(uint8_t pin, uint32_t servo_speed){
  for(uint32_t k=0;k<servo_speed;k++)        
  servo_pulse(pin,servo_left);
  for(uint32_t k=0;k<servo_speed;k++)
  servo_pulse(pin,servo_right);
}

void servo_sweep(uint8_t pin){
  for (int i=servo_left; i<=servo_right; i+=10){
    for(uint32_t k=0;k<servo_speed;k++)
    servo_pulse(pin,i);
    if(i>=servo_right){
      for (int i=servo_right; i>=servo_left; i-=10){
        for(uint32_t k=0;k<servo_speed;k++)
        servo_pulse(pin,i);      
      }
    }
  }  
}

void servo_pulse(uint8_t pin, uint16_t pwm){
  if(!servo_state && !servo_delay){
    digitalWrite(pin,HIGH);servo_state = 1;servo_st = micros();}
  if(servo_state && !servo_delay){
    servo_cr = micros();if(servo_cr - servo_st >= pwm){
    digitalWrite(pin,LOW);servo_delay = 1;servo_st = micros();}}
  if (servo_delay){
    servo_cr = micros();if(servo_cr - servo_st >= 20000 - pwm){
      servo_delay = 0; servo_state = 0;}}
}

Why are you not using the regular Servo library?

Have a look at the servo code in Several Things at a Time. if you want to control more servos just make copies of the function with suitable changes to the variable names.

Perhaps even simpler is to make an array of servo objects and just iterate over the array to change their positions.

...R

If you want a servo to move slowly but without using the Sweep technique with delay()s then give the VarSpeedServo library a try. I think that's non-blocking but it's a while since I looked at code in detail.

Steve

Robin2: Why are you not using the regular Servo library?

Have a look at the servo code in Several Things at a Time. if you want to control more servos just make copies of the function with suitable changes to the variable names.

Perhaps even simpler is to make an array of servo objects and just iterate over the array to change their positions.

...R

Thanks I added another servo and they work nicely !!

The work you're done of the application code along with the library is wonderful :)

I would try to improve my code to, I want to learn more about multitasking.

Even in my code I used millis but the behavior was not as expected !!

slipstick: If you want a servo to move slowly but without using the Sweep technique with delay()s then give the VarSpeedServo library a try. I think that's non-blocking but it's a while since I looked at code in detail.

Steve

OK, but I have a question about servos in general.

Now I learned that if I want to drive the servo to 0 degrees position then I have to write to the servo with 1ms duty cycle out of 20ms period.

So the ON time is 1ms and OFF time is 19ms

and for 180 degrees I have to write 2.5ms, so ON is 2.5ms OFF is 17.5ms.

But I think Robin has followed a different method, he moves with degrees which provide more control to the position of the servo .. wow

I was just thinking of a way of how to control the position of the servo. This is simple approach ..

wolfrose: Now I learned that if I want to drive the servo to 0 degrees position then I have to write to the servo with 1ms duty cycle out of 20ms period.

That's not strictly true. It's the 1ms pulse that moves it, the 20ms cycle is just a recommended (or some kind of de facto standard?) that causes a re-pulse to remind the servo to stay there, in the face of a force that's trying to move it away.

You will, I'm sure, move the servo if you send the 1ms high pulse and stay low after that for ever. It's just that a mechanically loaded servo may get moved away if you don't refresh.

and for 180 degrees I have to write 2.5ms

I think 180 is nominally 2ms not 2.5?

he moves with degrees which provide more control to the position of the servo

Surely using degrees is less control, since 0-180 gives you 180 possibilities, while 1000-2000 is 1000 possibilities? (Assuming the gearing on the servo allows anything like that precision in the first place.)

wolfrose: Now I learned that if I want to drive the servo to 0 degrees position then I have to write to the servo with 1ms duty cycle out of 20ms period.

So the ON time is 1ms and OFF time is 19ms

and for 180 degrees I have to write 2.5ms, so ON is 2.5ms OFF is 17.5ms.

But I think Robin has followed a different method, he moves with degrees which provide more control to the position of the servo .. wow

I am not doing anything different.

If you study the Servo library documentation you will see there is a function servo.writeMicroseconds() and that is what actually controls the position. The Servo library converts the angles to appropriate numbers of microsecs.

The servos expect pulses every 20 millisecs (50 per second) and the width of the pulse determines the position of the servo arm.

The mid point of the servo motion is a pulse width of about 1500 microsecs. The upper and lower limits seem to vary between different models of servo. Some require a short pulse width of about 500 µsecs and a long pulse width of about 2500 µsecs. You will need to experiment to find what is appropriate for your particular servo.

If you use a value that is too small or too large and which results in the servo pushing against its internal end-stop the servo will probably overheat and be damaged as they are not normally designed for a continuous heavy load.

...R

neiklot: I think 180 is nominally 2ms not 2.5?

If you look at the Servo.h code you'll see that the default range is 0 = .544ms and 180 = 2.4ms.

But it is true that using writeMicroseconds() gives theoretically finer control...though how much difference it makes in a real servo is a bit debatable.

Steve

slipstick: If you look at the Servo.h code you'll see that the default range is 0 = .544ms and 180 = 2.4ms.

You are correct about the millisec range. However the actual physical range depends on the particular servo. At the extreme you can get sail winch servos that turn through about 2000° and many regular servos cannot move through 180°

...R

Perhaps I should have been clearer. What I meant was write(0) corresponds to writeMicroseconds(544) and write(180) corresponds to writeMicroseconds(2400). As you say the actual movement you get will depend on the particular servo. The extreme condition is those really confusing 360 degree/continuous rotation not-really "servos" where write(0) will be full speed one way and write(180) full speed in the other direction.

It's no wonder people are so often confused about the things commonly called servos.

Steve

Very helpful discussion ..!!!

slipstick: If you look at the Servo.h code you'll see that the default range is 0 = .544ms and 180 = 2.4ms.

Actually I knew that, but had forgotten I knew it ;)

neiklot:
That’s not strictly true. It’s the 1ms pulse that moves it, the 20ms cycle is just a recommended (or some kind of de facto standard?) that causes a re-pulse to remind the servo to stay there, in the face of a force that’s trying to move it away.

yes the 20ms is the period of the PWM, and the HIGH pulse is within the 20ms duty cycle.

but do you mean that I can increase or decrease the time of the period? let say 25ms or 15ms?

===============

so yeah I learned that the specific pulse is measured in the internal electronics of the servo that if certain frequency is detected then the servo moves to a specific position and stay there, another frequency is another position … etc. In the range of 180 degrees and can go more than 180 degrees.

the servo I have works from 0.7ms to 2.6ms

You will, I’m sure, move the servo if you send the 1ms high pulse and stay low after that for ever. It’s just that a mechanically loaded servo may get moved away if you don’t refresh.

Yes I actually tested that servo with the delayMicroseconds() for one time and multiple times with a for loop.

this is only one pulse …

can be 2000 or 2500 … etc.

    digitalWrite(SERVO1,HIGH);
    delayMicroseconds(1000);
    digitalWrite(SERVO1,LOW);

or multiple times to move to successively to a position, the more loops the more distance it moves …

  for(int i=0;i<30;i++){
    digitalWrite(SERVO1,HIGH);
    delayMicroseconds(1000);
    digitalWrite(SERVO1,LOW);
    delayMicroseconds(19000);
  }
Surely using degrees is [i]less[/i] control, since 0-180 gives you 180 possibilities, while 1000-2000 is 1000 possibilities? (Assuming the gearing on the servo allows anything like that precision in the first place.)

Yes I’m now trying to develop a function that at least determine the position of the servo at the start

but I think that’s not possible with these hobbyist servos, I just have to drive it to; for example 90 degrees at start then move it from there with that I know the position is at 90, so I know how much counts it needs to move to 0 degrees and as well to 180 degrees.

thanks guys I learned that from your code examples :slight_smile:

wolfrose: yes the 20ms is the period of the PWM, and the HIGH pulse is within the 20ms duty cycle.

The point I was making was that it's not the duty cycle that positions the servo, it's the absolute length of the pulse. If you halve the cycle, and keep the pulse the same length, that doubles the duty cycle but does not change the position. (Not like a DC motor and PWM, where the speed depends on the duty cycle, not the absolute high time.)

That and a few other values are #define'd in servo.h. I changed it to 10000 once, it worked fine.

#define MIN_PULSE_WIDTH       544     // the shortest pulse sent to a servo  
#define MAX_PULSE_WIDTH      2400     // the longest pulse sent to a servo 
#define DEFAULT_PULSE_WIDTH  1500     // default pulse width when servo is attached
#define REFRESH_INTERVAL    20000     // minumim time to refresh servos in microseconds

wolfrose: Yes I'm now trying to develop a function that at least determine the position of the servo at the start

You can't determine it's position (if by that you mean "find out where it is"), unless you have a 4-wire servo like some of adafruit's, which bring the actual position out on that extra wire.

Robin2: If you study the Servo library documentation you will see there is a function servo.writeMicroseconds() and that is what actually controls the position. The Servo library converts the angles to appropriate numbers of microsecs.

Yep, I started studying the library yesterday :)

Yeah the arranging of the library is really nice ..

As I'm going through this Servo.h file I found this line and it's interesting to me.

#define SERVOS_PER_TIMER       12     // the maximum number of servos controlled by one timer

So does this mean that if I want to control more than 12 servos with 1 timer interrupt, then the balance between let say 16 or 20 servos would be out of control ? is this the reason of limiting the max number of servos?

but how it's working ? I mean I can drive any servo from any pin on the arduino with only using digitalWrite function instead of the actual PWM signal from the known pins of the PWM?

Are you driving the servos from hardware timers? if yes then I understand why you limited the number of servos.

The servos expect pulses every 20 millisecs (50 per second) and the width of the pulse determines the position of the servo arm.

The mid point of the servo motion is a pulse width of about 1500 microsecs. The upper and lower limits seem to vary between different models of servo. Some require a short pulse width of about 500 µsecs and a long pulse width of about 2500 µsecs.

Yes I understand that.

You will need to experiment to find what is appropriate for your particular servo.

Yep, I did it, I found the servo I have which is the S07NF, works from 700 to 2600 us. And as well moves more than 180 degrees.

If you use a value that is too small or too large and which results in the servo pushing against its internal end-stop the servo will probably overheat and be damaged as they are not normally designed for a continuous heavy load.

Yep, I did that I actually held the start of the sevo as it was moving forward and backward for testing, then I think I broke it from inside and now the servo cringes when I write 400us, I think I broke the lower end limiter :)

it's even not my servo _

but how it's working ? I mean I can drive any servo from any pin on the arduino with only using digitalWrite function instead of the actual PWM signal from the known pins of the PWM?

The servo library works on all pins, not just the ~PWM pins. It's got nothing to do with PWM as we know it. It even works on the analog input pins, Ax, which are digital pins anyway.

neiklot: The point I was making was that it's not the duty cycle that positions the servo, it's the absolute length of the pulse. If you halve the cycle, and keep the pulse the same length, that doubles the duty cycle but does not change the position. (Not like a DC motor and PWM, where the speed depends on the duty cycle, not the absolute high time.)

That and a few other values are #define'd in servo.h. I changed it to 10000 once, it worked fine.

You're totally right ! I tried 10 and 30ms, all worked fine. What matters is the length of the HIGH pulse.

So the numbers in the Servo library are the programmer preferences for the angles of the servo

I adjusted mine to 800 min 1652 mid 2450 max

which give me exact 180 degrees

You can't determine it's position (if by that you mean "find out where it is"), unless you have a 4-wire servo like some of adafruit's, which bring the actual position out on that extra wire.

Yep, that's what I thought about, I really can't determine the position of the servo.

The one way I thought about is to put the servo at a determined position at the start of the program; say 90 degrees, then save that position and move from there ..

Yeah problem solved .. now I just have to work on developing the functions, then I would like to post them here and receive the members opinions. I want to know what people think about my coding, is it good, nice, there're things to improve, modify ... etc.

wolfrose: So the numbers in the Servo library are the programmer preferences for the angles of the servo

I'm not sure what you had in mind when you wrote that but it does not seem to me to be entirely correct.

I would describe the numbers in the library as those which the programmer considered would be suitable for the full range of motion for a wide range of servos.

As I said earlier (and as you have discovered) the exact limits that you should use in your program will vary between different types of servos and have to determined by trial and error.

if you happen to have a servo that needs values beyond the limits in the library you will need to modify the limits in the library. However if you have a servo whose limits are within a narrower range than the library there is no need to modify the library.

...R

Robin2: I'm not sure what you had in mind when you wrote that but it does not seem to me to be entirely correct.

Yep, sorry I said it's preferences but it's according to the device specs. I know that but I meant that after doing the adjustments and the measurements .. etc.

Of course anything is electronics is done according to the specs!

I would describe the numbers in the library as those which the programmer considered would be suitable for the full range of motion for a wide range of servos.

That's what I was working on most the time, is to find the full range of 180 degrees exactly.

As I said earlier (and as you have discovered) the exact limits that you should use in your program will vary between different types of servos and have to determined by trial and error.

if you happen to have a servo that needs values beyond the limits in the library you will need to modify the limits in the library. However if you have a servo whose limits are within a narrower range than the library there is no need to modify the library.

Absolutely!

I also developed this function to send a pulse within microsecond range at any delay time!

How's it?

void servo_write(uint8_t pin, uint32_t tick_every, uint16_t pos){
  servo_cr = millis();  
  if(servo_cr - servo_st >= tick_every){

  if(!servo_state){digitalWrite(pin,HIGH);
  servo_state = 1;servo_st = micros();}
    
  if(servo_state){servo_cr = micros();
    if(servo_cr - servo_st >= pos){digitalWrite(pin,LOW);
    servo_state = 0;}}
  }
}

wolfrose: I also developed this function to send a pulse within microsecond range at any delay time!

How's it?

You tell us - what happens when you try it?

And please use the AutoFormat tool to lay out your code for easier reading. Don't put multiple statements on one line. Always put a } on a line of its own.

...R