UNO R4 WIFI - Unexpected servo movements

Hello, everyone!

I'm developing a video art project and I need to have a camera (my old iPhone SE) remotely controlled by wifi -OSC. On the test bench everything seemed to go well but after setting everything up I'm having problems stabilizing the servo motors.

In this project I integrate a patch in Max/msp to control the mechanical movements, sending the data via OSC to a UNO R4 WIFI board connected to two servo motors (MG995-180 and SG90-180). The board and servo motors are powered by a 10,000mAh powerbank (Xiaomi: - USB-A port: 5 V 2.4 A / 9.0 V 2 A / 12.0 V 1.5 A MAX, USB-C port: 5 V 3 A -22.5 W MAX).
Unexpected servo movements occur even when the servos are not receiving commands. On the other hand, the servo movements, when controlled remotely, are very jerky. I set up the system and recorded a video to demonstrate the problem ([https://youtu.be/EpTot-duJsw?si=ezK3EoUrbaT0PFxU]). I connected a multimeter in parallel to the power supply and I see fluctuations in the values (voltage) whenever the servo motors move. The values sent by Max/msp (OSC/Wifi) appear stable on the serial monitor, allowing me to assume that this is not the problem.
I would like your help and thank you for taking the time to read my request.
I enclose the code.

#include <WiFi.h>
#include <WiFiUdp.h>
#include <OSCMessage.h>
#include <Servo.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SPI.h>

const int ledPin = 2;

const char* ssid = "*******";
const char* password = "*******";

WiFiUDP Udp;
const IPAddress outIp(172, 20, 10, 2);
const unsigned int outPort = 9999;
const unsigned int localPort = 8888;

Servo servo1;
Servo servo2;

// Definições de pinos SPI
#define TFT_MOSI 11
#define TFT_SCLK 13
#define TFT_CS   10
#define TFT_DC   9
#define TFT_RST  8

Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);

void setup() {
  Serial.begin(9600);
  pinMode(ledPin, OUTPUT);

  // Inicialização do TFT
  tft.initR(INITR_BLACKTAB);
  tft.setRotation(3);

  connectWiFi();
  Udp.begin(localPort);
  
  servo1.attach(5);  
  servo2.attach(3);  
}

void connectWiFi() {
  Serial.print("Conectando-se a ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);

  unsigned long startTime = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - startTime < 15000) {
    delay(500);
    Serial.print(".");
  }

  tft.fillScreen(ST7735_BLACK);

  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("");
    Serial.println("Conectado ao WiFi");
    IPAddress ip = WiFi.localIP();
    Serial.print("Endereço IP: ");
    Serial.println(ip);

    tft.setCursor(10, 30);
    tft.print("IP: ");
    tft.println(ip);
    tft.setCursor(10, 50);
    tft.print("ONLINE");
  } else {
    Serial.println("\nFalha na conexão");

    tft.setCursor(10, 30);
    tft.print("OFFLINE");
  }
}

void loop() {
  if (WiFi.status() != WL_CONNECTED) {
    connectWiFi();
  }

  OSCMessage msgIN;
  int size;
  if ((size = Udp.parsePacket()) > 0) {
    while (size--) {
      msgIN.fill(Udp.read());
    }
    if (!msgIN.hasError()) {
      msgIN.route("/toggleLED", toggleOnOff);
      msgIN.route("/servo1", setServo1);
      msgIN.route("/servo2", setServo2);
    }
  }

  int servo1Value = servo1.read();
  int servo2Value = servo2.read();

  Serial.print("Servo 1: ");
  Serial.println(servo1Value);
  Serial.print("Servo 2: ");
  Serial.println(servo2Value);

  delay(10);
}

void toggleOnOff(OSCMessage &msg, int addrOffset) {
  bool ledState = msg.getFloat(0);
  digitalWrite(ledPin, ledState);
}

void setServo1(OSCMessage &msg, int addrOffset) {
  int servo1Value = msg.getFloat(0);
  servo1Value = constrain(servo1Value, 0, 180);
  servo1.write(servo1Value);
}

void setServo2(OSCMessage &msg, int addrOffset) {
  int servo2Value = msg.getFloat(0);
  servo2Value = constrain(servo2Value, 0, 180);
  servo2.write(servo2Value);
}


Thank you very much!

Hi @SN68

It sounds similar to this bug:

@SN68 - I believe there are several things that are probably impacting this.
As I mentioned in your other thread about this setup:
Arduino R4 WIFI Servo Jitters - UNO R4 / UNO R4 WiFi - Arduino Forum

The Servo library has always been susceptible to jitters when there are other things going on in the
sketch that produces interrupts. As if one pulse goes through without any interrupts delaying it and the next one is produced and another interrupt is being handled at either the start and/or end of the pulse, it can delay the setting of the IO pin, which can cause the pulse to be longer or shorter than the previous one...

That is why when I used to use RC servos, I would typically use a servo controller. Back then I used to use the Lynxmotion SSC-32. Later to control a small number of servos, I might use the PWMServo library I mentioned in the other thread.

