360 rotation (SOLVED)

Hello,

Hardware

  • Arduino mega2560 (ATMEGA2560, 16U-TW, 355E3F, 1826JOR)
  • 4 * 28BYJ-48 stepper motor, each with ULN2003 driver.

Arduino library
-Accelstepper

Code

//experimental build

#include <AccelStepper.h>

#define FULLSTEP 4
#define HALFSTEP 8


//Roll motor A
const int RollMotorAPin1 = 36;
const int RollMotorAPin2 = 34;
const int RollMotorAPin3 = 32;
const int RollMotorAPin4 = 30;


// Ball Steppers
AccelStepper RollStepperA(HALFSTEP, RollMotorAPin1, RollMotorAPin3, RollMotorAPin2, RollMotorAPin4);

String readString, data;
double PitchMomentum, RollMomentum, AccelX, AccelY, AccelZ, SimState, Pitch, Roll, AirborneState;

void setup() {


  //RollMotor A
  RollStepperA.setMaxSpeed(1500.0);
  RollStepperA.setAcceleration(4000.0);
  RollStepperA.setSpeed(950);
  RollStepperA.setCurrentPosition(0);


  Serial.begin(115200);


}

void loop() {

  /////////////Getting data from Xplane and packaging it/////////////

  if (Serial.available())  {
    char c = Serial.read();  //gets one byte from serial buffer

    if (c == ',') {
      /*       if (readString.indexOf("q") >= 0) {     // Pitch momentum data, stored in PitchMomentum double
               data = readString.substring(1);
               PitchMomentum = data.toDouble();

              }

              if (readString.indexOf("p") >= 0) {    // Roll momentum data, stored in RollMomentum double
               data = readString.substring(1);
               RollMomentum = data.toDouble();

              }
      */

      if (readString.indexOf("X") >= 0) {    // X acceleration data, stored in AccelX double
        data = readString.substring(1);
        AccelX = (data.toDouble() );        // data converted to steps in C#
      }

      if (readString.indexOf("Z") >= 0) {    // Z acceleration data, stored in AccelZ double
        data = readString.substring(1);
        AccelZ = data.toDouble();          // data converted to steps in C#
      }

      if (readString.indexOf("Y") >= 0) {    // Y acceleration data, stored in AccelY double
        data = readString.substring(1);
        AccelY = data.toDouble();

      }


      if (readString.indexOf("S") >= 0) {    // SimState data, stored in SimState double
        data = readString.substring(1);
        SimState = data.toDouble();

      }

      if (readString.indexOf("P") >= 0) {    // Pitch data, stored in Pitch double
        data = readString.substring(1);
        Pitch = (data.toDouble());       // data converted to steps in C#

      }

      if (readString.indexOf("Q") >= 0) {    // Roll data, stored in Roll double
        data = readString.substring(1);
        Roll = data.toDouble();      // data converted to steps in C#
      }

      if (readString.indexOf("A") >= 0) {    // Airborne state data, stored in AirborneState int
        data = readString.substring(1);       // (acft agl: is the aircraft on ground or not?)
        AirborneState = data.toInt();
      }


      readString = ""; //clears variable for new input
      data = "";

    }
    else {
      readString += c; //makes the string readString
    }
  }

/////////////Using the serial data to drive the steppers/////////////

  // Pitch motion
  PitchStepperA.moveTo(Pitch  - AccelZ);    //Aft acceleration (e.g. takeoff) is negative, so to pitch up to simulate gforce, I need to substract it from the pitch.
  PitchStepperB.moveTo(Pitch - AccelZ);
  PitchStepperA.setAcceleration(4000);
  PitchStepperB.setAcceleration(4000);
  PitchStepperA.run();
  PitchStepperB.run();

  // Roll motion

    RollStepperA.moveTo(-Roll + AccelX);               // Steppers need to move opposite for the Orb to go in the correct direction:
    RollStepperB.moveTo(-Roll + AccelX);              // Left roll is a negative value, so the steppers need to move with a positive value, therefore moving to Negative Roll.
    RollStepperA.setAcceleration(4000);
    RollStepperB.setAcceleration(4000);
    RollStepperA.run();
    RollStepperB.run();

}

