James Kuszmaul | 9f2f53c | 2023-02-19 14:08:18 -0800 | [diff] [blame] | 1 | #include "frc971/imu_reader/imu_watcher.h" |
| 2 | |
| 3 | #include "frc971/wpilib/imu_batch_generated.h" |
| 4 | |
| 5 | namespace frc971::controls { |
| 6 | namespace { |
| 7 | // Return the amount of distance that the drivetrain can travel before the |
| 8 | // encoders will wrap. Necessary because the pico only sends over the encoders |
| 9 | // in 16-bit counters, which will wrap relatively readily. |
| 10 | double EncoderWrapDistance(double drivetrain_distance_per_encoder_tick) { |
| 11 | return drivetrain_distance_per_encoder_tick * (1 << 16); |
| 12 | } |
| 13 | } // namespace |
| 14 | ImuWatcher::ImuWatcher( |
| 15 | aos::EventLoop *event_loop, |
| 16 | const control_loops::drivetrain::DrivetrainConfig<double> &dt_config, |
| 17 | const double drivetrain_distance_per_encoder_tick, |
| 18 | std::function< |
| 19 | void(aos::monotonic_clock::time_point, aos::monotonic_clock::time_point, |
| 20 | std::optional<Eigen::Vector2d>, Eigen::Vector3d, Eigen::Vector3d)> |
James Kuszmaul | 54fe9d0 | 2023-03-23 20:32:40 -0700 | [diff] [blame] | 21 | callback, |
| 22 | TimestampSource timestamp_source) |
James Kuszmaul | 9f2f53c | 2023-02-19 14:08:18 -0800 | [diff] [blame] | 23 | : dt_config_(dt_config), |
| 24 | callback_(std::move(callback)), |
| 25 | zeroer_(zeroing::ImuZeroer::FaultBehavior::kTemporary), |
| 26 | left_encoder_( |
| 27 | -EncoderWrapDistance(drivetrain_distance_per_encoder_tick) / 2.0, |
| 28 | EncoderWrapDistance(drivetrain_distance_per_encoder_tick)), |
| 29 | right_encoder_( |
| 30 | -EncoderWrapDistance(drivetrain_distance_per_encoder_tick) / 2.0, |
| 31 | EncoderWrapDistance(drivetrain_distance_per_encoder_tick)) { |
James Kuszmaul | 723e66e | 2023-04-05 21:22:57 -0700 | [diff] [blame] | 32 | event_loop->MakeWatcher("/localizer", [this, timestamp_source, event_loop]( |
James Kuszmaul | 54fe9d0 | 2023-03-23 20:32:40 -0700 | [diff] [blame] | 33 | const IMUValuesBatch &values) { |
James Kuszmaul | 9f2f53c | 2023-02-19 14:08:18 -0800 | [diff] [blame] | 34 | CHECK(values.has_readings()); |
| 35 | for (const IMUValues *value : *values.readings()) { |
| 36 | zeroer_.InsertAndProcessMeasurement(*value); |
| 37 | if (zeroer_.Faulted()) { |
| 38 | if (value->checksum_failed()) { |
| 39 | imu_fault_tracker_.pico_to_pi_checksum_mismatch++; |
James Kuszmaul | 5e5dc53 | 2024-02-14 19:54:09 -0800 | [diff] [blame] | 40 | } else if (value->has_previous_reading_diag_stat() && |
| 41 | value->previous_reading_diag_stat()->checksum_mismatch()) { |
James Kuszmaul | 9f2f53c | 2023-02-19 14:08:18 -0800 | [diff] [blame] | 42 | imu_fault_tracker_.imu_to_pico_checksum_mismatch++; |
| 43 | } else { |
| 44 | imu_fault_tracker_.other_zeroing_faults++; |
| 45 | } |
| 46 | } else { |
James Kuszmaul | 5e5dc53 | 2024-02-14 19:54:09 -0800 | [diff] [blame] | 47 | if (!first_valid_data_counter_.has_value() && |
| 48 | value->has_data_counter()) { |
James Kuszmaul | 9f2f53c | 2023-02-19 14:08:18 -0800 | [diff] [blame] | 49 | first_valid_data_counter_ = value->data_counter(); |
| 50 | } |
| 51 | } |
James Kuszmaul | 723e66e | 2023-04-05 21:22:57 -0700 | [diff] [blame] | 52 | int messages_dropped_this_cycle = 0; |
James Kuszmaul | 9f2f53c | 2023-02-19 14:08:18 -0800 | [diff] [blame] | 53 | if (first_valid_data_counter_.has_value()) { |
| 54 | total_imu_messages_received_++; |
| 55 | // Only update when we have good checksums, since the data counter |
| 56 | // could get corrupted. |
| 57 | if (!zeroer_.Faulted()) { |
| 58 | if (value->data_counter() < last_data_counter_) { |
| 59 | data_counter_offset_ += 1 << 16; |
| 60 | } |
James Kuszmaul | 723e66e | 2023-04-05 21:22:57 -0700 | [diff] [blame] | 61 | const int total_dropped = |
James Kuszmaul | 9f2f53c | 2023-02-19 14:08:18 -0800 | [diff] [blame] | 62 | (1 + value->data_counter() + data_counter_offset_ - |
| 63 | first_valid_data_counter_.value()) - |
| 64 | total_imu_messages_received_; |
James Kuszmaul | 723e66e | 2023-04-05 21:22:57 -0700 | [diff] [blame] | 65 | messages_dropped_this_cycle = |
| 66 | total_dropped - imu_fault_tracker_.missed_messages; |
| 67 | imu_fault_tracker_.missed_messages = total_dropped; |
James Kuszmaul | 9f2f53c | 2023-02-19 14:08:18 -0800 | [diff] [blame] | 68 | last_data_counter_ = value->data_counter(); |
| 69 | } |
| 70 | } |
| 71 | // Set encoders to nullopt if we are faulted at all (faults may include |
| 72 | // checksum mismatches). |
James Kuszmaul | 5e5dc53 | 2024-02-14 19:54:09 -0800 | [diff] [blame] | 73 | const bool have_encoders = !zeroer_.Faulted() && |
| 74 | value->has_left_encoder() && |
| 75 | value->has_right_encoder(); |
James Kuszmaul | 9f2f53c | 2023-02-19 14:08:18 -0800 | [diff] [blame] | 76 | const std::optional<Eigen::Vector2d> encoders = |
James Kuszmaul | 5e5dc53 | 2024-02-14 19:54:09 -0800 | [diff] [blame] | 77 | have_encoders ? std::make_optional(Eigen::Vector2d{ |
| 78 | left_encoder_.Unwrap(value->left_encoder()), |
| 79 | right_encoder_.Unwrap(value->right_encoder())}) |
| 80 | : std::nullopt; |
James Kuszmaul | 9f2f53c | 2023-02-19 14:08:18 -0800 | [diff] [blame] | 81 | { |
James Kuszmaul | 04a343c | 2023-02-20 16:38:22 -0800 | [diff] [blame] | 82 | const aos::monotonic_clock::time_point pi_read_timestamp = |
| 83 | aos::monotonic_clock::time_point( |
| 84 | std::chrono::nanoseconds(value->monotonic_timestamp_ns())); |
James Kuszmaul | 54fe9d0 | 2023-03-23 20:32:40 -0700 | [diff] [blame] | 85 | // If we can't trust the imu reading, just naively increment the |
| 86 | // pico timestamp. |
| 87 | const aos::monotonic_clock::time_point pico_timestamp = |
| 88 | timestamp_source == TimestampSource::kPi |
| 89 | ? pi_read_timestamp |
| 90 | : (zeroer_.Faulted() |
| 91 | ? (last_pico_timestamp_.has_value() |
| 92 | ? last_pico_timestamp_.value() + kNominalDt |
| 93 | : aos::monotonic_clock::epoch()) |
| 94 | : aos::monotonic_clock::time_point( |
| 95 | std::chrono::microseconds( |
| 96 | value->pico_timestamp_us()))); |
James Kuszmaul | 9f2f53c | 2023-02-19 14:08:18 -0800 | [diff] [blame] | 97 | // TODO(james): If we get large enough drift off of the pico, |
| 98 | // actually do something about it. |
| 99 | if (!pico_offset_.has_value()) { |
James Kuszmaul | 04a343c | 2023-02-20 16:38:22 -0800 | [diff] [blame] | 100 | pico_offset_ = pi_read_timestamp - pico_timestamp; |
James Kuszmaul | 9f2f53c | 2023-02-19 14:08:18 -0800 | [diff] [blame] | 101 | last_pico_timestamp_ = pico_timestamp; |
| 102 | } |
James Kuszmaul | 723e66e | 2023-04-05 21:22:57 -0700 | [diff] [blame] | 103 | // The pico will sleep for a minimum of 3 seconds when resetting the |
| 104 | // IMU. Any absence of messages for less than that time is probably not |
| 105 | // an actual reset. |
James Kuszmaul | 9f2f53c | 2023-02-19 14:08:18 -0800 | [diff] [blame] | 106 | if (pico_timestamp < last_pico_timestamp_) { |
| 107 | pico_offset_.value() += std::chrono::microseconds(1ULL << 32); |
| 108 | } |
| 109 | const aos::monotonic_clock::time_point sample_timestamp = |
| 110 | pico_offset_.value() + pico_timestamp; |
James Kuszmaul | 04a343c | 2023-02-20 16:38:22 -0800 | [diff] [blame] | 111 | pico_offset_error_ = pi_read_timestamp - sample_timestamp; |
James Kuszmaul | 723e66e | 2023-04-05 21:22:57 -0700 | [diff] [blame] | 112 | |
| 113 | if (last_imu_message_.has_value()) { |
| 114 | constexpr std::chrono::seconds kMinimumImuResetTime(3); |
| 115 | const std::chrono::nanoseconds message_time_gap = |
| 116 | last_imu_message_.value() - |
| 117 | event_loop->context().monotonic_event_time; |
| 118 | if (message_time_gap > kMinimumImuResetTime) { |
| 119 | bool assigned_fault = false; |
| 120 | // The IMU has probably reset; attempt to determine whether just the |
| 121 | // IMU was reset or if the IMU and pico reset. |
| 122 | if (std::chrono::abs(pico_offset_error_) > |
| 123 | std::chrono::seconds(1)) { |
| 124 | // If we have this big of a gap, then everything downstream of |
| 125 | // this is going to complain anyways, so reset the offset. |
| 126 | pico_offset_.reset(); |
| 127 | |
| 128 | imu_fault_tracker_.probable_pico_reset_count++; |
| 129 | assigned_fault = true; |
| 130 | } |
| 131 | // See if we dropped the "right" number of messages for the gap in |
| 132 | // question. |
| 133 | if (std::chrono::abs(messages_dropped_this_cycle * kNominalDt - |
| 134 | message_time_gap) > |
| 135 | std::chrono::milliseconds(100)) { |
| 136 | imu_fault_tracker_.probable_imu_reset_count++; |
| 137 | assigned_fault = true; |
| 138 | } |
| 139 | |
| 140 | if (!assigned_fault) { |
| 141 | imu_fault_tracker_.unassignable_reset_count++; |
| 142 | } |
| 143 | } |
| 144 | } |
James Kuszmaul | 9f2f53c | 2023-02-19 14:08:18 -0800 | [diff] [blame] | 145 | const bool zeroed = zeroer_.Zeroed(); |
| 146 | |
| 147 | // When not zeroed, we aim to approximate zero acceleration by doing a |
| 148 | // zero-order hold on the gyro and setting the accelerometer readings to |
| 149 | // gravity. |
James Kuszmaul | 04a343c | 2023-02-20 16:38:22 -0800 | [diff] [blame] | 150 | callback_(sample_timestamp, pi_read_timestamp, encoders, |
| 151 | zeroed ? zeroer_.ZeroedGyro().value() : last_gyro_, |
James Kuszmaul | 9f2f53c | 2023-02-19 14:08:18 -0800 | [diff] [blame] | 152 | zeroed ? zeroer_.ZeroedAccel().value() |
| 153 | : dt_config_.imu_transform.transpose() * |
| 154 | Eigen::Vector3d::UnitZ()); |
| 155 | |
| 156 | if (zeroed) { |
| 157 | last_gyro_ = zeroer_.ZeroedGyro().value(); |
| 158 | } |
| 159 | last_pico_timestamp_ = pico_timestamp; |
| 160 | } |
James Kuszmaul | 723e66e | 2023-04-05 21:22:57 -0700 | [diff] [blame] | 161 | last_imu_message_ = event_loop->context().monotonic_event_time; |
James Kuszmaul | 9f2f53c | 2023-02-19 14:08:18 -0800 | [diff] [blame] | 162 | } |
| 163 | }); |
| 164 | } |
| 165 | } // namespace frc971::controls |