How an LQR Controller Models a Motor

Hello everyone,

Our team has been utilizing feedforward combined with proportional (P) control for our robotic arm this year, and it has been performing very well. However, as we aim to delve deeper into modern control theory for the FIRST Robotics Competition (FRC), we have developed a keen interest in the Linear Quadratic Regulator (LQR) controller.

While we have a general understanding of how LQR operates, we are currently encountering difficulties in modeling our motor, particularly in calculating parameters such as the moment of inertia (( J )). We are currently calculating the moment of inertia using ( J = \frac{1}{3} m L^2 ), but we are uncertain if this is the most accurate method for our system. Additionally, we are unsure how to calculate the friction coefficient ( b ) and the back EMF constant ( k_e ).

We attempted to create a Java class to simulate a NEO motor controlled by LQR. Here is our code:

package frc.robot.util;

public class NEOMotorLQR {
    // State variables
    private double theta;   // Angular position (rad)
    private double omega;   // Angular velocity (rad/s)
    private double current; // Motor current (A)

    // Input voltage
    private double voltage; // Input voltage (V)

    // Load torque (can be modified as needed)
    private double tauLoad = 0.0; // Load torque (Nm)

    // Time step for updating motor state (seconds)
    private double deltaTime = 0.02;

    // LQR feedback gain matrix K
    private final double[] K = { 2.5819889, -5.13244592, 0.22729785 };

    // Target state variables
    private double thetaRef = 0.0;   // Target angular position (rad)
    private double omegaRef = 0.0;   // Target angular velocity (rad/s)
    private double currentRef = 0.0; // Target motor current (A)

    // Parameters of the NEO motor (need actual values)
    private final double b = 0.0;      // Friction coefficient (Nm·s/rad)
    private final double k_t = 0.016;  // Torque constant (Nm/A)
    private final double k_e = 0.0;    // Back EMF constant (V·s/rad)
    private final double L = 0.000017; // Phase inductance (H)
    private final double R = 0.033;    // Phase resistance (Ω)
    private final double J = 0.0;      // Moment of inertia (kg·m^2)

    /**
     * Constructs the motor model with initial angular position, velocity, and current.
     *
     * @param theta   Initial angular position (rad)
     * @param omega   Initial angular velocity (rad/s)
     * @param current Initial motor current (A)
     */
    public NEOMotorLQR(double theta, double omega, double current) {
        this.theta = theta;
        this.omega = omega;
        this.current = current;
    }

    /**
     * Updates the motor state using the given input voltage.
     * Utilizes a state-space model for rigorous and accurate computation.
     *
     * @param inputVoltage Input voltage (V)
     */
    public void updateState(double inputVoltage) {
        // Set input voltage
        this.voltage = inputVoltage;

        // Calculate derivatives of the current state-space model
        double dTheta1 = omega;
        double dOmega1 = (k_t * current - b * omega - tauLoad) / J;
        double dCurrent1 = (-R * current - k_e * omega + voltage) / L;

        // Estimate intermediate states
        double thetaMid = theta + dTheta1 * deltaTime / 2.0;
        double omegaMid = omega + dOmega1 * deltaTime / 2.0;
        double currentMid = current + dCurrent1 * deltaTime / 2.0;

        // Calculate k2 using estimated intermediate states
        double dTheta2 = omegaMid;
        double dOmega2 = (k_t * currentMid - b * omegaMid - tauLoad) / J;
        double dCurrent2 = (-R * currentMid - k_e * omegaMid + voltage) / L;

        // Update state variables
        theta += dTheta2 * deltaTime;
        omega += dOmega2 * deltaTime;
        current += dCurrent2 * deltaTime;
    }

    /**
     * Computes the control input voltage based on the LQR control law.
     *
     * @return Control input voltage (V)
     */
    public double computeControlInput() {
        // State vector [theta, omega, current]
        double[] x = { theta, omega, current };
        // Reference state vector [thetaRef, omegaRef, currentRef]
        double[] xRef = { thetaRef, omegaRef, currentRef };
        double controlInput = 0.0;

        // Compute state error xError = x - xRef
        double[] xError = new double[x.length];
        for (int i = 0; i < x.length; i++) {
            xError[i] = x[i] - xRef[i];
        }

        // Compute control input u = -K * xError
        for (int i = 0; i < K.length; i++) {
            controlInput -= K[i] * xError[i];
        }

        return controlInput;
    }

