Joystick control: gimbal not changing directions when joystick is moved

Hi! I’m trying to control a DJI Ronin S gimbal with this hall effect joystick using an Arduino Uno.

The Ronin S can be controlled with the Futaba S-bus-system. To make this work, I’m using the servo library, and to translate the ‘servo commands’ to S-bus, I’m running the signal through a PWM/PPM to S-bus-converter, like this one.

This works, and I’m able to control the pan and tilt axis of the gimbal.

However, the program/gimbal seems unable to change directions or speed without first centering the joystick, if I’ve initiated a move involving more than one axis.

If I, for example, push the joystick up and to the right, the camera moves down and to the right, which is correct. However, if I keep the joystick pushed upwards, but move it to the left, the gimbal still keeps going right. If I then pull the joystick towards me, while still keeping it to the left, the gimbal keeps going right and the camera down. In other words it doesn’t seem to want to change a move that has already started.

Once I recenter the joystick and pulls it towards me and to the left the gimbal will move in the right directions again.

Similarly, if I move the joystick to the left, and then initiate a camera down-movement by pushing the joystick forwards, the gimbal will only move to the left, but not start tilting down at all.

Using the joystick in just one axis at a time works as it should, and I’m able to do a two axes move if I’m careful and move the joystick so that movement in each axis (x, y) starts at the same time.

In other words: starting a move in two axes works, but correcting the move or changing directions does not work without first centering the joystick. Starting a one axis move and trying to start a second axis does not work.

The analogue values from the joystick as presented in the serial monitor all seems stable and valid to me, and they do change value when I move the joystick, but the gimbal doesn’t seem to respond.

Does anyone have a suggestion as to what I’m doing wrong?

/* This program controls one or more DJI Ronin S gimbals using a hall effect joystick. 

Revised 14 January 2020 
*/ 

#include <Servo.h>

#define xaxis (A1) //Puts X-axis of joystick to input A1
#define yaxis (A3) //Puts Y-axis of joystick on input A2
#define zaxis (A5) //Puts Z-axis of joystick on input A3

Servo xmotor;  //Names the 'servos': outputs for controlling the s.bus-converter 
Servo ymotor; 
Servo zmotor;

int xDJI; //Defines a value called xDJI for the variable speed of the x-axis 

int yDJI; //Defines a value called yDJI for the variable speed of the y-axis  

int zDJI; //Defines a value called zDJI for the variable speed of the z-axis  

int cmdRepeatCount; //Defines a value called cmdRepeatCount, to repeat commands more easily 

void setup() {

Serial.begin(9600);   
Serial.println("Starting up ..."); 

pinMode(LED_BUILTIN, OUTPUT); 

pinMode(xaxis, INPUT); //defines input-pins from joystick as inputs
pinMode(yaxis, INPUT);
pinMode(zaxis, INPUT);

Serial.println("Attaching servo x-axis"); 
xmotor.attach(3); //Sets output pin for for xmotor: pan, S.BUS channel 0 (PPM-PPS-SBUS-board CH 1)  
delay(200);

Serial.println("Attaching servo y-axis");
ymotor.attach(5); //Sets output pin for for ymotor: tilt, S.BUS channel 1 (PPM-PPS-SBUS-board CH 2) 
delay(200); 

Serial.println("Attaching servo z-axis"); 
zmotor.attach(6); //Sets output pin for for zmotor: roll, S.BUS channel 3 (PPM-PPS-SBUS-board CH 4) 
delay(200);

while (cmdRepeatCount < 5) { //repeats this command x times      
digitalWrite(LED_BUILTIN, HIGH); 
delay(200);
digitalWrite(LED_BUILTIN, LOW); //flash builtin LED once 
delay(200);
cmdRepeatCount++; //increase repeat count +1 
}

Serial.println("Startup complete");

}

