Setting Falcon 500 motor position using encoders and Robotpy

My team and I have been banging our heads against the wall for weeks now trying to set a motor to a specific position using the onboard encoders. This is one of many iterations over the last few weeks:

#!/usr/bin/env python3
import wpilib
import ctre

class MyRobot(wpilib.TimedRobot):

    def testInit(self):
        """Robot initialization function"""
        self.motor_control_mode = ctre.TalonFXControlMode.Position
        self.feedback_device = ctre.TalonFXFeedbackDevice.IntegratedSensor
        self.motor0 = ctre.TalonFX(0)
        self.motor0.configSelectedFeedbackSensor(self.feedback_device)
        self.joyStick = wpilib.XboxController(3)  # initialize the joystick on port 0

        print("#"*50)
        self.motor0.setSelectedSensorPosition(100)
        print(self.motor0.getSelectedSensorPosition())
        print("#"*50)

        self.sensor_count = 0

    def testPeriodic(self):
        #"""Runs the motor from a joystick."""
        #self.sensor_count += 1
        #self.motor0.set(self.motor_control_mode, self.sensor_count)
        pass

if __name__ == "__main__":
    wpilib.run(MyRobot)

The goal of this code is to get a motor, set its integrated sensor to a position, then print that new position out. The code I have never sets the sensor position and always prints out 0.0 in the simulation. There’s a ton of documentation for Robotpy, but there doesn’t seem to be any one page that says “THIS IS HOW TO GET AN ENCODER WORKING”. Any help would be appreciated.

