blob: 947a15c17dfad41789029e25a555423fa3da6e91 [file] [log] [blame]
Adam Snaiderc4b3c192015-02-01 01:30:39 +00001#include "frc971/zeroing/zeroing.h"
Adam Snaiderb4119252015-02-15 01:30:57 +00002
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -08003#include <algorithm>
Isaac Wilcove0851ffd2017-02-16 04:13:14 +00004#include <cmath>
5#include <limits>
6#include <vector>
Adam Snaiderc4b3c192015-02-01 01:30:39 +00007
Austin Schuh5f01f152017-02-11 21:34:08 -08008#include "frc971/zeroing/wrap.h"
9
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -080010
Adam Snaiderc4b3c192015-02-01 01:30:39 +000011namespace frc971 {
12namespace zeroing {
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -080013namespace {
14
15bool compare_encoder(const PotAndAbsolutePosition &left,
16 const PotAndAbsolutePosition &right) {
17 return left.encoder < right.encoder;
18}
19
20} // namespace
Adam Snaiderc4b3c192015-02-01 01:30:39 +000021
Tyler Chatowf8f03112017-02-05 14:31:34 -080022PotAndIndexPulseZeroingEstimator::PotAndIndexPulseZeroingEstimator(
Austin Schuh5f01f152017-02-11 21:34:08 -080023 const constants::PotAndIndexPulseZeroingConstants &constants)
24 : constants_(constants) {
25 start_pos_samples_.reserve(constants_.average_filter_size);
Adam Snaiderb4119252015-02-15 01:30:57 +000026 Reset();
Austin Schuh703b8d42015-02-01 14:56:34 -080027}
28
Isaac Wilcove0851ffd2017-02-16 04:13:14 +000029
Tyler Chatowf8f03112017-02-05 14:31:34 -080030void PotAndIndexPulseZeroingEstimator::Reset() {
Adam Snaiderc4b3c192015-02-01 01:30:39 +000031 samples_idx_ = 0;
Isaac Wilcove0851ffd2017-02-16 04:13:14 +000032 offset_ = 0;
Adam Snaiderb4119252015-02-15 01:30:57 +000033 start_pos_samples_.clear();
34 zeroed_ = false;
Philipp Schrader41d82912015-02-15 03:44:23 +000035 wait_for_index_pulse_ = true;
Philipp Schradere828be72015-02-15 07:07:37 +000036 last_used_index_pulse_count_ = 0;
Philipp Schrader53f4b6d2015-02-15 22:32:08 +000037 error_ = false;
38}
39
Tyler Chatowf8f03112017-02-05 14:31:34 -080040void PotAndIndexPulseZeroingEstimator::TriggerError() {
Philipp Schrader53f4b6d2015-02-15 22:32:08 +000041 if (!error_) {
42 LOG(ERROR, "Manually triggered zeroing error.\n");
43 error_ = true;
44 }
Philipp Schradere828be72015-02-15 07:07:37 +000045}
46
Tyler Chatowf8f03112017-02-05 14:31:34 -080047double PotAndIndexPulseZeroingEstimator::CalculateStartPosition(
48 double start_average, double latched_encoder) const {
Philipp Schradere828be72015-02-15 07:07:37 +000049 // We calculate an aproximation of the value of the last index position.
50 // Also account for index pulses not lining up with integer multiples of the
51 // index_diff.
Austin Schuh5f01f152017-02-11 21:34:08 -080052 double index_pos =
53 start_average + latched_encoder - constants_.measured_index_position;
Philipp Schradere828be72015-02-15 07:07:37 +000054 // We round index_pos to the closest valid value of the index.
Austin Schuh5f01f152017-02-11 21:34:08 -080055 double accurate_index_pos = (round(index_pos / constants_.index_difference)) *
56 constants_.index_difference;
Philipp Schradere828be72015-02-15 07:07:37 +000057 // Now we reverse the first calculation to get the accurate start position.
Austin Schuh5f01f152017-02-11 21:34:08 -080058 return accurate_index_pos - latched_encoder +
59 constants_.measured_index_position;
Adam Snaiderc4b3c192015-02-01 01:30:39 +000060}
61
Tyler Chatowf8f03112017-02-05 14:31:34 -080062void PotAndIndexPulseZeroingEstimator::UpdateEstimate(
63 const PotAndIndexPosition &info) {
Philipp Schrader41d82912015-02-15 03:44:23 +000064 // We want to make sure that we encounter at least one index pulse while
65 // zeroing. So we take the index pulse count from the first sample after
66 // reset and wait for that count to change before we consider ourselves
67 // zeroed.
68 if (wait_for_index_pulse_) {
Philipp Schradere828be72015-02-15 07:07:37 +000069 last_used_index_pulse_count_ = info.index_pulses;
Philipp Schrader41d82912015-02-15 03:44:23 +000070 wait_for_index_pulse_ = false;
71 }
72
Austin Schuh5f01f152017-02-11 21:34:08 -080073 if (start_pos_samples_.size() < constants_.average_filter_size) {
Adam Snaiderc4b3c192015-02-01 01:30:39 +000074 start_pos_samples_.push_back(info.pot - info.encoder);
75 } else {
76 start_pos_samples_[samples_idx_] = info.pot - info.encoder;
77 }
Adam Snaiderb4119252015-02-15 01:30:57 +000078
79 // Drop the oldest sample when we run this function the next time around.
Austin Schuh5f01f152017-02-11 21:34:08 -080080 samples_idx_ = (samples_idx_ + 1) % constants_.average_filter_size;
Adam Snaiderc4b3c192015-02-01 01:30:39 +000081
Adam Snaiderb4119252015-02-15 01:30:57 +000082 double sample_sum = 0.0;
83
Adam Snaiderc4b3c192015-02-01 01:30:39 +000084 for (size_t i = 0; i < start_pos_samples_.size(); ++i) {
Adam Snaiderb4119252015-02-15 01:30:57 +000085 sample_sum += start_pos_samples_[i];
Adam Snaiderc4b3c192015-02-01 01:30:39 +000086 }
87
88 // Calculates the average of the starting position.
Adam Snaiderb4119252015-02-15 01:30:57 +000089 double start_average = sample_sum / start_pos_samples_.size();
90
91 // If there are no index pulses to use or we don't have enough samples yet to
92 // have a well-filtered starting position then we use the filtered value as
93 // our best guess.
Austin Schuh7485dbb2016-02-08 00:21:58 -080094 if (!zeroed_ &&
95 (info.index_pulses == last_used_index_pulse_count_ || !offset_ready())) {
Isaac Wilcove0851ffd2017-02-16 04:13:14 +000096 offset_ = start_average;
Philipp Schradere828be72015-02-15 07:07:37 +000097 } else if (!zeroed_ || last_used_index_pulse_count_ != info.index_pulses) {
98 // Note the accurate start position and the current index pulse count so
99 // that we only run this logic once per index pulse. That should be more
100 // resilient to corrupted intermediate data.
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000101 offset_ = CalculateStartPosition(start_average, info.latched_encoder);
Philipp Schradere828be72015-02-15 07:07:37 +0000102 last_used_index_pulse_count_ = info.index_pulses;
Austin Schuh7485dbb2016-02-08 00:21:58 -0800103
104 // TODO(austin): Reject encoder positions which have x% error rather than
105 // rounding to the closest index pulse.
106
Adam Snaider3cd11c52015-02-16 02:16:09 +0000107 // Save the first starting position.
108 if (!zeroed_) {
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000109 first_start_pos_ = offset_;
Adam Snaider3cd11c52015-02-16 02:16:09 +0000110 LOG(INFO, "latching start position %f\n", first_start_pos_);
111 }
Adam Snaiderb4119252015-02-15 01:30:57 +0000112
113 // Now that we have an accurate starting position we can consider ourselves
114 // zeroed.
Austin Schuh703b8d42015-02-01 14:56:34 -0800115 zeroed_ = true;
Adam Snaider3cd11c52015-02-16 02:16:09 +0000116 // Throw an error if first_start_pos is bigger/smaller than
Austin Schuh5f01f152017-02-11 21:34:08 -0800117 // constants_.allowable_encoder_error * index_diff + start_pos.
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000118 if (::std::abs(first_start_pos_ - offset_) >
Austin Schuh5f01f152017-02-11 21:34:08 -0800119 constants_.allowable_encoder_error * constants_.index_difference) {
Adam Snaider3cd11c52015-02-16 02:16:09 +0000120 if (!error_) {
121 LOG(ERROR,
122 "Encoder ticks out of range since last index pulse. first start "
Austin Schuh1c85bc82016-04-03 21:36:31 -0700123 "position: %f recent starting position: %f, allowable error: %f\n",
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000124 first_start_pos_, offset_,
Austin Schuh5f01f152017-02-11 21:34:08 -0800125 constants_.allowable_encoder_error * constants_.index_difference);
Adam Snaider3cd11c52015-02-16 02:16:09 +0000126 error_ = true;
127 }
128 }
Adam Snaiderc4b3c192015-02-01 01:30:39 +0000129 }
Adam Snaiderb4119252015-02-15 01:30:57 +0000130
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000131 position_ = offset_ + info.encoder;
Austin Schuhbe133ed2016-03-11 21:23:34 -0800132 filtered_position_ = start_average + info.encoder;
Adam Snaiderc4b3c192015-02-01 01:30:39 +0000133}
134
Brian Silverman4f2e2ce2017-02-19 17:49:47 -0800135PotAndIndexPulseZeroingEstimator::State
136PotAndIndexPulseZeroingEstimator::GetEstimatorState() const {
137 State r;
138 r.error = error_;
139 r.zeroed = zeroed_;
140 r.position = position_;
141 r.pot_position = filtered_position_;
142 return r;
143}
144
145
Austin Schuh5f01f152017-02-11 21:34:08 -0800146PotAndAbsEncoderZeroingEstimator::PotAndAbsEncoderZeroingEstimator(
147 const constants::PotAndAbsoluteEncoderZeroingConstants &constants)
148 : constants_(constants) {
149 relative_to_absolute_offset_samples_.reserve(constants_.average_filter_size);
150 offset_samples_.reserve(constants_.average_filter_size);
151 Reset();
152}
153
154void PotAndAbsEncoderZeroingEstimator::Reset() {
155 zeroed_ = false;
156 relative_to_absolute_offset_samples_.clear();
157 offset_samples_.clear();
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -0800158 buffered_samples_.clear();
Brian Silvermana10d20a2017-02-19 14:28:53 -0800159 error_ = false;
Austin Schuh5f01f152017-02-11 21:34:08 -0800160}
161
162// So, this needs to be a multistep process. We need to first estimate the
163// offset between the absolute encoder and the relative encoder. That process
164// should get us an absolute number which is off by integer multiples of the
165// distance/rev. In parallel, we can estimate the offset between the pot and
166// encoder. When both estimates have converged, we can then compute the offset
167// in a cycle, and which cycle, which gives us the accurate global offset.
168//
169// It's tricky to compute the offset between the absolute and relative encoder.
170// We need to compute this inside 1 revolution. The easiest way to do this
171// would be to wrap the encoder, subtract the two of them, and then average the
172// result. That will struggle when they are off by PI. Instead, we need to
173// wrap the number to +- PI from the current averaged offset.
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -0800174//
175// To guard against the robot moving while updating estimates, buffer a number
176// of samples and check that the buffered samples are not different than the
177// zeroing threshold. At any point that the samples differ too much, do not
178// update estimates based on those samples.
Austin Schuh5f01f152017-02-11 21:34:08 -0800179void PotAndAbsEncoderZeroingEstimator::UpdateEstimate(
180 const PotAndAbsolutePosition &info) {
Neil Balch16275e32017-02-18 16:38:45 -0800181 // Check for Abs Encoder NaN value that would mess up the rest of the zeroing
182 // code below. NaN values are given when the Absolute Encoder is disconnected.
183 if (::std::isnan(info.absolute_encoder)) {
184 error_ = true;
185 return;
186 }
187
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -0800188 bool moving = true;
189 if (buffered_samples_.size() < constants_.moving_buffer_size) {
190 // Not enough samples to start determining if the robot is moving or not,
191 // don't use the samples yet.
192 buffered_samples_.push_back(info);
Austin Schuh5f01f152017-02-11 21:34:08 -0800193 } else {
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -0800194 // Have enough samples to start determining if the robot is moving or not.
195 buffered_samples_[buffered_samples_idx_] = info;
196 auto max_value =
197 ::std::max_element(buffered_samples_.begin(), buffered_samples_.end(),
198 compare_encoder)
199 ->encoder;
200 auto min_value =
201 ::std::min_element(buffered_samples_.begin(), buffered_samples_.end(),
202 compare_encoder)
203 ->encoder;
204 if (::std::abs(max_value - min_value) < constants_.zeroing_threshold) {
205 // Robot isn't moving, use middle sample to determine offsets.
206 moving = false;
207 }
208 }
209 buffered_samples_idx_ =
210 (buffered_samples_idx_ + 1) % constants_.moving_buffer_size;
211
212 if (!moving) {
213 // The robot is not moving, use the middle sample to determine offsets.
214 const int middle_index =
215 (buffered_samples_idx_ + (constants_.moving_buffer_size - 1) / 2) %
216 constants_.moving_buffer_size;
217
218 // Compute the sum of all the offset samples.
219 double relative_to_absolute_offset_sum = 0.0;
220 for (size_t i = 0; i < relative_to_absolute_offset_samples_.size(); ++i) {
221 relative_to_absolute_offset_sum +=
222 relative_to_absolute_offset_samples_[i];
223 }
224
225 // Compute the average offset between the absolute encoder and relative
226 // encoder. If we have 0 samples, assume it is 0.
227 double average_relative_to_absolute_offset =
228 relative_to_absolute_offset_samples_.size() == 0
229 ? 0.0
230 : relative_to_absolute_offset_sum /
231 relative_to_absolute_offset_samples_.size();
232
Austin Schuh0e1c2c62017-02-21 02:03:25 -0800233 const double adjusted_incremental_encoder =
234 buffered_samples_[middle_index].encoder +
235 average_relative_to_absolute_offset;
236
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -0800237 // Now, compute the nearest absolute encoder value to the offset relative
238 // encoder position.
239 const double adjusted_absolute_encoder =
Austin Schuh0e1c2c62017-02-21 02:03:25 -0800240 Wrap(adjusted_incremental_encoder,
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -0800241 buffered_samples_[middle_index].absolute_encoder -
242 constants_.measured_absolute_position,
243 constants_.one_revolution_distance);
244
Austin Schuh0e1c2c62017-02-21 02:03:25 -0800245 // Reverse the math on the previous line to compute the absolute encoder.
246 // Do this by taking the adjusted encoder, and then subtracting off the
247 // second argument above, and the value that was added by Wrap.
248 filtered_absolute_encoder_ =
249 ((buffered_samples_[middle_index].encoder +
250 average_relative_to_absolute_offset) -
251 (-constants_.measured_absolute_position +
252 (adjusted_absolute_encoder -
253 (buffered_samples_[middle_index].absolute_encoder -
254 constants_.measured_absolute_position))));
255
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -0800256 const double relative_to_absolute_offset =
257 adjusted_absolute_encoder - buffered_samples_[middle_index].encoder;
258
259 // Add the sample and update the average with the new reading.
260 const size_t relative_to_absolute_offset_samples_size =
261 relative_to_absolute_offset_samples_.size();
262 if (relative_to_absolute_offset_samples_size <
263 constants_.average_filter_size) {
264 average_relative_to_absolute_offset =
265 (average_relative_to_absolute_offset *
266 relative_to_absolute_offset_samples_size +
267 relative_to_absolute_offset) /
268 (relative_to_absolute_offset_samples_size + 1);
269
270 relative_to_absolute_offset_samples_.push_back(
271 relative_to_absolute_offset);
272 } else {
273 average_relative_to_absolute_offset -=
274 relative_to_absolute_offset_samples_[samples_idx_] /
275 relative_to_absolute_offset_samples_size;
276 relative_to_absolute_offset_samples_[samples_idx_] =
277 relative_to_absolute_offset;
278 average_relative_to_absolute_offset +=
279 relative_to_absolute_offset /
280 relative_to_absolute_offset_samples_size;
281 }
282
283 // Now compute the offset between the pot and relative encoder.
284 if (offset_samples_.size() < constants_.average_filter_size) {
285 offset_samples_.push_back(buffered_samples_[middle_index].pot -
286 buffered_samples_[middle_index].encoder);
287 } else {
288 offset_samples_[samples_idx_] = buffered_samples_[middle_index].pot -
289 buffered_samples_[middle_index].encoder;
290 }
291
292 // Drop the oldest sample when we run this function the next time around.
293 samples_idx_ = (samples_idx_ + 1) % constants_.average_filter_size;
294
295 double pot_relative_encoder_offset_sum = 0.0;
296 for (size_t i = 0; i < offset_samples_.size(); ++i) {
297 pot_relative_encoder_offset_sum += offset_samples_[i];
298 }
299 pot_relative_encoder_offset_ =
300 pot_relative_encoder_offset_sum / offset_samples_.size();
301
302 offset_ = Wrap(buffered_samples_[middle_index].encoder +
303 pot_relative_encoder_offset_,
304 average_relative_to_absolute_offset +
305 buffered_samples_[middle_index].encoder,
306 constants_.one_revolution_distance) -
307 buffered_samples_[middle_index].encoder;
308 if (offset_ready()) {
Brian Silvermana10d20a2017-02-19 14:28:53 -0800309 if (!zeroed_) {
310 first_offset_ = offset_;
311 }
312
313 if (::std::abs(first_offset_ - offset_) >
314 constants_.allowable_encoder_error *
315 constants_.one_revolution_distance) {
316 LOG(ERROR,
317 "Offset moved too far. Initial: %f, current %f, allowable change: "
318 "%f\n",
319 first_offset_, offset_, constants_.allowable_encoder_error *
320 constants_.one_revolution_distance);
321 error_ = true;
322 }
323
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -0800324 zeroed_ = true;
325 }
Austin Schuh5f01f152017-02-11 21:34:08 -0800326 }
327
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -0800328 // Update the position.
329 filtered_position_ = pot_relative_encoder_offset_ + info.encoder;
Austin Schuh5f01f152017-02-11 21:34:08 -0800330 position_ = offset_ + info.encoder;
331}
332
Brian Silverman4f2e2ce2017-02-19 17:49:47 -0800333PotAndAbsEncoderZeroingEstimator::State
334PotAndAbsEncoderZeroingEstimator::GetEstimatorState() const {
335 State r;
336 r.error = error_;
337 r.zeroed = zeroed_;
338 r.position = position_;
339 r.pot_position = filtered_position_;
Austin Schuh0e1c2c62017-02-21 02:03:25 -0800340 r.absolute_position = filtered_absolute_encoder_;
Brian Silverman4f2e2ce2017-02-19 17:49:47 -0800341 return r;
342}
343
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000344void PulseIndexZeroingEstimator::Reset() {
345 max_index_position_ = ::std::numeric_limits<double>::lowest();
346 min_index_position_ = ::std::numeric_limits<double>::max();
347 offset_ = 0;
348 last_used_index_pulse_count_ = 0;
349 zeroed_ = false;
350 error_ = false;
351}
352
353void PulseIndexZeroingEstimator::StoreIndexPulseMaxAndMin(
354 const IndexPosition &info) {
355 // If we have a new index pulse.
356 if (last_used_index_pulse_count_ != info.index_pulses) {
357 // If the latest pulses's position is outside the range we've currently
358 // seen, record it appropriately.
359 if (info.latched_encoder > max_index_position_) {
360 max_index_position_ = info.latched_encoder;
361 }
362 if (info.latched_encoder < min_index_position_) {
363 min_index_position_ = info.latched_encoder;
364 }
365 last_used_index_pulse_count_ = info.index_pulses;
366 }
367}
368
Brian Silvermanf37839c2017-02-19 18:07:15 -0800369int PulseIndexZeroingEstimator::IndexPulseCount() const {
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000370 if (min_index_position_ > max_index_position_) {
371 // This condition means we haven't seen a pulse yet.
372 return 0;
373 }
374
375 // Calculate the number of pulses encountered so far.
376 return 1 + static_cast<int>(
377 ::std::round((max_index_position_ - min_index_position_) /
378 constants_.index_difference));
379}
380
381void PulseIndexZeroingEstimator::UpdateEstimate(const IndexPosition &info) {
382 StoreIndexPulseMaxAndMin(info);
383 const int index_pulse_count = IndexPulseCount();
384 if (index_pulse_count > constants_.index_pulse_count) {
385 error_ = true;
386 }
387
388 // TODO(austin): Detect if the encoder or index pulse is unplugged.
389 // TODO(austin): Detect missing counts.
390
391 if (index_pulse_count == constants_.index_pulse_count && !zeroed_) {
392 offset_ = constants_.measured_index_position -
393 constants_.known_index_pulse * constants_.index_difference -
394 min_index_position_;
395 zeroed_ = true;
396 }
Brian Silvermanf37839c2017-02-19 18:07:15 -0800397
398 position_ = info.encoder + offset_;
399}
400
401PulseIndexZeroingEstimator::State
402PulseIndexZeroingEstimator::GetEstimatorState() const {
403 State r;
404 r.error = error_;
405 r.zeroed = zeroed_;
406 r.position = position_;
407 r.min_index_position = min_index_position_;
408 r.max_index_position = max_index_position_;
409 r.index_pulses_seen = IndexPulseCount();
410 return r;
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000411}
412
Adam Snaiderc4b3c192015-02-01 01:30:39 +0000413} // namespace zeroing
414} // namespace frc971