13 Hours of my life chasing a 3.5ms gain lmao. BNO055 IMU Driver Rust Optimization.

51 points Diligent_Comb5668 2 comments

I'm messing around with a personal project, laying some foundations, see how it works understand it. Came across some relevant repo's for my project, also perfect for my foundational understanding on a low-level since for most of us it may have been a while with how many ready frameworks there are today.

So for this project I needed the Bosch Sensortec BNO055 it's basically a gyroscope sensor or a "9 axis IMU driver" one can connect with stuff like raspberry pi's and pico's. Froked this repo: https://github.com/eupn/bno055 saw that it wasn't maintained anymore, understanding this is pretty basic stuff that doesn't need constant updates or optimizations. But it had some bug's in it and saw some minor optimization opportunities.

So my approach was pretty simple: Create an over complete test harness around it, make sure that works as expected, small refactor, optimize, test again.

### Before optimizations (upstream baseline)

Read type I2C bytes Time Max throughput
temperature 1 1.02 ms ~980 Hz
calibration_status 1 1.04 ms ~960 Hz
accel_data 6 1.69 ms ~590 Hz
gyro_data 6 1.67 ms ~600 Hz
mag_data 6 1.71 ms ~585 Hz
euler_angles 6 1.70 ms ~588 Hz
linear_acceleration 6 1.70 ms ~588 Hz
gravity 6 1.68 ms ~595 Hz
quaternion 8 1.97 ms ~508 Hz
all 6 sensors (individual) ~33 9.83 ms ~102 Hz
calibration_profile 22 + mode switch 43.9 ms —
init reset + configure 653.6 ms —

Notice > Every sensor read wasted an I2C write to set the register page even when already on the correct page. A full sensor loop barely fit in a 10 ms window (100 Hz).

### After optimizations (this fork)

Read type I2C bytes Time Improvement
temperature 1 0.60 ms -41%
calibration_status 1 0.60 ms -42%
accel_data 6 1.26 ms -25%
gyro_data 6 1.24 ms -26%
mag_data 6 1.26 ms -26%
euler_angles 6 1.24 ms -27%
linear_acceleration 6 1.26 ms -26%
gravity 6 1.26 ms -25%
quaternion 8 1.53 ms -22%
all 6 sensors (individual) ~33 7.16 ms -27%
all_sensor_data (bulk) 45 6.28 ms -36%
calibration_profile 22 + mode switch 42.6 ms -3%
init reset + configure 652.0 ms no change

## Changes from upstream

### Bug fixes

- **`AxisRemap::y()` returned wrong axis** — the getter returned `self.x` instead of `self.y`, hidden by `#[allow(clippy::misnamed_getters)]`. Fixed and lint allow removed.

### Safety

- **Removed both `unsafe` blocks** in `BNO055Calibration`. `from_buf()` now constructs field-by-field. `as_bytes()` now returns an owned `[u8; 22]` instead of an unsafe `&[u8]` tied to a raw pointer cast.

### Performance

- **Page tracking** — `set_page()` tracks the current page and skips the I2C write when the requested page is already active. After `soft_reset()`, the tracker resets to page 0.
- **Bulk sensor read** — new `all_sensor_data()` method reads all sensor registers in one I2C transaction. Returns `AllSensorData` with `Option` fields based on mode availability.

### API changes

- `BNO055Calibration::as_bytes()` returns `[u8; 22]` instead of `&[u8]`.
- `AxisRemapBuilder::build()` returns `Result<AxisRemap, InvalidAxisRemap>` instead of `Result<AxisRemap, ()>`.
- New `all_sensor_data()` method and `AllSensorData` struct.

### Dependencies removed

- `byteorder` — replaced with `i16::from_le_bytes()` / `u16::from_le_bytes()` from core.
- `num-derive` — replaced `FromPrimitive` derive with manual match arms.
- `num-traits` — no longer needed without `FromPrimitive`.

### Architecture

- `lib.rs` (996 lines) split into 9 focused modules. No breaking public API change.
- Internal fields and helpers changed from private to `pub(crate)` to support the split.

LICENSE: MIT

Link: https://github.com/Niek-Kamer/BNO055/