blob: b7b315be9ed625528865a93c3b3e80889a95cc74 [file] [log] [blame]
#include "frc971/wpilib/ADIS16470.h"
#include <cinttypes>
#include "aos/containers/sized_array.h"
#include "aos/time/time.h"
#include "glog/logging.h"
#include "hal/HAL.h"
namespace frc971 {
namespace wpilib {
namespace {
namespace chrono = std::chrono;
namespace registers {
// Flash memory write count
constexpr uint8_t FLASH_CNT = 0x00;
// Diagnostic and operational status
constexpr uint8_t DIAG_STAT = 0x02;
// X-axis gyroscope output, lower word
constexpr uint8_t X_GYRO_LOW = 0x04;
// X-axis gyroscope output, upper word
constexpr uint8_t X_GYRO_OUT = 0x06;
// Y-axis gyroscope output, lower word
constexpr uint8_t Y_GYRO_LOW = 0x08;
// Y-axis gyroscope output, upper word
constexpr uint8_t Y_GYRO_OUT = 0x0A;
// Z-axis gyroscope output, lower word
constexpr uint8_t Z_GYRO_LOW = 0x0C;
// Z-axis gyroscope output, upper word
constexpr uint8_t Z_GYRO_OUT = 0x0E;
// X-axis accelerometer output, lower word
constexpr uint8_t X_ACCL_LOW = 0x10;
// X-axis accelerometer output, upper word
constexpr uint8_t X_ACCL_OUT = 0x12;
// Y-axis accelerometer output, lower word
constexpr uint8_t Y_ACCL_OUT = 0x16;
// Y-axis accelerometer output, upper word
constexpr uint8_t Z_ACCL_LOW = 0x18;
// Z-axis accelerometer output, lower word
constexpr uint8_t Z_ACCL_OUT = 0x1A;
// Z-axis accelerometer output, upper word
constexpr uint8_t TEMP_OUT = 0x1C;
// Temperature output (internal, not calibrated)
constexpr uint8_t TIME_STAMP = 0x1E;
// PPS mode time stamp
constexpr uint8_t X_DELTANG_LOW = 0x24;
// X-axis delta angle output, lower word
constexpr uint8_t X_DELTANG_OUT = 0x26;
// X-axis delta angle output, upper word
constexpr uint8_t Y_DELTANG_LOW = 0x28;
// Y-axis delta angle output, lower word
constexpr uint8_t Y_DELTANG_OUT = 0x2A;
// Y-axis delta angle output, upper word
constexpr uint8_t Z_DELTANG_LOW = 0x2C;
// Z-axis delta angle output, lower word
constexpr uint8_t Z_DELTANG_OUT = 0x2E;
// Z-axis delta angle output, upper word
constexpr uint8_t X_DELTVEL_LOW = 0x30;
// X-axis delta velocity output, lower word
constexpr uint8_t X_DELTVEL_OUT = 0x32;
// X-axis delta velocity output, upper word
constexpr uint8_t Y_DELTVEL_LOW = 0x34;
// Y-axis delta velocity output, lower word
constexpr uint8_t Y_DELTVEL_OUT = 0x36;
// Y-axis delta velocity output, upper word
constexpr uint8_t Z_DELTVEL_LOW = 0x38;
// Z-axis delta velocity output, lower word
constexpr uint8_t Z_DELTVEL_OUT = 0x3A;
// Z-axis delta velocity output, upper word
constexpr uint8_t XG_BIAS_LOW = 0x40;
// X-axis gyroscope bias offset correction, lower word
constexpr uint8_t XG_BIAS_HIGH = 0x42;
// X-axis gyroscope bias offset correction, upper word
constexpr uint8_t YG_BIAS_LOW = 0x44;
// Y-axis gyroscope bias offset correction, lower word
constexpr uint8_t YG_BIAS_HIGH = 0x46;
// Y-axis gyroscope bias offset correction, upper word
constexpr uint8_t ZG_BIAS_LOW = 0x48;
// Z-axis gyroscope bias offset correction, lower word
constexpr uint8_t ZG_BIAS_HIGH = 0x4A;
// Z-axis gyroscope bias offset correction, upper word
constexpr uint8_t XA_BIAS_LOW = 0x4C;
// X-axis accelerometer bias offset correction, lower word
constexpr uint8_t XA_BIAS_HIGH = 0x4E;
// X-axis accelerometer bias offset correction, upper word
constexpr uint8_t YA_BIAS_LOW = 0x50;
// Y-axis accelerometer bias offset correction, lower word
constexpr uint8_t YA_BIAS_HIGH = 0x52;
// Y-axis accelerometer bias offset correction, upper word
constexpr uint8_t ZA_BIAS_LOW = 0x54;
// Z-axis accelerometer bias offset correction, lower word
constexpr uint8_t ZA_BIAS_HIGH = 0x56;
// Z-axis accelerometer bias offset correction, upper word
constexpr uint8_t FILT_CTRL = 0x5C;
// Filter control
constexpr uint8_t MSC_CTRL = 0x60;
// Miscellaneous control
constexpr uint8_t UP_SCALE = 0x62;
// Clock scale factor, PPS mode
constexpr uint8_t DEC_RATE = 0x64;
// Decimation rate control (output data rate)
constexpr uint8_t NULL_CNFG = 0x66;
// Auto-null configuration control
constexpr uint8_t GLOB_CMD = 0x68;
// Global commands
constexpr uint8_t FIRM_REV = 0x6C;
// Firmware revision
constexpr uint8_t FIRM_DM = 0x6E;
// Firmware revision date, month and day
constexpr uint8_t FIRM_Y = 0x70;
// Firmware revision date, year
constexpr uint8_t PROD_ID = 0x72;
// Product identification
constexpr uint8_t SERIAL_NUM = 0x74;
// Serial number (relative to assembly lot)
constexpr uint8_t USER_SCR1 = 0x76;
// User scratch register 1
constexpr uint8_t USER_SCR2 = 0x78;
// User scratch register 2
constexpr uint8_t USER_SCR3 = 0x7A;
// User scratch register 3
constexpr uint8_t FLSHCNT_LOW = 0x7C;
// Flash update count, lower word
constexpr uint8_t FLSHCNT_HIGH = 0x7E;
// Flash update count, upper word
constexpr uint8_t Y_ACCL_LOW = 0x14;
} // namespace registers
// The complete automatic packet we will send. This needs to include the dummy 0
// bytes making up full 16-bit frames.
// Note that in addition to the 24-byte limit from the FPGA, this is also
// limited to 12 16-bit register reads by the IMU itself given that we're
// reading at the full 2kHz rate.
// We rotate the registers here by 1, such that the first thing we read is the
// last thing triggered by the previous reading. We put DIAG_STAT in this
// position because we don't care if it's one cycle stale.
constexpr uint8_t kAutospiPacket[] = {
// X
registers::X_GYRO_OUT,
0,
registers::X_ACCL_OUT,
0,
registers::X_ACCL_LOW,
0,
// Y
registers::Y_GYRO_OUT,
0,
registers::Y_ACCL_OUT,
0,
registers::Y_ACCL_LOW,
0,
// Z
registers::Z_GYRO_OUT,
0,
registers::Z_ACCL_OUT,
0,
registers::Z_ACCL_LOW,
0,
// Other
registers::TEMP_OUT,
0,
registers::DIAG_STAT,
0,
};
// clang-format on
static_assert((sizeof(kAutospiPacket) % 2) == 0,
"Need a whole number of register reads");
static constexpr size_t kAutospiDataSize =
sizeof(kAutospiPacket) + 1 /* timestamp */;
// radian/second/LSB for the gyros (for just the 16-bit value).
constexpr double kGyroLsbRadianSecond =
1.0 / 10.0 * (2.0 * M_PI / 360.0) /* degrees -> radians */;
// G/LSB for the accelerometers (for the full 32-bit value).
constexpr double kAccelerometerLsbG = 1.0 / 52'428'800.0;
// C/LSB for the temperature.
constexpr double kTemperatureLsbDegree = 0.1;
// This is what the datasheet says PROD_ID should be.
constexpr uint16_t kExpectedProductId = 0x4056;
// This is the PROD_ID we observe.
constexpr uint16_t kObservedProductId = 0x4256;
} // namespace
ADIS16470::ADIS16470(aos::EventLoop *event_loop, frc::SPI *spi,
frc::DigitalInput *data_ready, frc::DigitalOutput *reset)
: event_loop_(event_loop),
imu_values_sender_(
event_loop_->MakeSender<::frc971::IMUValuesBatch>("/drivetrain")),
initialize_timer_(
event_loop_->AddTimer([this]() { DoInitializeStep(); })),
spi_(spi),
data_ready_(data_ready),
reset_(reset) {
// Rather than put the entire data packet into the header, just put a size
// there and verify it matches here.
CHECK_EQ(kAutospiDataSize, read_data_.size());
// We're not doing burst mode, so this is the IMU's rated speed.
spi_->SetClockRate(2'000'000);
spi_->SetChipSelectActiveLow();
spi_->SetClockActiveLow();
spi_->SetSampleDataOnTrailingEdge();
spi_->SetMSBFirst();
// NI's SPI driver defaults to SCHED_OTHER. Find it's PID with ps, and change
// it to a RT priority of 33.
PCHECK(system("busybox ps -ef | grep '\\[spi0\\]' | awk '{print $1}' | xargs "
"chrt -f -p "
"33") == 0);
PCHECK(system("busybox ps -ef | grep '\\[spi1\\]' | awk '{print $1}' | xargs "
"chrt -f -p "
"33") == 0);
event_loop_->OnRun([this]() { BeginInitialization(); });
}
void ADIS16470::DoReads() {
if (state_ != State::kRunning) {
// Not sure how to interpret data received now, so ignore it.
return;
}
auto builder = imu_values_sender_.MakeBuilder();
int amount_to_read =
spi_->ReadAutoReceivedData(to_read_.data(), 0, 0 /* don't block */);
aos::SizedArray<flatbuffers::Offset<IMUValues>, 50> readings_offsets;
while (true) {
if (amount_to_read == 0) break;
CHECK(!to_read_.empty());
const int amount_read_now = std::min<int>(amount_to_read, to_read_.size());
CHECK_GT(amount_read_now, 0) << "amount_to_read: " << amount_to_read
<< ", to_read_.size(): " << to_read_.size();
spi_->ReadAutoReceivedData(to_read_.data(), amount_read_now,
0 /* don't block */);
to_read_ = to_read_.subspan(amount_read_now);
amount_to_read -= amount_read_now;
if (to_read_.empty()) {
flatbuffers::Offset<IMUValues> reading_offset =
ProcessReading(builder.fbb());
readings_offsets.push_back(reading_offset);
// Reset for the next reading.
to_read_ = absl::MakeSpan(read_data_);
} else {
CHECK_EQ(amount_to_read, 0);
break;
}
}
flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<IMUValues>>>
readings_offset = builder.fbb()->CreateVector(readings_offsets.data(),
readings_offsets.size());
IMUValuesBatch::Builder imu_values_batch_builder =
builder.MakeBuilder<IMUValuesBatch>();
imu_values_batch_builder.add_readings(readings_offset);
builder.CheckOk(builder.Send(imu_values_batch_builder.Finish()));
}
void ADIS16470::DoInitializeStep() {
switch (state_) {
case State::kUninitialized: {
to_read_ = absl::MakeSpan(read_data_);
// First, set the SPI to normal mode so it stops trying to talk
// automatically.
spi_->StopAuto();
reset_->Set(false);
// Datasheet says it needs a 1 us pulse, so make sure we do something in
// between asserting and deasserting.
std::this_thread::sleep_for(chrono::milliseconds(1));
reset_->Set(true);
state_ = State::kWaitForReset;
// Datasheet says it takes 193 ms to come out of reset, so give it some
// margin on top of that.
initialize_timer_->Setup(event_loop_->monotonic_now() +
chrono::milliseconds(250));
} break;
case State::kWaitForReset: {
flatbuffers::Offset<ADIS16470DiagStat> start_diag_stat;
flatbuffers::Offset<ADIS16470DiagStat> self_test_diag_stat;
bool success = false;
auto builder = imu_values_sender_.MakeBuilder();
// Configure the IMU the way we want it.
const uint16_t product_id = ReadRegister(registers::PROD_ID, 0);
if (product_id == kExpectedProductId ||
product_id == kObservedProductId) {
const uint16_t start_diag_stat_value =
ReadRegister(registers::DIAG_STAT, 0);
start_diag_stat = PackDiagStat(builder.fbb(), start_diag_stat_value);
if (!DiagStatHasError(
*GetTemporaryPointer(*builder.fbb(), start_diag_stat))) {
WriteRegister(registers::FILT_CTRL, 0 /* no filtering */);
WriteRegister(
registers::MSC_CTRL,
(1 << 7) /* enable gyro linear g compensation */ |
(1 << 6) /* enable point of percussion alignment */ |
(0 << 2) /* internal clock mode */ |
(0 << 1) /* sync polarity, doesn't matter */ |
(1 << 0) /* data ready is active high */);
// Rate of the output will be 2000 / (DEC_RATE + 1) Hz.
WriteRegister(registers::DEC_RATE,
1 /* Average every pair of values. */);
// Start a sensor self test.
WriteRegister(registers::GLOB_CMD, 1 << 2);
// Datasheet says it takes 14ms, so give it some margin.
std::this_thread::sleep_for(chrono::milliseconds(25));
// Read DIAG_STAT again, and queue up a read of the first part of the
// autospi data packet.
const uint16_t self_test_diag_stat_value =
ReadRegister(registers::DIAG_STAT, kAutospiPacket[0]);
self_test_diag_stat =
PackDiagStat(builder.fbb(), self_test_diag_stat_value);
if (!DiagStatHasError(
*GetTemporaryPointer(*builder.fbb(), self_test_diag_stat))) {
// Initialize automatic mode, but don't start it yet.
spi_->InitAuto(kAutospiDataSize * 100);
spi_->SetAutoTransmitData(kAutospiPacket,
0 /* no extra 0s at the end */);
// No idea what units the "stall period" is in. This value is just
// bigger than the 16us min from the datasheet. It does not appear
// to scale with SPICLK frequency. Empirically, this value comes out
// to 16.7us.
spi_->ConfigureAutoStall(
0 /* the minimum CS delay is enough for this IMU */, 670,
1 /* toggle CS every 2 8-bit bytes */);
// Read any data queued up by the FPGA.
while (true) {
uint32_t buffer;
if (spi_->ReadAutoReceivedData(&buffer, 1, 0 /* don't block */) ==
0) {
break;
}
}
// Finally, enable automatic mode so it starts reading data.
spi_->StartAutoTrigger(*data_ready_, true, false);
// We need a bit of time for the auto trigger to start up so we have
// something to throw out. 1 khz trigger, so 2 ms gives us 2 cycles
// to hit it worst case.
std::this_thread::sleep_for(chrono::milliseconds(2));
// Throw out the first sample. It is almost always faulted due to
// how we start up, and it isn't worth tracking for downstream users
// to look at.
to_read_ = absl::MakeSpan(read_data_);
CHECK_EQ(spi_->ReadAutoReceivedData(
to_read_.data(), to_read_.size(),
1000.0 /* block for up to 1 second */),
static_cast<int>(to_read_.size()))
<< ": Failed to read first sample.";
success = true;
}
}
}
IMUValues::Builder imu_builder = builder.MakeBuilder<IMUValues>();
imu_builder.add_product_id(product_id);
if (!start_diag_stat.IsNull()) {
imu_builder.add_start_diag_stat(start_diag_stat);
}
if (!self_test_diag_stat.IsNull()) {
imu_builder.add_self_test_diag_stat(self_test_diag_stat);
}
const flatbuffers::Offset<IMUValues> readings_offsets =
imu_builder.Finish();
const flatbuffers::Offset<
flatbuffers::Vector<flatbuffers::Offset<IMUValues>>>
readings_offset = builder.fbb()->CreateVector(&readings_offsets, 1);
IMUValuesBatch::Builder imu_batch_builder =
builder.MakeBuilder<IMUValuesBatch>();
imu_batch_builder.add_readings(readings_offset);
builder.CheckOk(builder.Send(imu_batch_builder.Finish()));
if (success) {
state_ = State::kRunning;
} else {
BeginInitialization();
}
} break;
case State::kRunning:
LOG(FATAL) << "Not a reset state";
}
}
flatbuffers::Offset<IMUValues> ADIS16470::ProcessReading(
flatbuffers::FlatBufferBuilder *fbb) {
// If we ever see this, we'll need to decide how to handle it. Probably reset
// everything and try again.
CHECK_EQ(0, spi_->GetAutoDroppedCount());
absl::Span<const uint32_t> to_process = read_data_;
hal::fpga_clock::time_point fpga_time;
{
int32_t status = 0;
const uint64_t fpga_expanded = HAL_ExpandFPGATime(to_process[0], &status);
CHECK_EQ(0, status);
fpga_time =
hal::fpga_clock::time_point(hal::fpga_clock::duration(fpga_expanded));
}
to_process = to_process.subspan(1);
const uint16_t diag_stat_value = (static_cast<uint16_t>(to_process[0]) << 8) |
static_cast<uint16_t>(to_process[1]);
const auto diag_stat = PackDiagStat(fbb, diag_stat_value);
to_process = to_process.subspan(2);
IMUValues::Builder imu_builder(*fbb);
imu_builder.add_fpga_timestamp(
aos::time::DurationInSeconds(fpga_time.time_since_epoch()));
imu_builder.add_monotonic_timestamp_ns(
time_converter_.FpgaToMonotonic(fpga_time).time_since_epoch().count());
imu_builder.add_previous_reading_diag_stat(diag_stat);
imu_builder.add_gyro_x(ConvertValue16(to_process, kGyroLsbRadianSecond));
to_process = to_process.subspan(2);
imu_builder.add_accelerometer_x(
ConvertValue32(to_process, kAccelerometerLsbG));
to_process = to_process.subspan(4);
imu_builder.add_gyro_y(ConvertValue16(to_process, kGyroLsbRadianSecond));
to_process = to_process.subspan(2);
imu_builder.add_accelerometer_y(
ConvertValue32(to_process, kAccelerometerLsbG));
to_process = to_process.subspan(4);
imu_builder.add_gyro_z(ConvertValue16(to_process, kGyroLsbRadianSecond));
to_process = to_process.subspan(2);
imu_builder.add_accelerometer_z(
ConvertValue32(to_process, kAccelerometerLsbG));
to_process = to_process.subspan(4);
imu_builder.add_temperature(
ConvertValue16(to_process, kTemperatureLsbDegree));
to_process = to_process.subspan(2);
CHECK(to_process.empty()) << "Have leftover bytes: " << to_process.size();
return imu_builder.Finish();
}
double ADIS16470::ConvertValue32(absl::Span<const uint32_t> data,
double lsb_per_output) {
const uint32_t unsigned_value = (static_cast<uint32_t>(data[0]) << 24) |
(static_cast<uint32_t>(data[1]) << 16) |
(static_cast<uint32_t>(data[2]) << 8) |
static_cast<uint32_t>(data[3]);
int32_t signed_value;
memcpy(&signed_value, &unsigned_value, sizeof(unsigned_value));
return static_cast<double>(signed_value) * lsb_per_output;
}
double ADIS16470::ConvertValue16(absl::Span<const uint32_t> data,
double lsb_per_output) {
const uint16_t unsigned_value =
(static_cast<uint16_t>(data[0]) << 8) | static_cast<uint16_t>(data[1]);
int16_t signed_value;
memcpy(&signed_value, &unsigned_value, sizeof(unsigned_value));
return static_cast<double>(signed_value) * lsb_per_output;
}
flatbuffers::Offset<ADIS16470DiagStat> ADIS16470::PackDiagStat(
flatbuffers::FlatBufferBuilder *fbb, uint16_t value) {
ADIS16470DiagStat::Builder diag_stat_builder(*fbb);
diag_stat_builder.add_clock_error(value & (1 << 7));
diag_stat_builder.add_memory_failure(value & (1 << 6));
diag_stat_builder.add_sensor_failure(value & (1 << 5));
diag_stat_builder.add_standby_mode(value & (1 << 4));
diag_stat_builder.add_spi_communication_error(value & (1 << 3));
diag_stat_builder.add_flash_memory_update_error(value & (1 << 2));
diag_stat_builder.add_data_path_overrun(value & (1 << 1));
return diag_stat_builder.Finish();
}
bool ADIS16470::DiagStatHasError(const ADIS16470DiagStat &diag_stat) {
return diag_stat.clock_error() || diag_stat.memory_failure() ||
diag_stat.sensor_failure() || diag_stat.standby_mode() ||
diag_stat.spi_communication_error() ||
diag_stat.flash_memory_update_error() || diag_stat.data_path_overrun();
}
uint16_t ADIS16470::ReadRegister(uint8_t register_address,
uint8_t next_register_address) {
uint8_t send_buffer[2] = {static_cast<uint8_t>(register_address & 0x7f), 0};
uint8_t dummy[2];
spi_->Transaction(send_buffer, dummy, sizeof(send_buffer));
uint8_t receive_buffer[2];
uint8_t next_send_buffer[2] = {
static_cast<uint8_t>(next_register_address & 0x7f), 0};
spi_->Transaction(next_send_buffer, receive_buffer, sizeof(receive_buffer));
return (static_cast<uint16_t>(receive_buffer[0]) << 8) |
static_cast<uint16_t>(receive_buffer[1]);
}
void ADIS16470::WriteRegister(uint8_t register_address, uint16_t value) {
uint8_t buffer1[2] = {static_cast<uint8_t>(register_address | 0x80),
static_cast<uint8_t>(value & 0xff)};
uint8_t buffer2[2] = {static_cast<uint8_t>(register_address | 0x81),
static_cast<uint8_t>(value >> 8)};
spi_->Write(buffer1, sizeof(buffer1));
spi_->Write(buffer2, sizeof(buffer2));
}
} // namespace wpilib
} // namespace frc971