Explanation
in short, I'm making a prototype of a home full motion simulator in the form of a hamster ball.
see below video for a reference to get an idea what I'm trying to recreate:

The concept is that I have 4 motors with omniwheels turning a ball in which the user would sit.
see reply#36 to get an idea of the setup
Initially I'm focussing on Xplane 11 as its flight dynamics are the closest to real life in my opinion.

I'm getting access to the datarefs (acceleration, roll, pitch, ...) through a program I wrote in C#. (thanks to the wonderful code of Max Ferretti, xplaneconnector)
I'm sending the data from C# to arduino and used some basic math to convert it into stepper movement.

For now I've come to the point where I have the basic movement and acceleration for my Hamster Ball but I've run into an issue and would love your insight into this as I've broken my mind over it but can't see a solution:

Attached you can find a picture on how Xplane handles pitch and roll (yaw is not of much interest to me as it isn't required for the motion of the ball itself, I will use it for something else but I'm digressing).

here's a video of the values in action:

Problem(s)
I have 2 problems (provided image as reference):

  • Roll:
    goes from 0° (wings level) to -180° (left turn to inverted) and from 0° (wings level) to 180° (right turn to inverted)
    Whenever I try to do a full 360° roll, the motors turn opposite whenever the aircraft reaches the initial inverted position and continues, becaese the value changes from positive to negative or vice versa.

-Pitch:
Starting from wings level, if you pitch down the value goes from 0° to -90° for full nose down and if you continue to inverted, the value increases from -90° to 0°. Same goes for pitch up, with the value being positive.

The issue in both cases is that the motors will change direction because of the value change and it not being full 360 values (makes sense?)

Solution?
For the roll I tried pre-processing it by adding 180° to the value. This initially would work as you have following situation:
Wings level | full right turn to inverted | full left turn to inverted:

**-Sim value: ** 0° ** | ** 180° ** | **-180°
**-Arduino value: 180 ** | ** 360 ** | 0

However, there's still a point where the value goes from 360 to 0 and vice versa, which means the motor will sort of 'reset' to 0 or 360 depending which way you're turning.

I haven't touched the pitch yet, but I feel that this is just a matter of copying the solution from my roll issue (be it in a slightly modified manner).

I tried using a momentum value from xplane, which gave me radials per second and this worked in giving me full 360 range. However, because it's a 'motion' if I keep pulling the yoke/stick in a turn, the pitch value would keep increasing and keep turning the ball while in reality this shouldn't be the case. Same goes for the roll.

You will need to provide some examples of the numbers that are being used to control the motors.

Why have you got

PitchStepperA.setAcceleration(4000);

in loop() when it has already been done in setup(). Repeating it in loop() will just slow things up.

If this was my project I would create a struct to hold the values from the PC and I would send all of the values every time, even if some of them don't change. Then the whole message could be copied to the struct without any need for parsing the content. I am assuming that C# has a function that is equivalent to the Python struct.pack() to convert the data into datatypes that the Arduino uses.

...R

You will need to provide some examples of the numbers that are being used to control the motors.

I’ll give a roll example as I want to tackle that issue first:

  • The aircraft is rolled 10° to the right.
  • C# will retrieve a float value 10.000000.
  • I round it off to 10.000, multiply it by 67, store it in a string and send this string via serial to arduino.

The reason I used a string is because initially I was having issues sending any values from C# to arduino. Asked Max Ferretti for help (guy who provided the Xplaneconnector package for c#), and he provided me with this solution.
Anyway, the way all my data ends up in arduino from C# is as follows:
q0,p0,X51.115,Y0,Z-94.951,S1,P59.362,Q670.000,

