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