HF-BNO08x  0.1.0-dev
Loading...
Searching...
No Matches
esp32_bno08x_bus.hpp
Go to the documentation of this file.
1
21#pragma once
22
23// System headers
24#include <cstdint>
25#include <memory>
26
27// Third-party headers (ESP-IDF)
28#ifdef __cplusplus
29extern "C" {
30#endif
31#include "driver/gpio.h"
32#include "driver/i2c_master.h"
33#include "esp_err.h"
34#include "esp_log.h"
35#include "esp_timer.h"
36#include "freertos/FreeRTOS.h"
37#include "freertos/task.h"
38#ifdef __cplusplus
39}
40#endif
41
42// Project headers
44
45static constexpr const char* TAG_I2C = "BNO08x_I2C";
46
63class Esp32Bno08xI2cBus : public bno08x::CommInterface<Esp32Bno08xI2cBus> {
64public:
68 struct I2CConfig {
69 i2c_port_t port = I2C_NUM_0;
70 gpio_num_t sda_pin = GPIO_NUM_4;
71 gpio_num_t scl_pin = GPIO_NUM_5;
72 uint32_t frequency = 400000;
74 0x4B;
75 bool pullup_enable = true;
76 gpio_num_t int_pin = GPIO_NUM_17;
77 gpio_num_t rst_pin = GPIO_NUM_16;
78 };
79
84
89 explicit Esp32Bno08xI2cBus(const I2CConfig& config)
90 : config_(config), bus_handle_(nullptr), dev_handle_(nullptr), initialized_(false) {}
91
96 Deinit();
97 }
98
99 // ── CommInterface required methods ─────────────────────────────────────
100
107
117 bool Open() noexcept {
118 return Init();
119 }
120
124 void Close() noexcept {
125 Deinit();
126 }
127
138 int Write(const uint8_t* data, uint32_t length) noexcept {
139 if (!initialized_ || dev_handle_ == nullptr) {
140 ESP_LOGE(TAG_I2C, "I2C bus not initialized");
141 return -1;
142 }
143
144 esp_err_t ret = i2c_master_transmit(dev_handle_, data, length, pdMS_TO_TICKS(1000));
145 if (ret != ESP_OK) {
146 ESP_LOGE(TAG_I2C, "I2C write failed: %s (addr=0x%02X, len=%lu)", esp_err_to_name(ret),
147 config_.device_address, static_cast<unsigned long>(length));
148 return -1;
149 }
150
151 return static_cast<int>(length);
152 }
153
172 int Read(uint8_t* data, uint32_t length) noexcept {
173 if (!initialized_ || dev_handle_ == nullptr) {
174 ESP_LOGE(TAG_I2C, "I2C bus not initialized");
175 return -1;
176 }
177
178 // If interrupt pin is configured, check it first to avoid unnecessary I2C transactions
179 if (config_.int_pin != GPIO_NUM_NC && !DataAvailable()) {
180 // Interrupt pin indicates no data available (pin is high, active low)
181 return 0; // Return 0 to indicate no data (SH-2 library expects this)
182 }
183
184 esp_err_t ret = i2c_master_receive(dev_handle_, data, length, pdMS_TO_TICKS(1000));
185 if (ret != ESP_OK) {
186 // ESP_ERR_TIMEOUT can occur if device is clock stretching (no data yet)
187 // This is acceptable for BNO08x - return 0 to indicate no data available
188 if (ret == ESP_ERR_TIMEOUT) {
189 return 0;
190 }
191 ESP_LOGE(TAG_I2C, "I2C read failed: %s (addr=0x%02X, len=%lu)", esp_err_to_name(ret),
192 config_.device_address, static_cast<unsigned long>(length));
193 return -1;
194 }
195
196 return static_cast<int>(length);
197 }
198
210 bool Probe() noexcept {
211 if (!initialized_ || dev_handle_ == nullptr) {
212 return false;
213 }
214
215 // Try to read 1 byte with longer timeout to allow for clock stretching
216 // BNO08x will stretch clock if no data (timeout is OK - means device is present)
217 // NAK means device not present
218 uint8_t dummy;
219 esp_err_t ret = i2c_master_receive(dev_handle_, &dummy, 1, pdMS_TO_TICKS(100));
220
221 // Log the actual error for debugging
222 if (ret != ESP_OK && ret != ESP_ERR_TIMEOUT) {
223 ESP_LOGW(TAG_I2C, "Probe at 0x%02X returned: %s (0x%x)", config_.device_address,
224 esp_err_to_name(ret), ret);
225 }
226
227 // ESP_OK = data received (ACK + data) - device present and responding
228 // ESP_ERR_TIMEOUT = clock stretching exceeded timeout (device present, no data yet - OK for
229 // BNO08x) ESP_ERR_INVALID_RESPONSE = NAK during data phase (but ACK on address means device is
230 // present!) For BNO08x, if we get ACK on address (which we see on logic analyzer), the device
231 // IS present, even if it NAKs during data phase (no data ready) So we accept timeout and also
232 // check if it's a response error (which might indicate ACK then NAK)
233 if (ret == ESP_OK || ret == ESP_ERR_TIMEOUT) {
234 return true;
235 }
236
237 // ESP_ERR_INVALID_RESPONSE typically means NAK during data transfer
238 // But if address was ACKed (seen on logic analyzer), device is present
239 // BNO08x might NAK if it has no data ready, so we should still consider it present
240 if (ret == ESP_ERR_INVALID_RESPONSE) {
241 ESP_LOGI(TAG_I2C, "Probe got NAK during data phase, but address was ACKed - device present");
242 return true; // Device ACKed address, so it's present
243 }
244
245 return false; // Other errors indicate real problems
246 }
247
256 bool DataAvailable() noexcept {
257 if (config_.int_pin == GPIO_NUM_NC) {
258 return true; // No interrupt pin configured, always return true
259 }
260 // BNO08x INT pin is active low
261 return (gpio_get_level(config_.int_pin) == 0);
262 }
263
268 void Delay(uint32_t ms) noexcept {
269 vTaskDelay(pdMS_TO_TICKS(ms));
270 }
271
276 uint32_t GetTimeUs() noexcept {
277 return static_cast<uint32_t>(esp_timer_get_time());
278 }
279
290 void GpioSet(bno08x::CtrlPin pin, bno08x::GpioSignal signal) noexcept {
291 switch (pin) {
293 if (config_.rst_pin != GPIO_NUM_NC) {
294 // RSTN is active-low: ACTIVE → drive LOW (assert reset)
295 gpio_set_level(config_.rst_pin,
296 signal == bno08x::GpioSignal::ACTIVE ? 0 : 1);
297 }
298 break;
303 break; // Not wired in this I2C implementation
304 }
305 }
306
307 // ── Lifecycle management ───────────────────────────────────────────────
308
318 bool Init() noexcept {
319 if (initialized_) {
320 ESP_LOGW(TAG_I2C, "I2C bus already initialized");
321 return true;
322 }
323
324 ESP_LOGI(TAG_I2C,
325 "Initializing I2C bus on port %d (SDA:GPIO%d, SCL:GPIO%d, "
326 "Freq:%lu Hz, Addr:0x%02X)",
327 config_.port, config_.sda_pin, config_.scl_pin,
328 static_cast<unsigned long>(config_.frequency), config_.device_address);
329
330 // Step 1: Configure GPIO pins (interrupt, reset)
331 if (!initGPIO()) {
332 ESP_LOGE(TAG_I2C, "Failed to initialize GPIO pins");
333 return false;
334 }
335
336 // Step 2: Create I2C master bus
337 i2c_master_bus_config_t bus_config = {};
338 bus_config.i2c_port = config_.port;
339 bus_config.sda_io_num = config_.sda_pin;
340 bus_config.scl_io_num = config_.scl_pin;
341 bus_config.clk_source = I2C_CLK_SRC_DEFAULT;
342 bus_config.glitch_ignore_cnt = 7;
343 bus_config.flags.enable_internal_pullup = config_.pullup_enable;
344 bus_config.intr_priority = 0;
345 bus_config.trans_queue_depth = 0;
346
347 esp_err_t ret = i2c_new_master_bus(&bus_config, &bus_handle_);
348 if (ret != ESP_OK) {
349 ESP_LOGE(TAG_I2C, "Failed to create I2C master bus: %s", esp_err_to_name(ret));
350 return false;
351 }
352
353 // Step 3: Create and cache device handle for the BNO08x address
354 // Note: scl_wait_us sets maximum clock stretching timeout per byte
355 // BNO08x can stretch clock when it has no data, so we allow up to 50ms
356 // This must be long enough for the device to prepare data or timeout gracefully
357 i2c_device_config_t dev_config = {
358 .dev_addr_length = I2C_ADDR_BIT_LEN_7,
359 .device_address = config_.device_address,
360 .scl_speed_hz = config_.frequency,
361 .scl_wait_us = 50000, // 50ms max clock stretching per byte (BNO08x datasheet requirement)
362 .flags = {},
363 };
364
365 ret = i2c_master_bus_add_device(bus_handle_, &dev_config, &dev_handle_);
366 if (ret != ESP_OK) {
367 ESP_LOGE(TAG_I2C, "Failed to add device 0x%02X: %s", config_.device_address,
368 esp_err_to_name(ret));
369 i2c_del_master_bus(bus_handle_);
370 bus_handle_ = nullptr;
371 return false;
372 }
373
374 initialized_ = true;
375 ESP_LOGI(TAG_I2C, "I2C bus initialized successfully");
376 return true;
377 }
378
382 void Deinit() noexcept {
383 if (!initialized_) {
384 return;
385 }
386
387 // Remove cached device handle before deleting bus
388 if (dev_handle_ != nullptr) {
389 i2c_master_bus_rm_device(dev_handle_);
390 dev_handle_ = nullptr;
391 }
392
393 if (bus_handle_ != nullptr) {
394 i2c_del_master_bus(bus_handle_);
395 bus_handle_ = nullptr;
396 }
397
398 // Reset GPIO pins
399 if (config_.int_pin != GPIO_NUM_NC) {
400 gpio_reset_pin(config_.int_pin);
401 }
402 if (config_.rst_pin != GPIO_NUM_NC) {
403 gpio_reset_pin(config_.rst_pin);
404 }
405
406 initialized_ = false;
407 ESP_LOGI(TAG_I2C, "I2C bus deinitialized");
408 }
409
410 // ── Accessors ──────────────────────────────────────────────────────────
411
416 [[nodiscard]] const I2CConfig& getConfig() const noexcept {
417 return config_;
418 }
419
424 [[nodiscard]] bool isInitialized() const noexcept {
425 return initialized_;
426 }
427
437 void HardwareReset(uint32_t lowMs = 2, uint32_t bootDelayMs = 200) noexcept {
438 if (config_.rst_pin == GPIO_NUM_NC) {
439 ESP_LOGW(TAG_I2C, "Reset pin not configured, skipping hardware reset");
440 return;
441 }
442
443 ESP_LOGI(TAG_I2C, "Performing hardware reset: asserting RSTN for %lu ms",
444 static_cast<unsigned long>(lowMs));
445
446 // Assert reset (drive LOW)
448 Delay(lowMs);
449
450 // Release reset (drive HIGH)
452 ESP_LOGI(TAG_I2C, "Reset released, waiting %lu ms for sensor boot",
453 static_cast<unsigned long>(bootDelayMs));
454 Delay(bootDelayMs);
455 }
456
457private:
458 I2CConfig config_;
459 i2c_master_bus_handle_t bus_handle_;
460 i2c_master_dev_handle_t dev_handle_;
461 bool initialized_;
462
467 bool initGPIO() noexcept {
468 // Configure interrupt pin (input, pull-up enabled, active low)
469 if (config_.int_pin != GPIO_NUM_NC) {
470 gpio_config_t io_conf = {};
471 io_conf.pin_bit_mask = (1ULL << config_.int_pin);
472 io_conf.mode = GPIO_MODE_INPUT;
473 io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
474 io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
475 io_conf.intr_type = GPIO_INTR_DISABLE;
476
477 esp_err_t ret = gpio_config(&io_conf);
478 if (ret != ESP_OK) {
479 ESP_LOGE(TAG_I2C, "Failed to configure interrupt pin: %s", esp_err_to_name(ret));
480 return false;
481 }
482 ESP_LOGI(TAG_I2C, "Interrupt pin configured: GPIO%d", config_.int_pin);
483 }
484
485 // Configure reset pin (output, high by default = not asserted)
486 if (config_.rst_pin != GPIO_NUM_NC) {
487 gpio_config_t io_conf = {};
488 io_conf.pin_bit_mask = (1ULL << config_.rst_pin);
489 io_conf.mode = GPIO_MODE_OUTPUT;
490 io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
491 io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
492 io_conf.intr_type = GPIO_INTR_DISABLE;
493
494 esp_err_t ret = gpio_config(&io_conf);
495 if (ret != ESP_OK) {
496 ESP_LOGE(TAG_I2C, "Failed to configure reset pin: %s", esp_err_to_name(ret));
497 return false;
498 }
499
500 // Release reset (set high)
501 gpio_set_level(config_.rst_pin, 1);
502 ESP_LOGI(TAG_I2C, "Reset pin configured: GPIO%d", config_.rst_pin);
503 }
504
505 return true;
506 }
507};
508
517inline std::unique_ptr<Esp32Bno08xI2cBus> CreateEsp32Bno08xI2cBus(
519 auto bus = std::make_unique<Esp32Bno08xI2cBus>(config);
520 if (!bus->Init()) {
521 ESP_LOGE(TAG_I2C, "Failed to initialize I2C bus");
522 return nullptr;
523 }
524 return bus;
525}
CRTP-based communication interface for the BNO08x IMU family.
BNO085Interface
Identifies the host interface type that a CommInterface provides.
Definition bno08x_comm_interface.hpp:25
@ I2C
I2C bus (PS1=0, PS0=0). Supports SH-2 and DFU.
ESP32 implementation of bno08x::CommInterface using ESP-IDF I2C master driver.
Definition esp32_bno08x_bus.hpp:63
void Delay(uint32_t ms) noexcept
Delay execution for specified time (required by CommInterface)
Definition esp32_bno08x_bus.hpp:268
bool Probe() noexcept
Probe the I2C device to verify it's responding.
Definition esp32_bno08x_bus.hpp:210
void HardwareReset(uint32_t lowMs=2, uint32_t bootDelayMs=200) noexcept
Perform hardware reset pulse on the BNO08x.
Definition esp32_bno08x_bus.hpp:437
bool Init() noexcept
Initialize the I2C bus, GPIO, and device handle.
Definition esp32_bno08x_bus.hpp:318
const I2CConfig & getConfig() const noexcept
Get the I2C configuration.
Definition esp32_bno08x_bus.hpp:416
~Esp32Bno08xI2cBus()
Destructor - cleans up I2C resources.
Definition esp32_bno08x_bus.hpp:95
BNO085Interface GetInterfaceType() noexcept
Identify this transport as I2C (required by CommInterface).
Definition esp32_bno08x_bus.hpp:104
Esp32Bno08xI2cBus()
Constructor with default configuration.
Definition esp32_bno08x_bus.hpp:83
void GpioSet(bno08x::CtrlPin pin, bno08x::GpioSignal signal) noexcept
Set a control pin to the specified signal state (required by CommInterface)
Definition esp32_bno08x_bus.hpp:290
uint32_t GetTimeUs() noexcept
Get current time in microseconds (required by CommInterface)
Definition esp32_bno08x_bus.hpp:276
bool isInitialized() const noexcept
Check if the bus is initialized.
Definition esp32_bno08x_bus.hpp:424
int Read(uint8_t *data, uint32_t length) noexcept
Read raw data from the BNO08x sensor (required by CommInterface)
Definition esp32_bno08x_bus.hpp:172
bool Open() noexcept
Open the I2C bus and initialize communication (required by CommInterface)
Definition esp32_bno08x_bus.hpp:117
void Deinit() noexcept
Deinitialize the I2C bus and release all resources.
Definition esp32_bno08x_bus.hpp:382
Esp32Bno08xI2cBus(const I2CConfig &config)
Constructor with custom I2C configuration.
Definition esp32_bno08x_bus.hpp:89
bool DataAvailable() noexcept
Check if new data is available (required by CommInterface)
Definition esp32_bno08x_bus.hpp:256
int Write(const uint8_t *data, uint32_t length) noexcept
Write raw data to the BNO08x sensor (required by CommInterface)
Definition esp32_bno08x_bus.hpp:138
void Close() noexcept
Close the I2C bus and release resources (required by CommInterface)
Definition esp32_bno08x_bus.hpp:124
CRTP base class for BNO08x communication interfaces.
Definition bno08x_comm_interface.hpp:92
std::unique_ptr< Esp32Bno08xI2cBus > CreateEsp32Bno08xI2cBus(const Esp32Bno08xI2cBus::I2CConfig &config=Esp32Bno08xI2cBus::I2CConfig{})
Factory function to create an ESP32 BNO08x I2C bus instance.
Definition esp32_bno08x_bus.hpp:517
CtrlPin
Identifies the hardware control pins of the BNO08x.
Definition bno08x_comm_interface.hpp:58
@ PS0
Protocol select bit 0 (active-high on the physical pin)
@ BOOTN
Bootloader entry (active-low on the physical pin)
@ RSTN
Hardware reset (active-low on the physical pin)
@ PS1
Protocol select bit 1 (active-high on the physical pin)
@ WAKE
Wake from suspend, SPI mode only (active-low on the physical pin)
GpioSignal
Abstract signal level for control pins.
Definition bno08x_comm_interface.hpp:74
@ ACTIVE
Pin function is asserted.
@ INACTIVE
Pin function is deasserted.
I2C bus and device configuration structure.
Definition esp32_bno08x_bus.hpp:68
gpio_num_t sda_pin
SDA pin (default GPIO4, same as pcal95555/pca9685)
Definition esp32_bno08x_bus.hpp:70
uint32_t frequency
I2C frequency in Hz (default 400kHz)
Definition esp32_bno08x_bus.hpp:72
uint8_t device_address
7-bit I2C device address (default 0x4B, SA0=HIGH; 0x4A if SA0=LOW)
Definition esp32_bno08x_bus.hpp:73
bool pullup_enable
Enable internal pullups.
Definition esp32_bno08x_bus.hpp:75
gpio_num_t scl_pin
SCL pin (default GPIO5, same as pcal95555/pca9685)
Definition esp32_bno08x_bus.hpp:71
gpio_num_t int_pin
Interrupt pin (default GPIO17, GPIO_NUM_NC if not used)
Definition esp32_bno08x_bus.hpp:76
i2c_port_t port
I2C port number.
Definition esp32_bno08x_bus.hpp:69
gpio_num_t rst_pin
Reset pin (default GPIO16, GPIO_NUM_NC if not used)
Definition esp32_bno08x_bus.hpp:77