The Units Library is very powerful and useful for robot programming. It is a bit difficult to learn and understand initially but it will help keep your code’s units consistent and avoid conversion errors.
One of the more useful aspects of using the Units Library is being able to define custom units that pertain to your robot code. One example is converting from motor revolutions to distance traveled for the robot drivetrain. If you have a gear box between the motor and the wheel that has a gear ratio of 6.86 to 1 and a wheel diameter of 4 inches then you could define a custom unit type of "meters per rotation" and then create a constant with those units that you can use to multiply desired linear velocities by to get motor angular velocities.
Custom Unit For Drivetrain
constexpr double kDriveGearRatio = 6.86;
using meters_per_rev = units::compound_unit<units::meters, units::inverse<units::turns>>;
using meters_per_rev_t = units::unit_t<meters_per_rev> meters_per_rev_t;
constexpr meters_per_rev_t kDriveMetersPerRotation = std::numbers::pi * 4_in / (kDriveGearRatio * 1_tr );
Note that the units library uses "turns" for rotations with the suffix "tr". Also notice that on the last line the wheel diameter is specified in inches but the units library automatically converts inches to meters.
|
The new Phoenix 6 library no longer uses "tics_per_100ms" units so the info below is not as relevant.
|
Another example is using the TalonFX smart motor controller library’s Set()
function. It requires the position or velocity inputs in very awkward units (See [talon_pid_table]). The position should be in "encoder tics" and the velocity should be in "encoder tics" per 100 milliseconds. There are 2048 encoder tics per rotation for the TalonFX built-in encoder. Custom units can help with converting from these strange units to more physically meaningful units. You could define a custom angular position unit that is ("tics") and a custom angular velocity unit that is ("tics" / 100_ms).
Custom Unit for Encoder Tics
using tics = units::unit<std::ratio<1,2048>, units::turns>;
using tics_t = units::unit_t<tics>;
using tics_per_100ms = units::compound_unit<tics, units::inverse<units::deciseconds>>;
using tics_per_100ms_t = units::unit_t<tics_per_100ms>;
Once these types are defined then the programmer doesn’t need to worry about converting from tics
to degrees or from RPM to tics_per_100ms
. The units types will do all the conversions automatically. The code below shows how to use these types.
Automatic Units Conversion
tics_t talon_position;
tics_per_100ms_t talon_velocity;
ctre::phoenix::motorcontrol::can::TalonFX talon{2};
talon_position = 45_deg;
talon.Set( ctre::phoenix::motorcontrol::ControlMode::Position,
talon_position.value() );
talon_velocity = 2400_rpm;
talon.Set( ctre::phoenix::motorcontrol::ControlMode::Velocity,
talon_velocity.value() );
printf( "Talon Velocity = %7.2f rpm\n",
units::revolutions_per_minute_t(talon_velocity).value() );
This choice could be a bit inconvienent as well since in order to print out the value of the talon_velocity
or to send it to the Network Tables (See NetworkTables) in RPM you must use the syntax units::revolutions_per_minute_t(talon_velocity).value()
. A better approach might be to create a class
that encapsulates the mechanism that the motor is used in (like a shooter). Then create a member function in that class that sets the velocity of the motor and does the necessary conversion from RPM to tics_per_100ms
. So for example if the TalonFX motor was connected to a flywheel that is used to shoot a ball then you might want to create a Shooter
class that looks like:
Encapsulating Unit Conversion
class Shooter {
public:
Shooter( const int canId ) : m_talon{canId} {}
void SetVelocity( units::revolutions_per_minute_t rpm ) {
m_talon.Set( ctre::phoenix::motorcontrol::ControlMode::Velocity,
rpm.value() * 2048.0 / 600.0 );
}
void Stop( void ) { m_talon.Set( ctre::phoenix::motorcontrol::TalonFXControlMode::PercentOutput, 0.0); }
private:
ctre::phoenix::motorcontrol::can::TalonFX m_talon;
};
The Shooter
class is then used in the main robot program and can be called with whatever angular velocity units you want (RPM, radians per second, etc) and it will convert them to the correct units for the Set()
command inside Shooter::SetVelocity()
.