Skip to content

API + Documentation Feedback #78

@modelmat

Description

@modelmat

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

image-20210119163433329

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

image-20210119165452115

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:

image-20210119165924351

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.

image-20210119170413677
image-20210119170351268

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

image-20210119171040628

The API Documentation is also (more useful) than the documentation: https://first.wpi.edu/wpilib/allwpilib/docs/release/cpp/classfrc_1_1Encoder.html
image
and GetRate()
image

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 second

Also 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.
image

The doxygen documentation for all of the methods, e.g. GetPeriod() all mention that they are scaled by SetDistancePerPulse() which helps a lot.

image-20210119171956016

constexpr double kEncoderDistancePerPulse = 6 /* inch */ * 0.0254 * 3.14 / 4096;
encoder.SetDistancePerPulse(kEncoderDistancePerPulse);
encoder.GetDistance(); // meters
encoder.GetRate(); // meters per second

Part 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
image

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 ConfigSelectedFeedbackCoefficient or 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions