Homing Mechanisms
Use a limit switch to safely reset an elevator's encoder position back to zero.
By the end you can
- Understand why motor encoders lose their zero position.
- Read a limit switch using DigitalChannel.
- Write a Homing Command to reset the encoder.
If you build an elevator (lift) mechanism, you must use PID Control to move it to specific heights (e.g., Target = 1000 ticks for the High Bucket).
But there is a major problem with standard DC Motor encoders: They forget where they are when they turn off.
If your lift is fully extended (1000 ticks) at the end of a match, and you turn the robot off, the Control Hub forgets everything. When you turn the robot back on for the next match, the Control Hub looks at the lift, which is still physically extended in the air, and says: "I just turned on, so my current position is 0 ticks!"
If you try to tell the lift to move to 1000 ticks now, it will drive the lift up another 1000 ticks, ripping your robot apart!
The Solution: Homing
To solve this, we place a physical Limit Switch at the very bottom of the elevator.
During the init() phase of the match, we run a special "Homing" routine. The robot slowly lowers the elevator until it clicks the limit switch. The exact moment the switch clicks, the robot stops the motor and forces the encoder to reset its value to 0.
Now, the robot knows exactly where it is!
1. The Lift Subsystem
First, let's look at how the Unearthed-Alberta repository sets up the LiftSubsystem.java to read the limit switch and reset the encoder.
package org.firstinspires.ftc.teamcode.subsystems;
import com.arcrobotics.ftclib.command.SubsystemBase;
import com.qualcomm.robotcore.hardware.DcMotor;
import com.qualcomm.robotcore.hardware.DcMotorEx;
import com.qualcomm.robotcore.hardware.DigitalChannel;
import com.qualcomm.robotcore.hardware.HardwareMap;
public class LiftSubsystem extends SubsystemBase {
private final DcMotorEx liftMotor;
private final DigitalChannel limitSwitch;
public LiftSubsystem(HardwareMap hardwareMap) {
liftMotor = hardwareMap.get(DcMotorEx.class, "liftMotor");
// 1. Initialize the limit switch
limitSwitch = hardwareMap.get(DigitalChannel.class, "liftResetSensor");
limitSwitch.setMode(DigitalChannel.Mode.INPUT);
}
// Allows us to slowly drive the lift down manually
public void setPower(double power) {
liftMotor.setPower(power);
}
/**
* Helper method to check the switch.
* Often, REV Magnetic Limit Switches return FALSE when triggered!
*/
public boolean isLimitSwitchPressed() {
return !limitSwitch.getState();
}
/**
* Forces the motor encoder back to 0 ticks!
*/
public void resetEncoder() {
// We must stop the motor before resetting the mode
liftMotor.setPower(0);
liftMotor.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
// Return to standard run mode so PID can take over later
liftMotor.setMode(DcMotor.RunMode.RUN_WITHOUT_ENCODER);
}
}2. The Homing Command
Now we create a Command that drives the lift downwards slowly until the switch clicks, and then resets the encoder.
package org.firstinspires.ftc.teamcode.commands;
import com.arcrobotics.ftclib.command.CommandBase;
import org.firstinspires.ftc.teamcode.subsystems.LiftSubsystem;
public class HomeLiftCommand extends CommandBase {
private final LiftSubsystem lift;
public HomeLiftCommand(LiftSubsystem lift) {
this.lift = lift;
addRequirements(lift);
}
@Override
public void initialize() {
// Start moving the lift downwards slowly! (Negative power)
lift.setPower(-0.3);
}
@Override
public boolean isFinished() {
// Finish instantly when the limit switch is pressed
return lift.isLimitSwitchPressed();
}
@Override
public void end(boolean interrupted) {
if (!interrupted) {
// The switch was pressed normally, reset the encoder to 0!
lift.resetEncoder();
} else {
// The command was cancelled early, just stop the motor safely
lift.setPower(0);
}
}
}This Command is incredibly safe. It drives downwards slowly (-0.3 power) so it doesn't slam into the chassis. As soon as isFinished() triggers, the end() method stops the motor and resets the encoder.
You can bind this Command to a button during TeleOp, or run it automatically during the init() phase of Autonomous!
Check Your Understanding
Check yourself