Note: some jitter may also be caused by the servos themself. I know back then some of the cheaper servos could have issues.

Maybe, but I also suspect a couple of other things:

Timing of the code: The current released Hardware serial code does not use the Software FIFO queue, as such the code will hang there until the last character to be output, has started to be output.

This will probably impacts both the USB Serial (Serial.print statements), the impact of this might be reduced by using a higher baud rate. You currently have Serial.begin(9600);
Maybe try Serial.begin(1000000);

Likewise, the Wifi code, uses another Hardware serial port (Serial2) for the processor to communicate with the ESP32. I don't remember what baud rate they communicate with. Nor how much if any gain or loss, they get due to the UART used has a hardware FIFO. The FIFO might help in potentially reducing the number of interrupts they need to process for RX data coming from the ESP32. But that could also add latency issues, to when the main processor sees the data.

Nature of Servo library: unless it has changed over recent years and you probably already know the stuff below.

When you tell a servo to go to a position: like Servo2.write(value)
The servo will try to instantly move the servo to that position, as fast as the motor on the servo will allow it to. Which is also impacted by load and the like on the servo.

The Servo library has no way to know where the servo actually is. That is Servo1.read(), does not return the actual position of the servo, only the last position you asked the servo to go to.

So for example, if you tell the servo that is currently at lets say 90 degrees, to now go to 85 then 80 degrees, the servo is going to jump those 5 degrees to 85 and then jerk again to 80 degrees.

So again, long ago when I used the SSC-32 or when I experimented with Servo library, I had my own version of ServoEx which was setup to act like the SSC-32.

What both of these would do, was instead of saying: Servo1.write(85), you would say
Servo1.write(85, 200)
Which would say move from current position to 85 degrees in 200ms (1/5 of a second).
Instead lets say that is from 1500us to 1400us,

The code knew that the servo library updates the servos 50 times per second. So the move above
So the 200ms would so the move would take 10 servo cycles, and would adjust the position on each of these cycles: 1500 1490 1480 ... 1400 to give a smoother action.

You can emulate this external from the Servo library, although I found it cleaner to do it within it.

Hope that makes sense.

Good luck
Kurt

Simple follow up on my above:

Things that I would try if I were doing this:

a) Increase the Serial baud rate, and minimize Serial outputs.

b) Try increasing the priority of the interrupt that does the servos.
What I would try would be to edit the Servo library. On my machine it is located
C:\Users\kurte\Documents\Arduino\libraries\Servo\src\renesas\Servo.cpp
In this function:

static int servo_timer_config(uint32_t period_us)
{
    static bool configured = false;
    if (configured == false) {
        // Configure and enable the servo timer.
        uint8_t type = 0;
        int8_t channel = FspTimer::get_available_timer(type);
        if (channel != -1) {
            servo_timer.begin(TIMER_MODE_PERIODIC, type, channel,
                    1000000.0f/period_us, 50.0f, servo_timer_callback, nullptr);
            servo_timer.setup_overflow_irq();
            servo_timer.open();
            servo_timer.stop();
            configured = true;
        }
    }
    return configured ? 0 : -1;
}

Try changing the setup overflow line maybe like:
servo_timer.setup_overflow_irq(10);

And see if that helps with the jitters. Currently it defaults to priority 12.

c) check where it is telling the servos to go and if much of a delta, break it up into smaller delta moves.

Dear KurtE

First of all, thank you very much for your valuable help. I have read your recommendations in the first reply and implemented the following changes:

  1. changed library to your ServoPWR and the operation changed radically for the better. The servos moved more smoothly and stabilized. It is possible to have the servos stationary without any oscillation. Just what I wanted.
  2. I changed Serial.begin from 9600 to 1000000 but I didn't see any major changes in the system's behavior. In any case, I kept the change.
    I just notice something strange. When the board starts OSC communication, one of the servos (SG90-180) vibrates but stops as soon as I send a command from my Max/msp patch.
    These tests were carried out with an external power supply consisting of 4 x 1.5V 2100 mAh batteries duly connected to the board's GND. This power supply will be replaced by another (which I have ordered) consisting of two 3.6V 3000 mAh batteries (Samsung INR18650-30Q) and two step-down converters (LM2596) to regulate the voltage for each of the servos.
    I need to know more about the servo controller model you recommended in order to implement it in a more developed phase of my prototype.
    I will continue to follow your advice that you have sent in the meantime. I'll give you feedback with the presentation of the final results.

Thank you very much for your help!

You are welcome,

I should note that I personally have not used RC servos for probably 10 years now, maybe longer.
And currently I am not doing much at all with any servos, mostly plying with different boards and the like.

When I do use servos, I mostly use Dynamixel servos by Robotis.
For a long time I was using mostly AX-12 servos, but these days, I would mostly use the
XL servos, like: XL430-W250-T > XL Series | ROBOTIS

