Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 1 | #include "frc971/wpilib/ADIS16470.h" |
| 2 | |
| 3 | #include <inttypes.h> |
| 4 | |
| 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 |
| 142 | registers::X_GYRO_OUT, 0, |
| 143 | registers::X_ACCL_OUT, 0, registers::X_ACCL_LOW, 0, |
| 144 | // Y |
| 145 | registers::Y_GYRO_OUT, 0, |
| 146 | registers::Y_ACCL_OUT, 0, registers::Y_ACCL_LOW, 0, |
| 147 | // Z |
| 148 | registers::Z_GYRO_OUT, 0, |
| 149 | registers::Z_ACCL_OUT, 0, registers::Z_ACCL_LOW, 0, |
| 150 | // Other |
| 151 | registers::TEMP_OUT, 0, registers::DIAG_STAT, 0, |
| 152 | }; |
| 153 | // clang-format on |
| 154 | |
| 155 | static_assert((sizeof(kAutospiPacket) % 2) == 0, |
| 156 | "Need a whole number of register reads"); |
| 157 | |
| 158 | static constexpr size_t kAutospiDataSize = sizeof(kAutospiPacket) + 1 /* timestamp */; |
| 159 | |
| 160 | // radian/second/LSB for the gyros (for just the 16-bit value). |
| 161 | constexpr double kGyroLsbRadianSecond = |
| 162 | 1.0 / 10.0 * (2.0 * M_PI / 360.0) /* degrees -> radians */; |
| 163 | // G/LSB for the accelerometers (for the full 32-bit value). |
James Kuszmaul | b145b32 | 2020-01-22 22:31:52 -0800 | [diff] [blame] | 164 | constexpr double kAccelerometerLsbG = 1.0 / 52'428'800.0; |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 165 | // C/LSB for the temperature. |
| 166 | constexpr double kTemperatureLsbDegree = 0.1; |
| 167 | |
| 168 | // This is what the datasheet says PROD_ID should be. |
| 169 | constexpr uint16_t kExpectedProductId = 0x4056; |
| 170 | // This is the PROD_ID we observe. |
| 171 | constexpr uint16_t kObservedProductId = 0x4256; |
| 172 | |
| 173 | } // namespace |
| 174 | |
| 175 | ADIS16470::ADIS16470(aos::EventLoop *event_loop, frc::SPI *spi, |
| 176 | frc::DigitalInput *data_ready, frc::DigitalOutput *reset) |
| 177 | : event_loop_(event_loop), |
| 178 | imu_values_sender_( |
Austin Schuh | ac17fba | 2020-03-28 15:55:33 -0700 | [diff] [blame] | 179 | event_loop_->MakeSender<::frc971::IMUValuesBatch>("/drivetrain")), |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 180 | initialize_timer_( |
| 181 | event_loop_->AddTimer([this]() { DoInitializeStep(); })), |
| 182 | spi_(spi), |
| 183 | data_ready_(data_ready), |
| 184 | reset_(reset) { |
| 185 | // Rather than put the entire data packet into the header, just put a size |
| 186 | // there and verify it matches here. |
| 187 | CHECK_EQ(kAutospiDataSize, read_data_.size()); |
| 188 | |
| 189 | // We're not doing burst mode, so this is the IMU's rated speed. |
| 190 | spi_->SetClockRate(2'000'000); |
| 191 | spi_->SetChipSelectActiveLow(); |
| 192 | spi_->SetClockActiveLow(); |
| 193 | spi_->SetSampleDataOnTrailingEdge(); |
| 194 | spi_->SetMSBFirst(); |
| 195 | |
| 196 | // NI's SPI driver defaults to SCHED_OTHER. Find it's PID with ps, and change |
| 197 | // it to a RT priority of 33. |
| 198 | PCHECK( |
Austin Schuh | a3f2a31 | 2020-02-22 21:03:00 -0800 | [diff] [blame] | 199 | system("busybox ps -ef | grep '\\[spi0\\]' | awk '{print $1}' | xargs chrt -f -p " |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 200 | "33") == 0); |
James Kuszmaul | 57c2baa | 2020-01-19 14:52:52 -0800 | [diff] [blame] | 201 | PCHECK( |
Austin Schuh | a3f2a31 | 2020-02-22 21:03:00 -0800 | [diff] [blame] | 202 | system("busybox ps -ef | grep '\\[spi1\\]' | awk '{print $1}' | xargs chrt -f -p " |
James Kuszmaul | 57c2baa | 2020-01-19 14:52:52 -0800 | [diff] [blame] | 203 | "33") == 0); |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 204 | |
| 205 | event_loop_->OnRun([this]() { BeginInitialization(); }); |
| 206 | } |
| 207 | |
| 208 | void ADIS16470::DoReads() { |
| 209 | if (state_ != State::kRunning) { |
| 210 | // Not sure how to interpret data received now, so ignore it. |
| 211 | return; |
| 212 | } |
| 213 | |
Austin Schuh | ac17fba | 2020-03-28 15:55:33 -0700 | [diff] [blame] | 214 | auto builder = imu_values_sender_.MakeBuilder(); |
| 215 | |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 216 | int amount_to_read = |
| 217 | spi_->ReadAutoReceivedData(to_read_.data(), 0, 0 /* don't block */); |
Austin Schuh | ac17fba | 2020-03-28 15:55:33 -0700 | [diff] [blame] | 218 | |
| 219 | aos::SizedArray<flatbuffers::Offset<IMUValues>, 50> readings_offsets; |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 220 | while (true) { |
| 221 | if (amount_to_read == 0) break; |
| 222 | CHECK(!to_read_.empty()); |
| 223 | const int amount_read_now = std::min<int>(amount_to_read, to_read_.size()); |
| 224 | CHECK_GT(amount_read_now, 0) << "amount_to_read: " << amount_to_read |
| 225 | << ", to_read_.size(): " << to_read_.size(); |
| 226 | spi_->ReadAutoReceivedData(to_read_.data(), amount_read_now, |
| 227 | 0 /* don't block */); |
| 228 | to_read_ = to_read_.subspan(amount_read_now); |
| 229 | amount_to_read -= amount_read_now; |
| 230 | |
| 231 | if (to_read_.empty()) { |
Austin Schuh | ac17fba | 2020-03-28 15:55:33 -0700 | [diff] [blame] | 232 | flatbuffers::Offset<IMUValues> reading_offset = |
| 233 | ProcessReading(builder.fbb()); |
| 234 | readings_offsets.push_back(reading_offset); |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 235 | |
| 236 | // Reset for the next reading. |
| 237 | to_read_ = absl::MakeSpan(read_data_); |
| 238 | } else { |
| 239 | CHECK_EQ(amount_to_read, 0); |
| 240 | break; |
| 241 | } |
| 242 | } |
Austin Schuh | ac17fba | 2020-03-28 15:55:33 -0700 | [diff] [blame] | 243 | |
| 244 | flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<IMUValues>>> |
| 245 | readings_offset = builder.fbb()->CreateVector(readings_offsets.data(), |
| 246 | readings_offsets.size()); |
| 247 | |
| 248 | IMUValuesBatch::Builder imu_values_batch_builder = |
| 249 | builder.MakeBuilder<IMUValuesBatch>(); |
| 250 | imu_values_batch_builder.add_readings(readings_offset); |
| 251 | builder.Send(imu_values_batch_builder.Finish()); |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 252 | } |
| 253 | |
| 254 | void ADIS16470::DoInitializeStep() { |
| 255 | switch (state_) { |
| 256 | case State::kUninitialized: { |
| 257 | to_read_ = absl::MakeSpan(read_data_); |
| 258 | |
| 259 | // First, set the SPI to normal mode so it stops trying to talk |
| 260 | // automatically. |
| 261 | spi_->StopAuto(); |
| 262 | |
| 263 | reset_->Set(false); |
| 264 | // Datasheet says it needs a 1 us pulse, so make sure we do something in |
| 265 | // between asserting and deasserting. |
Austin Schuh | b51692d | 2021-04-10 15:27:25 -0700 | [diff] [blame^] | 266 | std::this_thread::sleep_for(chrono::milliseconds(1)); |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 267 | reset_->Set(true); |
| 268 | |
| 269 | state_ = State::kWaitForReset; |
| 270 | // Datasheet says it takes 193 ms to come out of reset, so give it some |
| 271 | // margin on top of that. |
| 272 | initialize_timer_->Setup(event_loop_->monotonic_now() + |
Austin Schuh | b51692d | 2021-04-10 15:27:25 -0700 | [diff] [blame^] | 273 | chrono::milliseconds(250)); |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 274 | } |
| 275 | break; |
| 276 | |
| 277 | case State::kWaitForReset: { |
| 278 | flatbuffers::Offset<ADIS16470DiagStat> start_diag_stat; |
| 279 | flatbuffers::Offset<ADIS16470DiagStat> self_test_diag_stat; |
| 280 | bool success = false; |
| 281 | auto builder = imu_values_sender_.MakeBuilder(); |
| 282 | |
| 283 | // Configure the IMU the way we want it. |
| 284 | const uint16_t product_id = ReadRegister(registers::PROD_ID, 0); |
| 285 | if (product_id == kExpectedProductId || |
| 286 | product_id == kObservedProductId) { |
| 287 | const uint16_t start_diag_stat_value = |
| 288 | ReadRegister(registers::DIAG_STAT, 0); |
| 289 | start_diag_stat = PackDiagStat(builder.fbb(), start_diag_stat_value); |
| 290 | if (!DiagStatHasError( |
| 291 | *GetTemporaryPointer(*builder.fbb(), start_diag_stat))) { |
| 292 | WriteRegister(registers::FILT_CTRL, 0 /* no filtering */); |
| 293 | WriteRegister( |
| 294 | registers::MSC_CTRL, |
| 295 | (1 << 7) /* enable gyro linear g compensation */ | |
| 296 | (1 << 6) /* enable point of percussion alignment */ | |
| 297 | (0 << 2) /* internal clock mode */ | |
| 298 | (0 << 1) /* sync polarity, doesn't matter */ | |
| 299 | (1 << 0) /* data ready is active high */); |
James Kuszmaul | 26dbd0d | 2020-09-28 20:08:08 -0700 | [diff] [blame] | 300 | // Rate of the output will be 2000 / (DEC_RATE + 1) Hz. |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 301 | WriteRegister(registers::DEC_RATE, |
James Kuszmaul | 26dbd0d | 2020-09-28 20:08:08 -0700 | [diff] [blame] | 302 | 1 /* Average every pair of values. */); |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 303 | |
| 304 | // Start a sensor self test. |
| 305 | WriteRegister(registers::GLOB_CMD, 1 << 2); |
| 306 | // Datasheet says it takes 14ms, so give it some margin. |
Austin Schuh | b51692d | 2021-04-10 15:27:25 -0700 | [diff] [blame^] | 307 | std::this_thread::sleep_for(chrono::milliseconds(25)); |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 308 | // Read DIAG_STAT again, and queue up a read of the first part of the |
| 309 | // autospi data packet. |
| 310 | const uint16_t self_test_diag_stat_value = |
| 311 | ReadRegister(registers::DIAG_STAT, kAutospiPacket[0]); |
| 312 | self_test_diag_stat = |
| 313 | PackDiagStat(builder.fbb(), self_test_diag_stat_value); |
| 314 | if (!DiagStatHasError( |
| 315 | *GetTemporaryPointer(*builder.fbb(), self_test_diag_stat))) { |
| 316 | // Initialize automatic mode, but don't start it yet. |
| 317 | spi_->InitAuto(kAutospiDataSize * 100); |
| 318 | spi_->SetAutoTransmitData(kAutospiPacket, |
| 319 | 0 /* no extra 0s at the end */); |
| 320 | // No idea what units the "stall period" is in. This value is just |
| 321 | // bigger than the 16us min from the datasheet. It does not appear |
| 322 | // to scale with SPICLK frequency. Empirically, this value comes out |
| 323 | // to 16.7us. |
| 324 | spi_->ConfigureAutoStall( |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 325 | 0 /* the minimum CS delay is enough for this IMU */, 670, |
| 326 | 1 /* toggle CS every 2 8-bit bytes */); |
| 327 | |
| 328 | // Read any data queued up by the FPGA. |
| 329 | while (true){ |
| 330 | uint32_t buffer; |
| 331 | if (spi_->ReadAutoReceivedData(&buffer, 1, 0 /* don't block */) == |
| 332 | 0) { |
| 333 | break; |
| 334 | } |
| 335 | } |
| 336 | |
| 337 | // Finally, enable automatic mode so it starts reading data. |
| 338 | spi_->StartAutoTrigger(*data_ready_, true, false); |
Austin Schuh | b51692d | 2021-04-10 15:27:25 -0700 | [diff] [blame^] | 339 | |
| 340 | // We need a bit of time for the auto trigger to start up so we have |
| 341 | // something to throw out. 1 khz trigger, so 2 ms gives us 2 cycles |
| 342 | // to hit it worst case. |
| 343 | std::this_thread::sleep_for(chrono::milliseconds(2)); |
| 344 | |
| 345 | // Throw out the first sample. It is almost always faulted due to |
| 346 | // how we start up, and it isn't worth tracking for downstream users |
| 347 | // to look at. |
| 348 | to_read_ = absl::MakeSpan(read_data_); |
| 349 | CHECK_EQ(spi_->ReadAutoReceivedData( |
| 350 | to_read_.data(), to_read_.size(), |
| 351 | 1000.0 /* block for up to 1 second */), |
| 352 | static_cast<int>(to_read_.size())) |
| 353 | << ": Failed to read first sample."; |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 354 | success = true; |
| 355 | } |
| 356 | } |
| 357 | } |
| 358 | |
| 359 | IMUValues::Builder imu_builder = builder.MakeBuilder<IMUValues>(); |
| 360 | imu_builder.add_product_id(product_id); |
| 361 | if (!start_diag_stat.IsNull()) { |
| 362 | imu_builder.add_start_diag_stat(start_diag_stat); |
| 363 | } |
| 364 | if (!self_test_diag_stat.IsNull()) { |
| 365 | imu_builder.add_self_test_diag_stat(self_test_diag_stat); |
| 366 | } |
Austin Schuh | ac17fba | 2020-03-28 15:55:33 -0700 | [diff] [blame] | 367 | |
| 368 | const flatbuffers::Offset<IMUValues> readings_offsets = |
| 369 | imu_builder.Finish(); |
| 370 | const flatbuffers::Offset< |
| 371 | flatbuffers::Vector<flatbuffers::Offset<IMUValues>>> |
| 372 | readings_offset = builder.fbb()->CreateVector(&readings_offsets, 1); |
| 373 | |
| 374 | IMUValuesBatch::Builder imu_batch_builder = |
| 375 | builder.MakeBuilder<IMUValuesBatch>(); |
| 376 | imu_batch_builder.add_readings(readings_offset); |
| 377 | builder.Send(imu_batch_builder.Finish()); |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 378 | if (success) { |
| 379 | state_ = State::kRunning; |
| 380 | } else { |
| 381 | BeginInitialization(); |
| 382 | } |
| 383 | } |
| 384 | break; |
| 385 | |
| 386 | case State::kRunning: |
| 387 | LOG(FATAL) << "Not a reset state"; |
| 388 | } |
| 389 | } |
| 390 | |
Austin Schuh | ac17fba | 2020-03-28 15:55:33 -0700 | [diff] [blame] | 391 | flatbuffers::Offset<IMUValues> ADIS16470::ProcessReading( |
| 392 | flatbuffers::FlatBufferBuilder *fbb) { |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 393 | // If we ever see this, we'll need to decide how to handle it. Probably reset |
| 394 | // everything and try again. |
| 395 | CHECK_EQ(0, spi_->GetAutoDroppedCount()); |
| 396 | |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 397 | absl::Span<const uint32_t> to_process = read_data_; |
| 398 | hal::fpga_clock::time_point fpga_time; |
| 399 | { |
| 400 | int32_t status = 0; |
| 401 | const uint64_t fpga_expanded = HAL_ExpandFPGATime(to_process[0], &status); |
| 402 | CHECK_EQ(0, status); |
| 403 | fpga_time = |
| 404 | hal::fpga_clock::time_point(hal::fpga_clock::duration(fpga_expanded)); |
| 405 | } |
| 406 | to_process = to_process.subspan(1); |
| 407 | |
| 408 | const uint16_t diag_stat_value = (static_cast<uint16_t>(to_process[0]) << 8) | |
| 409 | static_cast<uint16_t>(to_process[1]); |
Austin Schuh | ac17fba | 2020-03-28 15:55:33 -0700 | [diff] [blame] | 410 | const auto diag_stat = PackDiagStat(fbb, diag_stat_value); |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 411 | to_process = to_process.subspan(2); |
| 412 | |
Austin Schuh | ac17fba | 2020-03-28 15:55:33 -0700 | [diff] [blame] | 413 | IMUValues::Builder imu_builder(*fbb); |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 414 | imu_builder.add_fpga_timestamp( |
| 415 | aos::time::DurationInSeconds(fpga_time.time_since_epoch())); |
| 416 | imu_builder.add_monotonic_timestamp_ns( |
| 417 | time_converter_.FpgaToMonotonic(fpga_time).time_since_epoch().count()); |
| 418 | imu_builder.add_previous_reading_diag_stat(diag_stat); |
| 419 | |
| 420 | imu_builder.add_gyro_x(ConvertValue16(to_process, kGyroLsbRadianSecond)); |
| 421 | to_process = to_process.subspan(2); |
| 422 | imu_builder.add_accelerometer_x( |
| 423 | ConvertValue32(to_process, kAccelerometerLsbG)); |
| 424 | to_process = to_process.subspan(4); |
| 425 | imu_builder.add_gyro_y(ConvertValue16(to_process, kGyroLsbRadianSecond)); |
| 426 | to_process = to_process.subspan(2); |
| 427 | imu_builder.add_accelerometer_y( |
| 428 | ConvertValue32(to_process, kAccelerometerLsbG)); |
| 429 | to_process = to_process.subspan(4); |
| 430 | imu_builder.add_gyro_z(ConvertValue16(to_process, kGyroLsbRadianSecond)); |
| 431 | to_process = to_process.subspan(2); |
| 432 | imu_builder.add_accelerometer_z( |
| 433 | ConvertValue32(to_process, kAccelerometerLsbG)); |
| 434 | to_process = to_process.subspan(4); |
| 435 | |
| 436 | imu_builder.add_temperature( |
| 437 | ConvertValue16(to_process, kTemperatureLsbDegree)); |
| 438 | to_process = to_process.subspan(2); |
| 439 | |
| 440 | CHECK(to_process.empty()) << "Have leftover bytes: " << to_process.size(); |
| 441 | |
Austin Schuh | ac17fba | 2020-03-28 15:55:33 -0700 | [diff] [blame] | 442 | return imu_builder.Finish(); |
Brian Silverman | 7be68ba | 2020-01-08 22:08:40 -0800 | [diff] [blame] | 443 | } |
| 444 | |
| 445 | double ADIS16470::ConvertValue32(absl::Span<const uint32_t> data, |
| 446 | double lsb_per_output) { |
| 447 | const uint32_t unsigned_value = (static_cast<uint32_t>(data[0]) << 24) | |
| 448 | (static_cast<uint32_t>(data[1]) << 16) | |
| 449 | (static_cast<uint32_t>(data[2]) << 8) | |
| 450 | static_cast<uint32_t>(data[3]); |
| 451 | int32_t signed_value; |
| 452 | memcpy(&signed_value, &unsigned_value, sizeof(unsigned_value)); |
| 453 | return static_cast<double>(signed_value) * lsb_per_output; |
| 454 | } |
| 455 | |
| 456 | double ADIS16470::ConvertValue16(absl::Span<const uint32_t> data, |
| 457 | double lsb_per_output) { |
| 458 | const uint16_t unsigned_value = |
| 459 | (static_cast<uint16_t>(data[0]) << 8) | static_cast<uint16_t>(data[1]); |
| 460 | int16_t signed_value; |
| 461 | memcpy(&signed_value, &unsigned_value, sizeof(unsigned_value)); |
| 462 | return static_cast<double>(signed_value) * lsb_per_output; |
| 463 | } |
| 464 | |
| 465 | flatbuffers::Offset<ADIS16470DiagStat> ADIS16470::PackDiagStat( |
| 466 | flatbuffers::FlatBufferBuilder *fbb, uint16_t value) { |
| 467 | ADIS16470DiagStat::Builder diag_stat_builder(*fbb); |
| 468 | diag_stat_builder.add_clock_error(value & (1 << 7)); |
| 469 | diag_stat_builder.add_memory_failure(value & (1 << 6)); |
| 470 | diag_stat_builder.add_sensor_failure(value & (1 << 5)); |
| 471 | diag_stat_builder.add_standby_mode(value & (1 << 4)); |
| 472 | diag_stat_builder.add_spi_communication_error(value & (1 << 3)); |
| 473 | diag_stat_builder.add_flash_memory_update_error(value & (1 << 2)); |
| 474 | diag_stat_builder.add_data_path_overrun(value & (1 << 1)); |
| 475 | return diag_stat_builder.Finish(); |
| 476 | } |
| 477 | |
| 478 | bool ADIS16470::DiagStatHasError(const ADIS16470DiagStat &diag_stat) { |
| 479 | return diag_stat.clock_error() || diag_stat.memory_failure() || |
| 480 | diag_stat.sensor_failure() || diag_stat.standby_mode() || |
| 481 | diag_stat.spi_communication_error() || |
| 482 | diag_stat.flash_memory_update_error() || diag_stat.data_path_overrun(); |
| 483 | } |
| 484 | |
| 485 | uint16_t ADIS16470::ReadRegister(uint8_t register_address, |
| 486 | uint8_t next_register_address) { |
| 487 | uint8_t send_buffer[2] = {static_cast<uint8_t>(register_address & 0x7f), 0}; |
| 488 | uint8_t dummy[2]; |
| 489 | spi_->Transaction(send_buffer, dummy, sizeof(send_buffer)); |
| 490 | uint8_t receive_buffer[2]; |
| 491 | uint8_t next_send_buffer[2] = { |
| 492 | static_cast<uint8_t>(next_register_address & 0x7f), 0}; |
| 493 | spi_->Transaction(next_send_buffer, receive_buffer, sizeof(receive_buffer)); |
| 494 | return (static_cast<uint16_t>(receive_buffer[0]) << 8) | |
| 495 | static_cast<uint16_t>(receive_buffer[1]); |
| 496 | } |
| 497 | |
| 498 | void ADIS16470::WriteRegister(uint8_t register_address, uint16_t value) { |
| 499 | uint8_t buffer1[2] = {static_cast<uint8_t>(register_address | 0x80), |
| 500 | static_cast<uint8_t>(value & 0xff)}; |
| 501 | uint8_t buffer2[2] = {static_cast<uint8_t>(register_address | 0x81), |
| 502 | static_cast<uint8_t>(value >> 8)}; |
| 503 | spi_->Write(buffer1, sizeof(buffer1)); |
| 504 | spi_->Write(buffer2, sizeof(buffer2)); |
| 505 | } |
| 506 | |
| 507 | } // namespace wpilib |
| 508 | } // namespace frc971 |