blob: e969a88fcc0e08f0c933e824123580e333bdf0bf [file] [log] [blame]
Adam Snaiderc4b3c192015-02-01 01:30:39 +00001#ifndef FRC971_ZEROING_ZEROING_H_
2#define FRC971_ZEROING_ZEROING_H_
3
Austin Schuh66c59ba2019-01-26 20:34:35 -08004#include <algorithm>
5#include <cmath>
Philipp Schrader41d82912015-02-15 03:44:23 +00006#include <cstdint>
Adam Snaiderc4b3c192015-02-01 01:30:39 +00007#include <vector>
Philipp Schrader41d82912015-02-15 03:44:23 +00008
Austin Schuh703b8d42015-02-01 14:56:34 -08009#include "frc971/control_loops/control_loops.q.h"
10#include "frc971/constants.h"
Adam Snaiderc4b3c192015-02-01 01:30:39 +000011
Adam Snaiderb4119252015-02-15 01:30:57 +000012// TODO(pschrader): Flag an error if encoder index pulse is not n revolutions
13// away from the last one (i.e. got extra counts from noise, etc..)
14//
15// TODO(pschrader): Flag error if the pot disagrees too much with the encoder
16// after being zeroed.
17//
18// TODO(pschrader): Watch the offset over long periods of time and flag if it
19// gets too far away from the initial value.
20
Adam Snaiderc4b3c192015-02-01 01:30:39 +000021namespace frc971 {
22namespace zeroing {
23
Lee Mracekcdd19ee2019-01-07 10:19:54 -050024template <typename TPosition, typename TZeroingConstants, typename TState>
Neil Balch1049be92017-02-15 23:20:49 -080025class ZeroingEstimator {
26 public:
Lee Mracekcdd19ee2019-01-07 10:19:54 -050027 using Position = TPosition;
28 using ZeroingConstants = TZeroingConstants;
29 using State = TState;
Neil Balch1049be92017-02-15 23:20:49 -080030 virtual ~ZeroingEstimator(){}
31
32 // Returns true if the logic considers the corresponding mechanism to be
Isaac Wilcove0851ffd2017-02-16 04:13:14 +000033 // zeroed.
Neil Balch1049be92017-02-15 23:20:49 -080034 virtual bool zeroed() const = 0;
35
Isaac Wilcove0851ffd2017-02-16 04:13:14 +000036 // Returns the estimated position of the corresponding mechanism.
Neil Balch1049be92017-02-15 23:20:49 -080037 virtual double offset() const = 0;
38
Isaac Wilcove0851ffd2017-02-16 04:13:14 +000039 // Returns true if there has been an error.
Neil Balch1049be92017-02-15 23:20:49 -080040 virtual bool error() const = 0;
Lee Mracekcdd19ee2019-01-07 10:19:54 -050041
42 // Returns true if an offset is ready.
43 virtual bool offset_ready() const = 0;
44
45 // Triggers an internal error. This is used for testing the error
46 // logic.
47 virtual void TriggerError() = 0;
48
49 // Resets the estimator, clearing error and zeroing states.
50 virtual void Reset() = 0;
51
52 // Updates the internal logic with new sensor values
53 virtual void UpdateEstimate(const Position &) = 0;
54
55 // Returns the state of the estimator
56 virtual State GetEstimatorState() const = 0;
Neil Balch1049be92017-02-15 23:20:49 -080057};
58
Brian Silvermanab0b6772017-02-05 16:16:21 -080059// Estimates the position with an incremental encoder with an index pulse and a
60// potentiometer.
Lee Mracekcdd19ee2019-01-07 10:19:54 -050061class PotAndIndexPulseZeroingEstimator : public ZeroingEstimator<PotAndIndexPosition,
62 constants::PotAndIndexPulseZeroingConstants,
63 EstimatorState> {
Adam Snaiderc4b3c192015-02-01 01:30:39 +000064 public:
Campbell Crowley36e93e92017-12-23 14:21:43 -080065 explicit PotAndIndexPulseZeroingEstimator(
Tyler Chatowf8f03112017-02-05 14:31:34 -080066 const constants::PotAndIndexPulseZeroingConstants &constants);
Austin Schuh703b8d42015-02-01 14:56:34 -080067
Adam Snaiderb4119252015-02-15 01:30:57 +000068 // Update the internal logic with the next sensor values.
Lee Mracekcdd19ee2019-01-07 10:19:54 -050069 void UpdateEstimate(const PotAndIndexPosition &info) override;
Adam Snaiderb4119252015-02-15 01:30:57 +000070
71 // Reset the internal logic so it needs to be re-zeroed.
Lee Mracekcdd19ee2019-01-07 10:19:54 -050072 void Reset() override;
Adam Snaiderb4119252015-02-15 01:30:57 +000073
Philipp Schrader53f4b6d2015-02-15 22:32:08 +000074 // Manually trigger an internal error. This is used for testing the error
75 // logic.
Lee Mracekcdd19ee2019-01-07 10:19:54 -050076 void TriggerError() override;
Philipp Schrader53f4b6d2015-02-15 22:32:08 +000077
Neil Balch1049be92017-02-15 23:20:49 -080078 bool error() const override { return error_; }
Philipp Schrader53f4b6d2015-02-15 22:32:08 +000079
Neil Balch1049be92017-02-15 23:20:49 -080080 bool zeroed() const override { return zeroed_; }
Adam Snaiderb4119252015-02-15 01:30:57 +000081
Isaac Wilcove0851ffd2017-02-16 04:13:14 +000082 double offset() const override { return offset_; }
Adam Snaiderb4119252015-02-15 01:30:57 +000083
84 // Returns a number between 0 and 1 that represents the percentage of the
85 // samples being used in the moving average filter. A value of 0.0 means that
86 // no samples are being used. A value of 1.0 means that the filter is using
87 // as many samples as it has room for. For example, after a Reset() this
88 // value returns 0.0. As more samples get added with UpdateEstimate(...) the
89 // return value starts increasing to 1.0.
Austin Schuh703b8d42015-02-01 14:56:34 -080090 double offset_ratio_ready() const {
Austin Schuh5f01f152017-02-11 21:34:08 -080091 return start_pos_samples_.size() /
92 static_cast<double>(constants_.average_filter_size);
Austin Schuh703b8d42015-02-01 14:56:34 -080093 }
94
Austin Schuh7485dbb2016-02-08 00:21:58 -080095 // Returns true if the sample buffer is full.
Lee Mracekcdd19ee2019-01-07 10:19:54 -050096 bool offset_ready() const override {
Austin Schuh5f01f152017-02-11 21:34:08 -080097 return start_pos_samples_.size() == constants_.average_filter_size;
Austin Schuh7485dbb2016-02-08 00:21:58 -080098 }
99
Brian Silverman4f2e2ce2017-02-19 17:49:47 -0800100 // Returns information about our current state.
Lee Mracekcdd19ee2019-01-07 10:19:54 -0500101 State GetEstimatorState() const override;
Brian Silverman4f2e2ce2017-02-19 17:49:47 -0800102
Adam Snaiderc4b3c192015-02-01 01:30:39 +0000103 private:
Philipp Schradere828be72015-02-15 07:07:37 +0000104 // This function calculates the start position given the internal state and
105 // the provided `latched_encoder' value.
106 double CalculateStartPosition(double start_average,
107 double latched_encoder) const;
108
Austin Schuh5f01f152017-02-11 21:34:08 -0800109 // The zeroing constants used to describe the configuration of the system.
110 const constants::PotAndIndexPulseZeroingConstants constants_;
111
Adam Snaiderb4119252015-02-15 01:30:57 +0000112 // The estimated position.
Austin Schuh5f01f152017-02-11 21:34:08 -0800113 double position_;
Austin Schuhbe133ed2016-03-11 21:23:34 -0800114 // The unzeroed filtered position.
115 double filtered_position_ = 0.0;
Adam Snaiderb4119252015-02-15 01:30:57 +0000116 // The next position in 'start_pos_samples_' to be used to store the next
117 // sample.
Adam Snaiderc4b3c192015-02-01 01:30:39 +0000118 int samples_idx_;
119 // Last 'max_sample_count_' samples for start positions.
120 std::vector<double> start_pos_samples_;
Adam Snaiderb4119252015-02-15 01:30:57 +0000121 // The estimated starting position of the mechanism. We also call this the
122 // 'offset' in some contexts.
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000123 double offset_;
Philipp Schrader41d82912015-02-15 03:44:23 +0000124 // Flag for triggering logic that takes note of the current index pulse count
Philipp Schradere828be72015-02-15 07:07:37 +0000125 // after a reset. See `last_used_index_pulse_count_'.
Philipp Schrader41d82912015-02-15 03:44:23 +0000126 bool wait_for_index_pulse_;
127 // After a reset we keep track of the index pulse count with this. Only after
128 // the index pulse count changes (i.e. increments at least once or wraps
Philipp Schradere828be72015-02-15 07:07:37 +0000129 // around) will we consider the mechanism zeroed. We also use this to store
130 // the most recent `PotAndIndexPosition::index_pulses' value when the start
131 // position was calculated. It helps us calculate the start position only on
132 // index pulses to reject corrupted intermediate data.
133 uint32_t last_used_index_pulse_count_;
Adam Snaiderb4119252015-02-15 01:30:57 +0000134 // Marker to track whether we're fully zeroed yet or not.
135 bool zeroed_;
Philipp Schrader53f4b6d2015-02-15 22:32:08 +0000136 // Marker to track whether an error has occurred. This gets reset to false
137 // whenever Reset() is called.
138 bool error_;
Adam Snaider3cd11c52015-02-16 02:16:09 +0000139 // Stores the position "start_pos" variable the first time the program
140 // is zeroed.
141 double first_start_pos_;
Austin Schuh5f01f152017-02-11 21:34:08 -0800142};
143
Campbell Crowley36e93e92017-12-23 14:21:43 -0800144// Estimates the position with an incremental encoder and a hall effect sensor.
Lee Mracekcdd19ee2019-01-07 10:19:54 -0500145class HallEffectAndPositionZeroingEstimator
146 : public ZeroingEstimator<HallEffectAndPosition,
147 constants::HallEffectZeroingConstants,
148 HallEffectAndPositionEstimatorState> {
Austin Schuh55934032017-03-11 12:45:27 -0800149 public:
Austin Schuh55934032017-03-11 12:45:27 -0800150 explicit HallEffectAndPositionZeroingEstimator(const ZeroingConstants &constants);
151
152 // Update the internal logic with the next sensor values.
Lee Mracekcdd19ee2019-01-07 10:19:54 -0500153 void UpdateEstimate(const Position &info) override;
Austin Schuh55934032017-03-11 12:45:27 -0800154
155 // Reset the internal logic so it needs to be re-zeroed.
Lee Mracekcdd19ee2019-01-07 10:19:54 -0500156 void Reset() override;
Austin Schuh55934032017-03-11 12:45:27 -0800157
158 // Manually trigger an internal error. This is used for testing the error
159 // logic.
Lee Mracekcdd19ee2019-01-07 10:19:54 -0500160 void TriggerError() override;
Austin Schuh55934032017-03-11 12:45:27 -0800161
162 bool error() const override { return error_; }
163
164 bool zeroed() const override { return zeroed_; }
165
166 double offset() const override { return offset_; }
167
Lee Mracekcdd19ee2019-01-07 10:19:54 -0500168 bool offset_ready() const override { return zeroed_; }
169
Austin Schuh55934032017-03-11 12:45:27 -0800170 // Returns information about our current state.
Lee Mracekcdd19ee2019-01-07 10:19:54 -0500171 State GetEstimatorState() const override;
Austin Schuh55934032017-03-11 12:45:27 -0800172
173 private:
174 // Sets the minimum and maximum posedge position values.
175 void StoreEncoderMaxAndMin(const HallEffectAndPosition &info);
176
177 // The zeroing constants used to describe the configuration of the system.
178 const ZeroingConstants constants_;
179
180 // The estimated state of the hall effect.
181 double current_ = 0.0;
182 // The estimated position.
183 double position_ = 0.0;
184 // The smallest and largest positions of the last set of encoder positions
185 // while the hall effect was low.
186 double min_low_position_;
187 double max_low_position_;
188 // If we've seen the hall effect high for enough times without going low, then
189 // we can be sure it isn't a false positive.
190 bool high_long_enough_;
191 size_t cycles_high_;
192
193 bool last_hall_ = false;
194
195 // The estimated starting position of the mechanism. We also call this the
196 // 'offset' in some contexts.
197 double offset_;
198 // Flag for triggering logic that takes note of the current posedge count
199 // after a reset. See `last_used_posedge_count_'.
200 bool initialized_;
201 // After a reset we keep track of the posedge count with this. Only after the
202 // posedge count changes (i.e. increments at least once or wraps around) will
203 // we consider the mechanism zeroed. We also use this to store the most recent
204 // `HallEffectAndPosition::posedge_count' value when the start position
205 // was calculated. It helps us calculate the start position only on posedges
206 // to reject corrupted intermediate data.
207 int32_t last_used_posedge_count_;
208 // Marker to track whether we're fully zeroed yet or not.
209 bool zeroed_;
210 // Marker to track whether an error has occurred. This gets reset to false
211 // whenever Reset() is called.
212 bool error_ = false;
213 // Stores the position "start_pos" variable the first time the program
214 // is zeroed.
215 double first_start_pos_;
216};
217
Austin Schuh66c59ba2019-01-26 20:34:35 -0800218// Class to encapsulate the logic to decide when we are moving and which samples
219// are safe to use.
220template <typename Position>
221class MoveDetector {
222 public:
223 MoveDetector(size_t filter_size) {
224 buffered_samples_.reserve(filter_size);
225 Reset();
226 }
227
228 // Clears all the state.
229 void Reset() {
230 buffered_samples_.clear();
231 buffered_samples_idx_ = 0;
232 }
233
234 // Updates the detector with a new sample. Returns true if we are moving.
235 // buffer_size is the number of samples in the moving buffer, and
236 // zeroing_threshold is the max amount we can move within the period specified
237 // by buffer_size.
238 bool Update(const Position &position, size_t buffer_size,
239 double zeroing_threshold) {
240 bool moving = true;
241 if (buffered_samples_.size() < buffer_size) {
242 // Not enough samples to start determining if the robot is moving or not,
243 // don't use the samples yet.
244 buffered_samples_.push_back(position);
245 } else {
246 // Have enough samples to start determining if the robot is moving or not.
247 buffered_samples_[buffered_samples_idx_] = position;
248 const auto minmax_value = ::std::minmax_element(
249 buffered_samples_.begin(), buffered_samples_.end(),
250 [](const Position &left, const Position &right) {
251 return left.encoder < right.encoder;
252 });
253 const double min_value = minmax_value.first->encoder;
254 const double max_value = minmax_value.second->encoder;
255
256 if (::std::abs(max_value - min_value) < zeroing_threshold) {
257 // Robot isn't moving, use middle sample to determine offsets.
258 moving = false;
259 }
260 }
261 buffered_samples_idx_ = (buffered_samples_idx_ + 1) % buffer_size;
262 return moving;
263 }
264
265 // Returns a safe sample if we aren't moving as reported by Update.
266 const Position &GetSample() const {
267 // The robot is not moving, use the middle sample to determine offsets.
268 // The middle sample makes it so that we don't use the samples from the
269 // beginning or end of periods when the robot is moving.
270 const int middle_index =
271 (buffered_samples_idx_ + (buffered_samples_.size() / 2)) %
272 buffered_samples_.size();
273 return buffered_samples_[middle_index];
274 }
275
276 private:
277 // The last buffer_size samples.
278 ::std::vector<Position> buffered_samples_;
279 // The index to place the next sample in.
280 size_t buffered_samples_idx_;
281};
282
Austin Schuh5f01f152017-02-11 21:34:08 -0800283// Estimates the position with an absolute encoder which also reports
284// incremental counts, and a potentiometer.
Austin Schuh72db9a12019-01-21 18:02:51 -0800285class PotAndAbsoluteEncoderZeroingEstimator
Lee Mracekcdd19ee2019-01-07 10:19:54 -0500286 : public ZeroingEstimator<PotAndAbsolutePosition,
Austin Schuh72db9a12019-01-21 18:02:51 -0800287 constants::PotAndAbsoluteEncoderZeroingConstants,
288 PotAndAbsoluteEncoderEstimatorState> {
Austin Schuh5f01f152017-02-11 21:34:08 -0800289 public:
Austin Schuh72db9a12019-01-21 18:02:51 -0800290 explicit PotAndAbsoluteEncoderZeroingEstimator(
Austin Schuh5f01f152017-02-11 21:34:08 -0800291 const constants::PotAndAbsoluteEncoderZeroingConstants &constants);
292
293 // Resets the internal logic so it needs to be re-zeroed.
Lee Mracekcdd19ee2019-01-07 10:19:54 -0500294 void Reset() override;
Austin Schuh5f01f152017-02-11 21:34:08 -0800295
296 // Updates the sensor values for the zeroing logic.
Lee Mracekcdd19ee2019-01-07 10:19:54 -0500297 void UpdateEstimate(const PotAndAbsolutePosition &info) override;
298
299 void TriggerError() override { error_ = true; }
Austin Schuh5f01f152017-02-11 21:34:08 -0800300
Neil Balch1049be92017-02-15 23:20:49 -0800301 bool zeroed() const override { return zeroed_; }
Austin Schuh5f01f152017-02-11 21:34:08 -0800302
Neil Balch1049be92017-02-15 23:20:49 -0800303 double offset() const override { return offset_; }
Austin Schuh5f01f152017-02-11 21:34:08 -0800304
Neil Balch16275e32017-02-18 16:38:45 -0800305 bool error() const override { return error_; }
Austin Schuh5f01f152017-02-11 21:34:08 -0800306
307 // Returns true if the sample buffer is full.
Lee Mracekcdd19ee2019-01-07 10:19:54 -0500308 bool offset_ready() const override {
Austin Schuh5f01f152017-02-11 21:34:08 -0800309 return relative_to_absolute_offset_samples_.size() ==
310 constants_.average_filter_size &&
311 offset_samples_.size() == constants_.average_filter_size;
312 }
313
Brian Silverman4f2e2ce2017-02-19 17:49:47 -0800314 // Returns information about our current state.
Lee Mracekcdd19ee2019-01-07 10:19:54 -0500315 State GetEstimatorState() const override;
Austin Schuh5f01f152017-02-11 21:34:08 -0800316
317 private:
318 // The zeroing constants used to describe the configuration of the system.
319 const constants::PotAndAbsoluteEncoderZeroingConstants constants_;
Brian Silvermana10d20a2017-02-19 14:28:53 -0800320
Austin Schuh5f01f152017-02-11 21:34:08 -0800321 // True if the mechanism is zeroed.
322 bool zeroed_;
Brian Silvermana10d20a2017-02-19 14:28:53 -0800323 // Marker to track whether an error has occurred.
324 bool error_;
325 // The first valid offset we recorded. This is only set after zeroed_ first
326 // changes to true.
327 double first_offset_;
328
Austin Schuh0e1c2c62017-02-21 02:03:25 -0800329 // The filtered absolute encoder. This is used in the status for calibration.
330 double filtered_absolute_encoder_ = 0.0;
331
Austin Schuh5f01f152017-02-11 21:34:08 -0800332 // Samples of the offset needed to line the relative encoder up with the
333 // absolute encoder.
334 ::std::vector<double> relative_to_absolute_offset_samples_;
335 // Offset between the Pot and Relative encoder position.
336 ::std::vector<double> offset_samples_;
Austin Schuh66c59ba2019-01-26 20:34:35 -0800337
338 MoveDetector<PotAndAbsolutePosition> move_detector_;
339
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -0800340 // Estimated offset between the pot and relative encoder.
341 double pot_relative_encoder_offset_ = 0;
Austin Schuh5f01f152017-02-11 21:34:08 -0800342 // Estimated start position of the mechanism
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -0800343 double offset_ = 0;
Austin Schuh5f01f152017-02-11 21:34:08 -0800344 // The next position in 'relative_to_absolute_offset_samples_' and
345 // 'encoder_samples_' to be used to store the next sample.
Austin Schuhddd08f82018-03-02 20:05:29 -0800346 int samples_idx_ = 0;
347
348 size_t nan_samples_ = 0;
Brian Silvermana10d20a2017-02-19 14:28:53 -0800349
Austin Schuh5f01f152017-02-11 21:34:08 -0800350 // The unzeroed filtered position.
351 double filtered_position_ = 0.0;
352 // The filtered position.
353 double position_ = 0.0;
Adam Snaiderc4b3c192015-02-01 01:30:39 +0000354};
355
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000356// Zeros by seeing all the index pulses in the range of motion of the mechanism
357// and using that to figure out which index pulse is which.
Lee Mracekcdd19ee2019-01-07 10:19:54 -0500358class PulseIndexZeroingEstimator : public ZeroingEstimator<IndexPosition,
359 constants::EncoderPlusIndexZeroingConstants,
360 IndexEstimatorState> {
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000361 public:
Campbell Crowley36e93e92017-12-23 14:21:43 -0800362 explicit PulseIndexZeroingEstimator(const ZeroingConstants &constants)
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000363 : constants_(constants) {
364 Reset();
365 }
366
367 // Resets the internal logic so it needs to be re-zeroed.
Lee Mracekcdd19ee2019-01-07 10:19:54 -0500368 void Reset() override;
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000369
370 bool zeroed() const override { return zeroed_; }
371
Austin Schuh6a90cd92017-02-19 20:55:33 -0800372 // It's as ready as it'll ever be...
Lee Mracekcdd19ee2019-01-07 10:19:54 -0500373 bool offset_ready() const override { return true; }
Austin Schuh6a90cd92017-02-19 20:55:33 -0800374
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000375 double offset() const override { return offset_; }
376
377 bool error() const override { return error_; }
378
379 // Updates the internal logic with the next sensor values.
Lee Mracekcdd19ee2019-01-07 10:19:54 -0500380 void UpdateEstimate(const IndexPosition &info) override;
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000381
Brian Silvermanf37839c2017-02-19 18:07:15 -0800382 // Returns information about our current state.
Lee Mracekcdd19ee2019-01-07 10:19:54 -0500383 State GetEstimatorState() const override;
Brian Silvermanf37839c2017-02-19 18:07:15 -0800384
Lee Mracekcdd19ee2019-01-07 10:19:54 -0500385 void TriggerError() override { error_ = true; }
Austin Schuhd93160a2017-03-05 01:00:54 -0800386
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000387 private:
388 // Returns the current real position using the relative encoder offset.
389 double CalculateCurrentPosition(const IndexPosition &info);
390
391 // Sets the minimum and maximum index pulse position values.
392 void StoreIndexPulseMaxAndMin(const IndexPosition &info);
393
394 // Returns the number of index pulses we should have seen so far.
Brian Silvermanf37839c2017-02-19 18:07:15 -0800395 int IndexPulseCount() const;
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000396
397 // Contains the physical constants describing the system.
Philipp Schrader3f5b6182017-03-25 22:36:37 +0000398 const ZeroingConstants constants_;
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000399
400 // The smallest position of all the index pulses.
401 double min_index_position_;
402 // The largest position of all the index pulses.
403 double max_index_position_;
404
405 // The estimated starting position of the mechanism.
406 double offset_;
407 // After a reset we keep track of the index pulse count with this. Only after
408 // the index pulse count changes (i.e. increments at least once or wraps
409 // around) will we consider the mechanism zeroed. We also use this to store
410 // the most recent `PotAndIndexPosition::index_pulses' value when the start
411 // position was calculated. It helps us calculate the start position only on
412 // index pulses to reject corrupted intermediate data.
413 uint32_t last_used_index_pulse_count_;
414
415 // True if we are fully zeroed.
416 bool zeroed_;
417 // Marker to track whether an error has occurred.
418 bool error_;
419
420 // The estimated position.
421 double position_;
422};
423
Adam Snaiderb4119252015-02-15 01:30:57 +0000424} // namespace zeroing
425} // namespace frc971
Adam Snaiderc4b3c192015-02-01 01:30:39 +0000426
427#endif // FRC971_ZEROING_ZEROING_H_