void loop() {

int xvalue = analogRead(xaxis); //reading values from joystick axis 
int yvalue = analogRead(yaxis); 
int zvalue = analogRead(zaxis); 

Serial.print("\t xvalue: "); //printing values from joystick axis 
Serial.print(xvalue); 
Serial.print("\t yvalue: "); 
Serial.print(yvalue); 
Serial.print("\t zvalue: "); 
Serial.print(zvalue); 
Serial.println(" "); 

xDJI = xvalue; //Link xvalue to xDJI
yDJI = yvalue; //Link yvalue to yDJI

xDJI = map(xDJI, 57, 950, 0, 180);  //maps xDJI values to servo-values: min value from joystick, max, degrees min, degrees max
yDJI = map(yDJI, 57, 950, 0, 180); //maps yDJI values to servo-values: min value from joystick, max, degrees min, degrees max

if (xDJI > 480 && xDJI < 505){xDJI = 510;} //sets range of deadband low and high, and gives a replacement value. 
if (yDJI > 480 && yDJI < 505){yDJI = 510;} //sets range of deadband low and high, and gives a replacement value. 

if (xDJI != 510) {
    xmotor.write(xDJI);}
    
if (yDJI != 510) {
    ymotor.write(yDJI);}

}

You are mapping your analog inputs from (57..950) -> (0..180), but the map() function does not constrain the output to be within range. If you analog input reads for example, 12, it will stay at 12.

Not sure if that is the issue...

And your "deadband" calculations are useless. You've just mapped xDJI, yDJI to 0-180. Then you check if their value is between 481-504. How can it be? Perhaps those lines should be BEFORE the map()s.

And usually for deadband the replacement value is within the band so 481-504 replaced by 510 is very odd.

Steve

blh64:
You are mapping your analog inputs from (57..950) -> (0..180), but the map() function does not constrain the output to be within range. If you analog input reads for example, 12, it will stay at 12.

Not sure if that is the issue...

Wouldn't the output of map() be somewhere around -9 if the input is 12?

Hi all, and thank you so much for your suggestions!

I’ve moved the deadband calculations up from where it was, so that it now comes before “map”. I’ve also included the entire theoretical range from the joystick (0-1023) instead of my original, limited range from 57-960.

I’ve done some more testing, and I find that I’m able to change directions of the gimbal if the joystick isn’t moved to it’s extremes.

In other words: as long as I stay within 40-60 % of the joystick’s max throw, I can change the speed and direction of the gimbal as much as I like without first centering the joystick. If I move the joystick over those values, I’m not able to change directions, and the gimbal keeps going in the direction it was started.

This corresponds to analogue values outside of roughly 690 and 400, so I gave replacement values for any readings outside of this in the code, and it now works like it should: I can control the speed and change direction any time I like.

However, this also means a lot of the joystick’s throw isn’t used for anything, which is rather unfortunate!

Below is the last version of the code. If you have any other suggestions, I’d be even happier than I already am with the help so far! =)

/* This program controls one or more DJI Ronin S gimbals using a hall effect joystick. 

Revised 14 January 2020 
*/ 

#include <Servo.h>

#define xaxis (A1) //Puts X-axis of joystick to input A1
#define yaxis (A3) //Puts Y-axis of joystick on input A2
#define zaxis (A5) //Puts Z-axis of joystick on input A3

Servo xmotor;  //Names the 'servos': outputs for controlling the s.bus-converter 
Servo ymotor; 
//Servo zmotor;

int xDJI; //Defines a value called xDJI for the variable speed of the x-axis 

int yDJI; //Defines a value called yDJI for the variable speed of the y-axis  

//int zDJI; //Defines a value called zDJI for the variable speed of the z-axis  

int cmdRepeatCount; //Defines a value called cmdRepeatCount, to repeat commands more easily 

