-
Notifications
You must be signed in to change notification settings - Fork 44
Description
I find using the CTRE motor API to be rather difficult, so what I'm trying to do here is explain my thought process and how I use documentation in order to demonstrate what I find lacking.
I'm going to try to go over creating a robot project in WPILib simulation. This robot will be a motor controller with encoder (4096 CPR) controlling a 6-inch higrip wheel, and converting the encoder positions and velocities into SI units. I will compare both the WPILib documentation and CTRE documentation.
Part 1: Creating a Motor Object
CTRE
For a TalonSRX, I open the documentation.
From there I scroll down the page through a large amount of description of using the config tool until I find some sample Java code. This is not very easy to find, nor does it show C++ code.
ctre-scrollllllll.mp4
If I look at the API Documentation you can see the namespaces and file includes required:
#include <ctre/phoenix/motorcontrol/can/TalonSRX.h>
ctre::phoenix::motorcontrol::can::TalonSRX motor{0};But if you look at your examples, the includes are different:
#include "ctre/Phoenix.h"
WPI_TalonSRX* motor = new WPI_TalonSRX{0};This has some issues already noted, but I'm not sure which header files which should be including (I'd lean towards the more specific one - as opposed to the style of "frc/WPILib.h" which includes everything).
This also demonstrates the existence of the WPI_TalonSRX class, which is hidden in the WP/NI integration page.
WPILib
The WPILib start-documentation is much easier to find
wpilib-scrollllllll.mp4
Part of the documentation for adding motors is explained here. There is also the Actuators section (also visible on the homepage) which includes documentation on the motors and how to use and initialise them. (I do realise that Actuators is not clear as to being "motors, etc")
#include <frc/PWMVictorSPX.h>
frc::PWMVictorSPX motor{0};Part 2: Setting Motor Speeds
CTRE
From the same Example Java Code as before we are introduced to the set(ControlMode.PercentOutput, speed) class (or not shown for C++: Set(ControlMode::PercentOutput, speed)).
If you look at the examples you also can see that WPI_TalonSRX has a Set(speed) function but this is neither obvious nor mentioned in the documentation clearly. It could also be found by finding the API Documentation.
motor.Set(ControlMode::PercentOutput, speed);
// or if WPI_TalonSRX is found
motor.Set(speed);WPILib
From the Motor Controller Actuators Page we learn about the Set(speed) command for motors, in both Java & C++.
Neither WPIlib's nor CTRE's Set() commands are difficult to understand, and both of them have easy-to-find-by-api-documentation Set() methods. This does not present much of an issue, but both could do with improvement.
motor.Set(speed);Part 3: Creating Encoder
CTRE
There is no Encoder class within the CTRE API AFAIK, just extra methods on the motor controller. This isn't evident from the documentation.
WPILib
The sensors page then contains an encoders page, which gives you the declaration for an encoder:
#include <frc/Encoder.h>
frc::Encoder encoder{0, 1};Part 4: Getting Encoder Values
CTRE
This part is very difficult to locate without prior knowledge of the API.
To get values from the encoder, we must find the Sensors page:
From this page we must scroll all the way down through explanations of the config application to a single code example, where one of the only mentions of getSelectedSensorPosition()/getSelectedSensorVelocity() is in the documentation, in an unrelated section.
ctre-scroll-2-elctric-boogaloo.mp4
There is no documentation as to what exactly these functions return within the RTD docs site as far as I can tell.
You can find it with doxygen, kinda.
The Velocity() tells you to see Phoenix-Documentation for how to interpret... though I cannot find anything searching for "100ms" or "raw sensor units".
WPILib
(barring the mistakes in the pages, these should be fixed soon)
The documentation has a simple GetDistance() example: as well as a GetRate() example
The API Documentation is also (more useful) than the documentation: https://first.wpi.edu/wpilib/allwpilib/docs/release/cpp/classfrc_1_1Encoder.html

and GetRate()

Part 5: Scaling to SI Units
CTRE
The CTRE Phoenix Documentation makes no mention of this as far as I can tell via searching.
There is a ConfigSelectedFeedbackCoefficient() mentioned in the API Documentation but none of the GetSelectedSensorPosition() mention that their output changes; they all mention that they still output native units. I believe that the GetSelectedSensor functions do return the scaled values, but I don't actually have a robot to test and haven't exactly managed to figure out simulation support yet.
I only found about of this function through discussion with others. It is not easy to find through documentation or through looking at doxygen, nor is it it named similarly to the one in WPILib.
constexpr double kEncoderDistancePerPulse = 6 /* inch */ * 0.0254 * 3.14 / 4096;
motor.ConfigSelectedFeedbackCoefficient(kEncoderDistancePerPulse);
motor.GetSelectedSensorPosition(); // meters
motor.GetSelectedSensorVelocity(); // metres per 100ms
motor.GetSelectedSensorVelocity() * 10; // meters per secondAlso due to the odd time units in Velocity() it is still necessary to multiply by 10 to get sane units, event with ConfigSelectedFeedbackCoefficient working.
WPILib
On the same page as before we see demonstration of the SetDistancePerPulse() function with explanation.

The doxygen documentation for all of the methods, e.g. GetPeriod() all mention that they are scaled by SetDistancePerPulse() which helps a lot.
constexpr double kEncoderDistancePerPulse = 6 /* inch */ * 0.0254 * 3.14 / 4096;
encoder.SetDistancePerPulse(kEncoderDistancePerPulse);
encoder.GetDistance(); // meters
encoder.GetRate(); // meters per secondPart 6: Simulation
CTRE
No documentation (yet?) but there are code examples it seems. There is also the API Documentation for the sim collection:
There is SetQuadratureRawPosition, which mentions

This documentation is odd, mentioning that it "integrates". This seems to be "integrate" as in combine, not mathematical integrate.
The functions are also oddly inconsistent with each other.
SetQuadratureRawPosition()- why does this one have raw in its name whilst none of the other examples do.AddQuadraturePosition()- why do we have add for this but not for velocity, or why do we need add at all if we're working with WPILib's PhysicsSim classes that give us values for position and velocity directly.SetQuadratureVelocity()
Furthermore, there a the {Analog,Quadrature,PulseWidth} functions for each in the simulation - this contrasts with the existing API of GetSelectedSensorPosition(). Would it make more sense to have SetSelectedSensorPosition()?
These functions also take integers, despite GetSelectedSensorVelocity returning a double. They do not seem to scale by the ConfigSelectedFeedbackCoefficient - and if they did, the integer arguments make it impossible to feed them useful values. This then requires dividing by kEncoderDistancePerPulse when giving values to these functions.
[sidenote that I'm still looking into, the values returned by GetSelectedSensor*() seem to be rounded)
motor.GetSimCollection().SetQuadratureRawPosition(physicsSim.GetDistance() / kEncoderDistancePerPulse);
motor.GetSimCollection().SetQuadratureVelocity(physicsSim.GetVelocity() / 10 / kEncoderDistancePerPulse);Note that this is an easy to do footgun (albeit not one that translates to the real world as it only affects the sim GUI), because it is easy to add integer values for distances when these should be taking the raw ticks per 100ms. In addition, the extra *10 or /10 is also easy to forgot and mess up.
WPILib
The WPILib simulation paradigm involves the creation of EncoderSim or XXXSim classes which hook into the XX classes when in simulation mode and change the values of Encoder.GetXX().
There is an example for multiple different types of subsystems within the documentation page, as well as a full tutorial for the Drivetrain.
#include <frc/sim/EncoderSim.h>
frc::sim::EncoderSim encoderSim{encoder};
encoderSim.SetDistance(physicsSim.GetDistance());
encoderSim.SetRate(physicsSim.GetRate());Conclusion
I hope this can explain part of the reason for why the CTRE API is a bit un-intuitive and hard to figure out, due to the inconsistency within the API as well as inadequate and confusing documentation.
I would love to see the following improvements
- Inconclusions of coded tutorials a la the "Drivetrain Simulation Tutorial" where the ways of doing certain common constructions are explained using the CTRE API rather than WPILib-native motor objects. This should help explain the ways you can do things.
- Inclusion of how you convert "raw sensors units" to physical distances using either the
ConfigSelectedFeedbackCoefficientor other methods where necessary, and updating of documentation to explain what functions return value scaled by the coefficients. - IMO, the CTRE API should provide objects such as Encoders and EncoderSims that match the API provided by WPILib such that it is easy to just replace the type of the variable and it will continue to work throughout the rest of the robot project without need for any changes. This also makes most of the WPILib Documentation relevant for your motor controllers, without the need to copy/adjust, excepting where you have additional features.
- This might include the addition of separate Encoder classes. I am unaware of any official reasoning aside from speculation behind having the motor controller control the functions of the encoder and other items. If there is a good reason I'd be happy to hear it.