Arduino then checks this serial for a certain letter and ‘reads’ what is behind it. In the case of roll, it is defined with the letter ‘Q’ and as shown is 670.000. Reason for this is because I multiply the value in C# with 67 as 1° ball rotation requires 76 steps of my motor and I don’t want to burden the arduino program with calculation as I want to focuse all of it’s ‘power’ as much as possible towards moving the steppers

Why have you got
Code: [Select]

PitchStepperA.setAcceleration(4000);

referencing moveTo() in the accelstepper library:
https://www.airspayce.com/mikem/arduino/AccelStepper/classAccelStepper.html#ace236ede35f87c63d18da25810ec9736
moveTo() resets the speed so it should be followed by setSpeed(). However, I tried that but my motors wouldn’t move. I then tried setAcceleration behind every moveTo which oddly enough worked. I also tried removing it because as you said it runs slower, but for some reason it stopped working if I did that.

If this was my project I would create a struct to…

If I understand you correctly that’s in essence what I’m doing by sending the full serial code with all the values. (non-native english speaker so bare with me if I’m misunderstanding you)

Does this clarify it a little?

The problem lies in the conversion of the 3D orientation to Euler angles, which can never be unique (look up "gimbal lock").

The solution is to represent the 3D orientation as a quaternion and perform all rotation operations with quaternion multiplication.

Useful introduction to quaternion operations here: Quaternions

jremington:
The problem lies in the conversion of the 3D orientation to Euler angles, which can never be unique (look up "gimbal lock").

The solution is to represent the 3D orientation as a quaternion and perform all rotation operations with quaternion multiplication.

Useful introduction to quaternion operations here: Quaternions

This sounds very promising, I'll have a look at this. Thank you very much!

quebeclima:
moveTo() resets the speed so it should be followed by setSpeed().

I don't think that should apply because you are not using constant speed - you are using the run() function. If you do want constant speed then you should use the runSpeed() function - but then you would not need setAcceleration().

But that has made me see another problem. You should only set moveTo() when a new value arrives - you are setting it for every iteration of loop() which is much too often.

that's in essence what I'm doing by sending the full serial code with all the values.

I have in mind a much more efficient way. Have a look at the receive program in this demo. Obviously your C# program would need to do what the send program does. If your C# program only sends a message after getting a request from the Arduino, and if the total size of the message is not more than 64 bytes it would not be necessary to use the start-marker concept.

Note also that it is not a good idea to use the String (capital S) class on an Arduino as it can cause memory corruption in the small memory on an Arduino. This can happen after the program has been running perfectly for some time. Just use cstrings - char arrays terminated with '\0' (NULL).

If you want to use cstrings have a look at the parse example in Serial Input Basics

...R

I don’t think that should apply because you are not using constant speed - you are using the run() function. If you do want constant speed then you should use the runSpeed() function - but then you would not need setAcceleration().

I don’t want constant speed as the speed would need to increase if my roll rate increases.
In addition I’m constantly receiving new values.

Also, I tried using move() as I initially set the zero point of the motors and would expect the motors to move an x-amount of steps, in this case the pitch translated into steps, but that didn’t work. Would like to clean up the code by removing all the setAcceleration() but for now this seems to be necessary for it to work.

I have in mind a much more …

I’ll have a look at the examples you gave, thank you.

quebeclima:
In addition I'm constantly receiving new values.

In this context the word "constantly" is meaningless. You need to identify the interval (in seconds or millisecs) between data messages. It won't be zero.

You may also want to consider what is the ideal interval between messages - my guess is that it is longer than the minimum possible interval. The Arduino must be allowed time to do useful stuff. It is likely that the PC can work very much faster than the Arduino. You also need to consider the step-rate for the stepper motors. You should probably allow time for the motors to make a minimum of 100 steps before you change the control data (based on 2048 steps per revolution).

If this was my project I would wish to operate at the lowest possible message frequency (i.e. longest interval) that gives an acceptable result.

