blob: 70fbb56bf7e57dc91a97452490cdc04517dd33fa [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;
Adam Snaider3cd11c52015-02-16 02:16:09 +000037 first_start_pos_ = 0.0;
Philipp Schrader53f4b6d2015-02-15 22:32:08 +000038 error_ = false;
39}
40
Tyler Chatowf8f03112017-02-05 14:31:34 -080041void PotAndIndexPulseZeroingEstimator::TriggerError() {
Philipp Schrader53f4b6d2015-02-15 22:32:08 +000042 if (!error_) {
43 LOG(ERROR, "Manually triggered zeroing error.\n");
44 error_ = true;
45 }
Philipp Schradere828be72015-02-15 07:07:37 +000046}
47
Tyler Chatowf8f03112017-02-05 14:31:34 -080048double PotAndIndexPulseZeroingEstimator::CalculateStartPosition(
49 double start_average, double latched_encoder) const {
Philipp Schradere828be72015-02-15 07:07:37 +000050 // We calculate an aproximation of the value of the last index position.
51 // Also account for index pulses not lining up with integer multiples of the
52 // index_diff.
Austin Schuh5f01f152017-02-11 21:34:08 -080053 double index_pos =
54 start_average + latched_encoder - constants_.measured_index_position;
Philipp Schradere828be72015-02-15 07:07:37 +000055 // We round index_pos to the closest valid value of the index.
Austin Schuh5f01f152017-02-11 21:34:08 -080056 double accurate_index_pos = (round(index_pos / constants_.index_difference)) *
57 constants_.index_difference;
Philipp Schradere828be72015-02-15 07:07:37 +000058 // Now we reverse the first calculation to get the accurate start position.
Austin Schuh5f01f152017-02-11 21:34:08 -080059 return accurate_index_pos - latched_encoder +
60 constants_.measured_index_position;
Adam Snaiderc4b3c192015-02-01 01:30:39 +000061}
62
Tyler Chatowf8f03112017-02-05 14:31:34 -080063void PotAndIndexPulseZeroingEstimator::UpdateEstimate(
64 const PotAndIndexPosition &info) {
Philipp Schrader41d82912015-02-15 03:44:23 +000065 // We want to make sure that we encounter at least one index pulse while
66 // zeroing. So we take the index pulse count from the first sample after
67 // reset and wait for that count to change before we consider ourselves
68 // zeroed.
69 if (wait_for_index_pulse_) {
Philipp Schradere828be72015-02-15 07:07:37 +000070 last_used_index_pulse_count_ = info.index_pulses;
Philipp Schrader41d82912015-02-15 03:44:23 +000071 wait_for_index_pulse_ = false;
72 }
73
Austin Schuh5f01f152017-02-11 21:34:08 -080074 if (start_pos_samples_.size() < constants_.average_filter_size) {
Adam Snaiderc4b3c192015-02-01 01:30:39 +000075 start_pos_samples_.push_back(info.pot - info.encoder);
76 } else {
77 start_pos_samples_[samples_idx_] = info.pot - info.encoder;
78 }
Adam Snaiderb4119252015-02-15 01:30:57 +000079
80 // Drop the oldest sample when we run this function the next time around.
Austin Schuh5f01f152017-02-11 21:34:08 -080081 samples_idx_ = (samples_idx_ + 1) % constants_.average_filter_size;
Adam Snaiderc4b3c192015-02-01 01:30:39 +000082
Adam Snaiderb4119252015-02-15 01:30:57 +000083 double sample_sum = 0.0;
84
Adam Snaiderc4b3c192015-02-01 01:30:39 +000085 for (size_t i = 0; i < start_pos_samples_.size(); ++i) {
Adam Snaiderb4119252015-02-15 01:30:57 +000086 sample_sum += start_pos_samples_[i];
Adam Snaiderc4b3c192015-02-01 01:30:39 +000087 }
88
89 // Calculates the average of the starting position.
Adam Snaiderb4119252015-02-15 01:30:57 +000090 double start_average = sample_sum / start_pos_samples_.size();
91
92 // If there are no index pulses to use or we don't have enough samples yet to
93 // have a well-filtered starting position then we use the filtered value as
94 // our best guess.
Austin Schuh7485dbb2016-02-08 00:21:58 -080095 if (!zeroed_ &&
96 (info.index_pulses == last_used_index_pulse_count_ || !offset_ready())) {
Isaac Wilcove0851ffd2017-02-16 04:13:14 +000097 offset_ = start_average;
Philipp Schradere828be72015-02-15 07:07:37 +000098 } else if (!zeroed_ || last_used_index_pulse_count_ != info.index_pulses) {
99 // Note the accurate start position and the current index pulse count so
100 // that we only run this logic once per index pulse. That should be more
101 // resilient to corrupted intermediate data.
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000102 offset_ = CalculateStartPosition(start_average, info.latched_encoder);
Philipp Schradere828be72015-02-15 07:07:37 +0000103 last_used_index_pulse_count_ = info.index_pulses;
Austin Schuh7485dbb2016-02-08 00:21:58 -0800104
105 // TODO(austin): Reject encoder positions which have x% error rather than
106 // rounding to the closest index pulse.
107
Adam Snaider3cd11c52015-02-16 02:16:09 +0000108 // Save the first starting position.
109 if (!zeroed_) {
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000110 first_start_pos_ = offset_;
Adam Snaider3cd11c52015-02-16 02:16:09 +0000111 LOG(INFO, "latching start position %f\n", first_start_pos_);
112 }
Adam Snaiderb4119252015-02-15 01:30:57 +0000113
114 // Now that we have an accurate starting position we can consider ourselves
115 // zeroed.
Austin Schuh703b8d42015-02-01 14:56:34 -0800116 zeroed_ = true;
Adam Snaider3cd11c52015-02-16 02:16:09 +0000117 // Throw an error if first_start_pos is bigger/smaller than
Austin Schuh5f01f152017-02-11 21:34:08 -0800118 // constants_.allowable_encoder_error * index_diff + start_pos.
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000119 if (::std::abs(first_start_pos_ - offset_) >
Austin Schuh5f01f152017-02-11 21:34:08 -0800120 constants_.allowable_encoder_error * constants_.index_difference) {
Adam Snaider3cd11c52015-02-16 02:16:09 +0000121 if (!error_) {
122 LOG(ERROR,
123 "Encoder ticks out of range since last index pulse. first start "
Austin Schuh1c85bc82016-04-03 21:36:31 -0700124 "position: %f recent starting position: %f, allowable error: %f\n",
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000125 first_start_pos_, offset_,
Austin Schuh5f01f152017-02-11 21:34:08 -0800126 constants_.allowable_encoder_error * constants_.index_difference);
Adam Snaider3cd11c52015-02-16 02:16:09 +0000127 error_ = true;
128 }
129 }
Adam Snaiderc4b3c192015-02-01 01:30:39 +0000130 }
Adam Snaiderb4119252015-02-15 01:30:57 +0000131
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000132 position_ = offset_ + info.encoder;
Austin Schuhbe133ed2016-03-11 21:23:34 -0800133 filtered_position_ = start_average + info.encoder;
Adam Snaiderc4b3c192015-02-01 01:30:39 +0000134}
135
Brian Silverman4f2e2ce2017-02-19 17:49:47 -0800136PotAndIndexPulseZeroingEstimator::State
137PotAndIndexPulseZeroingEstimator::GetEstimatorState() const {
138 State r;
139 r.error = error_;
140 r.zeroed = zeroed_;
141 r.position = position_;
142 r.pot_position = filtered_position_;
143 return r;
144}
145
146
Austin Schuh5f01f152017-02-11 21:34:08 -0800147PotAndAbsEncoderZeroingEstimator::PotAndAbsEncoderZeroingEstimator(
148 const constants::PotAndAbsoluteEncoderZeroingConstants &constants)
149 : constants_(constants) {
150 relative_to_absolute_offset_samples_.reserve(constants_.average_filter_size);
151 offset_samples_.reserve(constants_.average_filter_size);
152 Reset();
153}
154
155void PotAndAbsEncoderZeroingEstimator::Reset() {
156 zeroed_ = false;
157 relative_to_absolute_offset_samples_.clear();
158 offset_samples_.clear();
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -0800159 buffered_samples_.clear();
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()) {
295 zeroed_ = true;
296 }
Austin Schuh5f01f152017-02-11 21:34:08 -0800297 }
298
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -0800299 // Update the position.
300 filtered_position_ = pot_relative_encoder_offset_ + info.encoder;
Austin Schuh5f01f152017-02-11 21:34:08 -0800301 position_ = offset_ + info.encoder;
302}
303
Brian Silverman4f2e2ce2017-02-19 17:49:47 -0800304PotAndAbsEncoderZeroingEstimator::State
305PotAndAbsEncoderZeroingEstimator::GetEstimatorState() const {
306 State r;
307 r.error = error_;
308 r.zeroed = zeroed_;
309 r.position = position_;
310 r.pot_position = filtered_position_;
311 return r;
312}
313
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000314void PulseIndexZeroingEstimator::Reset() {
315 max_index_position_ = ::std::numeric_limits<double>::lowest();
316 min_index_position_ = ::std::numeric_limits<double>::max();
317 offset_ = 0;
318 last_used_index_pulse_count_ = 0;
319 zeroed_ = false;
320 error_ = false;
321}
322
323void PulseIndexZeroingEstimator::StoreIndexPulseMaxAndMin(
324 const IndexPosition &info) {
325 // If we have a new index pulse.
326 if (last_used_index_pulse_count_ != info.index_pulses) {
327 // If the latest pulses's position is outside the range we've currently
328 // seen, record it appropriately.
329 if (info.latched_encoder > max_index_position_) {
330 max_index_position_ = info.latched_encoder;
331 }
332 if (info.latched_encoder < min_index_position_) {
333 min_index_position_ = info.latched_encoder;
334 }
335 last_used_index_pulse_count_ = info.index_pulses;
336 }
337}
338
339int PulseIndexZeroingEstimator::IndexPulseCount() {
340 if (min_index_position_ > max_index_position_) {
341 // This condition means we haven't seen a pulse yet.
342 return 0;
343 }
344
345 // Calculate the number of pulses encountered so far.
346 return 1 + static_cast<int>(
347 ::std::round((max_index_position_ - min_index_position_) /
348 constants_.index_difference));
349}
350
351void PulseIndexZeroingEstimator::UpdateEstimate(const IndexPosition &info) {
352 StoreIndexPulseMaxAndMin(info);
353 const int index_pulse_count = IndexPulseCount();
354 if (index_pulse_count > constants_.index_pulse_count) {
355 error_ = true;
356 }
357
358 // TODO(austin): Detect if the encoder or index pulse is unplugged.
359 // TODO(austin): Detect missing counts.
360
361 if (index_pulse_count == constants_.index_pulse_count && !zeroed_) {
362 offset_ = constants_.measured_index_position -
363 constants_.known_index_pulse * constants_.index_difference -
364 min_index_position_;
365 zeroed_ = true;
366 }
367 if (zeroed_) {
368 position_ = info.encoder + offset_;
369 }
370}
371
Adam Snaiderc4b3c192015-02-01 01:30:39 +0000372} // namespace zeroing
373} // namespace frc971