/* * This file 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. * * This file 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 this program. If not, see . */ #include #include #include #include #include "AP_InertialSensor_BMI270.h" //#define BMI270_DEBUG // BMI270 registers (not the complete list) enum BMI270Register : uint8_t { BMI270_REG_CHIP_ID = 0x00, BMI270_REG_ERR_REG = 0x02, BMI270_REG_STATUS = 0x03, BMI270_REG_ACC_DATA_X_LSB = 0x0C, BMI270_REG_GYR_DATA_X_LSB = 0x12, BMI270_REG_SENSORTIME_0 = 0x18, BMI270_REG_SENSORTIME_1 = 0x19, BMI270_REG_SENSORTIME_2 = 0x1A, BMI270_REG_EVENT = 0x1B, BMI270_REG_INT_STATUS_0 = 0x1C, BMI270_REG_INT_STATUS_1 = 0x1D, BMI270_REG_INTERNAL_STATUS = 0x21, BMI270_REG_TEMPERATURE_LSB = 0x22, BMI270_REG_TEMPERATURE_MSB = 0x23, BMI270_REG_FIFO_LENGTH_LSB = 0x24, BMI270_REG_FIFO_LENGTH_MSB = 0x25, BMI270_REG_FIFO_DATA = 0x26, BMI270_REG_ACC_CONF = 0x40, BMI270_REG_ACC_RANGE = 0x41, BMI270_REG_GYRO_CONF = 0x42, BMI270_REG_GYRO_RANGE = 0x43, BMI270_REG_AUX_CONF = 0x44, BMI270_REG_FIFO_DOWNS = 0x45, BMI270_REG_FIFO_WTM_0 = 0x46, BMI270_REG_FIFO_WTM_1 = 0x47, BMI270_REG_FIFO_CONFIG_0 = 0x48, BMI270_REG_FIFO_CONFIG_1 = 0x49, BMI270_REG_SATURATION = 0x4A, BMI270_REG_INT1_IO_CTRL = 0x53, BMI270_REG_INT2_IO_CTRL = 0x54, BMI270_REG_INT_LATCH = 0x55, BMI270_REG_INT1_MAP_FEAT = 0x56, BMI270_REG_INT2_MAP_FEAT = 0x57, BMI270_REG_INT_MAP_DATA = 0x58, BMI270_REG_INIT_CTRL = 0x59, BMI270_REG_INIT_DATA = 0x5E, BMI270_REG_ACC_SELF_TEST = 0x6D, BMI270_REG_GYR_SELF_TEST_AXES = 0x6E, BMI270_REG_PWR_CONF = 0x7C, BMI270_REG_PWR_CTRL = 0x7D, BMI270_REG_CMD = 0x7E, }; /** * The following device config microcode has the following copyright: * * Copyright (c) 2020 Bosch Sensortec GmbH. All rights reserved. * * BSD-3-Clause * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ const uint8_t AP_InertialSensor_BMI270::maximum_fifo_config_file[] = { BMI270_REG_INIT_DATA, 0xc8, 0x2e, 0x00, 0x2e, 0x80, 0x2e, 0x1a, 0x00, 0xc8, 0x2e, 0x00, 0x2e, 0xc8, 0x2e, 0x00, 0x2e, 0xc8, 0x2e, 0x00, 0x2e, 0xc8, 0x2e, 0x00, 0x2e, 0xc8, 0x2e, 0x00, 0x2e, 0xc8, 0x2e, 0x00, 0x2e, 0x90, 0x32, 0x21, 0x2e, 0x59, 0xf5, 0x10, 0x30, 0x21, 0x2e, 0x6a, 0xf5, 0x1a, 0x24, 0x22, 0x00, 0x80, 0x2e, 0x3b, 0x00, 0xc8, 0x2e, 0x44, 0x47, 0x22, 0x00, 0x37, 0x00, 0xa4, 0x00, 0xff, 0x0f, 0xd1, 0x00, 0x07, 0xad, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x24, 0xfc, 0xf5, 0x80, 0x30, 0x40, 0x42, 0x50, 0x50, 0x00, 0x30, 0x12, 0x24, 0xeb, 0x00, 0x03, 0x30, 0x00, 0x2e, 0xc1, 0x86, 0x5a, 0x0e, 0xfb, 0x2f, 0x21, 0x2e, 0xfc, 0xf5, 0x13, 0x24, 0x63, 0xf5, 0xe0, 0x3c, 0x48, 0x00, 0x22, 0x30, 0xf7, 0x80, 0xc2, 0x42, 0xe1, 0x7f, 0x3a, 0x25, 0xfc, 0x86, 0xf0, 0x7f, 0x41, 0x33, 0x98, 0x2e, 0xc2, 0xc4, 0xd6, 0x6f, 0xf1, 0x30, 0xf1, 0x08, 0xc4, 0x6f, 0x11, 0x24, 0xff, 0x03, 0x12, 0x24, 0x00, 0xfc, 0x61, 0x09, 0xa2, 0x08, 0x36, 0xbe, 0x2a, 0xb9, 0x13, 0x24, 0x38, 0x00, 0x64, 0xbb, 0xd1, 0xbe, 0x94, 0x0a, 0x71, 0x08, 0xd5, 0x42, 0x21, 0xbd, 0x91, 0xbc, 0xd2, 0x42, 0xc1, 0x42, 0x00, 0xb2, 0xfe, 0x82, 0x05, 0x2f, 0x50, 0x30, 0x21, 0x2e, 0x21, 0xf2, 0x00, 0x2e, 0x00, 0x2e, 0xd0, 0x2e, 0xf0, 0x6f, 0x02, 0x30, 0x02, 0x42, 0x20, 0x26, 0xe0, 0x6f, 0x02, 0x31, 0x03, 0x40, 0x9a, 0x0a, 0x02, 0x42, 0xf0, 0x37, 0x05, 0x2e, 0x5e, 0xf7, 0x10, 0x08, 0x12, 0x24, 0x1e, 0xf2, 0x80, 0x42, 0x83, 0x84, 0xf1, 0x7f, 0x0a, 0x25, 0x13, 0x30, 0x83, 0x42, 0x3b, 0x82, 0xf0, 0x6f, 0x00, 0x2e, 0x00, 0x2e, 0xd0, 0x2e, 0x12, 0x40, 0x52, 0x42, 0x00, 0x2e, 0x12, 0x40, 0x52, 0x42, 0x3e, 0x84, 0x00, 0x40, 0x40, 0x42, 0x7e, 0x82, 0xe1, 0x7f, 0xf2, 0x7f, 0x98, 0x2e, 0x6a, 0xd6, 0x21, 0x30, 0x23, 0x2e, 0x61, 0xf5, 0xeb, 0x2c, 0xe1, 0x6f }; /* * End of Bosch microcode copyright */ #define BMI270_CHIP_ID 0x24 #define BMI270_CMD_SOFTRESET 0xB6 #define BMI270_CMD_FIFOFLUSH 0xB0 #define BMI270_FIFO_ACC_EN 0x40 #define BMI270_FIFO_GYR_EN 0x80 #define BMI270_BACKEND_SAMPLE_RATE 1600 /* Datasheet says that the device powers up and soft resets in 2s, so waiting for * 5ms before initialization is enough. */ #define BMI270_POWERUP_DELAY_MSEC 5 /* This number of samples should provide only one read burst operation on the * FIFO most of the time (99.99%). */ #define BMI270_MAX_FIFO_SAMPLES 8 #define BMI270_HARDWARE_INIT_MAX_TRIES 5 const uint32_t BACKEND_PERIOD_US = 1000000UL / BMI270_BACKEND_SAMPLE_RATE; extern const AP_HAL::HAL& hal; AP_InertialSensor_BMI270::AP_InertialSensor_BMI270(AP_InertialSensor &imu, AP_HAL::OwnPtr dev, enum Rotation rotation) : AP_InertialSensor_Backend(imu) , _dev(std::move(dev)) , _rotation(rotation) { } AP_InertialSensor_Backend * AP_InertialSensor_BMI270::probe(AP_InertialSensor &imu, AP_HAL::OwnPtr dev, enum Rotation rotation) { if (!dev) { return nullptr; } auto sensor = new AP_InertialSensor_BMI270(imu, std::move(dev), rotation); if (!sensor) { return nullptr; } if (!sensor->init()) { delete sensor; return nullptr; } return sensor; } AP_InertialSensor_Backend * AP_InertialSensor_BMI270::probe(AP_InertialSensor &imu, AP_HAL::OwnPtr dev, enum Rotation rotation) { if (!dev) { return nullptr; } auto sensor = new AP_InertialSensor_BMI270(imu, std::move(dev), rotation); if (!sensor) { return nullptr; } if (!sensor->init()) { delete sensor; return nullptr; } return sensor; } void AP_InertialSensor_BMI270::start() { _dev->get_semaphore()->take_blocking(); configure_accel(); configure_gyro(); configure_fifo(); _dev->get_semaphore()->give(); if (!_imu.register_accel(_accel_instance, BMI270_BACKEND_SAMPLE_RATE, _dev->get_bus_id_devtype(DEVTYPE_BMI270)) || !_imu.register_gyro(_gyro_instance, BMI270_BACKEND_SAMPLE_RATE, _dev->get_bus_id_devtype(DEVTYPE_BMI270))) { return; } // setup sensor rotations from probe() set_gyro_orientation(_gyro_instance, _rotation); set_accel_orientation(_accel_instance, _rotation); /* Call read_fifo() at 1600Hz */ periodic_handle = _dev->register_periodic_callback(BACKEND_PERIOD_US, FUNCTOR_BIND_MEMBER(&AP_InertialSensor_BMI270::read_fifo, void)); } bool AP_InertialSensor_BMI270::update() { update_accel(_accel_instance); update_gyro(_gyro_instance); return true; } /* read from registers, special SPI handling needed */ bool AP_InertialSensor_BMI270::read_registers(uint8_t reg, uint8_t *data, uint8_t len) { // for SPI we need to discard the first returned byte. See // datasheet for explanation uint8_t b[len+2]; b[0] = reg | 0x80; memset(&b[1], 0, len+1); if (!_dev->transfer(b, len+2, b, len+2)) { return false; } memcpy(data, &b[2], len); return true; } /* write registers with retries. The SPI sensor may take several tries to correctly write a register */ bool AP_InertialSensor_BMI270::write_register(uint8_t reg, uint8_t v) { for (uint8_t i=0; i<8; i++) { _dev->write_register(reg, v); uint8_t v2 = 0; if (read_registers(reg, &v2, 1) && v2 == v) { return true; } } return false; } void AP_InertialSensor_BMI270::check_err_reg() { #ifdef BMI270_DEBUG uint8_t err = 0; read_registers(BMI270_REG_ERR_REG, &err, 1); if (err) { if ((err & 1) == 1) { uint8_t status = 0; read_registers(BMI270_REG_INTERNAL_STATUS, &status, 1); switch (status & 0xF) { case 0: AP_HAL::panic("BMI270: not_init\n"); break; case 2: AP_HAL::panic("BMI270: init_err\n"); break; case 3: AP_HAL::panic("BMI270: drv_err\n"); break; case 4: AP_HAL::panic("BMI270: sns_stop\n"); break; case 5: AP_HAL::panic("BMI270: nvm_error\n"); break; case 6: AP_HAL::panic("BMI270: start_up_error\n"); break; case 7: AP_HAL::panic("BMI270: compat_error\n"); break; case 1: // init ok if ((status>>5 & 1) == 1) { AP_HAL::panic("BMI270: axes_remap_error\n"); } else if ((status>>6 & 1) == 1) { AP_HAL::panic("BMI270: odr_50hz_error\n"); } break; } } else if ((err>>6 & 1) == 1) { AP_HAL::panic("BMI270: fifo_err\n"); } else if ((err>>7 & 1) == 1) { AP_HAL::panic("BMI270: aux_err\n"); } else { AP_HAL::panic("BMI270: internal error detected %d\n", err>>1 & 0xF); } } #endif } void AP_InertialSensor_BMI270::configure_accel() { // set acc in high performance mode with OSR4 filtering (751Hz/4) at 1600Hz // see https://community.bosch-sensortec.com/t5/MEMS-sensors-forum/BMI270-OSR-mode-behaviour/td-p/52020 // OSR4 is a 188Hz filter cutoff, acc_bwp == 0, equivalent to other driver filters write_register(BMI270_REG_ACC_CONF, 1U<<7 | 0x0C); // set acc to 16G full scale write_register(BMI270_REG_ACC_RANGE, 0x03); check_err_reg(); } void AP_InertialSensor_BMI270::configure_gyro() { // set gyro in high performance filter mode, high performance noise mode, normal filtering at 3.2KHz // filter cutoff 751hz write_register(BMI270_REG_GYRO_CONF, 1U<<7 | 1<<6 | 2<<4 | 0x0D); // set gyro to 2000dps full scale // for some reason you have to enable the ois_range bit (bit 3) for 2000dps as well // or else the gyro scale will be 250dps when in prefiltered FIFO mode (not documented in datasheet!) write_register(BMI270_REG_GYRO_RANGE, 0x08); check_err_reg(); } void AP_InertialSensor_BMI270::configure_fifo() { // stop when full, disable sensortime frame write_register(BMI270_REG_FIFO_CONFIG_0, 0x01); // accel + gyro data in FIFO together with headers write_register(BMI270_REG_FIFO_CONFIG_1, 1U<<7 | 1U<<6 | 1U<<4); // filtered data downsampled by 2**1 to 1600Hz write_register(BMI270_REG_FIFO_DOWNS, 1U<<7 | 1U<<3 | 0x01); // disable advanced power save, enable FIFO self-wake write_register(BMI270_REG_PWR_CONF, 0x02); // Enable the gyro, accelerometer and temperature sensor - disable aux interface write_register(BMI270_REG_PWR_CTRL, 0x0E); fifo_reset(); check_err_reg(); } void AP_InertialSensor_BMI270::fifo_reset() { // flush and reset FIFO write_register(BMI270_REG_CMD, BMI270_CMD_FIFOFLUSH); notify_accel_fifo_reset(_accel_instance); notify_gyro_fifo_reset(_gyro_instance); } /* read fifo */ void AP_InertialSensor_BMI270::read_fifo(void) { // check for FIFO errors/overflow uint8_t err = 0; read_registers(BMI270_REG_ERR_REG, &err, 1); if ((err>>6 & 1) == 1) { fifo_reset(); return; } uint8_t len[2]; if (!read_registers(BMI270_REG_FIFO_LENGTH_LSB, len, 2)) { _inc_accel_error_count(_accel_instance); _inc_gyro_error_count(_gyro_instance); return; } uint16_t fifo_length = len[0] + (len[1]<<8); if (fifo_length & 0x8000) { // empty return; } // don't read more than 8 frames at a time if (fifo_length > BMI270_MAX_FIFO_SAMPLES*13) { fifo_length = BMI270_MAX_FIFO_SAMPLES*13; } if (fifo_length == 0) { return; } uint8_t data[fifo_length]; if (!read_registers(BMI270_REG_FIFO_DATA, data, fifo_length)) { _inc_accel_error_count(_accel_instance); _inc_gyro_error_count(_gyro_instance); return; } // adjust the periodic callback to be synchronous with the incoming data // this means that we rarely run read_fifo() without updating the sensor data _dev->adjust_periodic_callback(periodic_handle, BACKEND_PERIOD_US); const uint8_t *p = &data[0]; while (fifo_length >= 12) { /* the fifo frames are variable length, with the frame type in the first byte */ uint8_t frame_len = 2; switch (p[0] & 0xFC) { case 0x84: // accel frame_len = 7; parse_accel_frame(p+1); break; case 0x88: // gyro frame_len = 7; parse_gyro_frame(p+1); break; case 0x8C: // accel + gyro frame_len = 13; parse_gyro_frame(p+1); parse_accel_frame(p+7); break; case 0x40: // skip frame frame_len = 2; break; case 0x44: // sensortime frame frame_len = 4; break; case 0x48: // fifo config frame frame_len = 5; break; case 0x50: // sample drop frame frame_len = 2; break; case 0x80: // invalid frame fifo_reset(); return; } p += frame_len; fifo_length -= frame_len; } // temperature sensor updated every 10ms if (temperature_counter++ == 100) { temperature_counter = 0; uint8_t tbuf[2]; if (!read_registers(BMI270_REG_TEMPERATURE_LSB, tbuf, 2)) { _inc_accel_error_count(_accel_instance); _inc_gyro_error_count(_gyro_instance); } else { uint16_t tval = tbuf[0] | (tbuf[1] << 8); if (tval != 0x8000) { // 0x8000 is invalid int16_t klsb = static_cast(tval); float temp_degc = klsb * 0.002f + 23.0f; _publish_temperature(_accel_instance, temp_degc); } } } } void AP_InertialSensor_BMI270::parse_accel_frame(const uint8_t* d) { // assume configured for 16g range const float scale = (1.0/32768.0) * GRAVITY_MSS * 16.0; int16_t xyz[3] { int16_t(uint16_t(d[0] | (d[1]<<8))), int16_t(uint16_t(d[2] | (d[3]<<8))), int16_t(uint16_t(d[4] | (d[5]<<8)))}; Vector3f accel(xyz[0], xyz[1], xyz[2]); accel *= scale; _rotate_and_correct_accel(_accel_instance, accel); _notify_new_accel_raw_sample(_accel_instance, accel); } void AP_InertialSensor_BMI270::parse_gyro_frame(const uint8_t* d) { // data is 16 bits with 2000dps range const float scale = radians(2000.0f) / 32767.0f; int16_t xyz[3] { int16_t(uint16_t(d[0] | d[1]<<8)), int16_t(uint16_t(d[2] | d[3]<<8)), int16_t(uint16_t(d[4] | d[5]<<8)) }; Vector3f gyro(xyz[0], xyz[1], xyz[2]); gyro *= scale; _rotate_and_correct_gyro(_gyro_instance, gyro); _notify_new_gyro_raw_sample(_gyro_instance, gyro); } bool AP_InertialSensor_BMI270::hardware_init() { bool init = false; bool read_chip_id = false; hal.scheduler->delay(BMI270_POWERUP_DELAY_MSEC); WITH_SEMAPHORE(_dev->get_semaphore()); _dev->set_speed(AP_HAL::Device::SPEED_LOW); for (unsigned i = 0; i < BMI270_HARDWARE_INIT_MAX_TRIES; i++) { uint8_t chip_id = 0; /* If CSB sees a rising edge after power-up, the device interface switches to SPI * after 200 μs until a reset or the next power-up occurs therefore it is recommended * to perform a SPI single read of register CHIP_ID (the obtained value will be invalid) * before the actual communication start, in order to use the SPI interface */ read_registers(BMI270_REG_CHIP_ID, &chip_id, 1); hal.scheduler->delay(1); write_register(BMI270_REG_CMD, BMI270_CMD_SOFTRESET); hal.scheduler->delay(BMI270_POWERUP_DELAY_MSEC); // power on and soft reset time is 2ms // switch to SPI mode again read_registers(BMI270_REG_CHIP_ID, &chip_id, 1); hal.scheduler->delay(1); read_registers(BMI270_REG_CHIP_ID, &chip_id, 1); if (chip_id != BMI270_CHIP_ID) { continue; } // successfully identified the chip, proceed with initialisation read_chip_id = true; // disable power save write_register(BMI270_REG_PWR_CONF, 0x00); hal.scheduler->delay(1); // needs to be at least 450us // upload config write_register(BMI270_REG_INIT_CTRL, 0x00); // Transfer the config file, data packet includes INIT_DATA _dev->transfer(maximum_fifo_config_file, sizeof(maximum_fifo_config_file), nullptr, 0); // config is done write_register(BMI270_REG_INIT_CTRL, 1); // check the results hal.scheduler->delay(20); uint8_t status = 0; read_registers(BMI270_REG_INTERNAL_STATUS, &status, 1); if ((status & 1) == 1) { init = true; DEV_PRINTF("BMI270 initialized after %d retries\n", i+1); break; } } _dev->set_speed(AP_HAL::Device::SPEED_HIGH); if (read_chip_id && !init) { DEV_PRINTF("BMI270: failed to init\n"); } return init; } bool AP_InertialSensor_BMI270::init() { _dev->set_read_flag(0x80); return hardware_init(); }