1

What are the details of your problem? I am controlling a miniature gantry crane head using PID controllers. I am able to move the crane head along the x- and y-axis to their desired position. My issue is that both the x-axis controller and my angle controller write to the pin_pwm_x which is the motor in the x-axis. How am I supposed to make sure that the angle of the crane head is kept to zero while also being able to move in the x-axis to a desired position?

If the trolley was to move from x = 0 to x = 10, then when x == 10 and the crane head attached to the trolley would be swinging like a pendulum. This is where the angle controller should kick in and move the trolley in order to counteract the swing of the crane head until its angle is zero.

Illustration of the gantry crane.

Cascading loop of x-axis.

CODE explained Arduino sketch for controlling a miniature gantry crane head using PID controllers. Here's a breakdown of the code:

  • The code begins by including the necessary libraries and defining variables for connecting to the system.
  • PID tuning parameters are specified for the y-axis, x-axis, and theta (angle) controllers.
  • PID objects are created for each controller using the specified tuning parameters.
  • The getAngleFromHead() function reads the angle data from a serial connection (from a sensor on the crane head).
  • The readInput() function reads the inputs to the system, including the position values from the x-axis and y-axis sensors and the angle value from the crane head.
  • The setup() function initializes the serial communication, sets the pin modes, initializes the variables, sets the setpoints for each controller, and configures the PID objects.
  • The loop() function is the main execution loop of the program.
    • It calls the readInput() function to update the input values.
    • It computes the PID outputs for each controller using the Compute() method.
    • It sets the enable pins for the motors to high to enable movement.
    • It writes the computed outputs to the respective PWM pins for controlling the motors.
    • It prints out relevant information for debugging purposes.

Code itself

// Include libraries
#include <Arduino.h>
#include "pinDefinitions.h"
#include <PID_v1.h>

// Define Variables we'll be connecting to
double Setpoint_y, Input_y, Output_y,
    Setpoint_x, Input_x, Output_x,
    Setpoint_theta, Input_theta, Output_theta;

// Specify the links and initial tuning parameters
double Kp_y = 32.4,
       Ki_y = 0,     
       Kd_y = 12.96,     
       kg_y = 1,

       Kp_x = 2.4,
       Ki_x = 0,
       Kd_x = 1.92,
       kg_x = 1,
       
       Kp_theta = 22.6,
       Ki_theta = 0, 
       Kd_theta = 11.3, 
       kg_theta = 1;

PID_v1 yPID(&Input_y, &Output_y, &Setpoint_y, Kp_y *kg_y, Ki_y *kg_y, Kd_y *kg_y, DIRECT);
PID_v1 xPID(&Input_x, &Output_x, &Setpoint_x, Kp_x *kg_x, Ki_x *kg_x, Kd_x *kg_x, DIRECT);
PID_v1 thetaPID(&Input_theta, &Output_theta, &Setpoint_theta, Kp_theta *kg_theta, Ki_theta *kg_theta, Kd_theta *kg_theta, DIRECT);

float getAngleFromHead()
{
  float angle;
  if (Serial3.available() > 0)
  {
    String angleData = Serial3.readStringUntil('\n');
    angle = angleData.toFloat();
    // Serial.println(angle);
  }
  return angle;
}

// Function that reads the inputs to the system
void readInput()
{
  Input_y = analogRead(pin_pos_y); 
  Input_x = analogRead(pin_pos_x);

  Input_theta = getAngleFromHead();

  // Sanity check angle data
  while (Input_theta > 90 || Input_theta < -90)
  {
    digitalWrite(pin_enable_x, LOW); // Stop motors!
    digitalWrite(pin_enable_y, LOW);
    Serial.println("//Insane angle data");
    Input_theta = getAngleFromHead();
  }
}