void setup() {

Serial.begin(9600);   
Serial.println("Starting up ..."); 

pinMode(LED_BUILTIN, OUTPUT); 

pinMode(xaxis, INPUT); //defines input-pins from joystick as inputs
pinMode(yaxis, INPUT);
pinMode(zaxis, INPUT);

Serial.println("Attaching servo x-axis"); 
xmotor.attach(3); //Sets output pin for for xmotor: pan, S.BUS channel 0 (PPM-PPS-SBUS-board CH 1)  
delay(200);

Serial.println("Attaching servo y-axis");
ymotor.attach(5); //Sets output pin for for ymotor: tilt, S.BUS channel 1 (PPM-PPS-SBUS-board CH 2) 
delay(200); 

/*Serial.println("Attaching servo z-axis"); 
zmotor.attach(6); //Sets output pin for for zmotor: roll, S.BUS channel 3 (PPM-PPS-SBUS-board CH 4) 
delay(200);*/

while (cmdRepeatCount < 5) { //repeats this command x times      
digitalWrite(LED_BUILTIN, HIGH); 
delay(200);
digitalWrite(LED_BUILTIN, LOW); //flash builtin LED once 
delay(200);
cmdRepeatCount++; //increase repeat count +1 
}

Serial.println("Startup complete");

}

void loop() {

int xvalue = analogRead(xaxis); //reading values from joystick axis 
int yvalue = analogRead(yaxis); 
//int zvalue = analogRead(zaxis); 

Serial.print("\t xvalue: "); //printing values from joystick axis 
Serial.print(xvalue); 
Serial.print("\t yvalue: "); 
Serial.print(yvalue); 
/*Serial.print("\t zvalue: "); 
Serial.print(zvalue);*/ 
Serial.println(" "); 

xDJI = xvalue; //Link xvalue to xDJI
yDJI = yvalue; //Link yvalue to yDJI

if (xDJI > 480 && xDJI < 520){xDJI = 512;} //sets range of deadband low and high, and gives a replacement value. 
if (yDJI > 480 && yDJI < 520){yDJI = 512;} //sets range of deadband low and high, and gives a replacement value. 

if (xDJI > 690){xDJI = 690;} //Limits throw of x-axis, because readings outside these values inhibits gimbal control
if (xDJI < 400){xDJI = 410;}

if (yDJI > 690){yDJI = 690;} //Limits throw of y-axis, because readings outside these values inhibits gimbal control
if (yDJI < 400){yDJI = 410;}

xDJI = map(xDJI, 0, 1023, 0, 180);  //maps xDJI values to servo-values: min value from joystick, max, degrees min, degrees max
yDJI = map(yDJI, 0, 1023, 0, 180); //maps yDJI values to servo-values: min value from joystick, max, degrees min, degrees max

if (xDJI != 512) {
    xmotor.write(xDJI);}
    
if (yDJI != 512) {
    ymotor.write(yDJI);}

}

Your 'if (xDJI != 512)' and y equivalent is doing nothing. Again, it's AFTER the map() so value can only be in the ranges 0-180.

Personally I'd do the map() into separate variables (maybe xWriteVal, yWriteVal) leaving xDJI,yDJI to contain the original values from the analogReads.

Steve

Thanks a lot, again! I hope I’ve cleaned everything up now, so that every command is actually doing something useful. Also thanks for the tip on using separate variables.

The program now works, but I still have to limit the values from the joystick in the program, otherwise I’m just seeing the problems I’ve already described.

I tried powering the joystick with an external source (though still with common ground). This made the values I’m reading from it go all the way from 0 to 1024, compared to 55-970. In other words it doesn’t seem to get enough power from the arduino itself. However, this made no difference in the behaviour where positions over 40-60 % of stick travel makes the gimbal spin eternally even though the joystick is moved.

I feel like I’m running out of options, but I begin to suspect the problem might lie with the PWM/PPM to s.bus-converter (or me, of course, and my code).

/* This program controls one or more DJI Ronin S gimbals using a hall effect joystick. 

Revised 14 January 2020 
*/ 

#include <Servo.h>

#define xaxis (A1) //Puts X-axis of joystick to input A1
#define yaxis (A3) //Puts Y-axis of joystick on input A2
#define zaxis (A5) //Puts Z-axis of joystick on input A3

