How to control two servo motors at once

Hello everyone, we're trying to drive a mid size tank (industrial purposes, rough terrains) forwards and backwards initially and later on add sideways and rotations, but we've come to a roadblock, we've first tried using pulse_in but when reading from 3 channels there were timeouts and inconsistencies, so we've read that the best way is to use a digital interrupt, to kickstart the project fast, we found this code and tried to adopt it.

It works flawlessly even for our use case but only when we're printing the values, if we use writeMicroSeconds to the servos, then the values are jumping like crazy, it seems to me that writing to the servo is blocking the execution of the loop function.

If we need to add more details like schematics and what's being used, let me know, for starters we're using: Arduino Mega 2560, RadioLink AT10, 48v 3KW Brushless dc servo drivers x2 and two brushless dc motors.

#include <Servo.h>

Servo leftMotor;
Servo rightMotor;

// Set the port speed for host communication
#define SERIAL_PORT_SPEED 9600

// Set the size of the arrays (increase for more channels)
#define RC_NUM_CHANNELS 3

// Set up our receiver channels - these are the channels from the receiver
#define RC_CH1 0  // rotation
#define RC_CH2 1  // forward backwards
#define RC_CH3 2  // sideways

// ARDUINO MEGA 2560
#define RC_CH1_INPUT  19 // receiver pin 2
#define RC_CH2_INPUT  2 // receiver pin 5
#define RC_CH3_INPUT  3 // receiver pin 6


// Set up some arrays to store our pulse starts and widths
uint16_t RC_VALUES[RC_NUM_CHANNELS] = { 1508, 1508, 1508 };
uint32_t RC_START[RC_NUM_CHANNELS];
volatile uint16_t RC_SHARED[RC_NUM_CHANNELS];


// to the extent possible, all the variables are scaleable, you will need to add more values
// here if you use a higher number of channels
uint16_t RC_LOW[RC_NUM_CHANNELS] = { 1092, 1092, 1092 };
uint16_t RC_MID[RC_NUM_CHANNELS] = { 1508, 1508, 1508 };
uint16_t RC_HIGH[RC_NUM_CHANNELS] = { 1924, 1924, 1924 };

// The RC Channel mode helps us know how to use and refine the signal
// Settings are:
// 0 = a joystick with a centerpoint (deadzone in middle)
// 1 = a throttle that goes from low to high (deadzone at start)
// 2 = a a switch (either on or off)
uint16_t RC_CHANNEL_MODE[RC_NUM_CHANNELS] = { 0, 0, 0};

// a place to store our mapped values
float RC_TRANSLATED_VALUES[RC_NUM_CHANNELS] = { 1500, 1500, 1500};

// some boundaries for our mapped values
float RC_TRANSLATED_LOW[RC_NUM_CHANNELS] = { 2000, 2000, 2000 };
float RC_TRANSLATED_MID[RC_NUM_CHANNELS] = { 1500, 1500, 1500 };
float RC_TRANSLATED_HIGH[RC_NUM_CHANNELS] = { 1000, 1000, 1000 };

// What percentage deadzone is allowed? values in percent e.g. 10 = 10%
uint16_t RC_DZPERCENT[RC_NUM_CHANNELS] = { 30, 30, 5 };


// Setup our program
void setup() {

  // Set the speed to communicate with the host PC
  Serial.begin(SERIAL_PORT_SPEED);

  leftMotor.attach(5);
  rightMotor.attach(6);


  // Set our pin modes to input for the pins connected to the receiver
  pinMode(RC_CH1_INPUT, INPUT_PULLUP);
  pinMode(RC_CH2_INPUT, INPUT_PULLUP);
  pinMode(RC_CH3_INPUT, INPUT_PULLUP);

  // Attach interrupts to our pins
  attachInterrupt(digitalPinToInterrupt(RC_CH1_INPUT), READ_RC1, CHANGE);
  attachInterrupt(digitalPinToInterrupt(RC_CH2_INPUT), READ_RC2, CHANGE);
  attachInterrupt(digitalPinToInterrupt(RC_CH3_INPUT), READ_RC3, CHANGE);
}

unsigned long current = 0;
unsigned long prev = 0;
const unsigned long interval = 100000UL;


void loop() {

  // read the values from our RC Receiver
  rc_read_values();

  rc_clip_values();

  // map the radio values to the range we want
  rc_translate_values();

  driveBothMotors(RC_TRANSLATED_VALUES[1]);

  // keep track of time
  current = micros();

  // This is our plotter Chart output, we only do it every so often or the plotter moves too fast
  if (current - prev >= interval) {
    prev += interval;
  }
}