void setup()
{
  Serial.begin(115200);
  Serial3.begin(9600);

  ; // Set input pinMode
  pinMode(pin_pos_x, INPUT);
  pinMode(pin_pos_y, INPUT);

  // Set output pinMode
  pinMode(pin_enable_x, OUTPUT);
  pinMode(pin_enable_y, OUTPUT);
  pinMode(pin_pwm_x, OUTPUT);
  pinMode(pin_pwm_y, OUTPUT);

  // Initialize the variables we're linked to
  Input_y = analogRead(pin_pos_y);
  Input_x = analogRead(pin_pos_x);

  Setpoint_y = 400;
  Setpoint_x = 300;
  Setpoint_theta = 0;

  yPID.SetSampleTime(10);  //Sample rate expressed in ms, 10ms = 100 Hz
  xPID.SetSampleTime(10);
  thetaPID.SetSampleTime(1); //Angle sampling rate is 10x times faster than xPID

  xPID.SetOutputLimits(255 * 0.1, 255 * 0.9); // PWM Range for the specific motors
  yPID.SetOutputLimits(255 * 0.1, 255 * 0.9);
  thetaPID.SetOutputLimits(255 * 0.1, 255 * 0.9);

  // turn the PID on
  yPID.SetMode(AUTOMATIC);
  xPID.SetMode(AUTOMATIC);
  thetaPID.SetMode(AUTOMATIC);

  Serial.println("Setup done!");
}

void loop()
{
  readInput();
  yPID.Compute();
  xPID.Compute();
  thetaPID.Compute();

  digitalWrite(pin_enable_y, HIGH); //Turns on motor
  digitalWrite(pin_enable_x, HIGH);

  analogWrite(pin_pwm_y, Output_y);
  analogWrite(pin_pwm_x, Output_x);
  analogWrite(pin_pwm_x, Output_theta); // Pin_pwm_x is being overwritten, not so good. 

  Serial.println("PID Input_x: " + String(Input_x) + ", PID Setpoint_x: " + String(Setpoint_x) + ", PID Output_x: " + String(Output_x) + ", Angle: " + String(Input_theta) + ", Kp_y: " + String(Kp_y));
}

What have I tried? I tried subtracting the output of the angle controller and the x-controller and vice versa, but to no avail.

analogWrite(pin_pwm_x, Output_x-Output_theta); and analogWrite(pin_pwm_x, Output_theta-Output_x);

Here is a video of the gantry crane that I am working on. In this video it is seen working as intended, fast and with basically no oscillation. I don't know how they did it but this is the goal: The working gantry crane setup

Unrelated to this but but nice to see how it reduces the angle. Pendulum setup

elomarjc
  • 11
  • 2
  • In the `Cascading loop of x-axis` the D0 and Dz are PID? There is a +O- block before ouput. Why not `analogWrite(pin_pwm_x, Output_x - Output_theta);`? `Ki_theta = 1,` Try 0. I guess you are getting oscillations - are the oscillations in phase or out of phase with the position of the pendulum when it's swinging? Is it stable? – KamilCuk May 10 '23 at 08:49
  • Yes D0 and Dx are the PID, I have added `analogWrite(pin_pwm_x, Output_x - Output_theta);` to my code. And updated the k values to the one I am using in the setup as of now. When I run the code, the oscillations are being dampened, however the trolley has a hard time going to the desired position because it is constantly trying to maintain an angle of zero. – elomarjc May 10 '23 at 09:41
  • As someone who's designed lots of real crane electronics - one technical reason why these haven't yet gotten fully automated is because every lift is often more or less unique in terms of weight, volume, number of hoists used and so on - all depending on the nature of the item getting transported. So experienced human operators is still a pretty ideal solution. Also on existing cranes, you'll have weight and line length sensors but no accelerometer - it would have to be attached to the lifted object somehow and I don't think such equipment exists as of yet. – Lundin May 10 '23 at 09:45
  • Anyway, why torture yourself doing floating point math on an Arduino? Pick a MCU which has an actual FPU. >=Cortex M4. Or do the PID in fixed point - been there, done that. – Lundin May 10 '23 at 09:47
  • I'm using an Arduino Mega 2560 rev3, also this is unrelated to my issue. – elomarjc May 10 '23 at 11:17
  • It doesn't have a FPU either. Using floating point is very rough for your execution speed/stack/your overall memory consumption since it has to be carried out in software and 8 bitters are horrible at dealing with large numbers. PID regulators specifically rely on cyclic processing and if you hand it code that makes many ms to finish, you toss all real-time performance out the window. – Lundin May 10 '23 at 13:50
  • I think I've seen the video of the 'Pendulum Setup' elsewhere where it was demonstrating the capabilities of machine learning. I suspect that's how they did it. You could train a model on the actual setup or a theoretical model. It isn't clear how to make it behave the way you want with a simple equation. – Simon Goater May 10 '23 at 14:04

0 Answers0