blob: c94bb232a1777fe684f9bbaba972bc251e303a6a [file] [log] [blame]
Austin Schuh844960d2013-03-09 17:07:51 -08001#ifndef FRC971_CONTROL_LOOPS_ZEROED_JOINT_H_
2#define FRC971_CONTROL_LOOPS_ZEROED_JOINT_H_
3
4#include <memory>
5
James Kuszmaul61750662021-06-21 21:32:33 -07006#include "frc971/control_loops/control_loop.h"
Austin Schuh844960d2013-03-09 17:07:51 -08007#include "frc971/control_loops/state_feedback_loop.h"
Austin Schuh844960d2013-03-09 17:07:51 -08008
9namespace frc971 {
10namespace control_loops {
11namespace testing {
12class WristTest_NoWindupPositive_Test;
13class WristTest_NoWindupNegative_Test;
Philipp Schrader790cb542023-07-05 21:06:52 -070014}; // namespace testing
Austin Schuh844960d2013-03-09 17:07:51 -080015
Austin Schuhc1f68892013-03-16 17:06:27 -070016// Note: Everything in this file assumes that there is a 1 cycle delay between
17// power being requested and it showing up at the motor. It assumes that
18// X_hat(2, 1) is the voltage being applied as well. It will go unstable if
19// that isn't true.
20
Philipp Schrader790cb542023-07-05 21:06:52 -070021template <int kNumZeroSensors>
Austin Schuh844960d2013-03-09 17:07:51 -080022class ZeroedJoint;
23
24// This class implements the CapU function correctly given all the extra
25// information that we know about from the wrist motor.
Philipp Schrader790cb542023-07-05 21:06:52 -070026template <int kNumZeroSensors>
Austin Schuhc1f68892013-03-16 17:06:27 -070027class ZeroedStateFeedbackLoop : public StateFeedbackLoop<3, 1, 1> {
Austin Schuh844960d2013-03-09 17:07:51 -080028 public:
Austin Schuhc1f68892013-03-16 17:06:27 -070029 ZeroedStateFeedbackLoop(StateFeedbackLoop<3, 1, 1> loop,
Austin Schuh844960d2013-03-09 17:07:51 -080030 ZeroedJoint<kNumZeroSensors> *zeroed_joint)
Austin Schuhc1f68892013-03-16 17:06:27 -070031 : StateFeedbackLoop<3, 1, 1>(loop),
32 zeroed_joint_(zeroed_joint),
33 voltage_(0.0),
Philipp Schrader790cb542023-07-05 21:06:52 -070034 last_voltage_(0.0) {}
Austin Schuh844960d2013-03-09 17:07:51 -080035
36 // Caps U, but this time respects the state of the wrist as well.
37 virtual void CapU();
Austin Schuhc1f68892013-03-16 17:06:27 -070038
39 // Returns the accumulated voltage.
40 double voltage() const { return voltage_; }
41
42 // Returns the uncapped voltage.
43 double uncapped_voltage() const { return uncapped_voltage_; }
44
45 // Zeros the accumulator.
46 void ZeroPower() { voltage_ = 0.0; }
Philipp Schrader790cb542023-07-05 21:06:52 -070047
Austin Schuh844960d2013-03-09 17:07:51 -080048 private:
49 ZeroedJoint<kNumZeroSensors> *zeroed_joint_;
Austin Schuhc1f68892013-03-16 17:06:27 -070050
51 // The accumulated voltage to apply to the motor.
52 double voltage_;
53 double last_voltage_;
54 double uncapped_voltage_;
Austin Schuh844960d2013-03-09 17:07:51 -080055};
56
Philipp Schrader790cb542023-07-05 21:06:52 -070057template <int kNumZeroSensors>
Austin Schuh844960d2013-03-09 17:07:51 -080058void ZeroedStateFeedbackLoop<kNumZeroSensors>::CapU() {
Austin Schuhc1f68892013-03-16 17:06:27 -070059 const double old_voltage = voltage_;
60 voltage_ += U(0, 0);
61
62 uncapped_voltage_ = voltage_;
63
64 // Do all our computations with the voltage, and then compute what the delta
65 // is to make that happen.
Austin Schuh844960d2013-03-09 17:07:51 -080066 if (zeroed_joint_->state_ == ZeroedJoint<kNumZeroSensors>::READY) {
67 if (Y(0, 0) >= zeroed_joint_->config_data_.upper_limit) {
Austin Schuhc1f68892013-03-16 17:06:27 -070068 voltage_ = std::min(0.0, voltage_);
Austin Schuh844960d2013-03-09 17:07:51 -080069 }
70 if (Y(0, 0) <= zeroed_joint_->config_data_.lower_limit) {
Austin Schuhc1f68892013-03-16 17:06:27 -070071 voltage_ = std::max(0.0, voltage_);
Austin Schuh844960d2013-03-09 17:07:51 -080072 }
73 }
74
75 const bool is_ready =
76 zeroed_joint_->state_ == ZeroedJoint<kNumZeroSensors>::READY;
Philipp Schrader790cb542023-07-05 21:06:52 -070077 double limit =
78 is_ready ? 12.0 : zeroed_joint_->config_data_.max_zeroing_voltage;
Austin Schuh844960d2013-03-09 17:07:51 -080079
Austin Schuhc1f68892013-03-16 17:06:27 -070080 // Make sure that reality and the observer can't get too far off. There is a
81 // delay by one cycle between the applied voltage and X_hat(2, 0), so compare
82 // against last cycle's voltage.
83 if (X_hat(2, 0) > last_voltage_ + 2.0) {
Philipp Schrader790cb542023-07-05 21:06:52 -070084 // X_hat(2, 0) = last_voltage_ + 2.0;
Brian Silvermanae9d4b72013-03-16 20:11:29 -070085 voltage_ -= X_hat(2, 0) - (last_voltage_ + 2.0);
86 LOG(DEBUG, "X_hat(2, 0) = %f\n", X_hat(2, 0));
Philipp Schrader790cb542023-07-05 21:06:52 -070087 } else if (X_hat(2, 0) < last_voltage_ - 2.0) {
88 // X_hat(2, 0) = last_voltage_ - 2.0;
Brian Silvermanae9d4b72013-03-16 20:11:29 -070089 voltage_ += X_hat(2, 0) - (last_voltage_ - 2.0);
90 LOG(DEBUG, "X_hat(2, 0) = %f\n", X_hat(2, 0));
Austin Schuhc1f68892013-03-16 17:06:27 -070091 }
92
Brian Silvermanae9d4b72013-03-16 20:11:29 -070093 voltage_ = std::min(limit, voltage_);
94 voltage_ = std::max(-limit, voltage_);
95 U(0, 0) = voltage_ - old_voltage;
96 LOG(DEBUG, "abc %f\n", X_hat(2, 0) - voltage_);
97 LOG(DEBUG, "error %f\n", X_hat(0, 0) - R(0, 0));
98
Austin Schuhc1f68892013-03-16 17:06:27 -070099 last_voltage_ = voltage_;
Austin Schuh844960d2013-03-09 17:07:51 -0800100}
101
Austin Schuh844960d2013-03-09 17:07:51 -0800102// Class to zero and control a joint with any number of zeroing sensors with a
103// state feedback controller.
Philipp Schrader790cb542023-07-05 21:06:52 -0700104template <int kNumZeroSensors>
Austin Schuh844960d2013-03-09 17:07:51 -0800105class ZeroedJoint {
106 public:
107 // Sturcture to hold the hardware configuration information.
108 struct ConfigurationData {
109 // Angle at the lower hardware limit.
110 double lower_limit;
111 // Angle at the upper hardware limit.
112 double upper_limit;
113 // Speed (and direction) to move while zeroing.
114 double zeroing_speed;
Austin Schuhdd3bc412013-03-16 17:02:40 -0700115 // Speed (and direction) to move while moving off the sensor.
116 double zeroing_off_speed;
Austin Schuh844960d2013-03-09 17:07:51 -0800117 // Maximum voltage to apply when zeroing.
118 double max_zeroing_voltage;
Austin Schuh039d4f92013-04-04 05:52:03 +0000119 // Deadband voltage.
120 double deadband_voltage;
Austin Schuh844960d2013-03-09 17:07:51 -0800121 // Angles where we see a positive edge from the hall effect sensors.
122 double hall_effect_start_angle[kNumZeroSensors];
123 };
124
125 // Current position data for the encoder and hall effect information.
126 struct PositionData {
127 // Current encoder position.
128 double position;
129 // Array of hall effect values.
130 bool hall_effects[kNumZeroSensors];
131 // Array of the last positive edge position for the sensors.
132 double hall_effect_positions[kNumZeroSensors];
133 };
134
Austin Schuhc1f68892013-03-16 17:06:27 -0700135 ZeroedJoint(StateFeedbackLoop<3, 1, 1> loop)
Austin Schuh844960d2013-03-09 17:07:51 -0800136 : loop_(new ZeroedStateFeedbackLoop<kNumZeroSensors>(loop, this)),
Austin Schuhf2a50ba2016-12-24 16:16:26 -0800137 last_good_time_(::aos::monotonic_clock::min_time()),
Austin Schuh844960d2013-03-09 17:07:51 -0800138 state_(UNINITIALIZED),
139 error_count_(0),
140 zero_offset_(0.0),
Austin Schuhf2a50ba2016-12-24 16:16:26 -0800141 capped_goal_(false) {}
Austin Schuh844960d2013-03-09 17:07:51 -0800142
143 // Copies the provided configuration data locally.
144 void set_config_data(const ConfigurationData &config_data) {
145 config_data_ = config_data;
146 }
147
148 // Clips the goal to be inside the limits and returns the clipped goal.
149 // Requires the constants to have already been fetched.
150 double ClipGoal(double goal) const {
151 return ::std::min(config_data_.upper_limit,
152 std::max(config_data_.lower_limit, goal));
153 }
154
155 // Updates the loop and state machine.
156 // position is null if the position data is stale, output_enabled is true if
157 // the output will actually go to the motors, and goal_angle and goal_velocity
158 // are the goal position and velocities.
Austin Schuh9fe68f72019-08-10 19:32:03 -0700159 double Update(const ::aos::monotonic_clock::time_point monotonic_now,
160 const ZeroedJoint<kNumZeroSensors>::PositionData *position,
161 bool output_enabled, double goal_angle, double goal_velocity);
Austin Schuh844960d2013-03-09 17:07:51 -0800162
163 // True if the code is zeroing.
164 bool is_zeroing() const { return state_ == ZEROING; }
165
Austin Schuhe20e93c2013-03-09 19:54:16 -0800166 // True if the code is moving off the hall effect.
167 bool is_moving_off() const { return state_ == MOVING_OFF; }
168
Austin Schuh844960d2013-03-09 17:07:51 -0800169 // True if the state machine is uninitialized.
170 bool is_uninitialized() const { return state_ == UNINITIALIZED; }
171
172 // True if the state machine is ready.
173 bool is_ready() const { return state_ == READY; }
174
175 // Returns the uncapped voltage.
176 double U_uncapped() const { return loop_->U_uncapped(0, 0); }
177
178 // True if the goal was moved to avoid goal windup.
179 bool capped_goal() const { return capped_goal_; }
180
Austin Schuhe20e93c2013-03-09 19:54:16 -0800181 // Timestamp
182 static const double dt;
183
Brian Silverman893ba072013-03-16 14:03:50 -0700184 double absolute_position() const { return loop_->X_hat(0, 0); }
185
Austin Schuh844960d2013-03-09 17:07:51 -0800186 private:
187 friend class ZeroedStateFeedbackLoop<kNumZeroSensors>;
188 // Friend the wrist test cases so that they can simulate windeup.
189 friend class testing::WristTest_NoWindupPositive_Test;
190 friend class testing::WristTest_NoWindupNegative_Test;
191
Austin Schuhf2a50ba2016-12-24 16:16:26 -0800192 static constexpr ::aos::monotonic_clock::duration kRezeroTime;
Brian Silvermanede2d782013-03-22 18:11:34 -0700193
Austin Schuh844960d2013-03-09 17:07:51 -0800194 // The state feedback control loop to talk to.
195 ::std::unique_ptr<ZeroedStateFeedbackLoop<kNumZeroSensors>> loop_;
196
197 ConfigurationData config_data_;
198
Austin Schuhf2a50ba2016-12-24 16:16:26 -0800199 ::aos::monotonic_clock::time_point last_good_time_;
Brian Silvermanede2d782013-03-22 18:11:34 -0700200
Austin Schuhe20e93c2013-03-09 19:54:16 -0800201 // Returns the index of the first active sensor, or -1 if none are active.
202 int ActiveSensorIndex(
203 const ZeroedJoint<kNumZeroSensors>::PositionData *position) {
204 if (!position) {
205 return -1;
206 }
207 int active_index = -1;
208 for (int i = 0; i < kNumZeroSensors; ++i) {
209 if (position->hall_effects[i]) {
210 if (active_index != -1) {
211 LOG(ERROR, "More than one hall effect sensor is active\n");
212 } else {
213 active_index = i;
214 }
215 }
216 }
217 return active_index;
218 }
219 // Returns true if any of the sensors are active.
220 bool AnySensorsActive(
221 const ZeroedJoint<kNumZeroSensors>::PositionData *position) {
222 return ActiveSensorIndex(position) != -1;
223 }
224
Austin Schuh844960d2013-03-09 17:07:51 -0800225 // Enum to store the state of the internal zeroing state machine.
226 enum State {
227 UNINITIALIZED,
228 MOVING_OFF,
229 ZEROING,
230 READY,
Philipp Schrader790cb542023-07-05 21:06:52 -0700231 ESTOP,
Austin Schuh844960d2013-03-09 17:07:51 -0800232 };
233
234 // Internal state for zeroing.
235 State state_;
236
237 // Missed position packet count.
238 int error_count_;
239 // Offset from the raw encoder value to the absolute angle.
240 double zero_offset_;
241 // Position that gets incremented when zeroing the wrist to slowly move it to
242 // the hall effect sensor.
243 double zeroing_position_;
244 // Last position at which the hall effect sensor was off.
245 double last_off_position_;
246
247 // True if the zeroing goal was capped during this cycle.
248 bool capped_goal_;
249
Austin Schuhe20e93c2013-03-09 19:54:16 -0800250 // Returns true if number is between first and second inclusive.
251 bool is_between(double first, double second, double number) {
252 if ((number >= first || number >= second) &&
253 (number <= first || number <= second)) {
254 return true;
255 }
256 return false;
257 }
258
Austin Schuh844960d2013-03-09 17:07:51 -0800259 DISALLOW_COPY_AND_ASSIGN(ZeroedJoint);
260};
261
Austin Schuhe20e93c2013-03-09 19:54:16 -0800262template <int kNumZeroSensors>
Austin Schuhf2a50ba2016-12-24 16:16:26 -0800263constexpr ::aos::monotonic_clock::duration
264 ZeroedJoint<kNumZeroSensors>::kRezeroTime = ::std::chrono::seconds(2);
Brian Silvermanede2d782013-03-22 18:11:34 -0700265
266template <int kNumZeroSensors>
Austin Schuhe20e93c2013-03-09 19:54:16 -0800267/*static*/ const double ZeroedJoint<kNumZeroSensors>::dt = 0.01;
268
Austin Schuh844960d2013-03-09 17:07:51 -0800269// Updates the zeroed joint controller and state machine.
270template <int kNumZeroSensors>
271double ZeroedJoint<kNumZeroSensors>::Update(
Austin Schuh9fe68f72019-08-10 19:32:03 -0700272 const ::aos::monotonic_clock::time_point monotonic_now,
Austin Schuh844960d2013-03-09 17:07:51 -0800273 const ZeroedJoint<kNumZeroSensors>::PositionData *position,
Austin Schuh9fe68f72019-08-10 19:32:03 -0700274 bool output_enabled, double goal_angle, double goal_velocity) {
Austin Schuh844960d2013-03-09 17:07:51 -0800275 // Uninitialize the bot if too many cycles pass without an encoder.
276 if (position == NULL) {
277 LOG(WARNING, "no new pos given\n");
278 error_count_++;
Austin Schuh844960d2013-03-09 17:07:51 -0800279 }
280 if (error_count_ >= 4) {
Austin Schuh261c4052013-03-19 03:29:54 +0000281 output_enabled = false;
282 LOG(WARNING, "err_count is %d so disabling\n", error_count_);
Brian Silverman7992d6e2013-03-24 19:20:54 -0700283
Austin Schuh9fe68f72019-08-10 19:32:03 -0700284 if (monotonic_now > kRezeroTime + last_good_time_) {
Brian Silverman7992d6e2013-03-24 19:20:54 -0700285 LOG(WARNING, "err_count is %d (or 1st time) so forcing a re-zero\n",
286 error_count_);
287 state_ = UNINITIALIZED;
288 loop_->Reset();
289 }
Austin Schuh844960d2013-03-09 17:07:51 -0800290 }
Brian Silverman7992d6e2013-03-24 19:20:54 -0700291 if (position != NULL) {
Austin Schuh9fe68f72019-08-10 19:32:03 -0700292 last_good_time_ = monotonic_now;
Brian Silverman7992d6e2013-03-24 19:20:54 -0700293 error_count_ = 0;
Brian Silvermanede2d782013-03-22 18:11:34 -0700294 }
Austin Schuh844960d2013-03-09 17:07:51 -0800295
296 // Compute the absolute position of the wrist.
297 double absolute_position;
298 if (position) {
299 absolute_position = position->position;
300 if (state_ == READY) {
301 absolute_position -= zero_offset_;
302 }
303 loop_->Y << absolute_position;
Austin Schuhe20e93c2013-03-09 19:54:16 -0800304 if (!AnySensorsActive(position)) {
305 last_off_position_ = position->position;
Austin Schuh844960d2013-03-09 17:07:51 -0800306 }
307 } else {
308 // Dead recon for now.
309 absolute_position = loop_->X_hat(0, 0);
310 }
311
312 switch (state_) {
313 case UNINITIALIZED:
Austin Schuhb5191b92013-03-10 18:22:24 -0700314 LOG(DEBUG, "UNINITIALIZED\n");
Austin Schuh844960d2013-03-09 17:07:51 -0800315 if (position) {
316 // Reset the zeroing goal.
317 zeroing_position_ = absolute_position;
318 // Clear the observer state.
Austin Schuhc1f68892013-03-16 17:06:27 -0700319 loop_->X_hat << absolute_position, 0.0, 0.0;
320 loop_->ZeroPower();
Austin Schuh844960d2013-03-09 17:07:51 -0800321 // Set the goal to here to make it so it doesn't move when disabled.
322 loop_->R = loop_->X_hat;
323 // Only progress if we are enabled.
Brian Silverman699f0cb2015-02-05 19:45:01 -0500324 if (::aos::joystick_state->enabled) {
Austin Schuhe20e93c2013-03-09 19:54:16 -0800325 if (AnySensorsActive(position)) {
326 state_ = MOVING_OFF;
327 } else {
328 state_ = ZEROING;
Austin Schuh844960d2013-03-09 17:07:51 -0800329 }
330 }
331 }
332 break;
333 case MOVING_OFF:
Austin Schuhb5191b92013-03-10 18:22:24 -0700334 LOG(DEBUG, "MOVING_OFF\n");
Austin Schuhe20e93c2013-03-09 19:54:16 -0800335 {
336 // Move off the hall effect sensor.
Brian Silverman699f0cb2015-02-05 19:45:01 -0500337 if (!::aos::joystick_state->enabled) {
Austin Schuhe20e93c2013-03-09 19:54:16 -0800338 // Start over if disabled.
339 state_ = UNINITIALIZED;
340 } else if (position && !AnySensorsActive(position)) {
341 // We are now off the sensor. Time to zero now.
342 state_ = ZEROING;
343 } else {
344 // Slowly creep off the sensor.
Austin Schuhdd3bc412013-03-16 17:02:40 -0700345 zeroing_position_ -= config_data_.zeroing_off_speed * dt;
Austin Schuhc1f68892013-03-16 17:06:27 -0700346 loop_->R << zeroing_position_, -config_data_.zeroing_off_speed, 0.0;
Austin Schuhe20e93c2013-03-09 19:54:16 -0800347 break;
348 }
Austin Schuh844960d2013-03-09 17:07:51 -0800349 }
350 case ZEROING:
Austin Schuhb5191b92013-03-10 18:22:24 -0700351 LOG(DEBUG, "ZEROING\n");
Austin Schuhe20e93c2013-03-09 19:54:16 -0800352 {
353 int active_sensor_index = ActiveSensorIndex(position);
Brian Silverman699f0cb2015-02-05 19:45:01 -0500354 if (!::aos::joystick_state->enabled) {
Austin Schuhe20e93c2013-03-09 19:54:16 -0800355 // Start over if disabled.
356 state_ = UNINITIALIZED;
357 } else if (position && active_sensor_index != -1) {
358 state_ = READY;
359 // Verify that the calibration number is between the last off position
360 // and the current on position. If this is not true, move off and try
361 // again.
362 const double calibration =
363 position->hall_effect_positions[active_sensor_index];
Philipp Schrader790cb542023-07-05 21:06:52 -0700364 if (!is_between(last_off_position_, position->position,
Austin Schuhe20e93c2013-03-09 19:54:16 -0800365 calibration)) {
366 LOG(ERROR, "Got a bogus calibration number. Trying again.\n");
367 LOG(ERROR,
368 "Last off position was %f, current is %f, calibration is %f\n",
369 last_off_position_, position->position,
370 position->hall_effect_positions[active_sensor_index]);
371 state_ = MOVING_OFF;
372 } else {
373 // Save the zero, and then offset the observer to deal with the
374 // phantom step change.
375 const double old_zero_offset = zero_offset_;
376 zero_offset_ =
377 position->hall_effect_positions[active_sensor_index] -
378 config_data_.hall_effect_start_angle[active_sensor_index];
379 loop_->X_hat(0, 0) += old_zero_offset - zero_offset_;
380 loop_->Y(0, 0) += old_zero_offset - zero_offset_;
381 }
Austin Schuh844960d2013-03-09 17:07:51 -0800382 } else {
Austin Schuhe20e93c2013-03-09 19:54:16 -0800383 // Slowly creep towards the sensor.
384 zeroing_position_ += config_data_.zeroing_speed * dt;
Austin Schuhc1f68892013-03-16 17:06:27 -0700385 loop_->R << zeroing_position_, config_data_.zeroing_speed, 0.0;
Austin Schuh844960d2013-03-09 17:07:51 -0800386 }
Austin Schuhe20e93c2013-03-09 19:54:16 -0800387 break;
Austin Schuh844960d2013-03-09 17:07:51 -0800388 }
Austin Schuh844960d2013-03-09 17:07:51 -0800389
390 case READY:
Austin Schuhb5191b92013-03-10 18:22:24 -0700391 LOG(DEBUG, "READY\n");
Austin Schuh844960d2013-03-09 17:07:51 -0800392 {
Austin Schuh844960d2013-03-09 17:07:51 -0800393 const double limited_goal = ClipGoal(goal_angle);
Austin Schuhc1f68892013-03-16 17:06:27 -0700394 loop_->R << limited_goal, goal_velocity, 0.0;
Austin Schuh844960d2013-03-09 17:07:51 -0800395 break;
396 }
397
398 case ESTOP:
Austin Schuhb5191b92013-03-10 18:22:24 -0700399 LOG(DEBUG, "ESTOP\n");
Austin Schuh844960d2013-03-09 17:07:51 -0800400 LOG(WARNING, "have already given up\n");
401 return 0.0;
402 }
403
404 // Update the observer.
Austin Schuh844960d2013-03-09 17:07:51 -0800405 loop_->Update(position != NULL, !output_enabled);
Austin Schuh844960d2013-03-09 17:07:51 -0800406
Philipp Schrader790cb542023-07-05 21:06:52 -0700407 LOG(DEBUG, "X_hat={%f, %f, %f}\n", loop_->X_hat(0, 0), loop_->X_hat(1, 0),
408 loop_->X_hat(2, 0));
Brian Silvermanae9d4b72013-03-16 20:11:29 -0700409
Austin Schuh844960d2013-03-09 17:07:51 -0800410 capped_goal_ = false;
411 // Verify that the zeroing goal hasn't run away.
412 switch (state_) {
413 case UNINITIALIZED:
414 case READY:
415 case ESTOP:
416 // Not zeroing. No worries.
417 break;
418 case MOVING_OFF:
419 case ZEROING:
420 // Check if we have cliped and adjust the goal.
Austin Schuhc1f68892013-03-16 17:06:27 -0700421 if (loop_->uncapped_voltage() > config_data_.max_zeroing_voltage) {
Philipp Schrader790cb542023-07-05 21:06:52 -0700422 double dx =
423 (loop_->uncapped_voltage() - config_data_.max_zeroing_voltage) /
424 loop_->K(0, 0);
Austin Schuh844960d2013-03-09 17:07:51 -0800425 zeroing_position_ -= dx;
426 capped_goal_ = true;
Philipp Schrader790cb542023-07-05 21:06:52 -0700427 } else if (loop_->uncapped_voltage() <
428 -config_data_.max_zeroing_voltage) {
429 double dx =
430 (loop_->uncapped_voltage() + config_data_.max_zeroing_voltage) /
431 loop_->K(0, 0);
Austin Schuh844960d2013-03-09 17:07:51 -0800432 zeroing_position_ -= dx;
433 capped_goal_ = true;
434 }
435 break;
436 }
Austin Schuh261c4052013-03-19 03:29:54 +0000437 if (output_enabled) {
Austin Schuh039d4f92013-04-04 05:52:03 +0000438 double voltage = loop_->voltage();
439 if (voltage > 0) {
440 voltage += config_data_.deadband_voltage;
441 } else if (voltage < 0) {
442 voltage -= config_data_.deadband_voltage;
443 }
444 if (voltage > 12.0) {
445 voltage = 12.0;
446 } else if (voltage < -12.0) {
447 voltage = -12.0;
448 }
449 return voltage;
Austin Schuh261c4052013-03-19 03:29:54 +0000450 } else {
451 return 0.0;
452 }
Austin Schuh844960d2013-03-09 17:07:51 -0800453}
454
455} // namespace control_loops
456} // namespace frc971
457
458#endif // FRC971_CONTROL_LOOPS_ZEROED_JOINT_H_