...R

A] I have in mind a much more efficient way.

** B] **Note also that it is not a good idea to use the String

A] Unfortunately my knowledge of C# doesn’t reach that far (I do this as a hobby and only started programming 1 month ago) but appreciate the reference.

**B] this is referring to the arduino IDE? also not quite sure if I understand "use cstrings - char arrays terminated with ‘\0’ (NULL)."

If you want to use cstrings have a look at the parse example in Serial Input Basics

I’ve managed to get it to work the way it’s explained in the forum post. I now have my serial data as follows:
<Data:,80.467,30.418,0,0,-48.718,0,-128.786,0,0 >

and the program then parses it into the float values I need.
To a layman like myself it seems the end result is the same if I had done it with my initial method, but is the method you provided faster? (just wondering)

If this was my project I would wish to operate at the lowest possible message…

Until now I haven’t really thought about this actually. I just assumed the datastream to be constant and that to be sufficient as is. To me the way it updates now is good to be honest, but again I might be missing things which I know little to nothing about.

Maybe some background on the C# part: the way Max Ferretti explained it to me is that the data only gets retieved if it changes. It’s a callback function, he said.

And as the aircraft changes pitch, roll, acceleration,… pretty much constantly, I feel that’s what I want, but I think I know what you’re trying to say:

If I understand you correctly, it’s that it’s pointless to send data this fast as x-amount won’t be read anyway by arduino due to its ‘slow’ speed?

Would I be looking in the right direction if I look around how to time the serial.read() interval?

btw thank you all for your inputs so far!

You have an awful lot in this Thread. :slight_smile:

quebeclima:
B] this is referring to the arduino IDE? also not quite sure if I understand "use cstrings - char arrays terminated with ‘\0’ (NULL)."

There are two ways to deal with text in C++. The easier way for the programmer is to use the String class - which is what you are doing. But the String class chops up memory and in the small memory of an Arduino that system is likely to cause a crash when you least expect it. cstrings are fixed arrays of characters and because the memory requirement is known at compile time there is no risk of memory getting mangled. IIRC someone here has created a SafeString class - but I have no personal experience of it. Maybe Google Arduino SafeString.

To a layman like myself it seems the end result is the same if I had done it with my initial method, but is the method you provided faster? (just wondering)

There is probably little to choose between your original system and the “<Data:,80.467,30.418,0,0,-48.718,0,-128.786,0,0 >” approach. The start and end-markers should improve reliability - I don’t recall if your original system had them.

However there is another level that would be faster - sending the data in binary form so there is no need for the Arduino to parse it. Given your present knowledge that may be too complicated. Just keep it in mind in case you need to improve performance later.

Would I be looking in the right direction if I look around how to time the serial.read() interval?

Absolutely NOT. The timing control MUST take place on the sending side.

…R

You have an awful lot in this Thread. :)

better grab all the info I can while the experts are present :grinning:

There are two ways...

I see, I'm using c# so I'll have alook into it.

However there is another level that would be faster...

I actually started looking into it before, when I was trying to get data from C# to arduino, so I'll be sure to get an understanding of it if you say it's faster

Absolutely NOT. The timing control MUST take place on the sending side.

Ah, good to know. I'll see what I can put together to get the timing done beforehand.

Thank you very much for your inputs !

quebeclima:
I see, I'm using c# so I'll have alook into it.

The String vs cstring issue has nothing to do with C#. C# is a PC programming language and a PC has almost limitless memory compared with an Arduino.

Arduinos are programmed in C++

...R

Robin2:
The String vs cstring issue has nothing to do with C#. C# is a PC programming language and a PC has almost limitless memory compared with an Arduino.

Arduinos are programmed in C++

...R