    /**
     * Updates the motor state and applies LQR control.
     */
    public void updateMotor() {
        double inputVoltage = computeControlInput();
        updateState(inputVoltage);
    }

    // Getter and Setter methods
    public double getTheta() {
        return theta;
    }

    public double getOmega() {
        return omega;
    }

    public double getCurrent() {
        return current;
    }

    public void setLoadTorque(double tauLoad) {
        this.tauLoad = tauLoad;
    }

    public void setDeltaTime(double deltaTime) {
        this.deltaTime = deltaTime;
    }

    /**
     * Sets the target angular position (rad)
     *
     * @param thetaRef Target angular position (rad)
     */
    public void setThetaRef(double thetaRef) {
        this.thetaRef = thetaRef;
    }

    /**
     * Sets the target angular velocity (rad/s)
     *
     * @param omegaRef Target angular velocity (rad/s)
     */
    public void setOmegaRef(double omegaRef) {
        this.omegaRef = omegaRef;
    }

    /**
     * Sets the target motor current (A)
     *
     * @param currentRef Target motor current (A)
     */
    public void setCurrentRef(double currentRef) {
        this.currentRef = currentRef;
    }
}

We are facing challenges in the following areas:

  • Calculation of the Moment of Inertia (( J )): We are currently calculating the moment of inertia using ( J = \frac{1}{3} m L^2 ), but we are uncertain if this is the most accurate method for our system.

  • Determining Motor Parameters: We are unsure how to calculate the friction coefficient ( b ) and the back EMF constant ( k_e ).

  • Designing the LQR Gain Matrix ( K ): We have used a set of preset gain values but need to understand how to correctly compute these gains based on our system to ensure optimal controller performance.

If anyone has similar experience and can share insights or suggestions on motor modeling and controller design, we would greatly appreciate it.

Thank you!

Not knowing your system, neither do we :slight_smile: .

If you want to be theoretical, model the arm as a set of point masses and linearly-distributed masses rotating about the pivot, and sum up the MOI’s of each. Dealers choice how to subdivide what’s actually on the arm and model each component.

Alternatively, an accurate CAD model (with material densities modeled correctly) could calculate this number.

If you want to go crazy… Removing the mechanism, mounting it so it isn’t influenced by gravity, applying a torque, and measuring the resulting acceleration is another way. At some point, air resistance may corrupt this experiment. Hopefully not at the size/speeds/masses you’re dealing with though.

This can be looked up from motor datasheets - calculate it from the free-speed velocity, voltage/current at that velocity, and winding resistance. Winding resistance can be calculated from stall current and voltage. All neglecting friction, that is.

Unfortunately, this calculation is influenced by things like “how tight is that bolt?” and “are these shafts perfectly aligned?” and “how much grease did you put on yesterday?”. Empirical measurement (or gross assumptions) are the way to go here.

Personally, I’d assume zero and move on with my life. You want low-friction mechanisms anyway.

If you’re looking to go the extra mile, and it’s your last variable, you could inject a sine wave voltage into the motor, measure current and speed to calculate torque at the output shaft of the motor, subtract off expected torque (based on the arm acceleration and the accurate moment of inertia you found above) and average out that “remainder torque” as your friction.

Go slow enough you can neglect air resistance :slight_smile: .

ALL that being said:

All models are wrong, but some are useful.

A good way to evaluate usefulness is to take what you’re doing above, and set up a simulation that models the behavior of the plant and the system together.

Then, modify each LQR controller parameter by, say, ~10%, up and down, one by one. Observe what varying the parameter does to the actual voltage applied to the motor.

Remember that noise exists. In an FRC context, controlling anything under a few millivolts is pretty meaningless, motor controllers aren’t that good. Unavoidable battery voltage swings and wire losses will drown out any precision you achieve in that range through accurate modeling.

If you determine that skewing a parameter up and down doesn’t change the voltage output by more than a few millivolts, it’s safe to conclude its value doesn’t actually matter all that much to the final calculation, and you don’t have to worry about getting that number perfect.

On the other hand, if a parameter skew produces a BIG (say, 1 v) swing in motor voltage command, that’s a good candidate to spend time to get accurate.

And/or understand why the model is so sensitive to that parameter. The model could be wrong, or the mechanism could be very unstable.

3 Likes