blob: a1fd17e7112d4a500e44f386b92b4f85b2b95a06 [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
233 // Now, compute the nearest absolute encoder value to the offset relative
234 // encoder position.
235 const double adjusted_absolute_encoder =
236 Wrap(buffered_samples_[middle_index].encoder +
237 average_relative_to_absolute_offset,
238 buffered_samples_[middle_index].absolute_encoder -
239 constants_.measured_absolute_position,
240 constants_.one_revolution_distance);
241
242 const double relative_to_absolute_offset =
243 adjusted_absolute_encoder - buffered_samples_[middle_index].encoder;
244
245 // Add the sample and update the average with the new reading.
246 const size_t relative_to_absolute_offset_samples_size =
247 relative_to_absolute_offset_samples_.size();
248 if (relative_to_absolute_offset_samples_size <
249 constants_.average_filter_size) {
250 average_relative_to_absolute_offset =
251 (average_relative_to_absolute_offset *
252 relative_to_absolute_offset_samples_size +
253 relative_to_absolute_offset) /
254 (relative_to_absolute_offset_samples_size + 1);
255
256 relative_to_absolute_offset_samples_.push_back(
257 relative_to_absolute_offset);
258 } else {
259 average_relative_to_absolute_offset -=
260 relative_to_absolute_offset_samples_[samples_idx_] /
261 relative_to_absolute_offset_samples_size;
262 relative_to_absolute_offset_samples_[samples_idx_] =
263 relative_to_absolute_offset;
264 average_relative_to_absolute_offset +=
265 relative_to_absolute_offset /
266 relative_to_absolute_offset_samples_size;
267 }
268
269 // Now compute the offset between the pot and relative encoder.
270 if (offset_samples_.size() < constants_.average_filter_size) {
271 offset_samples_.push_back(buffered_samples_[middle_index].pot -
272 buffered_samples_[middle_index].encoder);
273 } else {
274 offset_samples_[samples_idx_] = buffered_samples_[middle_index].pot -
275 buffered_samples_[middle_index].encoder;
276 }
277
278 // Drop the oldest sample when we run this function the next time around.
279 samples_idx_ = (samples_idx_ + 1) % constants_.average_filter_size;
280
281 double pot_relative_encoder_offset_sum = 0.0;
282 for (size_t i = 0; i < offset_samples_.size(); ++i) {
283 pot_relative_encoder_offset_sum += offset_samples_[i];
284 }
285 pot_relative_encoder_offset_ =
286 pot_relative_encoder_offset_sum / offset_samples_.size();
287
288 offset_ = Wrap(buffered_samples_[middle_index].encoder +
289 pot_relative_encoder_offset_,
290 average_relative_to_absolute_offset +
291 buffered_samples_[middle_index].encoder,
292 constants_.one_revolution_distance) -
293 buffered_samples_[middle_index].encoder;
294 if (offset_ready()) {
Brian Silvermana10d20a2017-02-19 14:28:53 -0800295 if (!zeroed_) {
296 first_offset_ = offset_;
297 }
298
299 if (::std::abs(first_offset_ - offset_) >
300 constants_.allowable_encoder_error *
301 constants_.one_revolution_distance) {
302 LOG(ERROR,
303 "Offset moved too far. Initial: %f, current %f, allowable change: "
304 "%f\n",
305 first_offset_, offset_, constants_.allowable_encoder_error *
306 constants_.one_revolution_distance);
307 error_ = true;
308 }
309
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -0800310 zeroed_ = true;
311 }
Austin Schuh5f01f152017-02-11 21:34:08 -0800312 }
313
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -0800314 // Update the position.
315 filtered_position_ = pot_relative_encoder_offset_ + info.encoder;
Austin Schuh5f01f152017-02-11 21:34:08 -0800316 position_ = offset_ + info.encoder;
317}
318
Brian Silverman4f2e2ce2017-02-19 17:49:47 -0800319PotAndAbsEncoderZeroingEstimator::State
320PotAndAbsEncoderZeroingEstimator::GetEstimatorState() const {
321 State r;
322 r.error = error_;
323 r.zeroed = zeroed_;
324 r.position = position_;
325 r.pot_position = filtered_position_;
326 return r;
327}
328
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000329void PulseIndexZeroingEstimator::Reset() {
330 max_index_position_ = ::std::numeric_limits<double>::lowest();
331 min_index_position_ = ::std::numeric_limits<double>::max();
332 offset_ = 0;
333 last_used_index_pulse_count_ = 0;
334 zeroed_ = false;
335 error_ = false;
336}
337
338void PulseIndexZeroingEstimator::StoreIndexPulseMaxAndMin(
339 const IndexPosition &info) {
340 // If we have a new index pulse.
341 if (last_used_index_pulse_count_ != info.index_pulses) {
342 // If the latest pulses's position is outside the range we've currently
343 // seen, record it appropriately.
344 if (info.latched_encoder > max_index_position_) {
345 max_index_position_ = info.latched_encoder;
346 }
347 if (info.latched_encoder < min_index_position_) {
348 min_index_position_ = info.latched_encoder;
349 }
350 last_used_index_pulse_count_ = info.index_pulses;
351 }
352}
353
354int PulseIndexZeroingEstimator::IndexPulseCount() {
355 if (min_index_position_ > max_index_position_) {
356 // This condition means we haven't seen a pulse yet.
357 return 0;
358 }
359
360 // Calculate the number of pulses encountered so far.
361 return 1 + static_cast<int>(
362 ::std::round((max_index_position_ - min_index_position_) /
363 constants_.index_difference));
364}
365
366void PulseIndexZeroingEstimator::UpdateEstimate(const IndexPosition &info) {
367 StoreIndexPulseMaxAndMin(info);
368 const int index_pulse_count = IndexPulseCount();
369 if (index_pulse_count > constants_.index_pulse_count) {
370 error_ = true;
371 }
372
373 // TODO(austin): Detect if the encoder or index pulse is unplugged.
374 // TODO(austin): Detect missing counts.
375
376 if (index_pulse_count == constants_.index_pulse_count && !zeroed_) {
377 offset_ = constants_.measured_index_position -
378 constants_.known_index_pulse * constants_.index_difference -
379 min_index_position_;
380 zeroed_ = true;
381 }
382 if (zeroed_) {
383 position_ = info.encoder + offset_;
384 }
385}
386
Adam Snaiderc4b3c192015-02-01 01:30:39 +0000387} // namespace zeroing
388} // namespace frc971