Conversion between real-world units and “sensor ticks”

Hi all, I am looking for some help at converting sensor ticks from our encoder to represent an angle. Here is the code I am working with right now.

Any help is appreciated as I am new to this.

package frc.robot.subsystems;

import edu.wpi.first.wpilibj2.command.SubsystemBase;

import frc.robot.Constants;

import com.ctre.phoenix.motorcontrol.ControlMode;

import com.ctre.phoenix.motorcontrol.FeedbackDevice;

import com.ctre.phoenix.motorcontrol.NeutralMode;

import com.ctre.phoenix.motorcontrol.can.TalonSRX;

public class turnSubsystem extends SubsystemBase {

  /**

   * Creates a new Turner. https://github.com/CrossTheRoadElec/Phoenix-Examples-Languages/blob/master/Java%20General/PositionClosedLoop/src/main/java/frc/robot/Robot.java

   */

  

   TalonSRX turnTalon;

   

   public final double HOLD_POWER = 0;

   public final double Turn_POSITION = 0.12 * 10.0 * 1024;

   public final double Stop_POWER = 0;

   public final double Incremental_value = 0;

   StringBuilder _sb = new StringBuilder();

   double targetPositionRotations;

  

  public turnSubsystem() {

    turnTalon = new TalonSRX(Constants.talonTurn1);

    turnTalon.setNeutralMode(NeutralMode.Brake);

  

        /* Factory Default all hardware to prevent unexpected behaviour */

        turnTalon.configFactoryDefault();

        

        /* Config the sensor used for Primary PID and sensor direction */

        turnTalon.configSelectedFeedbackSensor(FeedbackDevice.Analog,

                                            Constants.kSlotIdx,

                                            Constants.kTimeoutMs);

        /* Ensure sensor is positive when output is positive */

        turnTalon.setSensorPhase(Constants.kSensorPhase);

        /**

         * Set based on what direction you want forward/positive to be.

         * This does not affect sensor phase. 

         */ 

        turnTalon.setInverted(Constants.kMotorInvert);

        /* Config the peak and nominal outputs, 12V means full */

        turnTalon.configNominalOutputForward(0, Constants.kTimeoutMs);

        turnTalon.configNominalOutputReverse(0, Constants.kTimeoutMs);

        turnTalon.configPeakOutputForward(1, Constants.kTimeoutMs);

        turnTalon.configPeakOutputReverse(-1, Constants.kTimeoutMs);

        /**

         * Config the allowable closed-loop error, Closed-Loop output will be

         * neutral within this range. See Table in Section 17.2.1 for native

         * units per rotation.

         */

        turnTalon.configAllowableClosedloopError(0, Constants.kSlotIdx, Constants.kTimeoutMs);

        /* Config Position Closed Loop gains in slot0, tsypically kF stays zero. */

        turnTalon.config_kF(Constants.kSlotIdx, Constants.kGains.kF, Constants.kTimeoutMs);

        turnTalon.config_kP(Constants.kSlotIdx, Constants.kGains.kP, Constants.kTimeoutMs);

        turnTalon.config_kI(Constants.kSlotIdx, Constants.kGains.kI, Constants.kTimeoutMs);

        turnTalon.config_kD(Constants.kSlotIdx, Constants.kGains.kD, Constants.kTimeoutMs);

  } 

  @Override

  public void periodic() {

    // This method will be called once per scheduler run

  }

  public void Turn() {

    turnTalon.set(ControlMode.Position, Turn_POSITION);

    System.out.println(turnTalon.getSelectedSensorPosition(0));

   }

    

  public void StopTurn() {

    turnTalon.set(ControlMode.PercentOutput, 0);

    System.out.println(turnTalon.getSelectedSensorPosition(0));

  }

  

  public void HoldHeight() {

    turnTalon.set(ControlMode.Position, HOLD_POWER);

  }

}

I’m assuming you have a relative encoder? If you know how many “ticks” are in a rotation, most likely 4096 (but it seems like you have 1024), you can find the angle by using ratios that may look like this:

x / 1024 = y / 360, where y is the angle in degrees and x is the ticks. You can then add on a number to act as a makeshift zero.

I’m not super familiar with this analog input encoder, so I’m not exactly sure how/what it returns. What encoder are you using exactly? What gear ratio do you have?

I’ll gladly help you more if you give me some more info.

2 Likes

We’re using the Lamprey Magnetic Encoder, it is an Analog encoder hence why we’re using 1024.

The set up is just a 12:1 gearbox motor with a chain sprocket on it attached to the same size chain sprocket on the rod that spins our turret.

The return we get from the code above is as it spins the number just keeps going up each 360 rotation gives us a value of Pos 1024u, spinning 720 degrees gives us 2048 and so on.

1 Like

Ah, I see.

To avoid this, use the modulo operator, the “%”. It will basically return the remainder instead of a quotient.

For example:
250 % 50 is equal to 0, because 50 goes in evenly.
11 % 3 is equal to 2, because three can go into 11 three times, leaving you with two left over.

Now, the gear ratio. The chain is a 1:1 it seems so you’re good there. If your encoder is on your motor, and thus has to travel through the gear ratio, do the following. Otherwise, simply replace the 30 with a multiplication by 360.

