Go to Post If you spend the money on a new stadium, you might make a profit. If you spend the money on upgrading school lab facilities, you might inspire someone to make a vaccine for HIV. - nathanww [more]
Home
Go Back   Chief Delphi > Technical > Programming > Java
CD-Media   CD-Spy  
portal register members calendar search Today's Posts Mark Forums Read FAQ rules

 
Reply
Thread Tools Rating: Thread Rating: 40 votes, 5.00 average. Display Modes
  #1   Spotlight this post!  
Unread 23-02-2014, 17:40
MichaelB MichaelB is offline
Registered User
FRC #3573 (The Ohms)
Team Role: Programmer
 
Join Date: Feb 2014
Rookie Year: 2011
Location: Conyers
Posts: 8
MichaelB is an unknown quantity at this point
Encoders, Mecanum, and PID

How do I set up a velocity PID to work with my existing Mecanum Drive code using the encoders (US Digital E4P-360-250-D-H-D-B) I have on all four wheels? I found some threads on it and understand it conceptually (I think), but actually implementing it leaves me puzzled.
Reply With Quote
  #2   Spotlight this post!  
Unread 23-02-2014, 17:45
Ether's Avatar
Ether Ether is offline
systems engineer (retired)
no team
 
Join Date: Nov 2009
Rookie Year: 1969
Location: US
Posts: 8,043
Ether has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond repute
Re: Encoders, Mecanum, and PID

Quote:
Originally Posted by MichaelB View Post
How do I set up a velocity PID to work with my existing Mecanum Drive code using the encoders (US Digital E4P-360-250-D-H-D-B) I have on all four wheels? I found some threads on it and understand it conceptually (I think), but actually implementing it leaves me puzzled.
Can you articulate in a bit more detail what part you do not understand?
Reply With Quote
  #3   Spotlight this post!  
Unread 23-02-2014, 19:25
MichaelB MichaelB is offline
Registered User
FRC #3573 (The Ohms)
Team Role: Programmer
 
Join Date: Feb 2014
Rookie Year: 2011
Location: Conyers
Posts: 8
MichaelB is an unknown quantity at this point
Re: Encoders, Mecanum, and PID