Servo xmotor;  //Names the 'servos': outputs for controlling the s.bus-converter 
Servo ymotor; 
//Servo zmotor;

int xDJI; //Defines a value called xDJI for the variable speed of the x-axis 

int yDJI; //Defines a value called yDJI for the variable speed of the y-axis  

//int zDJI; //Defines a value called zDJI for the variable speed of the z-axis  

int xWriteValue; 
int yWriteValue;
//int zWriteValue; 

int cmdRepeatCount; //Defines a value called cmdRepeatCount, to repeat commands more easily 

void setup() {

Serial.begin(9600);   
Serial.println("Starting up ..."); 

pinMode(LED_BUILTIN, OUTPUT); 

pinMode(xaxis, INPUT); //defines input-pins from joystick as inputs
pinMode(yaxis, INPUT);
pinMode(zaxis, INPUT);

Serial.println("Attaching servo x-axis"); 
xmotor.attach(3); //Sets output pin for for xmotor: pan, S.BUS channel 0 (PPM-PPS-SBUS-board CH 1)  
delay(200);

Serial.println("Attaching servo y-axis");
ymotor.attach(5); //Sets output pin for for ymotor: tilt, S.BUS channel 1 (PPM-PPS-SBUS-board CH 2) 
delay(200); 

/*Serial.println("Attaching servo z-axis"); 
zmotor.attach(6); //Sets output pin for for zmotor: roll, S.BUS channel 3 (PPM-PPS-SBUS-board CH 4) 
delay(200);*/

while (cmdRepeatCount < 5) { //repeats this command x times      
digitalWrite(LED_BUILTIN, HIGH); 
delay(200);
digitalWrite(LED_BUILTIN, LOW); //flash builtin LED once 
delay(200);
cmdRepeatCount++; //increase repeat count +1 
}

Serial.println("Startup complete");

}

void loop() {

int xvalue = analogRead(xaxis); //reading values from joystick axis 
int yvalue = analogRead(yaxis); 
//int zvalue = analogRead(zaxis); 

Serial.print("\t xvalue: "); //printing values from joystick axis 
Serial.print(xvalue); 
Serial.print("\t yvalue: "); 
Serial.print(yvalue); 
/*Serial.print("\t zvalue: "); 
Serial.print(zvalue);*/ 
Serial.println(" "); 

xDJI = xvalue; //Link xvalue to xDJI
yDJI = yvalue; //Link yvalue to yDJI

if (xDJI > 480 && xDJI < 520){xDJI = 512;} //sets range of deadband low and high, and gives a replacement value. 
if (yDJI > 480 && yDJI < 520){yDJI = 512;} //sets range of deadband low and high, and gives a replacement value. 

if (xDJI > 690){xDJI = 690;} //Limits throw of x-axis, because readings outside these values inhibits gimbal control
if (xDJI < 410){xDJI = 410;}

if (yDJI > 690){yDJI = 690;} //Limits throw of y-axis, because readings outside these values inhibits gimbal control
if (yDJI < 400){yDJI = 410;}

xWriteValue = map(xDJI, 0, 1023, 0, 180);  //maps xDJI values to servo-values: min value from joystick, max, degrees min, degrees max
yWriteValue = map(yDJI, 0, 1023, 0, 180); //maps yDJI values to servo-values: min value from joystick, max, degrees min, degrees max

xmotor.write(xWriteValue);
ymotor.write(yWriteValue);

}

I can't see any details of that PWM to S-Bus converter but I wonder if it only expects the input signal to be in the range 1 to 2ms. The default Servo library gives a range where 0 = .544ms and 180 = 2.4ms.

To test this possibility try changing your attach() commands to attach(3 or 5, 1000, 2000) which will limit the range of pulse widths and comment out all your code that limits the 0-1023 from the analogRead()s. See if that makes a difference.

Steve

