Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 1 | #include "frc971/wpilib/ADIS16470.h" |
| 2 | |
Tyler Chatow | bf0609c | 2021-07-31 16:13:27 -0700 | [diff] [blame] | 3 | #include <cinttypes> |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 4 | |
Philipp Schrader | 790cb54 | 2023-07-05 21:06:52 -0700 | [diff] [blame^] | 5 | #include "glog/logging.h" |
| 6 | |
Austin Schuh | ac17fba | 2020-03-28 15:55:33 -0700 | [diff] [blame] | 7 | #include "aos/containers/sized_array.h" |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 8 | #include "aos/time/time.h" |
| 9 | #include "hal/HAL.h" |
| 10 | |
| 11 | namespace frc971 { |
| 12 | namespace wpilib { |
| 13 | namespace { |
Austin Schuh | b51692d | 2021-04-10 15:27:25 -0700 | [diff] [blame] | 14 | namespace chrono = std::chrono; |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 15 | namespace registers { |
| 16 | |
| 17 | // Flash memory write count |
| 18 | constexpr uint8_t FLASH_CNT = 0x00; |
| 19 | // Diagnostic and operational status |
| 20 | constexpr uint8_t DIAG_STAT = 0x02; |
| 21 | // X-axis gyroscope output, lower word |
| 22 | constexpr uint8_t X_GYRO_LOW = 0x04; |
| 23 | // X-axis gyroscope output, upper word |
| 24 | constexpr uint8_t X_GYRO_OUT = 0x06; |
| 25 | // Y-axis gyroscope output, lower word |
| 26 | constexpr uint8_t Y_GYRO_LOW = 0x08; |
| 27 | // Y-axis gyroscope output, upper word |
| 28 | constexpr uint8_t Y_GYRO_OUT = 0x0A; |
| 29 | // Z-axis gyroscope output, lower word |
| 30 | constexpr uint8_t Z_GYRO_LOW = 0x0C; |
| 31 | // Z-axis gyroscope output, upper word |
| 32 | constexpr uint8_t Z_GYRO_OUT = 0x0E; |
| 33 | // X-axis accelerometer output, lower word |
| 34 | constexpr uint8_t X_ACCL_LOW = 0x10; |
| 35 | // X-axis accelerometer output, upper word |
| 36 | constexpr uint8_t X_ACCL_OUT = 0x12; |
| 37 | // Y-axis accelerometer output, lower word |
| 38 | constexpr uint8_t Y_ACCL_OUT = 0x16; |
| 39 | // Y-axis accelerometer output, upper word |
| 40 | constexpr uint8_t Z_ACCL_LOW = 0x18; |
| 41 | // Z-axis accelerometer output, lower word |
| 42 | constexpr uint8_t Z_ACCL_OUT = 0x1A; |
| 43 | // Z-axis accelerometer output, upper word |
| 44 | constexpr uint8_t TEMP_OUT = 0x1C; |
| 45 | // Temperature output (internal, not calibrated) |
| 46 | constexpr uint8_t TIME_STAMP = 0x1E; |
| 47 | // PPS mode time stamp |
| 48 | constexpr uint8_t X_DELTANG_LOW = 0x24; |
| 49 | // X-axis delta angle output, lower word |
| 50 | constexpr uint8_t X_DELTANG_OUT = 0x26; |
| 51 | // X-axis delta angle output, upper word |
| 52 | constexpr uint8_t Y_DELTANG_LOW = 0x28; |
| 53 | // Y-axis delta angle output, lower word |
| 54 | constexpr uint8_t Y_DELTANG_OUT = 0x2A; |
| 55 | // Y-axis delta angle output, upper word |
| 56 | constexpr uint8_t Z_DELTANG_LOW = 0x2C; |
| 57 | // Z-axis delta angle output, lower word |
| 58 | constexpr uint8_t Z_DELTANG_OUT = 0x2E; |
| 59 | // Z-axis delta angle output, upper word |
| 60 | constexpr uint8_t X_DELTVEL_LOW = 0x30; |
| 61 | // X-axis delta velocity output, lower word |
| 62 | constexpr uint8_t X_DELTVEL_OUT = 0x32; |
| 63 | // X-axis delta velocity output, upper word |
| 64 | constexpr uint8_t Y_DELTVEL_LOW = 0x34; |
| 65 | // Y-axis delta velocity output, lower word |
| 66 | constexpr uint8_t Y_DELTVEL_OUT = 0x36; |
| 67 | // Y-axis delta velocity output, upper word |
| 68 | constexpr uint8_t Z_DELTVEL_LOW = 0x38; |
| 69 | // Z-axis delta velocity output, lower word |
| 70 | constexpr uint8_t Z_DELTVEL_OUT = 0x3A; |
| 71 | // Z-axis delta velocity output, upper word |
| 72 | constexpr uint8_t XG_BIAS_LOW = 0x40; |
| 73 | // X-axis gyroscope bias offset correction, lower word |
| 74 | constexpr uint8_t XG_BIAS_HIGH = 0x42; |
| 75 | // X-axis gyroscope bias offset correction, upper word |
| 76 | constexpr uint8_t YG_BIAS_LOW = 0x44; |
| 77 | // Y-axis gyroscope bias offset correction, lower word |
| 78 | constexpr uint8_t YG_BIAS_HIGH = 0x46; |
| 79 | // Y-axis gyroscope bias offset correction, upper word |
| 80 | constexpr uint8_t ZG_BIAS_LOW = 0x48; |
| 81 | // Z-axis gyroscope bias offset correction, lower word |
| 82 | constexpr uint8_t ZG_BIAS_HIGH = 0x4A; |
| 83 | // Z-axis gyroscope bias offset correction, upper word |
| 84 | constexpr uint8_t XA_BIAS_LOW = 0x4C; |
| 85 | // X-axis accelerometer bias offset correction, lower word |
| 86 | constexpr uint8_t XA_BIAS_HIGH = 0x4E; |
| 87 | // X-axis accelerometer bias offset correction, upper word |
| 88 | constexpr uint8_t YA_BIAS_LOW = 0x50; |
| 89 | // Y-axis accelerometer bias offset correction, lower word |
| 90 | constexpr uint8_t YA_BIAS_HIGH = 0x52; |
| 91 | // Y-axis accelerometer bias offset correction, upper word |
| 92 | constexpr uint8_t ZA_BIAS_LOW = 0x54; |
| 93 | // Z-axis accelerometer bias offset correction, lower word |
| 94 | constexpr uint8_t ZA_BIAS_HIGH = 0x56; |
| 95 | // Z-axis accelerometer bias offset correction, upper word |
| 96 | constexpr uint8_t FILT_CTRL = 0x5C; |
| 97 | // Filter control |
| 98 | constexpr uint8_t MSC_CTRL = 0x60; |
| 99 | // Miscellaneous control |
| 100 | constexpr uint8_t UP_SCALE = 0x62; |
| 101 | // Clock scale factor, PPS mode |
| 102 | constexpr uint8_t DEC_RATE = 0x64; |
| 103 | // Decimation rate control (output data rate) |
| 104 | constexpr uint8_t NULL_CNFG = 0x66; |
| 105 | // Auto-null configuration control |
| 106 | constexpr uint8_t GLOB_CMD = 0x68; |
| 107 | // Global commands |
| 108 | constexpr uint8_t FIRM_REV = 0x6C; |
| 109 | // Firmware revision |
| 110 | constexpr uint8_t FIRM_DM = 0x6E; |
| 111 | // Firmware revision date, month and day |
| 112 | constexpr uint8_t FIRM_Y = 0x70; |
| 113 | // Firmware revision date, year |
| 114 | constexpr uint8_t PROD_ID = 0x72; |
| 115 | // Product identification |
| 116 | constexpr uint8_t SERIAL_NUM = 0x74; |
| 117 | // Serial number (relative to assembly lot) |
| 118 | constexpr uint8_t USER_SCR1 = 0x76; |
| 119 | // User scratch register 1 |
| 120 | constexpr uint8_t USER_SCR2 = 0x78; |
| 121 | // User scratch register 2 |
| 122 | constexpr uint8_t USER_SCR3 = 0x7A; |
| 123 | // User scratch register 3 |
| 124 | constexpr uint8_t FLSHCNT_LOW = 0x7C; |
| 125 | // Flash update count, lower word |
| 126 | constexpr uint8_t FLSHCNT_HIGH = 0x7E; |
| 127 | // Flash update count, upper word |
| 128 | constexpr uint8_t Y_ACCL_LOW = 0x14; |
| 129 | |
| 130 | } // namespace registers |
| 131 | |
| 132 | // The complete automatic packet we will send. This needs to include the dummy 0 |
| 133 | // bytes making up full 16-bit frames. |
| 134 | // Note that in addition to the 24-byte limit from the FPGA, this is also |
| 135 | // limited to 12 16-bit register reads by the IMU itself given that we're |
| 136 | // reading at the full 2kHz rate. |
| 137 | // We rotate the registers here by 1, such that the first thing we read is the |
| 138 | // last thing triggered by the previous reading. We put DIAG_STAT in this |
| 139 | // position because we don't care if it's one cycle stale. |
| 140 | constexpr uint8_t kAutospiPacket[] = { |
| 141 | // X |
Tyler Chatow | bf0609c | 2021-07-31 16:13:27 -0700 | [diff] [blame] | 142 | registers::X_GYRO_OUT, |
| 143 | 0, |
| 144 | registers::X_ACCL_OUT, |
| 145 | 0, |
| 146 | registers::X_ACCL_LOW, |
| 147 | 0, |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 148 | // Y |
Tyler Chatow | bf0609c | 2021-07-31 16:13:27 -0700 | [diff] [blame] | 149 | registers::Y_GYRO_OUT, |
| 150 | 0, |
| 151 | registers::Y_ACCL_OUT, |
| 152 | 0, |
| 153 | registers::Y_ACCL_LOW, |
| 154 | 0, |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 155 | // Z |
Tyler Chatow | bf0609c | 2021-07-31 16:13:27 -0700 | [diff] [blame] | 156 | registers::Z_GYRO_OUT, |
| 157 | 0, |
| 158 | registers::Z_ACCL_OUT, |
| 159 | 0, |
| 160 | registers::Z_ACCL_LOW, |
| 161 | 0, |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 162 | // Other |
Tyler Chatow | bf0609c | 2021-07-31 16:13:27 -0700 | [diff] [blame] | 163 | registers::TEMP_OUT, |
| 164 | 0, |
| 165 | registers::DIAG_STAT, |
| 166 | 0, |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 167 | }; |
| 168 | // clang-format on |
| 169 | |
| 170 | static_assert((sizeof(kAutospiPacket) % 2) == 0, |
| 171 | "Need a whole number of register reads"); |
| 172 | |
Tyler Chatow | bf0609c | 2021-07-31 16:13:27 -0700 | [diff] [blame] | 173 | static constexpr size_t kAutospiDataSize = |
| 174 | sizeof(kAutospiPacket) + 1 /* timestamp */; |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 175 | |
| 176 | // radian/second/LSB for the gyros (for just the 16-bit value). |
| 177 | constexpr double kGyroLsbRadianSecond = |
| 178 | 1.0 / 10.0 * (2.0 * M_PI / 360.0) /* degrees -> radians */; |
| 179 | // G/LSB for the accelerometers (for the full 32-bit value). |
James Kuszmaul | b145b32 | 2020-01-22 22:31:52 -0800 | [diff] [blame] | 180 | constexpr double kAccelerometerLsbG = 1.0 / 52'428'800.0; |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 181 | // C/LSB for the temperature. |
| 182 | constexpr double kTemperatureLsbDegree = 0.1; |
| 183 | |
| 184 | // This is what the datasheet says PROD_ID should be. |
| 185 | constexpr uint16_t kExpectedProductId = 0x4056; |
| 186 | // This is the PROD_ID we observe. |
| 187 | constexpr uint16_t kObservedProductId = 0x4256; |
| 188 | |
| 189 | } // namespace |
| 190 | |
| 191 | ADIS16470::ADIS16470(aos::EventLoop *event_loop, frc::SPI *spi, |
| 192 | frc::DigitalInput *data_ready, frc::DigitalOutput *reset) |
| 193 | : event_loop_(event_loop), |
| 194 | imu_values_sender_( |
Austin Schuh | ac17fba | 2020-03-28 15:55:33 -0700 | [diff] [blame] | 195 | event_loop_->MakeSender<::frc971::IMUValuesBatch>("/drivetrain")), |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 196 | initialize_timer_( |
| 197 | event_loop_->AddTimer([this]() { DoInitializeStep(); })), |
| 198 | spi_(spi), |
| 199 | data_ready_(data_ready), |
| 200 | reset_(reset) { |
| 201 | // Rather than put the entire data packet into the header, just put a size |
| 202 | // there and verify it matches here. |
| 203 | CHECK_EQ(kAutospiDataSize, read_data_.size()); |
| 204 | |
| 205 | // We're not doing burst mode, so this is the IMU's rated speed. |
| 206 | spi_->SetClockRate(2'000'000); |
| 207 | spi_->SetChipSelectActiveLow(); |
James Kuszmaul | 9776b39 | 2023-01-14 14:08:08 -0800 | [diff] [blame] | 208 | spi_->SetMode(frc::SPI::Mode::kMode3); |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 209 | |
| 210 | // NI's SPI driver defaults to SCHED_OTHER. Find it's PID with ps, and change |
| 211 | // it to a RT priority of 33. |
Tyler Chatow | bf0609c | 2021-07-31 16:13:27 -0700 | [diff] [blame] | 212 | PCHECK(system("busybox ps -ef | grep '\\[spi0\\]' | awk '{print $1}' | xargs " |
| 213 | "chrt -f -p " |
| 214 | "33") == 0); |
| 215 | PCHECK(system("busybox ps -ef | grep '\\[spi1\\]' | awk '{print $1}' | xargs " |
| 216 | "chrt -f -p " |
| 217 | "33") == 0); |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 218 | |
| 219 | event_loop_->OnRun([this]() { BeginInitialization(); }); |
| 220 | } |
| 221 | |
| 222 | void ADIS16470::DoReads() { |
| 223 | if (state_ != State::kRunning) { |
| 224 | // Not sure how to interpret data received now, so ignore it. |
| 225 | return; |
| 226 | } |
| 227 | |
Austin Schuh | ac17fba | 2020-03-28 15:55:33 -0700 | [diff] [blame] | 228 | auto builder = imu_values_sender_.MakeBuilder(); |
| 229 | |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 230 | int amount_to_read = |
| 231 | spi_->ReadAutoReceivedData(to_read_.data(), 0, 0 /* don't block */); |
Austin Schuh | ac17fba | 2020-03-28 15:55:33 -0700 | [diff] [blame] | 232 | |
| 233 | aos::SizedArray<flatbuffers::Offset<IMUValues>, 50> readings_offsets; |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 234 | while (true) { |
| 235 | if (amount_to_read == 0) break; |
| 236 | CHECK(!to_read_.empty()); |
| 237 | const int amount_read_now = std::min<int>(amount_to_read, to_read_.size()); |
| 238 | CHECK_GT(amount_read_now, 0) << "amount_to_read: " << amount_to_read |
| 239 | << ", to_read_.size(): " << to_read_.size(); |
| 240 | spi_->ReadAutoReceivedData(to_read_.data(), amount_read_now, |
| 241 | 0 /* don't block */); |
| 242 | to_read_ = to_read_.subspan(amount_read_now); |
| 243 | amount_to_read -= amount_read_now; |
| 244 | |
| 245 | if (to_read_.empty()) { |
Austin Schuh | ac17fba | 2020-03-28 15:55:33 -0700 | [diff] [blame] | 246 | flatbuffers::Offset<IMUValues> reading_offset = |
| 247 | ProcessReading(builder.fbb()); |
| 248 | readings_offsets.push_back(reading_offset); |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 249 | |
| 250 | // Reset for the next reading. |
| 251 | to_read_ = absl::MakeSpan(read_data_); |
| 252 | } else { |
| 253 | CHECK_EQ(amount_to_read, 0); |
| 254 | break; |
| 255 | } |
| 256 | } |
Austin Schuh | ac17fba | 2020-03-28 15:55:33 -0700 | [diff] [blame] | 257 | |
| 258 | flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<IMUValues>>> |
| 259 | readings_offset = builder.fbb()->CreateVector(readings_offsets.data(), |
| 260 | readings_offsets.size()); |
| 261 | |
| 262 | IMUValuesBatch::Builder imu_values_batch_builder = |
| 263 | builder.MakeBuilder<IMUValuesBatch>(); |
| 264 | imu_values_batch_builder.add_readings(readings_offset); |
milind | 1f1dca3 | 2021-07-03 13:50:07 -0700 | [diff] [blame] | 265 | builder.CheckOk(builder.Send(imu_values_batch_builder.Finish())); |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 266 | } |
| 267 | |
| 268 | void ADIS16470::DoInitializeStep() { |
| 269 | switch (state_) { |
| 270 | case State::kUninitialized: { |
| 271 | to_read_ = absl::MakeSpan(read_data_); |
| 272 | |
| 273 | // First, set the SPI to normal mode so it stops trying to talk |
| 274 | // automatically. |
| 275 | spi_->StopAuto(); |
| 276 | |
| 277 | reset_->Set(false); |
| 278 | // Datasheet says it needs a 1 us pulse, so make sure we do something in |
| 279 | // between asserting and deasserting. |
Austin Schuh | b51692d | 2021-04-10 15:27:25 -0700 | [diff] [blame] | 280 | std::this_thread::sleep_for(chrono::milliseconds(1)); |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 281 | reset_->Set(true); |
| 282 | |
| 283 | state_ = State::kWaitForReset; |
| 284 | // Datasheet says it takes 193 ms to come out of reset, so give it some |
| 285 | // margin on top of that. |
| 286 | initialize_timer_->Setup(event_loop_->monotonic_now() + |
Austin Schuh | b51692d | 2021-04-10 15:27:25 -0700 | [diff] [blame] | 287 | chrono::milliseconds(250)); |
Tyler Chatow | bf0609c | 2021-07-31 16:13:27 -0700 | [diff] [blame] | 288 | } break; |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 289 | |
| 290 | case State::kWaitForReset: { |
| 291 | flatbuffers::Offset<ADIS16470DiagStat> start_diag_stat; |
| 292 | flatbuffers::Offset<ADIS16470DiagStat> self_test_diag_stat; |
| 293 | bool success = false; |
| 294 | auto builder = imu_values_sender_.MakeBuilder(); |
| 295 | |
| 296 | // Configure the IMU the way we want it. |
| 297 | const uint16_t product_id = ReadRegister(registers::PROD_ID, 0); |
| 298 | if (product_id == kExpectedProductId || |
| 299 | product_id == kObservedProductId) { |
| 300 | const uint16_t start_diag_stat_value = |
| 301 | ReadRegister(registers::DIAG_STAT, 0); |
| 302 | start_diag_stat = PackDiagStat(builder.fbb(), start_diag_stat_value); |
| 303 | if (!DiagStatHasError( |
| 304 | *GetTemporaryPointer(*builder.fbb(), start_diag_stat))) { |
| 305 | WriteRegister(registers::FILT_CTRL, 0 /* no filtering */); |
| 306 | WriteRegister( |
| 307 | registers::MSC_CTRL, |
| 308 | (1 << 7) /* enable gyro linear g compensation */ | |
| 309 | (1 << 6) /* enable point of percussion alignment */ | |
| 310 | (0 << 2) /* internal clock mode */ | |
| 311 | (0 << 1) /* sync polarity, doesn't matter */ | |
| 312 | (1 << 0) /* data ready is active high */); |
James Kuszmaul | 26dbd0d | 2020-09-28 20:08:08 -0700 | [diff] [blame] | 313 | // Rate of the output will be 2000 / (DEC_RATE + 1) Hz. |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 314 | WriteRegister(registers::DEC_RATE, |
James Kuszmaul | 26dbd0d | 2020-09-28 20:08:08 -0700 | [diff] [blame] | 315 | 1 /* Average every pair of values. */); |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 316 | |
| 317 | // Start a sensor self test. |
| 318 | WriteRegister(registers::GLOB_CMD, 1 << 2); |
| 319 | // Datasheet says it takes 14ms, so give it some margin. |
Austin Schuh | b51692d | 2021-04-10 15:27:25 -0700 | [diff] [blame] | 320 | std::this_thread::sleep_for(chrono::milliseconds(25)); |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 321 | // Read DIAG_STAT again, and queue up a read of the first part of the |
| 322 | // autospi data packet. |
| 323 | const uint16_t self_test_diag_stat_value = |
| 324 | ReadRegister(registers::DIAG_STAT, kAutospiPacket[0]); |
| 325 | self_test_diag_stat = |
| 326 | PackDiagStat(builder.fbb(), self_test_diag_stat_value); |
| 327 | if (!DiagStatHasError( |
| 328 | *GetTemporaryPointer(*builder.fbb(), self_test_diag_stat))) { |
| 329 | // Initialize automatic mode, but don't start it yet. |
| 330 | spi_->InitAuto(kAutospiDataSize * 100); |
| 331 | spi_->SetAutoTransmitData(kAutospiPacket, |
| 332 | 0 /* no extra 0s at the end */); |
| 333 | // No idea what units the "stall period" is in. This value is just |
| 334 | // bigger than the 16us min from the datasheet. It does not appear |
| 335 | // to scale with SPICLK frequency. Empirically, this value comes out |
| 336 | // to 16.7us. |
| 337 | spi_->ConfigureAutoStall( |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 338 | 0 /* the minimum CS delay is enough for this IMU */, 670, |
| 339 | 1 /* toggle CS every 2 8-bit bytes */); |
| 340 | |
| 341 | // Read any data queued up by the FPGA. |
Tyler Chatow | bf0609c | 2021-07-31 16:13:27 -0700 | [diff] [blame] | 342 | while (true) { |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 343 | uint32_t buffer; |
| 344 | if (spi_->ReadAutoReceivedData(&buffer, 1, 0 /* don't block */) == |
| 345 | 0) { |
| 346 | break; |
| 347 | } |
| 348 | } |
| 349 | |
| 350 | // Finally, enable automatic mode so it starts reading data. |
| 351 | spi_->StartAutoTrigger(*data_ready_, true, false); |
Austin Schuh | b51692d | 2021-04-10 15:27:25 -0700 | [diff] [blame] | 352 | |
| 353 | // We need a bit of time for the auto trigger to start up so we have |
| 354 | // something to throw out. 1 khz trigger, so 2 ms gives us 2 cycles |
| 355 | // to hit it worst case. |
| 356 | std::this_thread::sleep_for(chrono::milliseconds(2)); |
| 357 | |
| 358 | // Throw out the first sample. It is almost always faulted due to |
| 359 | // how we start up, and it isn't worth tracking for downstream users |
| 360 | // to look at. |
| 361 | to_read_ = absl::MakeSpan(read_data_); |
| 362 | CHECK_EQ(spi_->ReadAutoReceivedData( |
| 363 | to_read_.data(), to_read_.size(), |
| 364 | 1000.0 /* block for up to 1 second */), |
| 365 | static_cast<int>(to_read_.size())) |
| 366 | << ": Failed to read first sample."; |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 367 | success = true; |
| 368 | } |
| 369 | } |
| 370 | } |
| 371 | |
| 372 | IMUValues::Builder imu_builder = builder.MakeBuilder<IMUValues>(); |
| 373 | imu_builder.add_product_id(product_id); |
| 374 | if (!start_diag_stat.IsNull()) { |
| 375 | imu_builder.add_start_diag_stat(start_diag_stat); |
| 376 | } |
| 377 | if (!self_test_diag_stat.IsNull()) { |
| 378 | imu_builder.add_self_test_diag_stat(self_test_diag_stat); |
| 379 | } |
Austin Schuh | ac17fba | 2020-03-28 15:55:33 -0700 | [diff] [blame] | 380 | |
| 381 | const flatbuffers::Offset<IMUValues> readings_offsets = |
| 382 | imu_builder.Finish(); |
| 383 | const flatbuffers::Offset< |
| 384 | flatbuffers::Vector<flatbuffers::Offset<IMUValues>>> |
| 385 | readings_offset = builder.fbb()->CreateVector(&readings_offsets, 1); |
| 386 | |
| 387 | IMUValuesBatch::Builder imu_batch_builder = |
| 388 | builder.MakeBuilder<IMUValuesBatch>(); |
| 389 | imu_batch_builder.add_readings(readings_offset); |
milind | 1f1dca3 | 2021-07-03 13:50:07 -0700 | [diff] [blame] | 390 | builder.CheckOk(builder.Send(imu_batch_builder.Finish())); |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 391 | if (success) { |
| 392 | state_ = State::kRunning; |
| 393 | } else { |
| 394 | BeginInitialization(); |
| 395 | } |
Tyler Chatow | bf0609c | 2021-07-31 16:13:27 -0700 | [diff] [blame] | 396 | } break; |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 397 | |
| 398 | case State::kRunning: |
| 399 | LOG(FATAL) << "Not a reset state"; |
| 400 | } |
| 401 | } |
| 402 | |
Austin Schuh | ac17fba | 2020-03-28 15:55:33 -0700 | [diff] [blame] | 403 | flatbuffers::Offset<IMUValues> ADIS16470::ProcessReading( |
| 404 | flatbuffers::FlatBufferBuilder *fbb) { |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 405 | // If we ever see this, we'll need to decide how to handle it. Probably reset |
| 406 | // everything and try again. |
| 407 | CHECK_EQ(0, spi_->GetAutoDroppedCount()); |
| 408 | |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 409 | absl::Span<const uint32_t> to_process = read_data_; |
| 410 | hal::fpga_clock::time_point fpga_time; |
| 411 | { |
| 412 | int32_t status = 0; |
| 413 | const uint64_t fpga_expanded = HAL_ExpandFPGATime(to_process[0], &status); |
| 414 | CHECK_EQ(0, status); |
| 415 | fpga_time = |
| 416 | hal::fpga_clock::time_point(hal::fpga_clock::duration(fpga_expanded)); |
| 417 | } |
| 418 | to_process = to_process.subspan(1); |
| 419 | |
| 420 | const uint16_t diag_stat_value = (static_cast<uint16_t>(to_process[0]) << 8) | |
| 421 | static_cast<uint16_t>(to_process[1]); |
Austin Schuh | ac17fba | 2020-03-28 15:55:33 -0700 | [diff] [blame] | 422 | const auto diag_stat = PackDiagStat(fbb, diag_stat_value); |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 423 | to_process = to_process.subspan(2); |
| 424 | |
Austin Schuh | ac17fba | 2020-03-28 15:55:33 -0700 | [diff] [blame] | 425 | IMUValues::Builder imu_builder(*fbb); |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 426 | imu_builder.add_fpga_timestamp( |
| 427 | aos::time::DurationInSeconds(fpga_time.time_since_epoch())); |
| 428 | imu_builder.add_monotonic_timestamp_ns( |
| 429 | time_converter_.FpgaToMonotonic(fpga_time).time_since_epoch().count()); |
| 430 | imu_builder.add_previous_reading_diag_stat(diag_stat); |
| 431 | |
| 432 | imu_builder.add_gyro_x(ConvertValue16(to_process, kGyroLsbRadianSecond)); |
| 433 | to_process = to_process.subspan(2); |
| 434 | imu_builder.add_accelerometer_x( |
| 435 | ConvertValue32(to_process, kAccelerometerLsbG)); |
| 436 | to_process = to_process.subspan(4); |
| 437 | imu_builder.add_gyro_y(ConvertValue16(to_process, kGyroLsbRadianSecond)); |
| 438 | to_process = to_process.subspan(2); |
| 439 | imu_builder.add_accelerometer_y( |
| 440 | ConvertValue32(to_process, kAccelerometerLsbG)); |
| 441 | to_process = to_process.subspan(4); |
| 442 | imu_builder.add_gyro_z(ConvertValue16(to_process, kGyroLsbRadianSecond)); |
| 443 | to_process = to_process.subspan(2); |
| 444 | imu_builder.add_accelerometer_z( |
| 445 | ConvertValue32(to_process, kAccelerometerLsbG)); |
| 446 | to_process = to_process.subspan(4); |
| 447 | |
| 448 | imu_builder.add_temperature( |
| 449 | ConvertValue16(to_process, kTemperatureLsbDegree)); |
| 450 | to_process = to_process.subspan(2); |
| 451 | |
| 452 | CHECK(to_process.empty()) << "Have leftover bytes: " << to_process.size(); |
| 453 | |
Austin Schuh | ac17fba | 2020-03-28 15:55:33 -0700 | [diff] [blame] | 454 | return imu_builder.Finish(); |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 455 | } |
| 456 | |
| 457 | double ADIS16470::ConvertValue32(absl::Span<const uint32_t> data, |
| 458 | double lsb_per_output) { |
| 459 | const uint32_t unsigned_value = (static_cast<uint32_t>(data[0]) << 24) | |
| 460 | (static_cast<uint32_t>(data[1]) << 16) | |
| 461 | (static_cast<uint32_t>(data[2]) << 8) | |
| 462 | static_cast<uint32_t>(data[3]); |
| 463 | int32_t signed_value; |
| 464 | memcpy(&signed_value, &unsigned_value, sizeof(unsigned_value)); |
| 465 | return static_cast<double>(signed_value) * lsb_per_output; |
| 466 | } |
| 467 | |
| 468 | double ADIS16470::ConvertValue16(absl::Span<const uint32_t> data, |
| 469 | double lsb_per_output) { |
| 470 | const uint16_t unsigned_value = |
| 471 | (static_cast<uint16_t>(data[0]) << 8) | static_cast<uint16_t>(data[1]); |
| 472 | int16_t signed_value; |
| 473 | memcpy(&signed_value, &unsigned_value, sizeof(unsigned_value)); |
| 474 | return static_cast<double>(signed_value) * lsb_per_output; |
| 475 | } |
| 476 | |
| 477 | flatbuffers::Offset<ADIS16470DiagStat> ADIS16470::PackDiagStat( |
| 478 | flatbuffers::FlatBufferBuilder *fbb, uint16_t value) { |
| 479 | ADIS16470DiagStat::Builder diag_stat_builder(*fbb); |
| 480 | diag_stat_builder.add_clock_error(value & (1 << 7)); |
| 481 | diag_stat_builder.add_memory_failure(value & (1 << 6)); |
| 482 | diag_stat_builder.add_sensor_failure(value & (1 << 5)); |
| 483 | diag_stat_builder.add_standby_mode(value & (1 << 4)); |
| 484 | diag_stat_builder.add_spi_communication_error(value & (1 << 3)); |
| 485 | diag_stat_builder.add_flash_memory_update_error(value & (1 << 2)); |
| 486 | diag_stat_builder.add_data_path_overrun(value & (1 << 1)); |
| 487 | return diag_stat_builder.Finish(); |
| 488 | } |
| 489 | |
| 490 | bool ADIS16470::DiagStatHasError(const ADIS16470DiagStat &diag_stat) { |
| 491 | return diag_stat.clock_error() || diag_stat.memory_failure() || |
| 492 | diag_stat.sensor_failure() || diag_stat.standby_mode() || |
| 493 | diag_stat.spi_communication_error() || |
| 494 | diag_stat.flash_memory_update_error() || diag_stat.data_path_overrun(); |
| 495 | } |
| 496 | |
| 497 | uint16_t ADIS16470::ReadRegister(uint8_t register_address, |
| 498 | uint8_t next_register_address) { |
| 499 | uint8_t send_buffer[2] = {static_cast<uint8_t>(register_address & 0x7f), 0}; |
| 500 | uint8_t dummy[2]; |
| 501 | spi_->Transaction(send_buffer, dummy, sizeof(send_buffer)); |
| 502 | uint8_t receive_buffer[2]; |
| 503 | uint8_t next_send_buffer[2] = { |
| 504 | static_cast<uint8_t>(next_register_address & 0x7f), 0}; |
| 505 | spi_->Transaction(next_send_buffer, receive_buffer, sizeof(receive_buffer)); |
| 506 | return (static_cast<uint16_t>(receive_buffer[0]) << 8) | |
| 507 | static_cast<uint16_t>(receive_buffer[1]); |
| 508 | } |
| 509 | |
| 510 | void ADIS16470::WriteRegister(uint8_t register_address, uint16_t value) { |
| 511 | uint8_t buffer1[2] = {static_cast<uint8_t>(register_address | 0x80), |
| 512 | static_cast<uint8_t>(value & 0xff)}; |
| 513 | uint8_t buffer2[2] = {static_cast<uint8_t>(register_address | 0x81), |
| 514 | static_cast<uint8_t>(value >> 8)}; |
| 515 | spi_->Write(buffer1, sizeof(buffer1)); |
| 516 | spi_->Write(buffer2, sizeof(buffer2)); |
| 517 | } |
| 518 | |
| 519 | } // namespace wpilib |
| 520 | } // namespace frc971 |