Quote:
Originally Posted by Ether View Post
Can you articulate in a bit more detail what part you do not understand?
I read a post from you (that I can't find anymore) about estimating a maximum speed for the wheels and then scaling that to 1. What I don't know is how to control each wheel individually like that. I tried to get the mecanum code from the DriveTrain class (changed a bit because we're not using a gyro or CAN), but now I'm not sure how to set the proper speeds. Do I just change in mecanumDrive_Cartesian "leftFront.set(...)" to "leftFrontPID.setSetpoint(...)" and et cetera?

Code:
public class DriveTrain extends Subsystem {
    // Put methods for controlling this subsystem
    // here. Call these from Commands.
    RobotDrive driveTrain;
    Encoder leftFrontEncoder;
    Encoder leftBackEncoder;
    Encoder rightFrontEncoder;
    Encoder rightBackEncoder;
    
    PIDController leftFrontPID;
    PIDController leftBackPID;
    PIDController rightFrontPID;
    PIDController rightBackPID;
    
    private static final double Kp = 0.3;
    private static final double Ki = 0.0;
    private static final double Kd = 0.0;
    
    public static final int kMaxNumberOfMotors = 4;
    public static Jaguar leftFront;
    public static Jaguar leftBack;
    public static Jaguar rightFront;
    public static Jaguar rightBack;
    
    protected final int m_invertedMotors[] = {1,1,1,1};
    
    public DriveTrain() {
        //super("DriveTrain", Kp, Ki, Kd);
        
        driveTrain = new RobotDrive(RobotMap.fLeftFrontMotorPort, RobotMap.rLeftBackMotorPort, RobotMap.fRightFrontMotorPort, RobotMap.rRightBackMotorPort);
        driveTrain.setSafetyEnabled(false);
        leftFrontEncoder = new Encoder(RobotMap.frontLeftEncoderPort1, RobotMap.frontLeftEncoderPort2, false, Encoder.EncodingType.k2X);
        leftBackEncoder = new Encoder(RobotMap.backLeftEncoderPort1, RobotMap.backLeftEncoderPort2, true, Encoder.EncodingType.k2X);
        rightFrontEncoder = new Encoder(RobotMap.frontRightEncoderPort1, RobotMap.frontRightEncoderPort2, true, Encoder.EncodingType.k2X);
        rightBackEncoder = new Encoder(RobotMap.backRightEncoderPort1, RobotMap.backRightEncoderPort2, true, Encoder.EncodingType.k2X);
        leftFrontEncoder.setDistancePerPulse(2*3*3.14/360/12); //should be in inches/rev? might be 250 instead of 360
        leftBackEncoder.setDistancePerPulse(2*3*3.14/360/12);
        rightFrontEncoder.setDistancePerPulse(2*3*3.14/360/12);
        rightBackEncoder.setDistancePerPulse(2*3*3.14/360/12);
        
        leftFrontEncoder.setSamplesToAverage(100);
        leftBackEncoder.setSamplesToAverage(100);
        rightFrontEncoder.setSamplesToAverage(100);
        rightBackEncoder.setSamplesToAverage(100);
        leftFrontEncoder.start();
        leftFrontEncoder.reset();
        rightFrontEncoder.start();
        rightFrontEncoder.reset();
        leftBackEncoder.start();
        leftBackEncoder.reset();
        rightBackEncoder.start();
        rightBackEncoder.reset();
        
        leftFrontPID =  new PIDController(Kp, Ki, Kd, leftFrontEncoder, leftFront);
        rightFrontPID =  new PIDController(Kp, Ki, Kd, rightFrontEncoder, rightFront);
        leftBackPID =  new PIDController(Kp, Ki, Kd, leftBackEncoder, leftBack);
        rightBackPID =  new PIDController(Kp, Ki, Kd, rightBackEncoder, rightBack);
        
        leftFrontPID.enable();
        leftBackPID.enable();
        rightFrontPID.enable();
        rightBackPID.enable();
    }

    public void initDefaultCommand() {
        // Set the default command for a subsystem here.
        setDefaultCommand(new DriveWithJoystickArcade());
        //setDefaultCommand(new DriveWithJoystick());
        //setDefaultCommand(new DriveWithJoystickPolar());    Doesn't work, don't know why
    }
    
    public void driveWithJoystick(double x, double y, double twist) {
        driveTrain.mecanumDrive_Cartesian(x,twist,-y, 0);
        //System.out.println("X: " + x + "Y: " + y + "Twist: " + twist);
        //System.out.println("Left Front: " + leftFront.get()/360 + " Left Back: " + leftBack.get()/360 + " Right Front: " + rightFront.get()/360 + " Right Back: " + rightBack.get()/360);
        System.out.println("Left Front: " + leftFrontEncoder.getRate() + " Left Back: " + leftBackEncoder.getRate() + " Right Front: " + rightFrontEncoder.getRate() + " Right Back: " + rightBackEncoder.getRate());
    }
    
    public void driveWithJoystickPolar(double magnitude, double direction, double twist) {
        driveTrain.mecanumDrive_Polar(magnitude, direction, twist);
        System.out.println("Mag: " + magnitude + " Dir: " + direction + " Twist: " + twist);
    }
    
    public void drive(double x, double twist){
        driveTrain.arcadeDrive(x,twist);
    }
    
    public void mecanumDrive_Cartesian(double x, double y, double rotation)
    {
        double xIn = x;
        double yIn = y;
        // Negate y for the joystick.
        yIn = -yIn;

        double wheelSpeeds[] = new double[kMaxNumberOfMotors];
        wheelSpeeds[RobotMap.kFrontLeft_val] = xIn + yIn + rotation;
        wheelSpeeds[RobotMap.kFrontRight_val] = -xIn + yIn - rotation;
        wheelSpeeds[RobotMap.kRearLeft_val] = -xIn + yIn + rotation;
        wheelSpeeds[RobotMap.kRearRight_val] = xIn + yIn - rotation;

        normalize(wheelSpeeds);

        leftFront.set(wheelSpeeds[RobotMap.kFrontLeft_val] * m_invertedMotors[RobotMap.kFrontLeft_val] * RobotMap.maxOutput);
        rightFront.set(wheelSpeeds[RobotMap.kFrontRight_val] * m_invertedMotors[RobotMap.kFrontRight_val] * RobotMap.maxOutput);
        leftBack.set(wheelSpeeds[RobotMap.kRearLeft_val] * m_invertedMotors[RobotMap.kRearLeft_val] * RobotMap.maxOutput);
        rightBack.set(wheelSpeeds[RobotMap.kRearRight_val] * m_invertedMotors[RobotMap.kRearRight_val] * RobotMap.maxOutput);
    }
        
    protected static void normalize(double wheelSpeeds[]) {
        double maxMagnitude = Math.abs(wheelSpeeds[0]);
        int i;
        for (i=1; i<kMaxNumberOfMotors; i++) {
            double temp = Math.abs(wheelSpeeds[i]);
            if (maxMagnitude < temp) maxMagnitude = temp;
        }
        if (maxMagnitude > 1.0) {
            for (i=0; i<kMaxNumberOfMotors; i++) {
                wheelSpeeds[i] = wheelSpeeds[i] / maxMagnitude;
            }
        }
    }
}
Reply With Quote
  #4   Spotlight this post!  
Unread 24-02-2014, 11:17
Ether's Avatar
Ether Ether is offline
systems engineer (retired)
no team
 
Join Date: Nov 2009
Rookie Year: 1969
Location: US
Posts: 8,043
Ether has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond repute
Re: Encoders, Mecanum, and PID


Yes, you want to take the four wheel speeds that are computed from the driver commands, and use each one as a setpoint for the PID (or other controller) for the corresponding wheel. The encoder signal for each wheel serves as the process variable for that wheel.

As with all closed-loop controllers, you need to make sure that your setpoint and process variable are appropriately scaled and offset so they work together properly.

I'm not a Java guru so I'll leave the Java coding questions to someone else.




Reply With Quote
  #5   Spotlight this post!  
Unread 24-02-2014, 14:09
pblankenbaker pblankenbaker is offline
Registered User
FRC #0868
 
Join Date: Feb 2012
Location: Carmel, IN, USA
Posts: 102
pblankenbaker is a glorious beacon of lightpblankenbaker is a glorious beacon of lightpblankenbaker is a glorious beacon of lightpblankenbaker is a glorious beacon of lightpblankenbaker is a glorious beacon of light
Re: Encoders, Mecanum, and PID

If I understand you correctly, you are using the PID loop to maintain a certain rate (how quickly your wheels spin) and not distance (how many times to rotate the wheels).

We've found that the standard PID controller assumes that you are always trying to "move a distance". It tries to get the error to 0 and basis the power value directly on the error. This works well for distance (where you want 0 power when you have 0 error). This doesn't work well for rate based PIDS (where you want the power to settle in to a good non-zero value when the error goes to 0).

Basically, when we run into this situation, we use a wrapper class around our speed controllers that can be used as the parameter to the PIDController constructer. The wrapper class implements PIDOutput and treats each write as an adjustment to the current power setting instead of a direct setting. For example, it would look something like:

Code:
rightBackPID =  new PIDController(Kp, Ki, Kd, rightBackEncoder, new RateContollerMotor(rightBack));
Here is a reference implementation (I don't have access to a robot to test it on anymore):

Code:
package com.techhounds.robot.subsystems;

import edu.wpi.first.wpilibj.PIDOutput;
import edu.wpi.first.wpilibj.SpeedController;

/**
 * A wrapper around a SpeedController that is used as a PIDOutput for the
 * purpose of controlling the rate at which something spins.
 */
public final class RateControlledMotor implements PIDOutput {

    /**
     * Motor to adjust power on.
     */
    private final SpeedController _Motor;

    /**
     * Construct a new instance and associate a speed controller with the
     * object.
     *
     * @param motor The speed controller that is controlling the motor.
     */
    public RateControlledMotor(SpeedController motor) {
        _Motor = motor;
    }

    /**
     * Apply power value computed by PID to the motor.
     *
     * <p>
     * The standard PID system basis the power output on the amount of "error".
     * This results in the power going to 0 as the error goes to 0. While this
     * works well for a distance based PID (where you want to stop once you get
     * to where you are going). It does not work well for a rate system (where
     * you want to keep spinning at the same rate).</p>
     *
     * <p>
     * Instead of treating the value passed as a new power level, we treat it as
     * an adjustment to the current power level when we apply it.</p>
     *
     * @param output Power value to apply (computed by PID loop). Goes to zero
     * as we reach the desired spin rate.
     */
    public void pidWrite(double output) {
        // Treat new PID computed power output as an "adjustment"
        double rateOutput = _Motor.get() + output;
        rateOutput = Math.min(1.0, rateOutput);
        rateOutput = Math.max(-1.0, rateOutput);
        _Motor.set(rateOutput);
    }

}
Some tips on tuning. If you use this method, we've found that you typically need to set Kd and Kp values to non-zero and that Kd often ends up larger than Kp. Also, it is often useful to use the SmartDashboard when tuning these values. I'd recommend putting the robot on wheels and focusing on tuning one PID. However, I'm not certain if having the weight of the robot off of the wheels will change the desired PID values - but it may at least help you get to a reasonable starting point.

Code:
rightBackPID =  new PIDController(Kp, Ki, Kd, rightBackEncoder, new RateContollerMotor(rightBack));
// Enable tuning the Kp, Ki, Kd parameters on the smart dashboard
SmartDashboard.putData("Right Back", rightBackPID);

Last edited by pblankenbaker : 24-02-2014 at 14:53. Reason: Added note about tuning
Reply With Quote
  #6   Spotlight this post!  
Unread 24-02-2014, 14:28
Ether's Avatar
Ether Ether is offline
systems engineer (retired)
no team
 
Join Date: Nov 2009
Rookie Year: 1969
Location: US
Posts: 8,043
Ether has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond reputeEther has a reputation beyond repute
Re: Encoders, Mecanum, and PID


Quote:
The wrapper class implements PIDOutput and treats each write as an adjustment to the current power setting instead of a direct setting
Here are 3 other ways to do it.

Reply With Quote
  #7   Spotlight this post!  
Unread 24-02-2014, 14:47
MikeE's Avatar
MikeE MikeE is offline
Wrecking nice beaches since 1990
no team (Volunteer)
Team Role: Engineer
 
Join Date: Nov 2008
Rookie Year: 2008
Location: New England -> Alaska
Posts: 381
MikeE has a reputation beyond reputeMikeE has a reputation beyond reputeMikeE has a reputation beyond reputeMikeE has a reputation beyond reputeMikeE has a reputation beyond reputeMikeE has a reputation beyond reputeMikeE has a reputation beyond reputeMikeE has a reputation beyond reputeMikeE has a reputation beyond reputeMikeE has a reputation beyond reputeMikeE has a reputation beyond repute
Re: Encoders, Mecanum, and PID

Feedforward is implemented in the PIDController class you are using now, see WPILib screensteps

It's a direct scaling factor between setpoint units and and output variable which for a motor controller is [-1, 1]. So if your max speed is N in encoder units then set the feedforward term to 1/N.
Reply With Quote
Reply


Thread Tools
Display Modes Rate This Thread
Rate This Thread:

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Forum Jump


All times are GMT -5. The time now is 11:02.

The Chief Delphi Forums are sponsored by Innovation First International, Inc.


Powered by vBulletin® Version 3.6.4
Copyright ©2000 - 2017, Jelsoft Enterprises Ltd.
Copyright © Chief Delphi