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