// Thee functions are called by the interrupts. We send them all to the same place to measure the pulse width
void READ_RC1() {
  Read_Input(RC_CH1, RC_CH1_INPUT);
}
void READ_RC2() {
  Read_Input(RC_CH2, RC_CH2_INPUT);
}
void READ_RC3() {
  Read_Input(RC_CH3, RC_CH3_INPUT);
}

// This function reads the pulse starts and uses the time between rise and fall to set the value for pulse width
void Read_Input(uint8_t channel, uint8_t input_pin) {
  if (digitalRead(input_pin) == HIGH) {
    RC_START[channel] = micros();
  } else {
    uint16_t rc_compare = (uint16_t)(micros() - RC_START[channel]);

    RC_SHARED[channel] = rc_compare;
  }
}

// this function pulls the current values from our pulse arrays for us to use.
void rc_read_values() {
  noInterrupts();
  memcpy(RC_VALUES, (const void *)RC_SHARED, sizeof(RC_SHARED));
  interrupts();
}

void rc_clip_values() {

  // loop through the channels
  if(RC_VALUES[0] < 1500) return;
  if(RC_VALUES[1] < 1500) return;
  if(RC_VALUES[2] < 1500) return;

  for (int i = 0; i < RC_NUM_CHANNELS; i++) {
    // a little clipping to make sure we dont go over or under the bounds

    // clip the high range so it doesnt go over the max
    if (RC_VALUES[i] > RC_HIGH[i]) {
      RC_VALUES[i] = RC_HIGH[i];
    }

    // clip the low range so it doesnt go under the min
    if (RC_VALUES[i] < RC_LOW[i]) {
      RC_VALUES[i] = RC_LOW[i];
    }
  }
}

void rc_translate_values() {

  // Loop through all our channels
  for (int i = 0; i < RC_NUM_CHANNELS; i++) {

    // translate the RC channel value into our new number range
    RC_TRANSLATED_VALUES[i] = translateValueIntoNewRange((float)RC_VALUES[i], (float)RC_HIGH[i], (float)RC_LOW[i], RC_TRANSLATED_LOW[i], RC_TRANSLATED_HIGH[i]);
  }
}


void driveBothMotors(long microSeconds){
  //just printing the values are correct
    Serial.println((String)"Forward and backwards "+microSeconds);
    //when writing to both motors then the values that are read using interrupts are jumping, what's the problem? 
    leftMotor.writeMicroseconds(microSeconds);
    rightMotor.writeMicroseconds(microSeconds);
}

float translateValueIntoNewRange(float currentvalue, float currentmax, float currentmin, float newmax, float newmin) {
  // Use this formula to work out where we are in the new range
  // NewValue = (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin
  //
  // this formula was lovingly stolen from https://stackoverflow.com/questions/929103/convert-a-number-range-to-another-range-maintaining-ratio

  return (((currentvalue - currentmin) * (newmax - newmin)) / (currentmax - currentmin)) + newmin;
}

Anyone?

You can try ServoTimer2 library instead and see if there is an improvement.

It doesn't seem to work with MEGA 2560

It happens when I give them values, i.e when i use writeMicroSeconds @Delta_G

I'll try to attach schematics ASAP

Is the writeMicroSeconds sync or async function?

Yes. In the code for the AVR, and I assume all versions, the value is constrained using a minimum and maximum pulse width.

Either the default, or the ones you can set when you create the servo object.

a7

Right.

@nkiko there would be nothing to block anyway, as these servos are running open loop.

You can read the servo position, but all that does is tell you what you told it last. There is no provision for the code to determine where the servo is physically.

So you trust, or make your own mechanism for verification, that the the servo is doing what you need it to do.

In many systems, trust is by virtue of the design and servos with sufficient power to be worthy of.

a7

Here are the schematics, the forum doesn't allow me to upload PDF so I added them to Proton Drive.

To be clear, AIN wire is the only wire from the controller that responds to pwm signal. At first I thought it's analog. We set the controller to work win pwm mode or as they call it "rc mode" (it has multiple modes, rs232, 0-5v etc).

Thank you for adding the image.

what is the range of values given driveBothMotors()?
should be between 1-2 msec

may make sense to limit the execution rate of loop ()

void loop () {
    ...
    delay (500);
}

1500 is idle
1000 is forwards full throttle
2000 is backwards full throttle

We tried with the delay but that doesn't help

are those the values actually given to driveBothMotors() or simply the what you think are being received and passed on?

how long a delay?