Thanks for your tip! I tried what you suggested, but unfortunately it didn’t make a difference.

Since my last post I’ve changed the PWM to S.BUS-converter to a more expensive (and thus, automatically, better, right?) one. Unfortunately, this hasn’t made a difference either. The new converter is an Accutec MC-32.

I’ve also hooked the gimbal up to a normal Futaba radio and receiver, and the gimbal then works as it should: I’m able to pan, tilt and roll and change directions at will at any speed. I’m also able to reset the heading of the gimbal: it’s supposed to reset it’s heading either to the front or to the back if sent a PWM signal at either 1000 or 2000 μs via S.BUS at channel 6.

It seems to me that the Arduino somehow isn’t sending the proper PWM signals, and that it ‘stalls’ when being asked to send PWM signals over a certain level.

I’ve tried using this bit of code:

// zoomkat 10-4-10 serial servo test
// type servo position 0 to 180 in serial monitor
// for writeMicroseconds, use a value like 1500
// for IDE 0019 and later
// Powering a servo from the arduino usually DOES NOT WORK.

String readString;
#include <Servo.h>
Servo myservo;  // create servo object to control a servo

void setup() {
 Serial.begin(9600);
 myservo.attach(3);  //the pin for the servo control
 Serial.println("servo-test-21"); // so I can keep track of what is loaded
}

void loop() {

 while (Serial.available()) {
   delay(10);  
   if (Serial.available() >0) {
     char c = Serial.read();  //gets one byte from serial buffer
     readString += c; //makes the string readString
   }
 }

 if (readString.length() >0) {
   Serial.println(readString);  //so you can see the captured string
   int n;
   char carray[6]; //converting string to number
   readString.toCharArray(carray, sizeof(carray));
   n = atoi(carray);
   myservo.writeMicroseconds(n); // for microseconds
   //myservo.write(n); //for degees 0-180
   readString="";
 }
}

that I found in this thread, to send values in microseconds to the different servos: somehow it doesn’t seem to respond to values over 1900 or below 1100.

In the latest version of the code I’ve included a button for resetting the heading of the gimbal. This has to give either a 1 or 2 ms signal, but even though I’m asking for it, the Arduino doesn’t seem to write it.

Can it be that the Arduino isn’t able to run three PWM outputs based on three analogue inputs at once? Is there something that would inhibit it from writing values higher than 1900 μs or lower than 1100?

Could power be an issue? The Arduino is powered via USB, and doesn’t power anything else than the S.BUS-converter.

This is the latest version of the code:

/* This program controls one or more DJI Ronin S gimbals using a hall effect joystick. 

Revised 23 January 2020 
*/ 

#include <Servo.h>

#define xaxis (A1) //Puts X-axis of joystick to input A1
#define yaxis (A3) //Puts Y-axis of joystick on input A2
#define zaxis (A5) //Puts Z-axis of joystick on input A3

Servo xmotor;  //Names the 'servos': outputs for controlling the s.bus-converter 
Servo ymotor; 
Servo zmotor;

Servo resetfront; 

int xDJI; //Defines a value called xDJI for the variable speed of the x-axis 
int yDJI; //Defines a value called yDJI for the variable speed of the y-axis 
int zDJI; //Defines a value called zDJI for the variable speed of the z-axis  

const int joykey1 = 12; //Defines a value called joykey1 for joystick button 1 
const int joykey2 = 13;  //Defines a value called joykey1 for joystick button 1 

int xWriteValue; 
int yWriteValue;
int zWriteValue; 
int resetfrontWriteValue;

int cmdRepeatCount; //Defines a value called cmdRepeatCount, to repeat commands more easily 

