Valkyrie 2026
Loading...
Searching...
No Matches
PhoenixController.h
1#pragma once
2#ifdef _MSC_VER
3#pragma warning(push)
4#pragma warning(disable : 4250) // Disable "inherits via dominance" warning
5#else
6#pragma GCC diagnostic push
7#pragma GCC diagnostic ignored "-Woverloaded-virtual"
8#endif
9
10#include <ctime>
11#include <cwchar>
12#include <memory>
13#include <string>
14#include <unordered_map>
15
16#include <ctre/phoenix6/CANBus.hpp>
17#include <ctre/phoenix6/CANcoder.hpp>
18#include <ctre/phoenix6/TalonFX.hpp>
19#include <ctre/phoenix6/configs/MotionMagicConfigs.hpp>
20#include <ctre/phoenix6/sim/ChassisReference.hpp>
21#include <frc/RobotController.h>
22#include <units/angular_acceleration.h>
23#include <units/angular_jerk.h>
24#include <units/angular_velocity.h>
25#include <units/base.h>
26#include <units/dimensionless.h>
27#include <units/frequency.h>
28#include <units/voltage.h>
29#include <valkyrie/controllers/BaseController.h>
30#include <valkyrie/util/PhoenixSignalManager.h>
31
32// Conversion guide:
33// https://v6.docs.ctr-electronics.com/en/latest/docs/migration/migration-guide/closed-loop-guide.html
34
35namespace valor {
36
37enum PhoenixControllerType {
38 KRAKEN_X60,
39 KRAKEN_X60_FOC,
40 KRAKEN_X44,
41 KRAKEN_X44_FOC,
42 FALCON,
43 FALCON_FOC,
44};
45
46class CorePhoenixController : public BaseController, public ctre::phoenix6::hardware::core::CoreTalonFX {
47 static inline const std::unordered_map<PhoenixControllerType, frc::DCMotor> MOTOR_SPEC_MAP{
48 {KRAKEN_X60, frc::DCMotor::KrakenX60()}, {KRAKEN_X60_FOC, frc::DCMotor::KrakenX60FOC()},
49 {KRAKEN_X44, frc::DCMotor::KrakenX44()}, {KRAKEN_X44_FOC, frc::DCMotor::KrakenX44FOC()},
50 {FALCON, frc::DCMotor::Falcon500()}, {FALCON_FOC, frc::DCMotor::Falcon500FOC()}};
51
52 public:
53 CorePhoenixController(PhoenixControllerType controllerType, int canID, valor::NeutralMode neutralMode, bool inverted,
54 std::string canbus = "");
55
56 explicit CorePhoenixController(PhoenixControllerType controllerType)
57 : BaseController{MOTOR_SPEC_MAP.at(controllerType)}, ctre::phoenix6::hardware::core::CoreTalonFX{-1} {}
58
59 virtual ~CorePhoenixController();
60
61 inline void ApplyConfig() override { GetConfigurator().Apply(config, 5_s); }
62
63 inline void Reset() override { SetEncoderPosition(0_tr); }
64
65 void SetNeutralMode(valor::NeutralMode mode, bool saveImmediately = false) override;
66
67 void SetCurrentLimits(units::ampere_t statorCurrentLimit, units::ampere_t supplyCurrentLimit, units::ampere_t supplyCurrentThreshold,
68 units::second_t supplyTimeThreshold, bool saveImmediately = false);
69
70 void SetVoltageLimits(units::volt_t reverseLimit, units::volt_t forwardLimit, bool saveImmediately = false);
71
73
74 inline ctre::phoenix6::StatusSignal<units::turn_t>& GetPositionSignal(bool refresh = true) {
75 return ctre::phoenix6::hardware::core::CoreTalonFX::GetPosition(refresh);
76 }
77
79
80 Signal<units::turns_per_second_squared_t> GetAcceleration() const override;
81
82 inline ctre::phoenix6::StatusSignal<units::turns_per_second_squared_t>& GetAccelerationSignal(bool refresh = true) {
83 return ctre::phoenix6::hardware::core::CoreTalonFX::GetAcceleration(refresh);
84 }
85
87
88 Signal<units::volt_t> GetVoltage() const override;
89
90 inline void SetEncoderPosition(units::turn_t position) override { ctre::phoenix6::hardware::core::CoreTalonFX::SetPosition(position); }
91
92 void SetContinuousWrap(bool continuousWrap, bool saveImmediately = false);
93
94 void PowerSetter(Output out) override;
95
96 void SetupFollower(int canID, bool followerInverted = false) override;
97
98 void SetupReverseHardwareLimit(int canID, ctre::phoenix6::signals::ReverseLimitTypeValue type, units::turn_t autosetPosition = 0_tr,
99 bool saveImmediately = false);
100
101 void SetupForwardHardwareLimit(int canID, ctre::phoenix6::signals::ForwardLimitTypeValue type, units::turn_t autosetPosition = 0_tr,
102 bool saveImmediately = false);
103
104 void SetForwardLimit(units::turn_t forward, bool saveImmediately = false) override;
105
106 void SetReverseLimit(units::turn_t reverse, bool saveImmediately = false) override;
107
108 void SetGearRatios(double _rotorToSensor, double _sensorToMech, bool saveImmediately = false) override;
109
110 ctre::phoenix6::signals::MagnetHealthValue GetMagnetHealth();
111
112 units::turn_t GetAbsoluteEncoderPosition() const override;
113
114 void SetupCANcoder(int deviceId, units::turn_t offset, bool clockwise, std::string canbus = "", units::turn_t absoluteRange = 1_tr,
115 bool saveImmediately = false);
116
117 inline ctre::phoenix6::hardware::CANcoder* GetCANcoder() { return cancoder.get(); }
118
119 void SetOpenLoopRamp(units::second_t time, bool saveImmediately = false) override;
120
121 units::volt_t CalculateAppliedVoltage() override;
122
123 using BaseController::SetSimState;
124 void SetSimState(units::turn_t pos, units::turns_per_second_t vel, units::turns_per_second_squared_t accel);
125
126 void LoggablePeriodic() override;
127
128 protected:
129 static constexpr units::ampere_t STATOR_CURRENT_LIMIT = 40_A;
130 static constexpr units::ampere_t SUPPLY_CURRENT_LIMIT = 30_A;
131 static constexpr units::ampere_t SUPPLY_CURRENT_THRESHOLD = 30_A;
132 static constexpr units::millisecond_t SUPPLY_TIME_THRESHOLD = 1_s;
133 static constexpr units::ampere_t PEAK_TORQUE_CURRENT = 100_A;
134 static constexpr units::turn_t DEADBAND = 0.01_tr;
135
136 std::unique_ptr<CorePhoenixController> followerMotor;
137
138 std::unique_ptr<ctre::phoenix6::hardware::CANcoder> cancoder;
139 std::unique_ptr<ctre::phoenix6::configs::MagnetSensorConfigs> cancoderConfig;
140
141 ctre::phoenix6::configs::TalonFXConfiguration config;
142
143 ctre::phoenix6::controls::VoltageOut voltageRequest{0_V};
144 ctre::phoenix6::controls::DutyCycleOut dutyCycleRequest{0};
145 ctre::phoenix6::controls::TorqueCurrentFOC currentRequest{0_A};
146};
147
148template <class VelocityOutput = ctre::phoenix6::controls::VelocityTorqueCurrentFOC,
149 class PositionOutput = ctre::phoenix6::controls::DynamicMotionMagicTorqueCurrentFOC>
150class PhoenixController : public CorePhoenixController {
151 static_assert(std::is_base_of_v<ctre::phoenix6::controls::ControlRequest, VelocityOutput>);
152 static_assert(std::is_base_of_v<ctre::phoenix6::controls::ControlRequest, PositionOutput>);
153
154 public:
155 PhoenixController(PhoenixControllerType controllerType, int canID, valor::NeutralMode neutralMode, bool inverted,
156 std::string canbus = "")
157 : CorePhoenixController{controllerType, canID, neutralMode, inverted, canbus} {
158 positionRequest.UpdateFreqHz = 0_Hz;
159 velocityRequest.UpdateFreqHz = 0_Hz;
160
161 bool enableFOC = controllerType == PhoenixControllerType::KRAKEN_X60_FOC ||
162 controllerType == PhoenixControllerType::KRAKEN_X44_FOC || controllerType == PhoenixControllerType::FALCON_FOC;
163 voltageRequest.EnableFOC = enableFOC;
164 dutyCycleRequest.EnableFOC = enableFOC;
165 TryEnableFOC(velocityRequest, enableFOC);
166 TryEnableFOC(positionRequest, enableFOC);
167 };
168
169 explicit PhoenixController(PhoenixControllerType controllerType = KRAKEN_X60_FOC) : CorePhoenixController{controllerType} {}
170
171 void SetNeutralMode(valor::NeutralMode mode, bool saveImmediately = false) override {
172 if constexpr (requires { positionRequest.OverrideBrakeDurNeutral; })
173 positionRequest.OverrideBrakeDurNeutral = mode == valor::NeutralMode::Brake;
174 else
175 positionRequest.OverrideCoastDurNeutral = mode == valor::NeutralMode::Coast;
176
177 if constexpr (requires { velocityRequest.OverrideBrakeDurNeutral; })
178 velocityRequest.OverrideBrakeDurNeutral = mode == valor::NeutralMode::Brake;
179 else
180 velocityRequest.OverrideCoastDurNeutral = mode == valor::NeutralMode::Coast;
181
182 CorePhoenixController::SetNeutralMode(mode, saveImmediately);
183 }
184
185 void PositionSetter(units::turn_t position, Output feedforward, int slot) override {
186 std::visit(
187 [=, this](auto&& ff) {
188 using FFType = decltype(positionRequest.FeedForward);
189 if constexpr (std::is_same_v<std::decay_t<decltype(ff)>, FFType>)
190 SetControl(positionRequest.WithSlot(slot).WithPosition(position).WithFeedForward(ff));
191 else {
192 if (ff.value())
193 FRC_ReportWarning("Position control feedforward is of the wrong unit, using zero instead...");
194 SetControl(positionRequest.WithSlot(slot).WithPosition(position).WithFeedForward(FFType{0}));
195 }
196 },
197 feedforward);
198 }
199
200 using BaseController::SetPosition;
201
202 void SetPosition(units::turn_t position, units::turns_per_second_t maxVelocity, units::turns_per_second_squared_t maxAcceleration,
203 units::turns_per_second_cubed_t maxJerk, Output feedforward = 0, int slot = 0) {
204 positionRequest.Velocity = maxVelocity;
205 positionRequest.Acceleration = maxAcceleration;
206 positionRequest.Jerk = maxJerk;
207 SetPosition(position, feedforward, slot);
208 }
209
210 void SpeedSetter(units::turns_per_second_t velocity, Output feedforward, int slot) override {
211 std::visit(
212 [=, this](auto&& ff) {
213 using FFType = decltype(velocityRequest.FeedForward);
214 if constexpr (std::is_same_v<std::decay_t<decltype(ff)>, FFType>)
215 SetControl(velocityRequest.WithSlot(slot).WithVelocity(velocity).WithFeedForward(ff));
216 else {
217 if (ff.value())
218 FRC_ReportWarning("Velocity control feedforward is of the wrong unit, using zero instead...");
219 SetControl(velocityRequest.WithSlot(slot).WithVelocity(velocity).WithFeedForward(FFType{0}));
220 }
221 },
222 feedforward);
223 }
224
225 void UseTimesync(units::hertz_t controlFreq, bool saveImmediately = false) {
226 config.MotorOutput.ControlTimesyncFreqHz = controlFreq;
227 positionRequest.UseTimesync = true;
228 velocityRequest.UseTimesync = true;
229
230 if (saveImmediately)
231 GetConfigurator().Apply(config.MotorOutput);
232 }
233
234 void SetPIDF(valor::PIDF<> pidf, int slot = 0, bool saveImmediately = false) override {
235 if constexpr (requires { positionRequest.Jerk; }) {
236 positionRequest.Jerk = decltype(positionRequest.Jerk)(pidf.maxJerk.value());
237 positionRequest.Acceleration = decltype(positionRequest.Acceleration)(pidf.maxAcceleration.value());
238 positionRequest.Velocity = decltype(positionRequest.Velocity)(pidf.maxVelocity.value());
239 }
240
241 config.MotionMagic.MotionMagicJerk = decltype(config.MotionMagic.MotionMagicJerk)(pidf.maxJerk.value());
242 config.MotionMagic.MotionMagicAcceleration = decltype(config.MotionMagic.MotionMagicAcceleration)(pidf.maxAcceleration.value());
243 config.MotionMagic.MotionMagicCruiseVelocity = decltype(config.MotionMagic.MotionMagicCruiseVelocity)(pidf.maxVelocity.value());
244
245 if (saveImmediately) {
246 GetConfigurator().Apply(config.MotionMagic);
247 }
248
249 if (slot == 1)
250 SetPIDFSlot(config.Slot1, pidf, saveImmediately);
251 else if (slot == 2)
252 SetPIDFSlot(config.Slot2, pidf, saveImmediately);
253 else
254 SetPIDFSlot(config.Slot0, pidf, saveImmediately); // Default case
255 }
256
257 private:
258 inline PositionOutput CreatePositionRequest() {
259 if constexpr (requires { PositionOutput{0_tr}; })
260 return {0_tr};
261 else
262 return {0_tr, 0_tps, 0_tr_per_s_sq};
263 }
264
265 // Helper function for enableFOC that only enables if the template has the field "EnableFOC"
266 template <typename T>
267 static constexpr void TryEnableFOC(T& request, bool enable) {
268 if constexpr (requires { request.EnableFOC; })
269 request.EnableFOC = enable;
270 }
271
272 template <class S>
273 void SetPIDFSlot(S& slot, valor::PIDF<> pidf, bool saveImmediately) {
274 // Generic PIDF configurations
275 // Numerator for closed loop controls will be in volts
276 slot.kP = pidf.P.value();
277 slot.kI = pidf.I.value();
278 slot.kD = pidf.D.value();
279 slot.kS = pidf.kS.value();
280 if (pidf.kV)
281 slot.kV = (*pidf.kV).value();
282 else if constexpr (std::is_same_v<decltype(positionRequest.FeedForward), units::ampere_t> ||
283 std::is_same_v<decltype(velocityRequest.FeedForward), units::ampere_t>)
284 slot.kV = 0;
285 else
286 slot.kV = (voltageCompensation / GetMaxMechanismSpeed()).value();
287
288 // Feedforward gain configuration
289 slot.GravityArmPositionOffset = pidf.GTarget;
290 slot.GravityType = pidf.GType == valor::GravityFFType::LINEAR ? ctre::phoenix6::signals::GravityTypeValue::Elevator_Static
291 : ctre::phoenix6::signals::GravityTypeValue::Arm_Cosine;
292 slot.kG = pidf.kG.value();
293
294 if (saveImmediately)
295 GetConfigurator().Apply(slot);
296 }
297
298 VelocityOutput velocityRequest{0_tps};
299 PositionOutput positionRequest = CreatePositionRequest();
300};
301
302} // namespace valor
303
304#ifdef _MSC_VER
305#pragma warning(pop)
306#else
307#pragma GCC diagnostic pop
308#endif
Definition BaseController.h:62
constexpr units::turns_per_second_t GetMaxMechanismSpeed()
Get the mechanisms's maximum speed.
Definition BaseController.h:119
BaseController(frc::DCMotor _motorSpec)
Construct a new Valor Controller object.
Definition BaseController.h:95
void LoggablePeriodic() override
Periodic callback for logging updates.
void SetupFollower(int canID, bool followerInverted=false) override
If a motor is paired with another motor, setup that other motor as a follower.
void SetNeutralMode(valor::NeutralMode mode, bool saveImmediately=false) override
Set the neutral mode of the motor.
Signal< units::turns_per_second_t > GetSpeed() const override
Get the motors speed.
Signal< units::ampere_t > GetCurrent() const override
Get the motors output current.
void SetForwardLimit(units::turn_t forward, bool saveImmediately=false) override
Set the forward soft limit for the motor.
Signal< units::turn_t > GetPosition() const override
Get the motors position.
void Reset() override
Resets the motor and any state.
Definition PhoenixController.h:63
void SetGearRatios(double _rotorToSensor, double _sensorToMech, bool saveImmediately=false) override
Set the gear ratios for the motor.
void SetReverseLimit(units::turn_t reverse, bool saveImmediately=false) override
Set the reverse soft limit for the motor.
void SetNeutralMode(valor::NeutralMode mode, bool saveImmediately=false) override
Set the neutral mode of the motor.
Definition PhoenixController.h:171
void SetPIDF(valor::PIDF<> pidf, int slot=0, bool saveImmediately=false) override
Change the PIDF values for the motor.
Definition PhoenixController.h:234
void PositionSetter(units::turn_t position, Output feedforward, int slot) override
Send the motor to a specific position.
Definition PhoenixController.h:185
void SpeedSetter(units::turns_per_second_t velocity, Output feedforward, int slot) override
Send the motor to a specific speed.
Definition PhoenixController.h:210
Container to hold PID and feed forward values for the motor controller.
Definition PIDF.h:26