You could simply do something like:
((getEncoderVal() % 1024) / 1024 * 30 + someZeroingValue

How did I create this? Working left to right, I call a method that gets the current position of the encoder. I then use the module operator to ensure that it stays under 1024. I would then divide that by 1024 to make a percentage. Then multiply by 360 and divide by 12 to account for the gear ratio and the degree conversion (this is the equivalent of multiplying by 30). Lastly, I add some constant to use as a “zero”.

I really hope this is what you meant lol.

Please ask if you have any questions or comments!

1 Like

Definitely what I meant, and understand how you broke it down. The issue I’m having is how to implement that into the code I already have above. Our encoder is hooked into our Talon SRX so getEncoderVal() gives an error. Would it be getSelectedSensorPosition() instead?

Yes, getSelectedSensorPosition() is the method you need because you set the analog encoder as the talons feedback device.

So would write it like this?
public turnSubsystem() {

        turnTalon = new TalonSRX(Constants.talonTurn1);

        turnTalon.setNeutralMode(NeutralMode.Brake);

        turn_angle = ((turnTalon.getSelectedSensorPosition(0) % 1024) / 1024 * 30 + 0);

Yup! You don’t need the +0 and I definitely recommend just trying the method alone first. If there any issues please let me know! Also, remember to update this value. By the looks of it, turnSubsystem is a constructor and will therefore only run once.

So this is the code I am running, I believe the angle conversion is working. The issue I am having is that my turn position is set to “public final double Turn_POSITION = 0.1 * 10.0 * 1024;” which in theory should turn the motor once around 360 degrees. When I run it though it only turns 70 degrees

public class turnSubsystem extends SubsystemBase {

  /**

   * Creates a new Turner. https://github.com/CrossTheRoadElec/Phoenix-Examples-Languages/blob/master/Java%20General/PositionClosedLoop/src/main/java/frc/robot/Robot.java

   */

  

   TalonSRX turnTalon;

   

   public final double HOLD_POWER = 0;

   public final double Turn_POSITION = 0.1 * 10.0 * 1024;

   public final double Stop_POWER = 0;

   public double turn_angle = 0;

    StringBuilder _sb = new StringBuilder();

    double targetPositionRotations;

    public turnSubsystem() {

        turnTalon = new TalonSRX(Constants.talonTurn1);

        turnTalon.setNeutralMode(NeutralMode.Brake);

        /* Factory Default all hardware to prevent unexpected behaviour */

        turnTalon.configFactoryDefault();

        

        /* Config the sensor used for Primary PID and sensor direction */

        turnTalon.configSelectedFeedbackSensor(FeedbackDevice.Analog,

                                            Constants.kSlotIdx,

                                            Constants.kTimeoutMs);

        /* Ensure sensor is positive when output is positive */

        turnTalon.setSensorPhase(Constants.kSensorPhase);

        /**

         * Set based on what direction you want forward/positive to be.

         * This does not affect sensor phase. 

         */ 

        turnTalon.setInverted(Constants.kMotorInvert);

        /* Config the peak and nominal outputs, 12V means full */

        turnTalon.configNominalOutputForward(0, Constants.kTimeoutMs);

        turnTalon.configNominalOutputReverse(0, Constants.kTimeoutMs);

        turnTalon.configPeakOutputForward(1, Constants.kTimeoutMs);

        turnTalon.configPeakOutputReverse(-1, Constants.kTimeoutMs);

        /**

         * Config the allowable closed-loop error, Closed-Loop output will be

         * neutral within this range. See Table in Section 17.2.1 for native

         * units per rotation.

         */

        turnTalon.configAllowableClosedloopError(0, Constants.kSlotIdx, Constants.kTimeoutMs);

        /* Config Position Closed Loop gains in slot0, tsypically kF stays zero. */

        turnTalon.config_kF(Constants.kSlotIdx, Constants.kGains.kF, Constants.kTimeoutMs);

        turnTalon.config_kP(Constants.kSlotIdx, Constants.kGains.kP, Constants.kTimeoutMs);

        turnTalon.config_kI(Constants.kSlotIdx, Constants.kGains.kI, Constants.kTimeoutMs);

        turnTalon.config_kD(Constants.kSlotIdx, Constants.kGains.kD, Constants.kTimeoutMs);

  } 

  @Override

  public void periodic() {

    // This method will be called once per scheduler run

  }

  public void Turn() {

    turn_angle = ((turnTalon.getSelectedSensorPosition() % 1024) / 1024 * 365);  

    turnTalon.set(ControlMode.Position, Turn_POSITION);

    

    //print sensor position and angle

    System.out.println(turnTalon.getSelectedSensorPosition(0));

    System.out.println(turn_angle);

   }

    

  public void StopTurn() {

    turnTalon.set(ControlMode.PercentOutput, 0);

    System.out.println(turnTalon.getSelectedSensorPosition(0));

  }

Are your PID values properly tuned? Missing the target sounds like PID to me. Have you heard of MotionMagic control? I highly recommend using that instead of position control.

I’m also assuming that you are referencing the Turn() method when you mention it’s shortcoming.

Yeah was referring to the Turn() method. I will mess with the PID numbers tomorrow to see if I can get it to hit the target degrees.

I’ve heard of MotionMagic Control, but I am new to coding and have heard it is more difficult to get going, so maybe in the future I will be able to switch over.

Thank you so much for all your help!

Ok. It’s not too bad and it has a lot of potential so don’t wait too long :wink:.

Don’t hesitate to ask if any issues arise!

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