If you have limited software resources, you’re probably best sticking to official WPI classes (which have accompanying documentation rather than vendor libraries. The official examples are for Java/C++, but they should match the Python wrappers pretty closely.

If I were attempting this, I think I would try to verify the setting of the sensor position through the Phoenix tuner software. You are adding another layer of possible issues by trying to verify the encoder value with the simulator. What do the print statements show you in the console?

If you are using 2022 RobotPy libraries please make sure that you have at least robotpy-ctre 2022.0.5 installed. The CTRE libraries in particular were very broken because of a regression in the binding technology we use, and could cause things to not work that would have otherwise worked.

For non-wpilib vendor libraries, it’s best to refer to the vendor’s documentation for the best way to use their software/hardware. CTRE has a very large documentation site that covers how to configure their motor controllers and use their sensors. One thing to note about the CTRE API is that most functions return an ErrorCode which can tell you why an operation failed (if it did), as opposed to throwing an exception.

Finally, while all WPILib hardware components have standardized simulation support, vendors have varying degrees of simulation support (pre-pandemic, it was basically non-existent… so it’s much better now!). Once again, refer to the vendor’s documentation for the kinds of things that they do and don’t support. You can also email them, usually they want teams to be successful with their product :slight_smile:

Now as for your example… well, it doesn’t work for me either. Yay. I played with it for a bit, and I couldn’t get it to work either, so I tried to simplify it. I noticed this weird message in my console:

CTR: CAN frame not received/too-stale.
        Talon FX 0 GetSelectedSensorPosition

It seems that no matter what call I make, that error shows up… which leads me to believe that it just doesn’t work in simulation?

Ok, fine, so I fired up my C++ vscode project, and this doesn’t work either:

#include <ctre/phoenix/motorcontrol/can/WPI_TalonFX.h>

int main() {
  ctre::phoenix::motorcontrol::can::WPI_TalonFX motor(0);
  auto& collection = motor.GetSensorCollection();
  collection.SetIntegratedSensorPosition(10);
  printf("Get %f\n", collection.GetIntegratedSensorPosition());
  return 1;
}

I also tried a variant of your example in C++, and got the same CAN frame not received/too-stale. error.

So my guess is that CTRE doesn’t fully support all features in simulation, or it requires some non-obvious configuration. Their documentation alludes to this, but I think I’ve spent enough time playing with it for tonight. @Jacob_C or someone else at CTRE might know?

One last thing, I had noticed in their documentation that there’s a getSimCollection API for setting values in simulation. That didn’t seem to work with integrated sensor either, even though it has functions for manipulating sensors.

Keep in mind that CAN bus timings are part of the simulation.

So if you modify a value and then call a getter that depends on updated data from the device, you need to wait the appropriate amount of time before attempting to call it or you’ll get the standard “not received” CAN error.

1 Like

That paragraph should definitely be in your simulation docs. When you say it it’s obvious, but hadn’t thought of that!

And so here we are, this works as expected:


import wpilib
import ctre

class MyRobot(wpilib.TimedRobot):
    def robotInit(self) -> None:
        self.motor = ctre.WPI_TalonFX(0)
        self.timer = wpilib.Timer()
        self.timer.start()

    def disabledInit(self) -> None:
        self.i = 0

    def disabledPeriodic(self) -> None:

        self.i += 1
        collection = self.motor.getSensorCollection()
        collection.setIntegratedSensorPosition(self.i)

        if self.timer.hasPeriodPassed(1):
            self.logger.info("TalonFX: %f", collection.getIntegratedSensorPosition())

if __name__ == "__main__":
    wpilib.run(MyRobot)

Which prints out (among other things):

********** Robot program startup complete **********
Default RobotPeriodic() method... Override me!
Default SimulationPeriodic() method... Override me!
11:10:29:652 INFO    : robot               : TalonFX: 48.000000
11:10:30:652 INFO    : robot               : TalonFX: 96.000000
11:10:31:652 INFO    : robot               : TalonFX: 144.000000
[phoenix] Library initialization is complete.

11:10:32:652 INFO    : robot               : TalonFX: 192.000000
11:10:33:652 INFO    : robot               : TalonFX: 240.000000
[phoenix-diagnostics] Server 1.9.0 (Jan  4 2022,20:30:06) running on port: 1250

11:10:34:652 INFO    : robot               : TalonFX: 288.000000
11:10:34:736 INFO    : robotpy             : Robot code exited
[phoenix-diagnostics] Server shutdown cleanly. (dur:10|0)

[phoenix] Library shutdown cleanly

[phoenix-diagnostics] Server shutdown cleanly. (dur:10|0)

Thank you so much for your helpful posts. Everything is working in the simulator now, but when I try to use those positions on a real motor it doesn’t work. It feels like I’m close though. Just for documentation’s sake, here is a list of things I’ve made sure to do:

After testing things manually to make sure the motor responds to joystick input, I tried your code. I changed the methods from your code snippet from disabled* to test* and the code unfortunately still doesn’t move the motor. Here is what I am trying:

import wpilib
import ctre

class MyRobot(wpilib.TimedRobot):
    def robotInit(self) -> None:
        self.motor = ctre.WPI_TalonFX(0)
        self.timer = wpilib.Timer()
        self.timer.start()
        self.joystick(0)

    def testInit(self) -> None:
        self.i = 0

    def testPeriodic(self) -> None:

        # self.motor.set(self.joystick.getY())

        self.i += 1
        collection = self.motor.getSensorCollection()
        collection.setIntegratedSensorPosition(self.i)

        if self.timer.hasPeriodPassed(1):
            self.logger.info("TalonFX: %f", collection.getIntegratedSensorPosition())

if __name__ == "__main__":
    wpilib.run(MyRobot)

I see that in the documentation for robotpy-ctre (found here: WPI_TalonFX — RobotPy CTRE 2022.0.5 documentation), it mentions that motors can be set to a position using ctre.ControlMode.Position. This doesn’t seem to work either:

import wpilib
import ctre

class MyRobot(wpilib.TimedRobot):
    def robotInit(self) -> None:
        self.motor = ctre.WPI_TalonFX(0)
        self.timer = wpilib.Timer()
        self.timer.start()
        self.joystick(0)
        self.mode = ctre.ControlMode.Position

    def testInit(self) -> None:
        self.i = 0

    def testPeriodic(self) -> None:

        # self.motor.set(self.joystick.getY())

        self.i += 1
        collection = self.motor.getSensorCollection()
        collection.setIntegratedSensorPosition(self.i)
        self.motor.set(self.mode, collection.getIntegratedSensorPosition())

        if self.timer.hasPeriodPassed(1):
            self.logger.info("TalonFX: %f", collection.getIntegratedSensorPosition())

if __name__ == "__main__":
    wpilib.run(MyRobot)

Right now this links to the simulation page. Is this correct, or is it supposed to link to Bring Up: Talon FX/SRX Sensors — Phoenix documentation?

Yes, sorry about that.

With a motor controller as complex as the CTRE controllers, it’s inevitable that simulation isn’t going to capture all the subtleties of the real thing. I often tell my students that “simulation is often primarily useful for testing the logical behavior of your code, so you’re still going to have to make it work for real once you get it working in sim” for this very reason.

I’d recommend going through CTREs sensor bring up guide that you linked to (and their motor guides as well). It’s very comprehensive. I haven’t tried any of the code in this post on a real robot.

OK, so after reading through CTRE’s documentation (found here: https://store.ctr-electronics.com/content/user-manual/Falcon%20500%20User%20Guide.pdf) I wasn’t able to find anything that explicitly tells me how to give a physical Falcon 500 motor a position to rotate to (either relative or absolute) and it just does it without any further intervention. The closest thing I’ve been able to do is tell it to go a certain speed until it’s integrated sensor position is above a certain value, which feels like a janky workaround.

import wpilib
import ctre

class MyRobot(wpilib.TimedRobot):

    def robotInit(self) -> None:
        self.motor = ctre.WPI_TalonFX(6)
        self.timer = wpilib.Timer()
        self.timer.start()
        self.joystick = wpilib.Joystick(0)
        self.collection = self.motor.getSensorCollection()
        self.collection.setIntegratedSensorPosition(0)

    def testInit(self) -> None:
        self.i = 0

    def testPeriodic(self) -> None:
        self.i += 1
        self.collection.setIntegratedSensorPosition(self.i)
        # self.motor.set(self.mode, self.collection.getIntegratedSensorPosition())

        if self.timer.hasPeriodPassed(1):
            if self.collection.getIntegratedSensorPosition() < 2048 * 10:
                self.motor.set(0.25)
            else:
                self.motor.set(0)
            self.logger.info("TalonFX: %f", self.collection.getIntegratedSensorPosition())

if __name__ == "__main__":
    wpilib.run(MyRobot)

If someone from CTRE could chime in on this I would really appreciate the help. Another mentor (who mainly does robot hardware) tells me that my code above is the way to go, but with how often I am seeing positional data being referenced in the documentation, it just doesn’t feel right. It feels like there has to be an elegant solution to this akin to what @virtuald posted above:

        collection = self.motor.getSensorCollection()
        collection.setIntegratedSensorPosition(self.i)

You won’t find anything that does this, because this is a nontrivial controls problem that depends a bunch on what you have attached to the end of the motor.

You can use the PID control functionality to achieve this, but it requires some tuning.

If you want to precisely move the motor to a position, as Oblarg mentioned, you can use PID and motion profiling, which requires some tuning. If you are unfamiliar with PID, I would check out WPILib’s Advanced Controls documentation.

From there, I would recommend looking at our examples, such as our Motion Magic example. Obviously the syntax between Java and robotpy is different, but the logic is the same. For more information on onboard closed-loop control, you can check out our Closed Loop docs.

If you prefer, you can instead use WPILib classes to run a PID closed loop or motion profiling, which they cover in their Advanced Controls documentation.

On a side note, I know you were using the SensorCollection methods for debugging, but I highly recommend you instead use the get/setSelectedSensor*() methods as you had originally done. By default, the values reported by the SensorCollection methods are only updated once every >100 ms, while the values reported by the getSelectedSensor methods are updated once every 20 ms. For more information, check out our Status Frame Periods documentation. Additionally, only the selectedSensor methods follow motor inversion.

4 Likes

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.