Repository navigation
BMI270: use Wire instead of the legacy I2C driver (fixes driver_ng CONFLICT abort on Arduino core 3.2+) - #8
Merged
weekroom merged 3 commits intoOct 27, 2026
Conversation
The invalid int -> i2c_port_t conversion in BMI270.h (dev_info::i2c_port) was already fixed on main by commit 1aaf0d6, but the library version was never bumped, so PlatformIO's Library Registry still serves the old 1.0.1 release without the fix, and users pulling M5Module-GNSS as a dependency still hit the -fpermissive warning. Bump to 1.0.2 so a new release ships the fix.
On Arduino core 3.x / pioarduino platform-espressif32 (ESP-IDF 5.x), BMI270
called the legacy driver/i2c.h API (i2c_master_write_to_device /
i2c_master_write_read_device) directly. When Arduino's own Wire also links
the new i2c_master driver, ESP-IDF's old i2c.c driver aborts at startup via
a global constructor:
E (1057) i2c: CONFLICT! driver_ng is not allowed to be used with this
old driver
abort() was called at PC 0x4204ef83 on core 0
firmware.map confirmed libdriver.a(i2c.c.obj) was pulled in solely by
BMI270.cpp.o, while libesp_driver_i2c.a(i2c_master.c.obj) was pulled in by
the Arduino core's own Wire (esp32-hal-i2c-ng.c.o), which cannot be
avoided.
Port BMI270's I2C access to Arduino's TwoWire (Wire) so it no longer
touches driver/i2c.h at all:
- dev_info::i2c_port (i2c_port_t) -> dev_info::wire (TwoWire*)
- BMI270::_i2c_port (i2c_port_t) -> BMI270::_wire (TwoWire*)
- bmi2_i2c_read/bmi2_i2c_write reimplemented on top of
beginTransmission/write/endTransmission/requestFrom/read instead of
i2c_master_write_read_device/i2c_master_write_to_device
- drop the file-scope static _data_buffer/_i2c_ret/_dev_info (shared
mutable state unsafe across instances/tasks) in favor of locals in each
callback
- add int init(TwoWire&, uint8_t); keep int init(i2c_port_t, uint8_t) as a
deprecated back-compat overload (I2C_NUM_0 -> Wire, I2C_NUM_1 -> Wire1),
now only needing the header-only <hal/i2c_types.h> for the i2c_port_t
type instead of <driver/i2c.h>, so it does not reintroduce the
i2c.c.obj link
- update examples/getSensorData to use the new Wire-based init()
- document the fix and new init() usage in README
- bump library version to 1.0.3
Not independently re-verified against a real firmware.map in this
environment (no ESP32/PlatformIO toolchain available here); please
confirm libdriver.a(i2c.c.obj) is absent from the link in your build.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KRT6bgzHXTPERGjx5tEHQj
The compatibility overload added with the Wire port referenced I2C_NUM_1 and Wire1 unconditionally, and the header used the global Wire as a default member initializer. Checked against the real arduino-esp32 and ESP-IDF headers, that breaks the build in several configurations: - Arduino core 2.x (ESP-IDF 4.4): I2C_NUM_0/I2C_NUM_1 are macros in driver/i2c.h, not in hal/i2c_types.h, so the library itself failed to compile and existing init(I2C_NUM_0, ...) sketches lost the constant. - ESP32-C3/C6 and other chips with one HP I2C controller: Wire1 (and I2C_NUM_1 on IDF 5.5) is not declared. - Arduino core 3.3 with NO_GLOBAL_WIRE: Wire is not declared at all. Include driver/i2c.h only on IDF 4.x, where no driver_ng exists to conflict with; keep the header-only hal/i2c_types.h on IDF 5.x. Map port 1 to Wire1 only on chips with a second HP I2C controller (which implies Wire1 in every core release), guard the global instances, and log an error otherwise. Default the TwoWire pointers to nullptr. README: the conflict starts with Arduino core 3.2.0, where Wire moved to the i2c_master driver (esp32-hal-i2c-ng.c, IDF >= 5.4). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KRT6bgzHXTPERGjx5tEHQj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #4.
On Arduino core 3.2+ (ESP-IDF 5.4+), adding this library to a project aborts before
setup()runs. This was observed on an M5Stack CoreS3 with pioarduino platform-espressif32 (stable):addr2line:
The "included because of" section of
firmware.map:Root cause
BMI270.cppcallsi2c_master_write_to_device()andi2c_master_write_read_device()directly. Despite their names, both belong to the legacy I2C driver (components/driver/i2c/i2c.c). Referencing them linksi2c.c.obj, and that object carries a constructor,check_i2c_driver_conflict(), which aborts if the newi2c_masterdriver is linked too.Wireis built on the new driver (cores/esp32/esp32-hal-i2c-ng.c→i2c_new_master_bus(), IDF ≥ 5.4), so both drivers end up in every binary. Core 3.1.x and earlier still used the legacy driver forWire, which is why ESP3.2 conflict #4 started with 3.2.Changes
src/utilities/(the Bosch BMI270/BMM150 Sensor API) is untouched.src/BMI270.h/src/BMI270.cppbmi2_i2c_readandbmi2_i2c_writenow useTwoWire:beginTransmission→write(reg)→endTransmission(false)(repeated start) →requestFrom(addr, len)(checked againstlen) →read()beginTransmission→write(reg)→write(data, len)→endTransmission(true)ESP_LOGEnow logs the address, the register and Wire's return code.static uint8_t _data_buffer[32],static esp_err_t _i2c_retandstruct dev_info* _dev_infowere mutable state shared by all instances and tasks. They are now locals. The 32-byte limit (bmi2.read_write_len = 30) is kept.dev_info::i2c_portand_i2c_port(i2c_port_t) are replaced bywireand_wire(TwoWire*).int init(TwoWire& wire, uint8_t bmiAddr).int init(i2c_port_t, uint8_t)is kept, deprecated, for source compatibility.I2C_NUM_0maps toWire, andI2C_NUM_1maps toWire1on chips with a second HP I2C controller.i2c_port_tcomes from the header-onlyhal/i2c_types.h.driver/i2c.h, which is whereI2C_NUM_xare defined there. IDF 4.x has no new driver to conflict with.portMAX_DELAY.Wire.begin(). Pins and clock remain the sketch's responsibility.Example, docs and metadata
examples/getSensorData:bmi270.init(I2C_NUM_0, ...)→bmi270.init(Wire, ...)init().1.0.3This branch is stacked on #7; its first commit is the version bump from #7. If #7 is merged first, only the Wire port remains here. Merging this PR alone brings in both.
Testing
I couldn't build with the ESP32 toolchain in my environment, so this branch has not had a hardware run or a
firmware.mapcheck yet. What I did instead:Compile check: compiled
src/BMI270.cpp, plus a sketch that calls bothinit()overloads, with host g++ (-Wall -Wextra). The stubs reproduce the relevant real declarations:TwoWireI/O overload sets and theWire/Wire1declaration conditions of arduino-esp32 2.0.17, 3.0.7, 3.1.3 and 3.3.12i2c_port_t/I2C_NUM_xdefinitions of ESP-IDF 4.4.7, 5.1, 5.3 and 5.5All 10 configurations compile, including core 3.3 with
NO_GLOBAL_WIRE. There are no new warnings; the three remaining-Wunused-parameterwarnings were already there.Symbol check:
nmon the resultingBMI270.ofinds no references toi2c_master_write_to_deviceori2c_master_write_read_device. The object built from currentmainreferences both, and those references are what make the linker pull inlibdriver.a(i2c.c.obj).To confirm end to end, build
examples/getSensorDatafor ESP32-S3 on core 3.2+ and rungrep 'libdriver.a(i2c.c.obj)' firmware.map. It should find nothing.🤖 Generated with Claude Code