Servo Actuator is implemented in the TrcServo class in the Framework Library. It abstracts a superset of functionalities of what a servo can do. Some functionalities are supported by the servo natively and others are simulated in software by TrcServo. Note that our Framework Library considers a Continuous Rotation Servo the same as a DC motors and not a servo, therefore, Servo Actuator only supports servos configured as regular servos (refer to Motor Actuator for Continuous Rotation Servos). Since our Framework Library is shared between FTC and FRC, TrcServo is platform independent and provides generic servo functionalities for both FTC and FRC. This allows our subsystem code to work in both FTC and FRC environment. In order for TrcServo to support both FTC and FRC platforms, corresponding FTC and FRC subclasses must extend TrcServo and will provide platform specific accesses to the corresponding servo hardware. For example, FtcServo class in FTC and FrcServo class in FRC. To make it even easier to use, the Framework Library also provide wrapper classes FtcServoActuator and FrcServoActuator that contain code to instantiate and configure the servo hardware for the corresponding platform.
The following are the most commonly called methods provided by TrcServo which is the object returned by the getActuator method:
public class Wrist
{
private final FtcServo wristServo;
/**
* Constructor: Creates an instance of the object.
*/
public Wrist()
{
FtcServoActuator.Params wristParams = new FtcServoActuator.Params()
.setServoInverted(RobotParams.WRIST_SERVO_INVERTED)
.setHasFollowerServo(RobotParams.WRIST_HAS_FOLLOWER_SERVO, RobotParams.WRIST_FOLLOWER_SERVO_INVERTED)
.setLogicalPosRange(RobotParams.WRIST_SERVO_LOGICAL_MIN, RobotParams.WRIST_SERVO_LOGICAL_MAX)
.setPhysicalPosRange(RobotParams.WRIST_SERVO_PHYSICAL_MIN, RobotParams.WRIST_SERVO_PHYSICAL_MAX)
.setMaxStepRate(WRIST_SERVO_MAX_STEPRATE)
.setPositionPresets(RobotParams.WRIST_PRESET_TOLERANCE, RobotParams.WRIST_PRESETS);
wristServo = new FtcServoActuator(RobotParams.HWNAME_WRIST, wristParams).getActuator();
}
public FtcServo getWristServo()
{
return wristServo;
}
}
...
public FtcServo wrist;
...
public Robot(TrcRobot.RunMode runMode)
{
...
if (RobotParams.Preferences.useSubsystems)
{
...
if (RobotParams.Preferences.useWrist)
{
wrist = new Wrist().getWristServo();
// Initialize the wrist position.
wrist.setPosition(RobotParams.WRIST_DOWN_POS);
}
...
}
...
}
The code above is referencing a lot of constants from RobotParams.java. We need to define all those constants. In RobotParams.Preferences, add a switch so that we can enable/disable the subsystem. This is very useful during development because the subsystem may not exist yet. At the end of the RobotParam.java class, add the Wrist subsystem section like below.
The wrist consists of two 5-turn servos mounted facing each other. That means the follower servo is inverted from the primary servo. Because of gear ratio, one turn of the wrist requires three turns of the servo. Since the servo is a 5-turn servo, it means one turn of the wrist would only require 3/5 of the logical range. Therefore, we limit our logical range to 0.0 and 0.6 and map it to a physical range of 0.0 to 360.0 degrees.
public static class Preferences
{
...
// Subsystems
public static boolean useSubsystems = true;
public static boolean useWrist = true;
...
}
...
//
// Wrist subsystem:
// All values below are just an example implementation, you need to change them to fit your subsystem.
//
public static final String HWNAME_WRIST = "wrist";
// Actuator parameters.
public static final boolean WRIST_SERVO_INVERTED = false;
public static final boolean WRIST_HAS_FOLLOWER_SERVO = true;
public static final boolean WRIST FOLLOWER_SERVO_INVERTED = true;
public static final double WRIST_GEAR_RATIO = 90.0 / 30.0; // 3:1
// goBilda 5-turn servo: https://www.gobilda.com/2000-series-5-turn-dual-mode-servo-25-3-speed/
// Wrist rotates 1 turn, servo rotates 3 turns => 3/5 of the servo range.
public static final double WRIST_SERVO_LOGICAL_MIN = 0.0;
public static final double WRIST_SERVO_LOGICAL_MAX = WRIST_GEAR_RATIO / 5.0 ;
public static final double WRIST_SERVO_PHYSICAL_MIN = 0.0;
public static final double WRIST_SERVO_PHYSICAL_MAX = 360.0; // in degrees
public static final double WRIST_SERVO_MAX_STEPRATE = 115.0 * 360.0 / 60.0 / WRIST_GEAR_RATIO; // in degrees/sec
public static final double WRIST_DOWN_POS = 0.0;
public static final double WRIST_POWER_SCALE = 1.0; // operate the wrist at full speed.
// Preset positions.
public static final double WRIST_PRESET_TOLERANCE = 1.0; // in deg
// Presets array must be sorted in ascending order in the unit of deg
public static final double[] WRIST_PRESETS = new double[] {
0.0, 60.0, 120.0, 180.0, 240.0, 300.0
};
public void updateStatus()
{
...
if (wrist != null)
{
dashboard.displayPrintf(++lineNum, "Wrist: pos=" + wrist.getPosition());
}
...
}
private double wristPrevPower = 0.0;
private boolean operatorAltFunc = false;
...
public void periodic(double elapsedTime, boolean slowPeriodicLoop)
{
...
//
// Other subsystems.
//
if (RobotParams.Preferences.useSubsystems)
{
// Wrist subsystem: only do this if the wrist is enabled.
if (robot.wrist != null)
{
// Send power value to the wrist if it is different from before.
// This prevents repeatedly sending zero power to the wrist if the joystick is not moved.
double wristPower = operatorGamepad.getRightStickY(true) * RobotParams.WRIST_POWER_SCALE;
if (wristPower != wristPrevPower)
{
robot.wrist.setPower(wristPower);
armPrevPower = armPower;
}
}
...
}
...
}
...
protected void operatorButtonEvent(TrcGameController gamepad, int button, boolean pressed)
{
...
switch (button)
{
...
case FtcGamepad.GAMEPAD_RBUMPER:
robot.globalTracer.traceInfo(moduleName, ">>>>> operatorAltFunc=" + pressed);
operatorAltFunc = pressed;
break;
case FtcGamepad.DPAD_UP:
if (pressed)
{
// Check if wrist is enabled.
if (operatorAltFunc && robot.wrist != null)
{
robot.globalTracer.traceInfo(moduleName, ">>>>> Wrist Preset Up");
robot.wrist.presetPositionUp(moduleName);
}
// Check if arm is enabled.
else (!operatorAltFunc && robot.arm != null)
{
robot.globalTracer.traceInfo(moduleName, ">>>>> Arm Preset Up");
robot.arm.presetPositionUp(moduleName, RobotParams.ARM_POWER_SCALE);
}
}
break;
case FtcGamepad.DPAD_DOWN:
if (pressed)
{
// Check if wrist is enabled.
if (operatorAltFunc && robot.wrist != null)
{
robot.globalTracer.traceInfo(moduleName, ">>>>> Wrist Preset Down");
robot.wrist.presetPositionDown(moduleName);
}
// Check if arm is enabled.
else (!operatorAltFunc && robot.arm != null)
{
robot.globalTracer.traceInfo(moduleName, ">>>>> Arm Preset Down");
robot.arm.presetPositionDown(moduleName, RobotParams.ARM_POWER_SCALE);
}
}
break;
}
...
}