Rotary Inverted Pendulum PID Control

Can someone write me a PID algorithm to keep the pendulum upright? I am struggling with this.

My servo goes from 0 to 270 degrees (0 degrees is all the way right, 270 is all the way left).
The pendulum is upright at the encoder position of 0 degrees.

The variables are servoPosition and pendulumAngle.

What I have so far is:
(keep in mind most of this is to read the encoder position)

/* Include the SPI library for the Arduino boards */
#include <SPI.h>
#include <Servo.h>

Servo myservo;  // create Servo object to control a servo
// twelve Servo objects can be created on most boards

int pos = 0;    // variable to store the servo position
#define CONTROL_THRESHOLD 15  // Only apply control if pendulum is within ±15 degrees of vertical



/* PID Constants for Pendulum Stabilization */
float Kp_pendulum = -1.5, Ki_pendulum = 2, Kd_pendulum = 0;
float pendulumSetpoint = 0; // Set pendulum upright reference to 133 degrees
float prevPendulumError = 0, integralPendulum = 0;





/* Serial rates for UART */
#define BAUDRATE      115200

/* SPI commands */
#define AMT22_NOP     0x00
#define AMT22_ZERO    0x70
#define AMT22_TURNS   0xA0

uint8_t cs_pins[] = {2}; // Only one encoder connected, using pin 2 on Arduino for CS
int RESOLUTION = 12;
float position_degrees;

/*
 * Using the equation on the datasheet we can calculate the checksums and then make sure they match what the encoder sent.
 */
bool verifyChecksumSPI(uint16_t message)
{
  // Checksum is the invert of XOR of bits, so start with 0b11, so things end up inverted
  uint16_t checksum = 0x3;
  for (int i = 0; i < 14; i += 2)
  {
    checksum ^= (message >> i) & 0x3;
  }
  return checksum == (message >> 14);
}
float unwrapAngle(float angle) {
    static float lastAngle = 0;
    static float revolutions = 0;
    float delta = angle - lastAngle;

    if (delta > 180) revolutions -= 360; // Wrapped from 359 to 0
    if (delta < -180) revolutions += 360; // Wrapped from 0 to 359

    lastAngle = angle;
    return angle + revolutions;}
void setup()
{
  uint8_t cs_pin = 2;
  myservo.attach(9);  // attaches the servo on pin 9 to the Servo object

  // Set the modes for the SPI CS
  pinMode(cs_pin, OUTPUT);
  // Get the CS line high which is the default inactive state
  digitalWrite(cs_pin, HIGH);

  // Initialize the UART serial connection for debugging
  Serial.begin(BAUDRATE);

  // Set the clock rate. Uno clock rate is 16Mhz, divider of 32 gives 500 kHz.
  SPI.setClockDivider(SPI_CLOCK_DIV32);    // 500 kHz
  // Start SPI bus
  SPI.begin();
}

void loop()
{
  for (int encoder = 0; encoder < sizeof(cs_pins); ++encoder)
  {
    uint8_t cs_pin = cs_pins[encoder];

    // Set the CS signal to low
    digitalWrite(cs_pin, LOW);
    delayMicroseconds(3);

    // Read the two bytes for position from the encoder, starting with the high byte
    uint16_t encoderPosition = SPI.transfer(AMT22_NOP) << 8; // Shift up 8 bits because this is the high byte
    delayMicroseconds(3);
    encoderPosition |= SPI.transfer(AMT22_NOP); // No-op command to get the encoder position

    // Set the CS signal to high
    digitalWrite(cs_pin, HIGH);

    if (verifyChecksumSPI(encoderPosition)) // Position was good, print to serial stream
    {
      encoderPosition &= 0x3FFF; // Discard upper two checksum bits
      if (RESOLUTION == 12) encoderPosition = encoderPosition >> 2; // On a 12-bit encoder, the lower two bits will always be zero

      // Convert position to degrees
      position_degrees = (encoderPosition * 360.0) / (1 << RESOLUTION);

      Serial.print("Encoder #");
      Serial.print(encoder, DEC);
      Serial.print(" position: ");
      Serial.print(position_degrees, 2); // Print the position in degrees with two decimal places
      Serial.println(" degrees");
    }
    else // Position is bad, let the user know
    {
      Serial.print("Encoder #");
      Serial.print(encoder, DEC);
      Serial.println(" position error.");
    }
  }

    // Simulated pendulum angle (assume IMU input in future)
    float pendulumAngle = unwrapAngle(position_degrees) - 329.3;

    // Calculate PID for Pendulum
    float pendulumError = pendulumSetpoint - pendulumAngle;
    integralPendulum += pendulumError;
    float derivativePendulum = pendulumError - prevPendulumError;
    float pendulumPID = Kp_pendulum * pendulumError + Ki_pendulum * integralPendulum + Kd_pendulum * derivativePendulum;
    prevPendulumError = pendulumError;

    // Combine both PID outputs
    float controlSignal =pendulumPID;

    // Set servo position based on control output
    int servoPosition = map(constrain(pendulumPID, -45, 45), -45, 45, 0, 270);
    myservo.write(servoPosition*2/3);

    // Debug Output
    Serial.print("\tPendulum Angle: "); Serial.print(pendulumAngle);
    Serial.print("\tServo: "); Serial.println(servoPosition);


  // Delay between reads
  delay(20);
}

Could You post a link to the datasheet of that servo?

I don't have the datasheet, but here is the Amazon link which has information:

It is a 270 degree servo. I think it has a 1 degree resolution from what I can tell. I am powering it with a 12V supply.

Okey, You tried but that's a sales site, not the datasheet.

Have you tried the PID library?
I would also strip down your Serial.print() statements to a bare minimum so you can process as fast as possible. Also get rid of your encoder for() loop since you only have 1 encorder.

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