Arduino RC Helicopter-More effective way to format and handle variables/hierarchy?

Hi everyone! I'm pretty new to Arduino and this is my first project.

I am working on a school project to control the positioning of a swashplate in an RC helicopter.

The 'Cyclic' or 3D dimension tilt of the plate, and the 'Collective' or vertical position are controlled by the positions of two separate joysticks.

the joysticks are controlling 4 servos arranged in a cross configuration; the joysticks are mapped so that their position relates to a min and max of a servo.
e.g.
Screenshot 2022-03-16 10.32.31 AM

When the Cyclic joystick is tilted forward, back, left, or right the Swashplate tilts accordingly.
(Cyclic modifies the positions of two opposite servos per axis)

When the Collective joystick is tilted forward or back the Swashplate moves up or down.
(Collective modifies the positions of all 4 servos at a time)

The Collective and Cyclic angles are calculated and then combined into the output variable that is fed into the servo.

frontGroup.frontOutput = frontCyclic.frontServo + colValue; // output angle for front servo
e.g

(output = 65+10)

I choose this approach because making the movements independent is beyond me. Additionally, I think this would allow for more consistent and smooth control.
e.g

The Cyclic could tilt all the way forward, then whilst retaining the tilt move up and down.
In the same vein as the Cyclic or Collective change, this method should yield smooth motion.
It is worth noting that I haven't tested this and this remains theoretical for now.

Currently, I am managing these variables with struct's and int's but I was wondering if it would be better to use an array or different data type to manage them that would contribute to better readability and further expansion of servo groups? (Admittedly the structs do a pretty good job as it is)

I am planning on eventually setting up some sort of wireless control and I would imagine that an array would lend itself well to communication, and it might be best to leave the changing of data structures until that point.

here's the code:

#include<Servo.h>

Servo frontServo1; //declare servo objects
Servo backServo1;
Servo leftServo1;
Servo rightServo1;

void setup() {
    Serial.begin(9600); 
    
    //attach servos to pwm pin(x)
    frontServo1.attach(3); 
    backServo1.attach(5); 
    leftServo1.attach(6); 
    rightServo1.attach(9); 
}

void loop() {
  
  //collective value
  int colValue = map(analogRead(A3), 0, 1023, -15, 15); // '0' from joystick should turn servo all the way down
  
  //struct for Cyclic
  struct Cyclic {
    int frontServo = map(analogRead(A0), 0, 1023, 75, -75); // '0' from joystick should turn servo all the way up
    int backServo = map(analogRead(A0), 0, 1023, -75, 75); // '0' from joystick should turn servo all the way down
    int leftServo = map(analogRead(A1), 0, 1023, -75, 75); // '0' from joystick should turn servo all the way down
    int rightServo = map(analogRead(A1), 0, 1023, 75, -75); // '0' from joystick should turn servo all the way up
  };
  
  Cyclic frontCyclic; // create a struct variable for front group
  //Cyclic backCyclic; // create a struct varaible for back group
  
  // struct for output angles 
  struct outputAngle {
    int frontOutput;
    int backOutput;
    int leftOutput;
    int rightOutput;
  };
  
  outputAngle frontGroup; // create a struct outputAngle variable for the 4 front servos
  //outputAngle backGroup; // create a struct outputAngle variable for the 4 back servos
  
  //define values for the front group
  
  frontGroup.frontOutput = frontCyclic.frontServo + colValue; // output angle for front servo
  frontGroup.backOutput = frontCyclic.backServo + colValue; // output angle for the back servo
  frontGroup.leftOutput = frontCyclic.leftServo + colValue; // output angle for the left servo
  frontGroup.rightOutput = frontCyclic.rightServo + colValue; // output angle for the right servo
  
  // forward the output values into the front servos;
  
  frontServo1.write(frontGroup.frontOutput);
  backServo1.write(frontGroup.backOutput);
  leftServo1.write(frontGroup.leftOutput);
  rightServo1.write(frontGroup.rightOutput);
  
    /*
    Relationships
    
    Cylic angle + collective angle = output control angle (per servo)
    frontServoCyc + colValue = frontServoOutput (could use struct!)
    
    X-Axis
    ------------------------------------------------------
    Xvalue map 0-1023
    
    frontServoCyc linear-negative, so that 1023 is min angle
    backServoCyc linear-positive, so that 1023 is max angle
    
    
    Y-Axis
    ------------------------------------------------------
    Yvalue map 0-1023
    leftServoCyc linear-positive, so that 1023 is max angle
    backSeroCyc linear-negative, so that 1023 is min angle
    
    Collective
    ------------------------------------------------------
    (Joystick 2's x-axis) 
    Xvalue2 map 0-1023
    *collective modifies all of the output variables
    
    colValue linear-positive, so that 0 is max and 1023 is min
    
    
    
    */
}

