ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
bmi270.cpp
Go to the documentation of this file.
1#include "bmi270.h"
2#include "bmi270_config.h"
3#include "esphome/core/log.h"
4#include "esphome/core/hal.h"
6
7namespace esphome::bmi270 {
8
9#ifdef USE_BMI270_BMM150
10using namespace bmm150; // NOLINT - sole consumer of the BMM150 aux-device definitions
11#endif
12
13ESPHOME_LOG_TAG(TAG, "bmi270");
14
15PROGMEM_STRING_TABLE(AccelRangeStrings, "±2g", "±4g", "±8g", "±16g");
16PROGMEM_STRING_TABLE(GyroRangeStrings, "±2000°/s", "±1000°/s", "±500°/s", "±250°/s", "±125°/s");
17
18#if defined(USE_ARDUINO) && !defined(USE_ESP32)
19static constexpr size_t MAX_I2C_BUFFER_SIZE = 32;
20#else
21static constexpr size_t MAX_I2C_BUFFER_SIZE = 256;
22#endif
23
24// Configuration blob upload
25// The BMI270 requires a firmware config blob to be written to its internal
26// memory after every power-on before sensors can be used.
27
29 // 1. Disable advanced power-save so the config port is accessible
30 if (!this->write_byte(BMI270_REG_PWR_CONF, BMI270_PWR_CONF_ADV_POWER_SAVE_OFF))
31 return false;
32 delay(1);
33
34 // 2. Prepare config load: write 0x00 to INIT_CTRL to start
35 if (!this->write_byte(BMI270_REG_INIT_CTRL, BMI270_INIT_CTRL_LOAD_START))
36 return false;
37
38 // 3. Burst-write the config in pages
39 const uint8_t *cfg = BMI270_CONFIG_FILE;
40 constexpr size_t cfg_len = sizeof(BMI270_CONFIG_FILE);
41 size_t index = 0;
42
43 while (index != cfg_len) {
44 // Set the page address in INIT_ADDR registers
45 uint8_t addr_lsb = (uint8_t) ((index / 2) & 0x0F);
46 uint8_t addr_msb = (uint8_t) ((index / 2) >> 4);
47 if (!this->write_byte(BMI270_REG_INIT_ADDR_0, addr_lsb))
48 return false;
49 if (!this->write_byte(BMI270_REG_INIT_ADDR_0 + 1, addr_msb))
50 return false;
51
52 // Write a burst of up to the maximum allowed size
53 size_t burst = clamp_at_most(cfg_len - index, MAX_I2C_BUFFER_SIZE);
54 if (this->write_register(BMI270_REG_INIT_DATA, cfg + index, burst) != i2c::ERROR_OK)
55 return false;
56
57 index += burst;
58 }
59
60 // 4. Signal end of config load
61 if (!this->write_byte(BMI270_REG_INIT_CTRL, BMI270_INIT_CTRL_LOAD_DONE))
62 return false;
63 delay(20); // spec: wait ≥20 ms for init to complete
64
65 // 5. Check INTERNAL_STATUS: bit[0:3] should be 0x01 ("initialisation OK")
66 uint8_t status = 0;
67 if (!this->read_byte(BMI270_REG_INTERNAL_STATUS, &status))
68 return false;
69 if ((status & BMI270_INTERNAL_STATUS_MASK) != BMI270_INTERNAL_STATUS_INIT_OK) {
70 ESP_LOGE(TAG, "Config load failed: INTERNAL_STATUS=0x%02X (expected 0x01)", status);
71 return false;
72 }
73 return true;
74}
75
76// setup() ─
77
79 MotionComponent::setup();
80 // 1. Verify chip ID
81 uint8_t chip_id = 0;
82 if (!this->read_byte(BMI270_REG_CHIP_ID, &chip_id)) {
83 this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
84 return;
85 }
86 if (chip_id != BMI270_CHIP_ID_VALUE) {
87 ESP_LOGE(TAG, "Wrong chip ID: 0x%02X (expected 0x%02X)", chip_id, BMI270_CHIP_ID_VALUE);
88 this->mark_failed(LOG_STR("Wrong chip ID"));
89 return;
90 }
91 ESP_LOGD(TAG, "Chip ID: 0x%02X", chip_id);
92
93 // 2. Soft-reset via CMD register (0x7E = 0xB6)
94 if (!this->write_byte(BMI270_REG_CMD, BMI270_CMD_SOFT_RESET)) {
95 this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
96 return;
97 }
98 delay(20);
99
100 // 4. Upload the configuration blob
101 if (!load_config_file_()) {
102 this->mark_failed(LOG_STR("Config file upload failed"));
103 return;
104 }
105 ESP_LOGD(TAG, "Config blob uploaded ✓");
106
107 // 5. Configure accelerometer
108 // ACC_CONF: ODR | BWP(0x2 = normal avg4) | perf_mode(1)
109 uint8_t acc_conf =
110 (uint8_t) (accel_odr_) | (BMI270_CONF_BWP_NORMAL << BMI270_CONF_BWP_SHIFT) | BMI270_ACC_CONF_PERF_MODE;
111 if (!this->write_byte(BMI270_REG_ACC_CONF, acc_conf)) {
112 this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
113 return;
114 }
115 if (!this->write_byte(BMI270_REG_ACC_RANGE, (uint8_t) accel_range_)) {
116 this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
117 return;
118 }
119
120 // 6. Configure gyroscope
121 // GYR_CONF: ODR | BWP(0x2 = normal) | noise_perf(1) | filter_perf(1)
122 uint8_t gyr_conf = (uint8_t) (gyro_odr_) | (BMI270_CONF_BWP_NORMAL << BMI270_CONF_BWP_SHIFT) |
123 BMI270_GYR_CONF_NOISE_PERF | BMI270_GYR_CONF_FILTER_PERF;
124 if (!this->write_byte(BMI270_REG_GYR_CONF, gyr_conf)) {
125 this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
126 return;
127 }
128 if (!this->write_byte(BMI270_REG_GYR_RANGE, (uint8_t) gyro_range_)) {
129 this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
130 return;
131 }
132
133 // 7. Enable accelerometer, gyroscope, and temperature sensor
134 // PWR_CTRL bits: temp_en[3] | gyr_en[2] | acc_en[1]
135 if (!this->write_byte(BMI270_REG_PWR_CTRL, BMI270_PWR_CTRL_IMU)) {
136 this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
137 return;
138 }
139 delay(5);
140
141 // 8. Re-enable advanced power save (optional; keeps current low between reads)
142 // Disabled here for simplicity – leave in performance mode
143 if (!this->write_byte(BMI270_REG_PWR_CONF, BMI270_PWR_CONF_FIFO_SELF_WAKEUP)) { // bit1 = fifo_self_wakeup
144 this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
145 return;
146 }
147
148 // 9. Optionally bring up whatever is wired to the aux interface.
149 // This is entirely additive: it is off by default, and failure only disables
150 // magnetometer reporting rather than the whole component.
151#ifdef USE_BMI270_BMM150
154 if (!this->magnetometer_ready_) {
155 ESP_LOGW(TAG, "BMM150 magnetometer setup failed; continuing without it");
156 }
157 }
158#endif
159
160 ESP_LOGCONFIG(TAG, "BMI270 initialised successfully");
161}
162
163#ifdef USE_BMI270_BMM150
165 // The STATUS aux-busy bit is set while an aux transaction is in flight. Aux transaction
166 // duration depends on the attached device's own timing, so poll with a bounded
167 // timeout instead of guessing a fixed delay.
168 uint32_t start = millis();
169 while (true) {
170 uint8_t status = 0;
171 if (!this->read_byte(BMI270_REG_STATUS, &status))
172 return false;
173 if ((status & BMI270_STATUS_AUX_BUSY) == 0)
174 return true;
175 if (millis() - start >= timeout_ms)
176 return false;
177 delay(1);
178 }
179}
180
181bool BMI270Component::write_aux_register_(uint8_t reg, uint8_t value) {
182 if (!this->write_byte(BMI270_REG_AUX_WR_DATA, value))
183 return false;
184 if (!this->write_byte(BMI270_REG_AUX_WR_ADDR, reg))
185 return false;
186 return this->wait_for_aux_idle_(20);
187}
188
189bool BMI270Component::read_aux_block_(uint8_t reg, uint8_t *buf) {
190 if (!this->write_byte(BMI270_REG_AUX_RD_ADDR, reg))
191 return false;
192 if (!this->wait_for_aux_idle_(20))
193 return false;
194 return this->read_bytes(BMI270_REG_AUX_X_LSB, buf, BMI270_AUX_DATA_LEN);
195}
196
198 uint8_t regs[BMM150_TRIM_BLOCKS * BMM150_TRIM_BLOCK_LEN];
199 for (uint8_t i = 0; i < BMM150_TRIM_BLOCKS; i++) {
200 if (!this->read_aux_block_(BMM150_REG_TRIM_START + i * BMM150_TRIM_BLOCK_LEN, regs + i * BMM150_TRIM_BLOCK_LEN))
201 return false;
202 }
203 this->mag_trim_ = bmm150_parse_trim(regs);
204 return true;
205}
206
208 // Sequence adapted from the Bosch BMI270 aux-interface protocol: enable the secondary
209 // I2C master in manual mode, point it at the BMM150's address, power the BMM150 up
210 // and soft-reset it, verify its chip ID, read its factory trim values, put it in
211 // normal/30Hz mode, then switch the aux interface to auto-burst mode so BMI270 keeps
212 // AUX_X/Y/Z/R refreshed from the BMM150's data registers.
213 ESP_LOGD(TAG, "Setting up BMM150 magnetometer via BMI270 aux interface...");
214
215 if (!this->write_byte(BMI270_REG_IF_CONF, BMI270_IF_CONF_AUX_IF_EN))
216 return false;
217 if (!this->write_byte(BMI270_REG_AUX_IF_CONF, BMI270_AUX_IF_CONF_MANUAL_EN | BMI270_AUX_IF_CONF_MAN_RD_BURST_8))
218 return false;
219 if (!this->write_byte(BMI270_REG_AUX_DEV_ID, (uint8_t) (this->aux_device_address_ << 1)))
220 return false;
221
222 // Suspend -> sleep, then a soft reset so the BMM150 starts from a known state even if it
223 // stayed powered across a restart. The chip ID is only readable once the power bit is set.
224 if (!this->write_aux_register_(BMM150_REG_POWER_CONTROL, BMM150_CMD_POWER_ON)) {
225 ESP_LOGW(TAG, "BMM150 power-on write timed out");
226 return false;
227 }
228 delay(BMM150_START_UP_TIME_MS);
229 if (!this->write_aux_register_(BMM150_REG_POWER_CONTROL, BMM150_CMD_SOFT_RESET)) {
230 ESP_LOGW(TAG, "BMM150 soft-reset write timed out");
231 return false;
232 }
233 delay(BMM150_SOFT_RESET_TIME_MS);
234
235 uint8_t chip_id[BMI270_AUX_DATA_LEN];
236 if (!this->read_aux_block_(BMM150_REG_CHIP_ID, chip_id)) {
237 ESP_LOGW(TAG, "BMM150 chip-ID read failed");
238 return false;
239 }
240 if (chip_id[0] != BMM150_CHIP_ID_VALUE) {
241 ESP_LOGW(TAG, "BMM150 not detected (chip ID 0x%02X, expected 0x%02X)", chip_id[0], BMM150_CHIP_ID_VALUE);
242 return false;
243 }
244
245 if (!this->read_magnetometer_trim_()) {
246 ESP_LOGW(TAG, "BMM150 trim data read failed");
247 return false;
248 }
249
250 if (!this->write_aux_register_(BMM150_REG_OP_MODE, BMM150_CMD_NORMAL_MODE_ODR_30HZ)) {
251 ESP_LOGW(TAG, "BMM150 mode write timed out");
252 return false;
253 }
254
255 // Switch to auto-burst mode: BMI270 will keep polling 8 bytes from the BMM150's
256 // data registers (0x42) into its own AUX_X/Y/Z/R registers automatically.
257 if (!this->write_byte(BMI270_REG_AUX_IF_CONF, BMI270_AUX_IF_CONF_FCU_WRITE_EN | BMI270_AUX_IF_CONF_MAN_RD_BURST_8 |
258 BMI270_AUX_IF_CONF_RD_BURST_8))
259 return false;
260 if (!this->write_byte(BMI270_REG_AUX_RD_ADDR, BMM150_REG_DATA_X_LSB))
261 return false;
262 if (!this->write_byte(BMI270_REG_PWR_CTRL, BMI270_PWR_CTRL_IMU | BMI270_PWR_CTRL_AUX_EN))
263 return false;
264
265 ESP_LOGD(TAG, "BMM150 magnetometer ready");
266 return true;
267}
268#endif // USE_BMI270_BMM150
269
271 ESP_LOGCONFIG(TAG, "BMI270 IMU:");
272 LOG_I2C_DEVICE(this);
273 if (this->is_failed())
274 return;
275
276 ESP_LOGCONFIG(TAG, " Accel range : %s",
277 LOG_STR_ARG(AccelRangeStrings::get_log_str(this->accel_range_, AccelRangeStrings::LAST_INDEX)));
278 ESP_LOGCONFIG(TAG, " Gyro range : %s",
279 LOG_STR_ARG(GyroRangeStrings::get_log_str(this->gyro_range_, GyroRangeStrings::LAST_INDEX)));
280#ifdef USE_BMI270_BMM150
282 ESP_LOGCONFIG(TAG, " Aux device : BMM150 magnetometer (%s)",
283 this->magnetometer_ready_ ? LOG_STR_LITERAL("ready") : LOG_STR_LITERAL("NOT DETECTED"));
284 }
285#endif
286 MotionComponent::dump_config();
287}
288
289// update() ─
290// Reads all 6 axes + temperature in one block
291
293 if (this->is_failed())
294 return false;
295
296 // Accelerometer: registers 0x0C–0x11 (6 bytes: x_lsb, x_msb, y_lsb, y_msb, z_lsb, z_msb)
297 // When the magnetometer is active, the 8 aux data bytes (0x04–0x0B) sit directly before
298 // the accel data, so one burst read from AUX_X_LSB fetches both.
299#ifdef USE_BMI270_BMM150
300 const bool read_mag = this->magnetometer_ready_ && !this->magnetometer_callback_.empty();
301 uint8_t buf[BMI270_AUX_DATA_LEN + REG_READ_LEN];
302 const uint8_t *mag_raw = buf;
303 const uint8_t *raw_data = read_mag ? buf + BMI270_AUX_DATA_LEN : buf;
304 const bool read_ok = read_mag ? this->read_bytes(BMI270_REG_AUX_X_LSB, buf, sizeof(buf))
305 : this->read_bytes(BMI270_REG_DATA_8, buf, REG_READ_LEN);
306#else
307 uint8_t buf[REG_READ_LEN];
308 const uint8_t *raw_data = buf;
309 const bool read_ok = this->read_bytes(BMI270_REG_DATA_8, buf, REG_READ_LEN);
310#endif
311 if (!read_ok) {
312 ESP_LOGW(TAG, "Failed to read IMU data");
313 return false;
314 }
315 // Scale factor: LSB/g depends on range
316 // raw is a signed 16-bit value; full-scale = range_g * 2^15 lsb
317 // In PROGMEM; each read is a 32-bit load, which is safe from flash on ESP8266
318 alignas(4) static constexpr float ACCEL_SCALE[] PROGMEM = {
319 2.0f / 32768.0f,
320 4.0f / 32768.0f,
321 8.0f / 32768.0f,
322 16.0f / 32768.0f,
323 };
324 float scale = ACCEL_SCALE[this->accel_range_];
325
326 data.acceleration[motion::X_AXIS] = (int16_t) ((raw_data[1] << 8) | raw_data[0]) * scale;
327 data.acceleration[motion::Y_AXIS] = (int16_t) ((raw_data[3] << 8) | raw_data[2]) * scale;
328 data.acceleration[motion::Z_AXIS] = (int16_t) ((raw_data[5] << 8) | raw_data[4]) * scale;
329
330 // Gyroscope: registers 0x12–0x17 (6 bytes)
331 // Scale: full-scale range / 2^15
332 alignas(4) static constexpr float GYRO_SCALE[] PROGMEM = {
333 2000.0f / 32768.0f, 1000.0f / 32768.0f, 500.0f / 32768.0f, 250.0f / 32768.0f, 125.0f / 32768.0f,
334 };
335 static constexpr uint8_t GYR_OFFS = BMI270_REG_DATA_14 - BMI270_REG_DATA_8;
336 scale = GYRO_SCALE[this->gyro_range_];
337
338 data.angular_rate[motion::X_AXIS] = (int16_t) ((raw_data[GYR_OFFS + 1] << 8) | raw_data[GYR_OFFS + 0]) * scale;
339 data.angular_rate[motion::Y_AXIS] = (int16_t) ((raw_data[GYR_OFFS + 3] << 8) | raw_data[GYR_OFFS + 2]) * scale;
340 data.angular_rate[motion::Z_AXIS] = (int16_t) ((raw_data[GYR_OFFS + 5] << 8) | raw_data[GYR_OFFS + 4]) * scale;
341
342 if (!this->temperature_callback_.empty()) {
343 // Temperature: registers 0x22–0x23
344 // Formula from datasheet: T[°C] = raw / 512 + 23
345 static constexpr uint8_t TEMP_OFFS = BMI270_REG_TEMP_0 - BMI270_REG_DATA_8;
346 int16_t raw_t = (int16_t) ((raw_data[TEMP_OFFS + 1] << 8) | raw_data[TEMP_OFFS + 0]);
347 float temperature = (raw_t / 512.0f) + 23.0f;
348 this->temperature_callback_.call(temperature);
349 }
350
351#ifdef USE_BMI270_BMM150
352 if (read_mag) {
353 const BMM150Data mag_data = bmm150_convert(this->mag_trim_, mag_raw);
354 this->magnetometer_callback_.call(mag_data);
355 }
356#endif
357 return true;
358}
359
360} // namespace esphome::bmi270
uint8_t status
Definition bl0942.h:8
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:274
bmm150::BMM150Trim mag_trim_
Definition bmi270.h:179
LazyCallbackManager< void(const bmm150::BMM150Data &)> magnetometer_callback_
Definition bmi270.h:178
LazyCallbackManager< void(float)> temperature_callback_
Definition bmi270.h:176
bool update_data(motion::MotionData &data) override
Definition bmi270.cpp:292
bool read_aux_block_(uint8_t reg, uint8_t *buf)
Definition bmi270.cpp:189
BMI270AuxDevice aux_device_
Definition bmi270.h:180
BMI270AccelRange accel_range_
Definition bmi270.h:172
bool wait_for_aux_idle_(uint32_t timeout_ms)
Definition bmi270.cpp:164
bool write_aux_register_(uint8_t reg, uint8_t value)
Definition bmi270.cpp:181
BMI270GyroRange gyro_range_
Definition bmi270.h:174
ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len) const
writes an array of bytes to a specific register in the I²C device
Definition i2c.cpp:34
bool write_byte(uint8_t a_register, uint8_t data) const
Definition i2c.h:265
bool read_byte(uint8_t a_register, uint8_t *data)
Definition i2c.h:240
I2CRegister reg(uint8_t a_register)
calls the I2CRegister constructor
Definition i2c.h:152
bool read_bytes(uint8_t a_register, uint8_t *data, uint8_t len)
Compat APIs All methods below have been added for compatibility reasons.
Definition i2c.h:217
BMM150Data bmm150_convert(const BMM150Trim &trim, const uint8_t *raw)
Definition bmm150_aux.h:110
BMM150Trim bmm150_parse_trim(const uint8_t *regs)
Definition bmm150_aux.h:68
ESPHOME_LOG_TAG(TAG, "bmi270")
PROGMEM_STRING_TABLE(AccelRangeStrings, "±2g", "±4g", "±8g", "±16g")
@ BMI270_AUX_DEVICE_BMM150
Definition bmi270.h:129
@ ERROR_OK
No error found during execution of method.
Definition i2c_bus.h:15
T clamp_at_most(T value, U max)
Definition helpers.h:2402
void HOT delay(uint32_t ms)
Definition hal.cpp:85
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:25
static void uint32_t
uint16_t temperature
Definition sun_gtil2.cpp:12