Quickstart¶
Read orientation from a BNO08x over I2C: wire seven signals, write a small port class, call four driver functions.
1. Wire it¶
| MCU | BNO08x | Notes |
|---|---|---|
| I2C SCL | H_SCL (pin 19) | 100 or 400 kHz; 2 kΩ to 4 kΩ pull-up to VDDIO |
| I2C SDA | H_SDA (pin 20) | Pull-up as for SCL |
| GPIO in | H_INTN (pin 14) | Active low: the hub has data. Use a pin that can wake the MCU |
| GPIO out | NRST (pin 11) | Active low reset; the driver pulses it in HardwareReset() |
| GND | PS0 (pin 6), PS1 (pin 5) | Both low selects I2C; sampled at reset |
| GND or VDDIO | SA0 (pin 17) | Low: address 0x4A, high: 0x4B |
| 3.3 V, GND | VDD, VDDIO, GND | VDD must be up no later than VDDIO |
Pull BOOTN (pin 4) high through 10 kΩ, or wire it to a GPIO if you want firmware updates. Breakout boards usually strap PS0, PS1, SA0 and BOOTN already; check yours. The Hardware page has the full pin list, SPI and UART wiring and the timing.
2. Add the driver¶
Add the repository as a submodule with its SH-2 library and link the hf_bno08x CMake target.
Installation covers CMake and ESP-IDF.
git submodule add https://github.com/hardfoc/hf-bno08x-driver.git external/hf-bno08x-driver
git submodule update --init external/hf-bno08x-driver/src/sh2
3. Write the port¶
The port is the only platform code. It moves bytes over I2C, reads H_INTN, drives NRST and BOOTN, waits and keeps
time. Replace the platform_* calls with your SDK's.
class MyI2c : public bno08x::CommInterface<MyI2c> {
public:
static constexpr int kIntnGpio = 17; // H_INTN, active low
static constexpr int kRstnGpio = 16; // NRST, active low
static constexpr int kBootnGpio = 18; // BOOTN, active low (optional, for firmware updates)
BNO085Interface GetInterfaceType() noexcept {
return BNO085Interface::I2C;
}
// Called by Begin() and the bootloader helpers, possibly more than once: initialise once.
bool Open() noexcept {
if (!open_) {
open_ = platform_i2c_open(400000); // standard 100 kHz or fast 400 kHz
}
return open_;
}
void Close() noexcept {
platform_i2c_close();
open_ = false;
}
// One SHTP transfer from the SH-2 library. Return the bytes accepted, or a negative value.
int Write(const uint8_t* data, uint32_t length) noexcept {
return platform_i2c_write(address_, data, length) ? static_cast<int>(length) : -1;
}
// Return one complete SHTP transfer (header included), 0 when the hub has nothing to send.
int Read(uint8_t* data, uint32_t length) noexcept {
if (!DataAvailable()) {
return 0;
}
uint8_t header[4];
if (!platform_i2c_read(address_, header, sizeof(header))) {
return -1;
}
uint32_t transfer = (header[0] | (header[1] << 8)) & 0x7FFFU; // bit 15: continuation flag
if (transfer == 0) {
return 0; // empty transfer
}
if (transfer > length) {
transfer = length;
}
// A new read starts again at the header, so read the whole transfer.
return platform_i2c_read(address_, data, transfer) ? static_cast<int>(transfer) : -1;
}
// H_INTN low: the hub has a transfer waiting.
bool DataAvailable() noexcept {
return !platform_gpio_read(kIntnGpio);
}
void Delay(uint32_t ms) noexcept {
platform_delay_ms(ms);
}
uint32_t GetTimeUs() noexcept {
return platform_micros();
}
// ACTIVE means "function asserted": low on NRST and BOOTN. PS0/PS1 are strapped to GND for I2C.
// Return false on a GPIO error; a pin the board does not wire is not an error.
bool GpioSet(bno08x::CtrlPin pin, bno08x::GpioSignal signal) noexcept {
const bool active = signal == bno08x::GpioSignal::ACTIVE;
switch (pin) {
case bno08x::CtrlPin::RSTN:
return platform_gpio_write(kRstnGpio, !active);
case bno08x::CtrlPin::BOOTN:
return platform_gpio_write(kBootnGpio, !active);
case bno08x::CtrlPin::WAKE:
case bno08x::CtrlPin::PS0:
case bno08x::CtrlPin::PS1:
break; // not wired on this board
}
return true;
}
// Optional, for firmware updates: the driver moves the port to the bootloader address (0x28 /
// 0x29) for the transfer and back to the application address afterwards.
uint8_t GetI2cAddress() noexcept {
return address_;
}
bool SetI2cAddress(uint8_t addr7) noexcept {
address_ = addr7;
return true;
}
private:
bool open_ = false;
uint8_t address_ = 0x4A; // SA0 low; 0x4B with SA0 high
};
Read() returns one complete SHTP transfer: it reads the 4-byte header for the length, then reads the whole
transfer again from the start. Porting explains each method and shows the UART-RVC port.
4. Read orientation¶
#include "bno08x.hpp"
#include <cstdio>
int print_orientation(MyI2c& bus) {
BNO085<MyI2c> imu(bus);
imu.HardwareReset(); // 1. pulse NRST
if (!imu.Begin()) { // 2. open the bus, start SH-2
return imu.GetLastError(); // SH2_ERR_* from sh2_err.h
}
if (!imu.EnableSensor(BNO085Sensor::RotationVector, 10)) { // 3. orientation every 10 ms
return imu.GetLastError();
}
for (;;) {
imu.Update(); // 4. service the hub
if (imu.HasNewData(BNO085Sensor::RotationVector)) {
const Quaternion q = imu.GetLatest(BNO085Sensor::RotationVector).rotation;
std::printf("w=%.3f x=%.3f y=%.3f z=%.3f accuracy=%u\n", q.w, q.x, q.y, q.z,
static_cast<unsigned>(q.accuracy));
}
bus.Delay(2);
}
}
What each step does:
HardwareReset()holds NRST low for 2 ms and waits 94 ms for the hub to start (datasheet figure 6-8). PS0, PS1, SA0 and BOOTN are sampled now.Begin()opens the port, starts the CEVA SH-2 library, waits for the hub's reset message and sends a reinitialise command. Nothing else works until it returnstrue.EnableSensor()asks the hub for a rotation vector every 10 ms (100 Hz).Update()services the SH-2 library: it reads pending transfers, decodes reports and runs your callbacks. Call it at least as often as your fastest report.
accuracy goes from 0 (unreliable) to 3 (high). Move the board through a few orientations to calibrate the
magnetometer. This example is built by the host CI, so it compiles against the current headers.
If it does not work¶
| Symptom | First thing to check |
|---|---|
Begin() returns false, GetLastError() is SH2_ERR (-1) |
Open() failed: bus pins, pull-ups, the address (0x4A or 0x4B) |
Begin() returns false with SH2_ERR_TIMEOUT (-6) |
H_INTN not wired or read inverted; PS0/PS1 not both low at reset |
No reports after EnableSensor() |
Update() not called often enough; HasNewData() checked for another sensor |
accuracy stays at 0 |
Magnetic interference near the board; move it in a figure eight |
Troubleshooting has more.
Next¶
- API guide: callbacks, polling, several reports, errors.
- Configuration: report rates and the 40 report types.
- UART-RVC mode: orientation over a single UART line, no protocol.