arduino-libraries / Servo

Servo Library for Arduino
http://arduino.cc/
GNU Lesser General Public License v2.1
233 stars 251 forks source link

PWM duty cycle range not continuous on UNO R4 #113

Closed ozangerger closed 5 months ago

ozangerger commented 11 months ago

Recently, I bought uno r4 and was planning to replace uno r3 to have more capacity for my hobby projects.

One thing I noticed is that when using servo library with uno r4 is that the motor throttle setting is no longer continuous and looked like it has 10 discrete speed setting. Unfortunately, I don't have an oscilloscope at home right now. My first suspicion was a change in the processor and clocks, which may affect the timers used in the libraries.

Do you have any idea what could be the reason?

eburgwedel commented 10 months ago

As an Arduino newbie, I don't exactly know if the above observation is the reason for a problem I'm encountering: I've been trying to move a servo in a very smooth and slow way — no chance. While it works on a Mega, the same code on the Uno R4 results in a stuttery, jumpy servo motion. It's essentially unusable for smooth, precise servo control.

EvanBottango commented 10 months ago

I have seen the same behavior, smooth or slow performance on a servo was not possible with my Uno R4 Wifi. I reduced variables down to the built in "sweep" example sketch, and analyzed the outgoing pulse lengths themselves. Here is the Arduino forum post I made: https://forum.arduino.cc/t/trouble-with-servos-on-r4-wifi/1151749

eburgwedel commented 10 months ago

Yes, this is exactly what I'm observing. I tried writeMicroseconds() with the same result (to be expected, though).

KurtE commented 10 months ago

I believe the problem is on how the servo library is implemented on the Renesas boards.

That is the library is implemented using a FSP timer object. With a fixed timer rate of I believe 100us.

So using the defaults: the servo range is from 544 to 2400us to cover 180 degrees. Which implies to move 1 degree, you would change the servo value by: 10.311us. Which if your resolution is 100us that is about: 9.7 degrees (Assuming my math is correct)

One solution is to increase the number of interrupts per second. Which may not be desirable.

Another solution would be to do like some of the other implementations, and that is when you receive the interrupt (callback) to complete a servo, you update the cycle time to time of the next servo and when you completed all of the servos, you delay up until the time to start the next servo frame.

I have experimented doing this with the Charlieplex code for the LEDs, as I have it setup to output the different LEDS for different periods as to change the intensity. I discuss some of this up on the forum thread: https://forum.arduino.cc/t/experimenting-with-the-wifi-charlieplex-matrix-code/1159619/9 I can go into more details, but code is up at: https://github.com/KurtE/UNOR4-stuff/tree/main/libraries/arduino_r4wifi_matrix_gfx/src

My first attempts were to use the FSP timer function to update the pulse width. The problem I ran into is that this sets the buffered register. So it won't take effect until after the timer hits the current settings. So was always one behind. Could potentially try to work around this, by looking ahead one each time, but instead, I turned off the buffer and set it directly. Turn off buffering. https://github.com/KurtE/UNOR4-stuff/blob/main/libraries/arduino_r4wifi_matrix_gfx/src/arduino_r4wifi_matrix_gfx.cpp#L135

Update the clock period. https://github.com/KurtE/UNOR4-stuff/blob/main/libraries/arduino_r4wifi_matrix_gfx/src/arduino_r4wifi_matrix_gfx.cpp#L135

Hope that helps Kurt

facchinm commented 10 months ago

@iabdalkader can you take a look?

KurtE commented 10 months ago

@facchinm @iabdalkader

I have started playing with the code, to update it to not used fixed timer period, but instead update the timer period for each servo as well as the end of the servo cycle period.

It is starting to work. So far only tested with one servo in the sweep sketch.

Current code is up at: https://github.com/KurtE/Arduino_Servo/tree/unor4_timer_period_updates

Let me know what you think.

Warning so far the code will only handle GPT timers. I have yet to run into case where it will go to the other timer type

Edit: I have tested it with two servos sweeping. I also updated the code to disable the simple debug output code I had to get an idea of resolution.

iabdalkader commented 10 months ago

@KurtE Thank you! I wrote this a while ago, so will need to refresh my memory, reproduce the issue and test your PR, will get back to you asap.

oracid commented 4 months ago

Usually I use an Arduino Nano. I have a little experience with using servos, I make quadrupeds (look for Oracid on YT). I tried using an UNO R4 and I am having problems with the servo.attach(pin, min, max) function. The max parameter seems to be taken into account while the min parameter is not taken into account at all. Overall, I have a lot of doubts about the compatibility of Servo.h

KurtE commented 4 months ago

@oracid - Sorry I don't and have not used the Servo library (or RC servos in general) for a long time now (probably a decade).

Nor done much with Nano boards. Except I have played some with a few of the ones like the 33IOT and BLE. Hopefully someone who uses RC servos and Nanos can help.

If there is some compatibility issue with this library on the UNO R4, you might want to raise a new issue against the library, such that hopefully the owners of the library will take a look at it. You might include in it some additional information, including things like: Which nano you normally use (AVR? RPI? IOT? BLE?) Also, what the actual problem is. On these boards when I do X it does this and on the R4 it does that...

As far: as servo.attach(pin, min, max)

The min and max are saved away into: servo->period_min, servo->period_max) And these are used in places like the write methods, to either map angles in degrees into microseconds, using the map function. like:

void Servo::write(int angle)
{
    if (servoIndex != SERVO_INVALID_INDEX) {
        ra_servo_t *servo = &ra_servos[servoIndex];
        angle = constrain(angle, 0, 180);
        writeMicroseconds(map(angle, 0, 180, servo->period_min, servo->period_max));
    }
}

Or constrains the range used when you write in microseconds, like:

void Servo::writeMicroseconds(int us)
{
    if (servoIndex != SERVO_INVALID_INDEX) {
        ra_servo_t *servo = &ra_servos[servoIndex];
        servo->period_us = constrain(us, servo->period_min, servo->period_max);
        servo->period_ticks = us_to_ticks(servo->period_us);
    }
}

I have noticed for example, that the write(value) is not fully compatible with the AVR version. For example, on the AVR version if you do: myservo.write(1500); It will see that value is > MIN_PULSE_WIDTH which is 544 and will pass it directly to: writeMicroseconds.

On the R4, it will see that 1500 > 180 and then decide to constrain the value to 180 and then map it to period_max,

So probably on R3 if you passed in 1500, it will move the servo to typically about the mid point, but on R4 to the max range defined for the servo. If this is your issue, you can convert to using writeMicroseconds.

Or maybe the developers such as: @iabdalkader might fix some compatibility issues. But your chances would likely be improved, if more detailed explanation of the problem raised in a new open issue.

Good luck

oracid commented 4 months ago

Thank you very much for your answer. My Nano is an AVR Nano. A basic Nano. I also use the writeMicroseconds() function associated with the map() function. I don't use the constrain() function, but it's a great idea. You can see here my video for beginner where I show how to program a servo, https://youtu.be/OYC-iuB75KA?si=Pb8J3kXnViEhlMlK