ok, then I think I don't understand at all what I have to do regarding the cstring.
I tried using cstring in arduino which didn't work, but I am using string in my program to send the data (lower case 's')
Looked up some info on the cstring part and as far as I can tell, using lower case string is the same as the cstring equivalent. (in c#)

btw, managed to get the timer working :wink:

quebeclima:
ok, then I think I don't understand at all what I have to do regarding the cstring.

Have you followed up the link to cstrings that I gave you in Reply #5 and have you studied how cstrings are used in Serial Input Basics

...R

Robin2:
Have you followed up the link to cstrings that I gave you in Reply #5 and have you studied how cstrings are used in Serial Input Basics

...R

Yup, thanks to that post I have the different format of data like I mentioned before, but it seems I'm not making the link in what you're trying to imply?

quebeclima:
but it seems I'm not making the link in what you're trying to imply?

I don't understand what you are having a problem with. Perhaps you can explain in more detail - ideally with some examples from your two different programs.

...R

Hmm I think we're talking next to eachother. I'll back up a little:

so I have enough information from this thread to work with my initial issue, being the full rotation I'm looking for .
But you mentioned something about Strings vs cstrings useage (since I'm sending a string via serial to arduino with the required data for the rotation of my motion sim.

Reason it's in string form is because of ease of use (for me) and limited knowledge by me.

Now you mentioned cstring as it would be more stable in useage, however I'm programming in c# and as you mentioned arduino uses c++ in which cstring is used, but not in c# (as far as I can see). To overcome this, as I would like the stability you mentioned (referencing the memory note about strings VS arduini you explained me) I tried looking up what the equivalent was to use in C#.

In my C# program I'm using 'string' (lowercase) and not String (Uppercase) which I've looked up and on initial glance it seems that this would be the equivalent of the mentioned cstring.

maybe I'm confusing you more than is necessary..

Either way, at the moment I'm looking into the quaternion as Xplane provides this dataref and I'm trying to calculate the angle out of it to get my motors to move x-amount of steps using the quaternion data.

quebeclima:
To overcome this, as I would like the stability you mentioned (referencing the memory note about strings VS arduini you explained me) I tried looking up what the equivalent was to use in C#.

This is not a C# issue. Do whatever you want in C#.

The String vs cstring issue is only an Arduino programming issue.

I already told you this in Reply #11

...R

This is not a C# issue. Do whatever you want in C#.

this clarifies it.

I already told you this in Reply #11

yes I see now, was a bit vague for me hence the confusion.

If anyone has any ideas? I'm unfortunately still stuck in how to get the full 360 motion going.
@jremington replied in reply #3 that quaternions might be the solution for my problem so I went looking for it.
Xplane offers quaternion data so I've looked up how to convert this into angles, but without succes.

the quaternion data in Xplane is structured as follows:

{1,i,j,k}

i = roll
j = pitch
k = heading

I'm trying to create stepper motion out of this data as I believe this will solve my issue.
However, I'm stuck as quaternions really confuse me atm and I've been looking around between all of the formulas but that has confused me even more.

I was wondering if there's anyone who grasps the concept of quaternions and could help me on my way?

Ideally I'd like to create angle data in degrees out of the quaternion data.

This is what I understand so far:

{1,i,j,k}

1 = the angle amount, which I need to multiply with the following axis:

i to get the roll
j to get the pitch
k to get the heading

the thing I don't understand is, what units are used? are i, j and k unitless? are they radians?
same with '1', is it a radian?

E.g. aircraft on the runway
pitch = -3,32 deg
roll = 0,41 deg
heading = 74,51 deg

(before anyone makes the remark "but you have the pitch and roll data, why are you bothering us with your complete nonsense?", please have a look at the image in my initial post. The problem is that for the pitch for example, it only goes to 90° full pitch up and -90° full pitch down. I can't use this data at the moment as this will revert my stepper movement as soon as I pass this 90° up or down to go inverted)

quaternion data:
q[0] = 0,794
q[1] = 0,018
q[2] = -0,018
q[3] = 0,607

Really hoping someone can help me out.