They communicate using a Half duplex Serial and they also have variations that communicate using RS485.

Some of the reasons I like using smarter servos, like this, is that your code does not have to babysit the servos, like most RC servos require the pulse in the nature of 50 times per second.

Whereas the Smart servos, you can simply tell them to go to some position and they will hold that position, until told otherwise. Likewise they can be setup, that when you tell them to go to a new position, you can tell them at what speed you wish for them to get there, or how long of a time to get there. And some of them allow you to setup things like, how fast the servos should ramp up to the target speed and likewise ramp down when they approach the target...

There are other brands of smart servos out there as well. Like Lynxmotion(RobotShop) sells their newer LSS parts and servos like:
Lynxmotion SES-V2 Standard Smart Servo (LSS-ST1) - RobotShop
But I have mostly used the Robotis ones.

Dear KurtE,

I haven't yet implemented your second recommendation (changing servo.h) but I will test it. In the meantime, I hope the ordered material arrives. Thank you for your advice on which servants to acquire. I intend to experiment with a more developed version of my prototype. Until then, I'll send you a video with the current results, thanking you for your help.

Thanks,

Sérgio

Quick note:

I have been hacking on the Arduino Servo library, to maybe address some of these issues.

As mentioned in the linked to GitHub issue as well as another thread, the current Servo code works with the timer going off every 100us and as such has the resolution of 100us or over 9 degrees.

I have a WIP version up at:

KurtE/Arduino_Servo at unor4_timer_period_updates (github.com)

That updates the period time to that needed for each servo as well as the gap between Servo refresh cycles.

So far I have tested it some with the example sketch sweep, plus I updated it to have two servos going in the opposite direction. And a pause with both servos a90 degrees to see if it looks like they both stay put...

/* Sweep
 by BARRAGAN <http://barraganstudio.com>
 This example code is in the public domain.

 modified 8 Nov 2013
 by Scott Fitzgerald
 https://www.arduino.cc/en/Tutorial/LibraryExamples/Sweep
*/

#include <Servo.h>

Servo myservo;  // create servo object to control a servo
Servo servo10;  // create servo object to control a servo
// twelve servo objects can be created on most boards

int pos = 0;  // variable to store the servo position

void setup() {
  while (!Serial && millis() < 5000)
    ;
  Serial.begin(9600);
  delay(500);
  myservo.attach(9);   // attaches the servo on pin 9 to the servo object
  servo10.attach(10);  // attaches the servo on pin 9 to the servo object
}

void loop() {
  for (pos = 0; pos <= 90; pos += 1) {  // goes from 0 degrees to 180 degrees
    // in steps of 1 degree
    myservo.write(pos);        // tell servo to go to position in variable 'pos'
    servo10.write(180 - pos);  // tell servo to go to position in variable 'pos'
    delay(15);                 // waits 15 ms for the servo to reach the position
  }
  delay(2000);
  for (pos = 90; pos <= 180; pos += 1) {  // goes from 0 degrees to 180 degrees
    // in steps of 1 degree
    myservo.write(pos);        // tell servo to go to position in variable 'pos'
    servo10.write(180 - pos);  // tell servo to go to position in variable 'pos'
    delay(15);                 // waits 15 ms for the servo to reach the position
  }

  for (pos = 180; pos >= 0; pos -= 1) {  // goes from 180 degrees to 0 degrees
    myservo.write(pos);                  // tell servo to go to position in variable 'pos'
    servo10.write(180 - pos);            // tell servo to go to position in variable 'pos'
    delay(15);                           // waits 15 ms for the servo to reach the position
  }
}

Like most other versions of the servo library, it also starts each of servos up with one after another. i.e., only one servo will get a pulse at a time. Which if I remember correctly, was done, such that not all of the servos will have their surge of startup current at the same time.

There are probably still lots of improvements that can be done, like:
a) verify the timing generated versus requested and maybe add in fudge factor.

b) Some implementations, compute what the next servo that will be started up, in the next interrupt, such that as soon as the interrupt happens, you quickly turn off pin for previous and turn on pin for new one... My current code looks for the next servo and then updates. It does maintain a bitmask of the active servos and uses __builtin_ctz to quickly find the lowest bit that is set...

c) work with the other timer. AFAIK - none of my code that asks for free timer has ever gotten anything other than GPT. So not sure how often that could happen.

But for now I will probably pause and wait for feedback. It has been so long since I have done much of anything with RC servos and so far I have not found any of the 6v power setups I used to use and most of my batteries have long since died.

2 Likes

Decided to issue a PR with my current stuff:
Unor4 timer period updates by KurtE · Pull Request #116 · arduino-libraries/Servo (github.com)

I now have it reasonably working with AGT timers. This required some fixes to the FspTimer code.
PR for that:
FspTimer does not initialize properly if AGT timer is selected. by KurtE · Pull Request #120 · arduino/ArduinoCore-renesas (github.com)

I think I am done playing with this for now.
Kurt

2 Likes