void setup() {

Serial.begin(9600);   
Serial.println("Starting up ..."); 

pinMode(LED_BUILTIN, OUTPUT); 

pinMode(xaxis, INPUT); //defines input-pins from joystick as inputs
pinMode(yaxis, INPUT);
pinMode(zaxis, INPUT);

pinMode(joykey1, INPUT); 

Serial.println("Attaching servo x-axis"); 
xmotor.attach(3, 1000, 2000); //Sets output pin for for xmotor: pan, S.BUS channel 0 (PPM-PPS-SBUS-board CH 1)  
delay(50);

Serial.println("Attaching servo y-axis");
ymotor.attach(5, 1000, 2000); //Sets output pin for for ymotor: tilt, S.BUS channel 1 (PPM-PPS-SBUS-board CH 2) 
delay(50); 

Serial.println("Attaching servo z-axis"); 
zmotor.attach(6); //Sets output pin for for zmotor: roll, S.BUS channel 3 (PPM-PPS-SBUS-board CH 4) 
delay(200);

Serial.println("Attaching servo reset center");
resetfront.attach(10, 1000, 2000); //Sets output pin for for centering: center, S.BUS channel 6 (PPM-PPS-SBUS-board CH 7) 
delay(50); 

while (cmdRepeatCount < 5) { //repeats this command x times      
digitalWrite(LED_BUILTIN, HIGH); 
delay(100);
digitalWrite(LED_BUILTIN, LOW); //flash builtin LED once 
delay(100);
cmdRepeatCount++; //increase repeat count +1 
}

Serial.println("Startup complete");

}

void loop() {

int xvalue = analogRead(xaxis); //reading values from joystick axis 
int yvalue = analogRead(yaxis); 
int zvalue = analogRead(zaxis); 
int joykey1value = digitalRead(joykey1); 

Serial.print("\t xvalue: "); //printing values from joystick axis 
Serial.print(xvalue); 
Serial.print("\t yvalue: "); 
Serial.print(yvalue); 
Serial.print("\t zvalue: "); 
Serial.print(zvalue); 
Serial.print("\t joykey1value: "); 
Serial.print(joykey1value); 
Serial.println(" "); 

if (joykey1value == 1) { 
digitalWrite(LED_BUILTIN, HIGH); 
resetfront.writeMicroseconds(1000);
}

if (joykey1value == 0) { 
digitalWrite(LED_BUILTIN, LOW); 
resetfront.writeMicroseconds(1500);
}


xDJI = xvalue; //Link xvalue to xDJI
yDJI = yvalue; //Link yvalue to yDJI
zDJI = zvalue; //link zvalue to zDJI 

if (xDJI > 480 && xDJI < 520){xDJI = 512;} //sets range of deadband low and high, and gives a replacement value. 
if (yDJI > 480 && yDJI < 520){yDJI = 512;} //sets range of deadband low and high, and gives a replacement value.
if (zDJI > 480 && zDJI < 520){zDJI = 512;} //sets range of deadband low and high, and gives a replacement value.


/*if (xDJI > 690){xDJI = 690;} //Limits throw of x-axis, because readings outside these values inhibits gimbal control
if (xDJI < 410){xDJI = 410;}

if (yDJI > 690){yDJI = 690;} //Limits throw of y-axis, because readings outside these values inhibits gimbal control
if (yDJI < 400){yDJI = 410;}*/

xWriteValue = map(xDJI, 0, 1023, 1000, 2000);  //maps xDJI values to servo-values: min value from joystick, max, degrees min, degrees max
yWriteValue = map(yDJI, 0, 1023, 1000, 2000); //maps yDJI values to servo-values: min value from joystick, max, degrees min, degrees max
zWriteValue = map(zDJI, 0, 1023, 1600, 1400); 

xmotor.writeMicroseconds(xWriteValue);
ymotor.writeMicroseconds(yWriteValue);
zmotor.writeMicroseconds(zWriteValue); 


}

You're limiting xDJI and yDJI to a much smaller range than 0-1023. So after you map 0-1023 to 1000 -2000 them xWriteValue and yWriteValue will only cover a much smaller range than 1000 - 2000.

Why not put some Serial.write()s in to see the actual values before and after the map() commands.

Steve