Go Down

Topic: [SOLVED] How to multitask with servos? (Read 1 time) previous topic - next topic

wolfrose

Mar 16, 2019, 09:16 pm Last Edit: Apr 13, 2019, 09:36 pm by wolfrose
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!


Code: [Select]
#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;}}
}


Robin2

#1
Mar 16, 2019, 09:44 pm Last Edit: Mar 16, 2019, 09:45 pm by 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
Two or three hours spent thinking and reading documentation solves most programming problems.

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

wolfrose

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 !!

wolfrose

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 ..

neiklot

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.

Quote
and for 180 degrees I have to write 2.5ms
I think 180 is nominally 2ms not 2.5?

Quote
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.)


Robin2

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
Two or three hours spent thinking and reading documentation solves most programming problems.

slipstick

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

Robin2

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
Two or three hours spent thinking and reading documentation solves most programming problems.

slipstick

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


neiklot

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 ;)


wolfrose

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


Quote
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.

Code: [Select]
    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 ..

Code: [Select]
  for(int i=0;i<30;i++){
    digitalWrite(SERVO1,HIGH);
    delayMicroseconds(1000);
    digitalWrite(SERVO1,LOW);
    delayMicroseconds(19000);
  }
 




Code: [Select]
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 :)



neiklot

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.

Code: [Select]

#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


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.


wolfrose

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.

Code: [Select]
#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
Code: [Select]
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.


Quote
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.

Quote
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.

Quote
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 *_*

Go Up