have you tried calling driveBothMotors() in setup() with the values to move forward for 3 seconds and then with values to stop the tank to verify that those work properly?

please post the complete sketch that you say this sketch works flawlessly when printing values

you should increase the baudrate form

#define SERIAL_PORT_SPEED 9600

to

#define SERIAL_PORT_SPEED 115200

Do you have an oscilloscope?
It would be very interesting to see how the RC-channel signals look like.

You are using the RC-channel inputs as INPUT_PULLUP.
Have you ever tried INPUT instead of INPUT_PULLUP?

Your brushless motors run on big currents.
How does all the wiring look like?
For a brushless motor the coils are switched on/off at high frequency which can cause EMV

another reason why it would be very interesting to see the RC-signals on an oscilloscope

best regards Stefan

Is the servo control a three-position switch or a potentiometer?
If a potentiometer, what will be the "idle" band? Out of 1024 steps, 100 bits in the middle is easy to dial to.

When printing values i am using the same sketch, just

leftMotor.writeMicroseconds(microSeconds);
rightMotor.writeMicroseconds(microSeconds);

is commented out and i am not passing the values to the servos.

we tried with the higher baud rate but then the values don't work at all.

Also changing to INPUT vs INPUT_PULLUP doesn't help.

Same behavior.

This is a too unprecise description.
You are working on a project where the hardware is minimum $2000.
This means you should engage much more and post precise descriptions of what you have done

what exact baudrate ? exact means post the number.
To what baudrate did you adjust the serial monitor of the arduino-IDE?
was it the same baudrate as in the code?

What does it mean if

did you not see anything in the serial monitor?
(which could be caused by a wrong adjusted baudrate of the serial monitor)
or did your BL-DC-motors not move at all?
what happened as you changed baudrates back?
did the BL-DC-motors then rotate?
and what was the exact behaviour of the serovs?
did the servos just run one direction ? or both or did the rpm jump up and down?

Can you see by all these questions that you have to post with much more precision
than just writing "values don't work at all" ?

As you work with hardware that is worth minimum $2000. You should seriously consider buying an oscilloscope for $300. Such an oscilloscope will serve you very valueable if any new problems occur.

Did you do a cross-test by using really small standard-RC servos too see how these standard-RC servos behave if you feed in your servo-signal?

By doing this you can narrow down if the problem is a software-problem (which I don't believe) or if it is a hardware-problem.

Next step would be to do a test with small ESC for brushless-motors of the 10 to 30A range
to see how these small brushless motors behave if you feed in your microseconds signal.

Another way for analysing would be to use another microcontroller to read in the signal by created for your BLDC-driver to analyse if the values are jumping.

Man ! -sitting around - just writing

Is too less engagement to solve the problem.
Additionally simply changing from INPUT to INPUT_PULLUP as the only change
can not work ! As this change inverts the logic!

You should not just try this try that
without understanding what you are doing
until your hardware finally starts to vapor into magic smoke

To make it easier for other users to help you should precisely describe what this behaviour is.

best regards Stefan

#define SERIAL_PORT_SPEED 115200

Just throws out random values we can't decipher.

When we're writing 1500 to the big servos the values don't jump, but when reading and writing the PWM with 1000 being forward full throttle and 2000 backwards full throttle we get random values.

Also we tried with smaller servos, the code works flawlessly without any problems.

We'll get oscilloscope, thanks for the advice, I'm not designing the hardware, I'm a software engineer trying to help a friend who does the hardware design, we're both beginners in this.

I'm sorry that I can't provide much more info as I don't know what to provide, when you're guiding me it's helping me to paint the picture, but I might give some irrelevant info or not connected to what we're doing and trying to achieve.

We also think that the wires coming out of the servo motors are causing interference, we're clueless to what's causing the issue with these big motors but with small servo motors everything works fine.

By everything i mean this exact code i posted, without any changes.

If it works with small servos this points towards a electromagnetic noise problem.

As long as you organise a posting in multiple paragraphs and gave the paragraphs a title you can post five computer screen high postings with 100 lines of text.

It is much more annoying and time-consuming to ask back than to cross-read and scroll!

dear @nkiko

can you please describe

precisely

what values!!

"Values" is a very generalised word.

values from encoders?
values of microseconds?

And it would help a lot if you would post theses values that get printed to the serial monitor as a code-section. (same formatting as a sketch)

If it is a electromagnetic noise problem.
Post pictures of

ALL

the wiring.

describe in detail

  • what kind of wires do you use?
  • are these wires shielded or not?
  • if the wires are shielded where and to what is the shielding connected?

best regards Stefan

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.