Program Listing for File bmi088.cpp
↰ Return to documentation for file (src/tap/communication/sensors/imu/bmi088/bmi088.cpp
)
/*
* Copyright (c) 2020-2022 Advanced Robotics at the University of Washington <robomstr@uw.edu>
*
* This file is part of Taproot.
*
* Taproot is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Taproot is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Taproot. If not, see <https://www.gnu.org/licenses/>.
*/
#include "bmi088.hpp"
#include "tap/architecture/endianness_wrappers.hpp"
#include "tap/board/board.hpp"
#include "tap/drivers.hpp"
#include "tap/errors/create_errors.hpp"
#include "modm/math/geometry/angle.hpp"
#include "modm/math/units.hpp"
#include "bmi088_data.hpp"
#include "bmi088_hal.hpp"
using namespace modm::literals;
using namespace tap::arch;
using namespace Board;
namespace tap::communication::sensors::imu::bmi088
{
#if defined(PLATFORM_HOSTED)
#define DELAY_MS(ms)
#define DELAY_US(us)
#else
#define DELAY_MS(ms) modm::delay_ms(ms);
#define DELAY_US(us) modm::delay_us(us);
#endif
Bmi088::Bmi088(tap::Drivers *drivers) : drivers(drivers), imuHeater(drivers) {}
Bmi088::ImuState Bmi088::getImuState() const { return imuState; }
void Bmi088::requestRecalibration()
{
if (imuState == ImuState::IMU_NOT_CALIBRATED || imuState == ImuState::IMU_CALIBRATED)
{
data.gyroOffsetRaw[ImuData::X] = 0;
data.gyroOffsetRaw[ImuData::Y] = 0;
data.gyroOffsetRaw[ImuData::Z] = 0;
data.accOffsetRaw[ImuData::X] = 0;
data.accOffsetRaw[ImuData::Y] = 0;
data.accOffsetRaw[ImuData::Z] = 0;
data.gyroDegPerSec[ImuData::X] = 0;
data.gyroDegPerSec[ImuData::Y] = 0;
data.gyroDegPerSec[ImuData::Z] = 0;
data.accG[ImuData::X] = 0;
data.accG[ImuData::Y] = 0;
data.accG[ImuData::Z] = 0;
calibrationSample = 0;
imuState = ImuState::IMU_CALIBRATING;
}
}
void Bmi088::initialize(float sampleFrequency, float mahonyKp, float mahonyKi)
{
#if !defined(PLATFORM_HOSTED)
ImuCS1Accel::configure(Gpio::OutputType::PushPull);
ImuCS1Gyro::configure(Gpio::OutputType::PushPull);
DELAY_MS(100);
ImuSpiMaster::connect<ImuMiso::Miso, ImuMosi::Mosi, ImuSck::Sck>();
ImuSpiMaster::initialize<SystemClock, 10_MHz>();
DELAY_MS(1);
#endif
imuState = ImuState::IMU_NOT_CALIBRATED;
initializeAcc();
initializeGyro();
imuHeater.initialize();
mahonyAlgorithm.begin(sampleFrequency, mahonyKp, mahonyKi);
}
void Bmi088::initializeAcc()
{
// Write to the accelerometer a few times to get it to wake up (without this the bmi088 will not
// turn on properly from cold boot).
Bmi088Hal::bmi088AccReadSingleReg(Acc::ACC_CHIP_ID);
DELAY_MS(1);
Bmi088Hal::bmi088AccReadSingleReg(Acc::ACC_CHIP_ID);
DELAY_MS(1);
// Page 13 of the bmi088 datasheet states:
// After the POR (power-on reset) the gyroscope is in normal mode, while the accelerometer is in
// suspend mode. To switch the accelerometer into normal mode, the user must perform the
// following steps:
// a. Power up the sensor
// b. Wait 1 ms
// c. Enter normal mode by writing '4' to ACC_PWR_CTRL
// d. Wait for 450 microseconds
Bmi088Hal::bmi088AccWriteSingleReg(Acc::ACC_PWR_CTRL, Acc::AccPwrCtrl::ACCELEROMETER_ON);
DELAY_US(450);
// read ACC_CHIP_ID to start SPI communication
// Page 45 of the bmi088 datasheet states:
// "To change the sensor to SPI mode in the initialization phase, the user
// could perform a dummy SPI read operation"
Bmi088Hal::bmi088AccReadSingleReg(Acc::ACC_CHIP_ID);
// check communication is normal after reset
uint8_t readChipID = Bmi088Hal::bmi088AccReadSingleReg(Acc::ACC_CHIP_ID);
DELAY_MS(1);
if (readChipID != Acc::ACC_CHIP_ID_VALUE)
{
RAISE_ERROR(drivers, "bmi088 accel init failed");
imuState = ImuState::IMU_NOT_CONNECTED;
return;
}
setAndCheckAccRegister(
Acc::ACC_CONF,
Acc::AccBandwidth_t(accOversampling) | Acc::AccOutputRate_t(accOutputRate));
setAndCheckAccRegister(Acc::ACC_RANGE, ACC_RANGE);
}
void Bmi088::initializeGyro()
{
// reset gyro
Bmi088Hal::bmi088GyroWriteSingleReg(Gyro::GYRO_SOFTRESET, Gyro::GyroSoftreset::RESET_SENSOR);
DELAY_MS(80);
// check communication normal after reset
Bmi088Hal::bmi088GyroReadSingleReg(Gyro::GYRO_CHIP_ID);
DELAY_MS(1);
uint8_t res = Bmi088Hal::bmi088GyroReadSingleReg(Gyro::GYRO_CHIP_ID);
DELAY_MS(1);
if (res != Gyro::GYRO_CHIP_ID_VALUE)
{
RAISE_ERROR(drivers, "bmi088 gyro init failed");
imuState = ImuState::IMU_NOT_CONNECTED;
}
setAndCheckGyroRegister(Gyro::GYRO_RANGE, GYRO_RANGE);
// extra 0x80 is because the bandwidth register will always have 0x80 masked
// so when checking, we want to mask as well to avoid an error
setAndCheckGyroRegister(Gyro::GYRO_BANDWIDTH, gyroOutputRate | Gyro::GyroBandwidth_t(0x80));
setAndCheckGyroRegister(Gyro::GYRO_LPM1, Gyro::GyroLpm1::PWRMODE_NORMAL);
}
void Bmi088::periodicIMUUpdate()
{
if (imuState == ImuState::IMU_NOT_CONNECTED)
{
RAISE_ERROR(drivers, "periodicIMUUpdate called w/ imu not connected");
return;
}
if (imuState == ImuState::IMU_CALIBRATING)
{
computeOffsets();
}
else
{
mahonyAlgorithm.updateIMU(
data.gyroDegPerSec[ImuData::X],
data.gyroDegPerSec[ImuData::Y],
data.gyroDegPerSec[ImuData::Z],
data.accG[ImuData::X],
data.accG[ImuData::Y],
data.accG[ImuData::Z]);
}
imuHeater.runTemperatureController(data.temperature);
}
void Bmi088::computeOffsets()
{
calibrationSample++;
data.gyroOffsetRaw[ImuData::X] += data.gyroRaw[ImuData::X];
data.gyroOffsetRaw[ImuData::Y] += data.gyroRaw[ImuData::Y];
data.gyroOffsetRaw[ImuData::Z] += data.gyroRaw[ImuData::Z];
data.accOffsetRaw[ImuData::X] += data.accRaw[ImuData::X];
data.accOffsetRaw[ImuData::Y] += data.accRaw[ImuData::Y];
data.accOffsetRaw[ImuData::Z] +=
data.accRaw[ImuData::Z] - (tap::algorithms::ACCELERATION_GRAVITY / ACC_G_PER_ACC_COUNT);
if (calibrationSample >= BMI088_OFFSET_SAMPLES)
{
calibrationSample = 0;
data.gyroOffsetRaw[ImuData::X] /= BMI088_OFFSET_SAMPLES;
data.gyroOffsetRaw[ImuData::Y] /= BMI088_OFFSET_SAMPLES;
data.gyroOffsetRaw[ImuData::Z] /= BMI088_OFFSET_SAMPLES;
data.accOffsetRaw[ImuData::X] /= BMI088_OFFSET_SAMPLES;
data.accOffsetRaw[ImuData::Y] /= BMI088_OFFSET_SAMPLES;
data.accOffsetRaw[ImuData::Z] /= BMI088_OFFSET_SAMPLES;
imuState = ImuState::IMU_CALIBRATED;
mahonyAlgorithm.reset();
}
}
void Bmi088::read()
{
uint8_t rxBuff[6] = {};
Bmi088Hal::bmi088AccReadMultiReg(Acc::ACC_X_LSB, rxBuff, 6);
prevIMUDataReceivedTime = tap::arch::clock::getTimeMicroseconds();
data.accRaw[ImuData::X] = bigEndianInt16ToFloat(rxBuff);
data.accRaw[ImuData::Y] = bigEndianInt16ToFloat(rxBuff + 2);
data.accRaw[ImuData::Z] = bigEndianInt16ToFloat(rxBuff + 4);
Bmi088Hal::bmi088GyroReadMultiReg(Gyro::RATE_X_LSB, rxBuff, 6);
data.gyroRaw[ImuData::X] = bigEndianInt16ToFloat(rxBuff);
data.gyroRaw[ImuData::Y] = bigEndianInt16ToFloat(rxBuff + 2);
data.gyroRaw[ImuData::Z] = bigEndianInt16ToFloat(rxBuff + 4);
Bmi088Hal::bmi088AccReadMultiReg(Acc::TEMP_MSB, rxBuff, 2);
data.temperature = parseTemp(rxBuff[0], rxBuff[1]);
data.gyroDegPerSec[ImuData::X] =
GYRO_DS_PER_GYRO_COUNT * (data.gyroRaw[ImuData::X] - data.gyroOffsetRaw[ImuData::X]);
data.gyroDegPerSec[ImuData::Y] =
GYRO_DS_PER_GYRO_COUNT * (data.gyroRaw[ImuData::Y] - data.gyroOffsetRaw[ImuData::Y]);
data.gyroDegPerSec[ImuData::Z] =
GYRO_DS_PER_GYRO_COUNT * (data.gyroRaw[ImuData::Z] - data.gyroOffsetRaw[ImuData::Z]);
data.accG[ImuData::X] =
ACC_G_PER_ACC_COUNT * (data.accRaw[ImuData::X] - data.accOffsetRaw[ImuData::X]);
data.accG[ImuData::Y] =
ACC_G_PER_ACC_COUNT * (data.accRaw[ImuData::Y] - data.accOffsetRaw[ImuData::Y]);
data.accG[ImuData::Z] =
ACC_G_PER_ACC_COUNT * (data.accRaw[ImuData::Z] - data.accOffsetRaw[ImuData::Z]);
}
void Bmi088::setAndCheckAccRegister(Acc::Register reg, Acc::Registers_t value)
{
Bmi088Hal::bmi088AccWriteSingleReg(reg, value);
DELAY_US(150);
uint8_t val = Bmi088Hal::bmi088AccReadSingleReg(reg);
DELAY_US(150);
if (val != value.value)
{
RAISE_ERROR(drivers, "bmi088 acc config failed");
imuState = ImuState::IMU_NOT_CONNECTED;
}
}
void Bmi088::setAndCheckGyroRegister(Gyro::Register reg, Gyro::Registers_t value)
{
Bmi088Hal::bmi088GyroWriteSingleReg(reg, value);
DELAY_US(150);
uint8_t val = Bmi088Hal::bmi088GyroReadSingleReg(reg);
DELAY_US(150);
if (val != value.value)
{
RAISE_ERROR(drivers, "bmi088 gyro config failed");
imuState = ImuState::IMU_NOT_CONNECTED;
}
}
} // namespace tap::communication::sensors::imu::bmi088