/* * bno055.c — Bosch BNO055 NDOF IMU driver over I2C1 * * Issue #135: auto-detect alongside MPU6000; fallback + yaw augmentation. * * Calibration offsets are persisted in STM32F7 RTC backup registers * (BKP0R–BKP6R) and restored automatically on next power-on, so the * sensor re-enters a calibrated state immediately after reset. * * Internal temperature compensation: * The BNO055 silicon compensates all three sensors (accel, gyro, mag) * continuously against its onboard thermometer. No external correction * is required. bno055_temperature() exposes the reading for telemetry. */ #include "bno055.h" #include "i2c1.h" #include "config.h" #include "stm32f7xx_hal.h" #include #include /* ---- I2C addresses (7-bit, shifted left 1 for HAL) ---- */ #define BNO055_ADDR_LOW (0x28u << 1) #define BNO055_ADDR_HIGH (0x29u << 1) #define BNO055_CHIP_ID_VAL 0xA0u /* ---- Register map (page 0) ---- */ #define REG_CHIP_ID 0x00u #define REG_PAGE_ID 0x07u #define REG_GYRO_X_LSB 0x14u /* 6 bytes: Gx, Gy, Gz */ #define REG_EULER_H_LSB 0x1Au /* 6 bytes: H, R, P (each int16) */ #define REG_LIA_X_LSB 0x28u /* 6 bytes: linear accel X,Y,Z */ #define REG_GRV_X_LSB 0x2Eu /* 6 bytes: gravity X,Y,Z */ #define REG_TEMP 0x34u #define REG_CALIB_STAT 0x35u #define REG_OPR_MODE 0x3Du #define REG_PWR_MODE 0x3Eu #define REG_SYS_TRIGGER 0x3Fu #define REG_TEMP_SOURCE 0x40u #define REG_OFFSET_BASE 0x55u /* 22 bytes: accel(6)+mag(6)+gyro(6)+radii(4) */ /* ---- Operating modes ---- */ #define MODE_CONFIG 0x00u #define MODE_IMUPLUS 0x08u /* 6DOF, no mag */ #define MODE_NDOF 0x0Cu /* 9DOF with mag */ /* ---- RTC backup register magic for saved offsets ---- */ /* * Layout: BKP0R..BKP5R hold 22 bytes of calibration offsets (packed * 4 bytes per register; BKP5R only uses its low 2 bytes). * BKP6R holds the magic word to validate the stored data. */ #define BNO055_BKP_MAGIC 0xB055CA10u #define BKP_REG(n) (*(volatile uint32_t *)(&RTC->BKP0R + (n))) /* ---- Scaling constants ---- */ /* * BNO055 default unit selection (UNIT_SEL=0x00): * Euler angles: 1 degree = 16 LSB → divide by 16.0f * Gyro rates: 1 °/s = 16 LSB → divide by 16.0f * Accel/LIA/GRV: 1 m/s² = 100 LSB → divide by 100.0f */ #define EULER_SCALE 16.0f #define GYRO_SCALE 16.0f #define ACCEL_SCALE 100.0f #define G_MS2 9.80665f /* m/s² per g */ /* ---- Module state ---- */ static uint16_t s_addr = 0; static bool s_present = false; static bool s_offsets_ok = false; /* true if offsets restored */ static uint8_t s_calib = 0; /* cached CALIB_STAT */ static int8_t s_temp = 25; /* cached temperature °C */ static uint16_t s_temp_ctr = 0; /* temp read divider */ /* ---- Low-level I2C helpers ---- */ static uint8_t rd(uint8_t reg) { uint8_t v = 0; HAL_I2C_Mem_Read(&hi2c1, s_addr, reg, 1, &v, 1, 100); return v; } static void wr(uint8_t reg, uint8_t val) { HAL_I2C_Mem_Write(&hi2c1, s_addr, reg, 1, &val, 1, 100); } static void rdblk(uint8_t reg, uint8_t *buf, uint8_t len) { HAL_I2C_Mem_Read(&hi2c1, s_addr, reg, 1, buf, len, 200); } static void wrblk(uint8_t reg, const uint8_t *buf, uint8_t len) { HAL_I2C_Mem_Write(&hi2c1, s_addr, reg, 1, (uint8_t *)buf, len, 200); } /* ---- bno055_restore_offsets() ---- */ bool bno055_restore_offsets(void) { __HAL_RCC_PWR_CLK_ENABLE(); HAL_PWR_EnableBkUpAccess(); if (BKP_REG(6) != BNO055_BKP_MAGIC) return false; /* Unpack 22 bytes from BKP0R..BKP5R */ uint8_t offsets[22]; for (int i = 0; i < 5; i++) { uint32_t w = BKP_REG(i); offsets[i * 4 + 0] = (uint8_t)(w & 0xFFu); offsets[i * 4 + 1] = (uint8_t)((w >> 8) & 0xFFu); offsets[i * 4 + 2] = (uint8_t)((w >> 16) & 0xFFu); offsets[i * 4 + 3] = (uint8_t)((w >> 24) & 0xFFu); } uint32_t w5 = BKP_REG(5); offsets[20] = (uint8_t)(w5 & 0xFFu); offsets[21] = (uint8_t)((w5 >> 8) & 0xFFu); /* Write offsets — must be in CONFIG mode */ wr(REG_OPR_MODE, MODE_CONFIG); HAL_Delay(25); wr(REG_PAGE_ID, 0x00); wrblk(REG_OFFSET_BASE, offsets, 22); /* Caller switches back to NDOF after this returns */ return true; } /* ---- bno055_save_offsets() ---- */ bool bno055_save_offsets(void) { if (!s_present) return false; /* Switch to CONFIG mode to read offsets */ wr(REG_OPR_MODE, MODE_CONFIG); HAL_Delay(25); wr(REG_PAGE_ID, 0x00); uint8_t offsets[22]; rdblk(REG_OFFSET_BASE, offsets, 22); /* Restore to NDOF */ wr(REG_OPR_MODE, MODE_NDOF); HAL_Delay(10); /* Pack into RTC backup registers */ __HAL_RCC_PWR_CLK_ENABLE(); HAL_PWR_EnableBkUpAccess(); for (int i = 0; i < 5; i++) { BKP_REG(i) = ((uint32_t)offsets[i * 4 + 0]) | ((uint32_t)offsets[i * 4 + 1] << 8) | ((uint32_t)offsets[i * 4 + 2] << 16) | ((uint32_t)offsets[i * 4 + 3] << 24); } BKP_REG(5) = (uint32_t)offsets[20] | ((uint32_t)offsets[21] << 8); BKP_REG(6) = BNO055_BKP_MAGIC; return true; } /* ---- bno055_init() ---- */ int bno055_init(void) { /* Probe 0x28 (ADR=0) first, then 0x29 (ADR=1) */ const uint16_t candidates[2] = { BNO055_ADDR_LOW, BNO055_ADDR_HIGH }; s_present = false; for (int i = 0; i < 2; i++) { s_addr = candidates[i]; if (rd(REG_CHIP_ID) == BNO055_CHIP_ID_VAL) { s_present = true; break; } } if (!s_present) return -1; /* Software reset via SYS_TRIGGER */ wr(REG_PAGE_ID, 0x00); wr(REG_SYS_TRIGGER, 0x20u); HAL_Delay(700); /* POR settle time from datasheet */ /* Confirm chip ID after reset */ if (rd(REG_CHIP_ID) != BNO055_CHIP_ID_VAL) { s_present = false; return -1; } wr(REG_PAGE_ID, 0x00); /* CONFIGMODE before any register writes */ wr(REG_OPR_MODE, MODE_CONFIG); HAL_Delay(25); /* Normal power mode */ wr(REG_PWR_MODE, 0x00u); HAL_Delay(10); /* * Unit selection (UNIT_SEL = 0x00, default): * Euler = degrees, Gyro = dps, Accel = m/s², Temp = °C * No write needed — defaults match what we want. */ /* Temperature source = gyro (more stable than accel) */ wr(REG_TEMP_SOURCE, 0x01u); /* Try to restore saved calibration offsets */ s_offsets_ok = bno055_restore_offsets(); /* Enter NDOF fusion mode */ wr(REG_OPR_MODE, MODE_NDOF); HAL_Delay(20); s_calib = 0; s_temp = 25; s_temp_ctr = 0; return 0; } /* ---- bno055_read() ---- */ /* * Performs two I2C burst reads (~3ms total at 100kHz): * 1. REG_GYRO_X_LSB → 12 bytes: gyro(6) + euler(6) * 2. REG_LIA_X_LSB → 12 bytes: linear_accel(6) + gravity(6) * * BNO055 coordinate frame (NDOF, no remapping): * X: right, Y: forward, Z: up * Euler H: yaw (0–360°, clockwise from north) * Euler R: roll (–180°..+180°, right bank positive) * Euler P: pitch (–90°..+90°, nose up positive) * Gyro Y: pitch rate (deg/s, nose-up positive) * * Linear accel X (m/s²): forward-backward acceleration (gravity removed) * Gravity Z (m/s²): gravity component on Z axis (≈9.81 when level) */ void bno055_read(IMUData *data) { uint8_t buf[12]; /* ---- Burst 1: Gyro (0x14–0x19) + Euler (0x1A–0x1F) ---- */ rdblk(REG_GYRO_X_LSB, buf, 12); /* Gyro: bytes 0..5 = [Gx_L, Gx_H, Gy_L, Gy_H, Gz_L, Gz_H] * Gy = pitch rate (Y axis = pitch axis in BNO055 frame) */ float pitch_rate = (float)(int16_t)((uint16_t)buf[3] << 8 | buf[2]) / GYRO_SCALE; /* Euler: bytes 6..11 = [H_L, H_H, R_L, R_H, P_L, P_H] */ float heading = (float)(int16_t)((uint16_t)buf[7] << 8 | buf[6]) / EULER_SCALE; float roll = (float)(int16_t)((uint16_t)buf[9] << 8 | buf[8]) / EULER_SCALE; float pitch = (float)(int16_t)((uint16_t)buf[11] << 8 | buf[10]) / EULER_SCALE; /* ---- Burst 2: Linear accel (0x28–0x2D) + Gravity (0x2E–0x33) ---- */ rdblk(REG_LIA_X_LSB, buf, 12); /* Linear accel X (forward, m/s²) — gravity-compensated */ float lia_x_ms2 = (float)(int16_t)((uint16_t)buf[1] << 8 | buf[0]) / ACCEL_SCALE; /* Gravity Z (up axis, m/s²) — should be ≈+9.81 when level */ float grv_z_ms2 = (float)(int16_t)((uint16_t)buf[11] << 8 | buf[10]) / ACCEL_SCALE; /* Fill IMUData in same units as mpu6000_read() */ data->pitch = pitch; data->pitch_rate = pitch_rate; data->roll = roll; data->yaw = heading; data->accel_x = lia_x_ms2 / G_MS2; /* m/s² → g */ data->accel_z = grv_z_ms2 / G_MS2; /* m/s² → g (≈1.0 when level) */ /* Periodically update calibration status and temperature (~1Hz at 250Hz loop) */ if (++s_temp_ctr >= 250u) { s_temp_ctr = 0; s_calib = rd(REG_CALIB_STAT); s_temp = (int8_t)rd(REG_TEMP); /* * Auto-save offsets once when full calibration is achieved for * the first time this session (sys=3, acc=3, gyr=3, don't wait * for mag=3 as it is harder to achieve and not critical for balance). */ static bool s_saved = false; if (!s_saved) { uint8_t sys_cal = (s_calib & BNO055_CAL_SYS_MASK) >> 6; uint8_t acc_cal = (s_calib & BNO055_CAL_ACC_MASK) >> 2; uint8_t gyr_cal = (s_calib & BNO055_CAL_GYR_MASK) >> 4; if (sys_cal >= 1u && acc_cal >= 3u && gyr_cal >= 3u) { bno055_save_offsets(); s_saved = true; } } } } /* ---- bno055_is_ready() ---- */ bool bno055_is_ready(void) { if (!s_present) return false; /* If we restored saved offsets, trust them immediately */ if (s_offsets_ok) return true; /* Otherwise require gyro ≥ 2 and accel ≥ 2 */ uint8_t acc_cal = (s_calib & BNO055_CAL_ACC_MASK) >> 2; uint8_t gyr_cal = (s_calib & BNO055_CAL_GYR_MASK) >> 4; return (acc_cal >= 2u) && (gyr_cal >= 2u); } /* ---- bno055_calib_status() ---- */ uint8_t bno055_calib_status(void) { return s_calib; } /* ---- bno055_temperature() ---- */ int8_t bno055_temperature(void) { return s_temp; }