Help Needed with PID Control for N20 Motor with AS5600 Encoder and MG90D Motor Driver

Hello everyone,

I’m working on a project that involves controlling an N20 motor using an AS5600 encoder for precise angle measurements and an MG90D motor driver. I'm utilizing the ArduPID library for PID control. My goal is to achieve smooth and accurate motor control based on the desired angles input via serial communication.

Setup:

Motor: N20 DC Motor
Encoder: AS5600 Magnetic Encoder
Motor Gears: MG90D Servo Motor (harvested from an MG90D servo)
Motor Driver: DRV8214
Library: ArduPID

Issue: When I input target angles of 0 or 180 degrees, the system works fine, and the motor moves smoothly to the desired position. However, when I input any other angle, the motor oscillates and cannot settle at the desird position. I suspect there might be an issue with my PID tuning or implementation, but I'm not sure how to resolve it. Also how can I improve my approach to make it fast?

My trials:
I tried everything from increase and trying to tune the PID to using different libraries to making sure my servo has low tolerances. It all doesn't work. I think there is something wrong my logic.

Code:

#include "ArduPID.h"
#include <Wire.h>

#define AS5600_ADDRESS 0x36  // I2C address of the AS5600 encoder, PINS A4, A5
#define RAW_ANGLE_REGISTER 0x0C 
#define motor_IN1 A2
#define motor_IN2 A3
#define DEADBAND 1.0  // Define the deadband range

int MIN_PWM = 0;

ArduPID myController;

double input, output;

// Arbitrary setpoint and gains - adjust these as fit for your project:
double setpoint = 0;
double KP = 10.0;
double KI = 0.0;
double KD = 0.0;
double OUTPUT_MIN = -255;
double OUTPUT_MAX = 255;

int i = 0;
int pwmValue;
int Delta_angle;

const int numReadings = 10;
float readings[numReadings];      // the readings from the analog input
int readIndex = 0;                // the index of the current reading
float total = 0;                  // the running total
float average = 0;                // the average

void setup() {
    Serial.begin(115200);
    Wire.begin();
    Wire.setClock(400000);
    delay(500);

    myController.begin(&input, &output, &setpoint, KP, KI, KD);
    //myController.setSampleTime(40);      // OPTIONAL - will ensure at least 50ms have past between successful compute() calls
    myController.setWindUpLimits(-10, 10); // Growth bounds for the integral term to prevent integral wind-up
    myController.setOutputLimits(OUTPUT_MIN, OUTPUT_MAX); // Set output limits
    myController.start();
}

void loop() {
    // Read the desired setpoint from serial input
    while (Serial.available() > 0) {
        String inputString = Serial.readStringUntil('\n');
        Delta_angle = inputString.toFloat();
        myController.reset();
    }

    double angle_error = calculateShortestPath(Delta_angle, readAngle());
    input = angle_error;
    myController.compute();

    if (abs(input) > DEADBAND) {
        moveMotor(output);
    } else {
        moveMotor(0);
    }
    
    serialprint();
}

float readAngle() {
    unsigned long startMillis = millis();
    Wire.beginTransmission(AS5600_ADDRESS);
    Wire.write(RAW_ANGLE_REGISTER);
    if (Wire.endTransmission() != 0) {
        return -1;
    }

    Wire.requestFrom(AS5600_ADDRESS, 2);
    while (Wire.available() < 2) {
        if (millis() - startMillis > 100) {
            Serial.println("I2C timeout in readAngle");
            return -1;
        }
    }

    uint8_t highByte = Wire.read();
    uint8_t lowByte = Wire.read();
    uint16_t rawAngle = (highByte << 8) | lowByte;
    float angle = (rawAngle * 360.0) / 4096.0;

    // Subtract the last reading
    total = total - readings[readIndex];
    // Read from the sensor
    readings[readIndex] = angle;
    // Add the reading to the total
    total = total + readings[readIndex];
    // Advance to the next position in the array
    readIndex = readIndex + 1;

    // If we're at the end of the array...
    if (readIndex >= numReadings) {
        readIndex = 0;
    }

    // Calculate the average
    average = total / numReadings;
    return average;
}

void moveMotor(float error) {
    pwmValue = (int)abs(error);
    if (pwmValue < MIN_PWM && pwmValue > 0) {
        pwmValue = MIN_PWM;
    }
    if (error > 0) {
        analogWrite(motor_IN1, pwmValue);
        analogWrite(motor_IN2, 0);
    } else if (error < 0) {
        analogWrite(motor_IN1, 0);
        analogWrite(motor_IN2, pwmValue);
    } else {
        analogWrite(motor_IN1, 0);
        analogWrite(motor_IN2, 0);
    }
}

void serialprint() {
    if (i == 100) {
        Serial.print("Angle:");
        Serial.print(readAngle());
        Serial.print(" ");
        Serial.print("Delta_angle:");
        Serial.print(Delta_angle);
        Serial.print(" ");
        Serial.print("INPUT:");
        Serial.print(input);
        Serial.print(" ");
        Serial.print("SETPOINT:");
        Serial.print(setpoint);
        Serial.print(" ");
        Serial.print("OUTPUT:");
        Serial.print(output);
        Serial.print(" ");
        Serial.print("PWM:");
        Serial.println(pwmValue);
        i = 0;
    } else {
        i++;
    }
}

double calculateShortestPath(double target_angle, double measureDelta_angle) {
    double angle_error = target_angle - measureDelta_angle;
    // Normalize the angle_error to be within -180 to 180 degrees
    angle_error = fmod((angle_error + 180), 360) - 180;
    if (angle_error < -180) {
        angle_error += 360;
    }
    return angle_error;
}


This is 0 or 180 angle.


This is 90.


This is me asking to go to 90 with Min pmw at 200 and deadban at 2. Doesnt matter if i increase the deadband it does the same thing.

Wiring, schematics, and datasheet provides necessary information that words don' do.

I don't have schematics or anything like that. Its typical AS5600 wiring using I2C. And DRV8214 DEV board. Only wired using IN1 and IN2 0,255 PWM.

Oscillation suggests that the PID algorithm is not correctly tuned. Use the search phrase "PID tuning" to find tutorials on how to do that systematically. It is not difficult if you follow the rules.

I did. That's not the problem. If it was it would produce oscillation for any angle not just for 90+-30 deg.

The PID algorithm is not correctly tuned for 90+/-30 deg.

That doesn't make sense. You can not tune the PID for specific values. It works the same regardless of the values. Ill be honest don't think that's even remotely close to the main problem. Even in my testing it doesn't change the behavior

Yes, you can and in some cases, you must tune the PID separately, for different operational ranges. Your specific problem is likely due to nonlinear behavior in the feedback or the motor driver.

But you forgot to post the required details, like a clear schematic diagram and links to the components.

Good luck with your project!

Your specific problem is likely due to nonlinear behavior in the feedback or the motor driver.

You are correct about this. Which is due to the code not schem. I suspect shortestpath function.

For anyone that's stuck with a problem similar to mine. DONT WASTE YOUR TIME TUNNING. It has nothing to do with PID tunning, you need to figure out if your PID can handle different directions. And how it handles generating the error. Suddenly, worked perfectly for me. BUT MAKE SURE YOUR VERIABLES ARE ACCURATE BEFORE TUNNING. That's it.

1 Like

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