I appreciate any advice or criticism!

Thanks for telling the big picture. What is the problem?
No reply for 8 hours. What could the reason be?

You forgot to tell, define, "what is better".

1 Like

Thanks for bringing that to my attention!

I should have made it more clear what I was asking, Is there a different approach that would lend itself either to better readability or implementing more servos. For example, if instead of struct I were to use an array.

Additionally, if the logic behind the code made sense.

I will make sure to include this much earlier on in the post :)!

You need a real-time operating system to accomplish this. Not usually available for a single processor Arduino.

But, how do you know you cannot do them sequentially, very rapidly? Have you tried and failed?

1 Like

I have yet to test it.

I leaned towards combining the values since the logic for quick individual motions is beyond me.

The reasoning behind this solution is it allows the servos to continue to adjust collective even if the cyclic is at max tilt. (So long as the Collective+Cyclic angle is less than the physical limits of the servos)

How fast is succinctly? A 6000RPM rotor is 10ms per rev, or 10000us per rev, or 27us per degree. And servos slew on the order of 0.16sec/60deg or 3ms/degree (Servos Explained). At 53us per ServoWrite(), you could command the servos much faster than they could physically respond.

1 Like

I hadn't considered that! What happens when you command servos faster than they respond? does it just skip over commands? would a delay() help keep this in check?

By succinctly I meant as close to simultaneously as possible. I'll edit the post to reflect that. Controlling the collective or cyclic in independent motions is beyond me, so this code instead combines their result positions into the output variable. As far as latency I am building this more of a proof of concept and am not too concerned with response time. If the model is able to hover I'll tackle response time, but that's down the line.

It not an RTOS you'd need to truly do multiple tasks simultaneously but a multi-core processor. A single-core processor with an RTOS (some variants of ESP32) could not do it. In fact, there's nothing you could do in a single-core processor with RTOS that you couldn't do without the RTOS.

Fortunately, as everyone in Arduino land knows, if the processor is fast enough and you break the tasks into short chunks, then it can look like things are happening simultaneously .

1 Like

The servo seeks to match its position to the pulse width. It has no memory, so if you tell it to go to 180 degrees in one instant, it starts moving from wherever it is towards 180 and if 50us later you tell it to go to 0 degrees, it forgets the 180 and heads towards 0.

Why would you want it to keep it in check? I would think if you compute a new position that the servo should be in, you would want it to head there as soon as possible. And in general, delay()s are best avoided.

On control systems it is good to choose an update cycle, for example 100ms, 10ms, or 1000ms and plan the input measurements, update calculations, and outputs around that cycle time.

1 Like

That's a great point the quick turnaround from input to output is crucial. I was confused as to how the servo interpreted the input, but this clarified it. You are right the servos should head to their new positions as soon as possible.

I have no experience with control systems how would I go about implementing an update cycle?

If that is the question, you implement it like this: Demonstration code for several things at the same time and put your read, map, write code into a function to be executed every 1000, 100, or 10ms.

1 Like

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