CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
Ardupilot

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.

GitHub Repository: Ardupilot/ardupilot
Path: blob/master/libraries/AP_Compass/AP_Compass_BMM350.cpp
Views: 1798
1
/*
2
* This file is free software: you can redistribute it and/or modify it
3
* under the terms of the GNU General Public License as published by the
4
* Free Software Foundation, either version 3 of the License, or
5
* (at your option) any later version.
6
*
7
* This file is distributed in the hope that it will be useful, but
8
* WITHOUT ANY WARRANTY; without even the implied warranty of
9
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
* See the GNU General Public License for more details.
11
*
12
* You should have received a copy of the GNU General Public License along
13
* with this program. If not, see <http://www.gnu.org/licenses/>.
14
*/
15
#include "AP_Compass_BMM350.h"
16
17
#if AP_COMPASS_BMM350_ENABLED
18
19
#include <AP_HAL/AP_HAL.h>
20
21
#include <utility>
22
23
#include <AP_HAL/utility/sparse-endian.h>
24
#include <AP_Math/AP_Math.h>
25
26
#define BMM350_REG_CHIP_ID 0x00
27
#define BMM350_REG_PMU_CMD_AGGR_SET 0x04
28
#define BMM350_REG_PMU_CMD_AXIS_EN 0x05
29
#define BMM350_REG_PMU_CMD 0x06
30
#define BMM350_REG_PMU_CMD_STATUS_0 0x07
31
#define BMM350_REG_INT_CTRL 0x2E
32
#define BMM350_REG_MAG_X_XLSB 0x31
33
#define BMM350_REG_OTP_CMD 0x50
34
#define BMM350_REG_OTP_DATA_MSB 0x52
35
#define BMM350_REG_OTP_DATA_LSB 0x53
36
#define BMM350_REG_OTP_STATUS 0x55
37
#define BMM350_REG_CMD 0x7E
38
39
// OTP(one-time programmable memory)
40
#define BMM350_OTP_CMD_DIR_READ (0x01<<5U)
41
#define BMM350_OTP_CMD_PWR_OFF_OTP (0x04<<5U)
42
43
#define BMM350_OTP_STATUS_ERROR_MASK 0xE0
44
#define BMM350_OTP_STATUS_CMD_DONE 0x01
45
46
#define BMM350_TEMP_OFF_SENS 0x0D
47
#define BMM350_MAG_OFFSET_X 0x0E
48
#define BMM350_MAG_OFFSET_Y 0x0F
49
#define BMM350_MAG_OFFSET_Z 0x10
50
#define BMM350_MAG_SENS_X 0x10
51
#define BMM350_MAG_SENS_Y 0x11
52
#define BMM350_MAG_SENS_Z 0x11
53
#define BMM350_MAG_TCO_X 0x12
54
#define BMM350_MAG_TCO_Y 0x13
55
#define BMM350_MAG_TCO_Z 0x14
56
#define BMM350_MAG_TCS_X 0x12
57
#define BMM350_MAG_TCS_Y 0x13
58
#define BMM350_MAG_TCS_Z 0x14
59
#define BMM350_MAG_DUT_T_0 0x18
60
#define BMM350_CROSS_X_Y 0x15
61
#define BMM350_CROSS_Y_X 0x15
62
#define BMM350_CROSS_Z_X 0x16
63
#define BMM350_CROSS_Z_Y 0x16
64
#define BMM350_SENS_CORR_Y 0.01f
65
#define BMM350_TCS_CORR_Z 0.0001f
66
67
#define BMM350_CMD_SOFTRESET 0xB6
68
#define BMM350_INT_MODE_PULSED (0<<0U)
69
#define BMM350_INT_POL_ACTIVE_HIGH (1<<1U)
70
#define BMM350_INT_OD_PUSHPULL (1<<2U)
71
#define BMM350_INT_OUTPUT_DISABLE (0<<3U)
72
#define BMM350_INT_DRDY_EN (1<<7U)
73
74
// Averaging performance
75
#define BMM350_AVERAGING_4 (0x02 << 4U)
76
#define BMM350_AVERAGING_8 (0x03 << 4U)
77
78
// Output data rate
79
#define BMM350_ODR_100HZ 0x04
80
#define BMM350_ODR_50HZ 0x05
81
82
// Power modes
83
#define BMM350_PMU_CMD_SUSPEND_MODE 0x00
84
#define BMM350_PMU_CMD_NORMAL_MODE 0x01
85
#define BMM350_PMU_CMD_UPD_OAE 0x02
86
#define BMM350_PMU_CMD_FGR 0x05
87
#define BMM350_PMU_CMD_BR 0x07
88
89
// OTP data length
90
#define BMM350_OTP_DATA_LENGTH 32U
91
92
// Chip ID of BMM350
93
#define BMM350_CHIP_ID 0x33
94
95
#define BMM350_XY_SENSITIVE 14.55f
96
#define BMM350_Z_SENSITIVE 9.0f
97
#define BMM350_TEMP_SENSITIVE 0.00204f
98
#define BMM350_XY_INA_GAIN 19.46f
99
#define BMM350_Z_INA_GAIN 31.0f
100
#define BMM350_ADC_GAIN (1.0f / 1.5f)
101
#define BMM350_LUT_GAIN 0.714607238769531f
102
#define BMM350_POWER ((float)(1000000.0 / 1048576.0))
103
104
#define BMM350_XY_SCALE (BMM350_POWER / (BMM350_XY_SENSITIVE * BMM350_XY_INA_GAIN * BMM350_ADC_GAIN * BMM350_LUT_GAIN))
105
#define BMM350_Z_SCALE (BMM350_POWER / (BMM350_Z_SENSITIVE * BMM350_Z_INA_GAIN * BMM350_ADC_GAIN * BMM350_LUT_GAIN))
106
#define BMM350_TEMP_SCALE (1.0f / (BMM350_TEMP_SENSITIVE * BMM350_ADC_GAIN * BMM350_LUT_GAIN * 1048576))
107
108
extern const AP_HAL::HAL &hal;
109
110
AP_Compass_Backend *AP_Compass_BMM350::probe(AP_HAL::OwnPtr<AP_HAL::Device> dev,
111
bool force_external,
112
enum Rotation rotation)
113
{
114
if (!dev) {
115
return nullptr;
116
}
117
AP_Compass_BMM350 *sensor = NEW_NOTHROW AP_Compass_BMM350(std::move(dev), force_external, rotation);
118
if (!sensor || !sensor->init()) {
119
delete sensor;
120
return nullptr;
121
}
122
123
return sensor;
124
}
125
126
AP_Compass_BMM350::AP_Compass_BMM350(AP_HAL::OwnPtr<AP_HAL::Device> dev,
127
bool force_external,
128
enum Rotation rotation)
129
: _dev(std::move(dev))
130
, _force_external(force_external)
131
, _rotation(rotation)
132
{
133
}
134
135
/**
136
* @brief Read out OTP(one-time programmable memory) data of sensor which is the compensation coefficients
137
* @see https://github.com/boschsensortec/BMM350-SensorAPI
138
*/
139
bool AP_Compass_BMM350::read_otp_data()
140
{
141
uint16_t otp_data[BMM350_OTP_DATA_LENGTH];
142
143
for (uint8_t index = 0; index < BMM350_OTP_DATA_LENGTH; index++) {
144
// Set OTP address
145
if (!_dev->write_register(BMM350_REG_OTP_CMD, (BMM350_OTP_CMD_DIR_READ | index))) {
146
return false;
147
}
148
149
// Wait for OTP status be ready
150
int8_t tries = 3; // Try polling 3 times
151
while (tries--)
152
{
153
uint8_t status;
154
hal.scheduler->delay_microseconds(300);
155
// Read OTP status
156
if (!read_bytes(BMM350_REG_OTP_STATUS, &status, 1) || ((status & BMM350_OTP_STATUS_ERROR_MASK) != 0)) {
157
return false;
158
}
159
if (status & BMM350_OTP_STATUS_CMD_DONE) {
160
break;
161
}
162
}
163
164
if (tries == -1) {
165
return false;
166
}
167
168
// Read OTP data
169
be16_t reg_data;
170
if (!read_bytes(BMM350_REG_OTP_DATA_MSB, (uint8_t *)&reg_data, 2)) {
171
return false;
172
}
173
174
otp_data[index] = be16toh(reg_data);
175
}
176
177
// Update magnetometer offset and sensitivity data.
178
// 12-bit unsigned integer to be left-aligned in a 16-bit integer
179
_mag_comp.offset_coef.x = float(int16_t((otp_data[BMM350_MAG_OFFSET_X] & 0x0FFF) << 4) >> 4);
180
_mag_comp.offset_coef.y = float(int16_t((((otp_data[BMM350_MAG_OFFSET_X] & 0xF000) >> 4) +
181
(otp_data[BMM350_MAG_OFFSET_Y] & 0x00FF)) << 4) >> 4);
182
_mag_comp.offset_coef.z = float(int16_t(((otp_data[BMM350_MAG_OFFSET_Y] & 0x0F00) +
183
(otp_data[BMM350_MAG_OFFSET_Z] & 0x00FF)) << 4) >> 4);
184
_mag_comp.offset_coef.temp = float(int8_t(otp_data[BMM350_TEMP_OFF_SENS])) * (1.0f / 5.0f);
185
186
_mag_comp.sensit_coef.x = float(int8_t((otp_data[BMM350_MAG_SENS_X] & 0xFF00) >> 8)) * (1.0f / 256.0f);
187
_mag_comp.sensit_coef.y = float(int8_t(otp_data[BMM350_MAG_SENS_Y])) * (1.0f / 256.0f) + BMM350_SENS_CORR_Y;
188
_mag_comp.sensit_coef.z = float(int8_t((otp_data[BMM350_MAG_SENS_Z] & 0xFF00) >> 8)) * (1.0f / 256.0f);
189
_mag_comp.sensit_coef.temp = float(int8_t((otp_data[BMM350_TEMP_OFF_SENS] & 0xFF00) >> 8)) * (1.0f / 512.0f);
190
191
_mag_comp.tco.x = float(int8_t(otp_data[BMM350_MAG_TCO_X])) * (1.0f / 32.0f);
192
_mag_comp.tco.y = float(int8_t(otp_data[BMM350_MAG_TCO_Y])) * (1.0f / 32.0f);
193
_mag_comp.tco.z = float(int8_t(otp_data[BMM350_MAG_TCO_Z])) * (1.0f / 32.0f);
194
195
_mag_comp.tcs.x = float(int8_t((otp_data[BMM350_MAG_TCS_X] & 0xFF00) >> 8)) * (1.0f / 16384.0f);
196
_mag_comp.tcs.y = float(int8_t((otp_data[BMM350_MAG_TCS_Y] & 0xFF00) >> 8)) * (1.0f / 16384.0f);
197
_mag_comp.tcs.z = float(int8_t((otp_data[BMM350_MAG_TCS_Z] & 0xFF00) >> 8)) * (1.0f / 16384.0f) - BMM350_TCS_CORR_Z;
198
199
_mag_comp.t0_reading = float(int16_t(otp_data[BMM350_MAG_DUT_T_0])) * (1.0f / 512.0f) + 23.0f;
200
201
_mag_comp.cross_axis.cross_x_y = float(int8_t(otp_data[BMM350_CROSS_X_Y])) * (1.0f / 800.0f);
202
_mag_comp.cross_axis.cross_y_x = float(int8_t((otp_data[BMM350_CROSS_Y_X] & 0xFF00) >> 8)) * (1.0f / 800.0f);
203
_mag_comp.cross_axis.cross_z_x = float(int8_t(otp_data[BMM350_CROSS_Z_X])) * (1.0f / 800.0f);
204
_mag_comp.cross_axis.cross_z_y = float(int8_t((otp_data[BMM350_CROSS_Z_Y] & 0xFF00) >> 8)) * (1.0f / 800.0f);
205
206
return true;
207
}
208
209
/**
210
* @brief Wait PMU_CMD register operation completed. Need to specify which command just sent
211
*/
212
bool AP_Compass_BMM350::wait_pmu_cmd_ready(const uint8_t cmd, const uint32_t timeout)
213
{
214
const uint32_t start_tick = AP_HAL::millis();
215
216
do {
217
hal.scheduler->delay(1);
218
uint8_t status;
219
if (!read_bytes(BMM350_REG_PMU_CMD_STATUS_0, &status, 1)) {
220
return false;
221
}
222
if (((status & 0x01) == 0x00) && (((status & 0xE0) >> 5) == cmd)) {
223
return true;
224
}
225
} while ((AP_HAL::millis() - start_tick) < timeout);
226
227
return false;
228
}
229
230
/**
231
* @brief Reset bit of magnetic register and wait for change to normal mode
232
*/
233
bool AP_Compass_BMM350::mag_reset_and_wait()
234
{
235
uint8_t data;
236
237
// Get PMU command status 0 data
238
if (!read_bytes(BMM350_REG_PMU_CMD_STATUS_0, &data, 1)) {
239
return false;
240
}
241
242
// Check whether the power mode is Normal
243
if (data & 0x08) {
244
// Set PMU command to suspend mode
245
if (!_dev->write_register(BMM350_REG_PMU_CMD, BMM350_PMU_CMD_SUSPEND_MODE)) {
246
return false;
247
}
248
wait_pmu_cmd_ready(BMM350_PMU_CMD_SUSPEND_MODE, 6);
249
}
250
251
// Set BR(bit reset) to PMU_CMD register
252
// In offical example, it wait for 14ms. But it may not be enough, so we wait an extra 5ms
253
if (!_dev->write_register(BMM350_REG_PMU_CMD, BMM350_PMU_CMD_BR) ||
254
!wait_pmu_cmd_ready(BMM350_PMU_CMD_BR, 19)) {
255
return false;
256
}
257
258
// Set FGR(flux-guide reset) to PMU_CMD register
259
// 18ms got from offical example, we wait an extra 5ms
260
if (!_dev->write_register(BMM350_REG_PMU_CMD, BMM350_PMU_CMD_FGR) ||
261
!wait_pmu_cmd_ready(BMM350_PMU_CMD_FGR, 23)) {
262
return false;
263
}
264
265
// Switch to normal mode
266
if (!set_power_mode(POWER_MODE_NORMAL)) {
267
return false;
268
}
269
270
return true;
271
}
272
273
/**
274
* @brief Switch sensor power mode
275
*/
276
bool AP_Compass_BMM350::set_power_mode(const enum power_mode mode)
277
{
278
uint8_t pmu_cmd;
279
280
// Get PMU register data as current mode
281
if (!read_bytes(BMM350_REG_PMU_CMD, &pmu_cmd, 1)) {
282
return false;
283
}
284
285
if (pmu_cmd == BMM350_PMU_CMD_NORMAL_MODE || pmu_cmd == BMM350_PMU_CMD_UPD_OAE) {
286
// Set PMU command to suspend mode
287
if (!_dev->write_register(BMM350_REG_PMU_CMD, POWER_MODE_SUSPEND)) {
288
return false;
289
}
290
// Wait for sensor switch to suspend mode
291
// Wait for maximum 6ms, it got from the official example, not explained in datasheet
292
wait_pmu_cmd_ready(POWER_MODE_SUSPEND, 6);
293
}
294
295
// Set PMU command to target mode
296
if (!_dev->write_register(BMM350_REG_PMU_CMD, mode)) {
297
return false;
298
}
299
300
// Wait for mode change. When switch from suspend mode to normal mode, we wait for at most 38ms.
301
// It got from official example too
302
wait_pmu_cmd_ready(mode, 38);
303
304
return true;
305
}
306
307
/**
308
* @brief Read bytes from sensor
309
*/
310
bool AP_Compass_BMM350::read_bytes(const uint8_t reg, uint8_t *out, const uint16_t read_len)
311
{
312
uint8_t data[read_len + 2];
313
314
if (!_dev->read_registers(reg, data, read_len + 2)) {
315
return false;
316
}
317
318
memcpy(out, &data[2], read_len);
319
320
return true;
321
}
322
323
bool AP_Compass_BMM350::init()
324
{
325
_dev->get_semaphore()->take_blocking();
326
327
// 10 retries for init
328
_dev->set_retries(10);
329
330
// Use checked registers to cope with bus errors
331
_dev->setup_checked_registers(4);
332
333
int8_t boot_retries = 5;
334
while (boot_retries--) {
335
// Soft reset
336
if (!_dev->write_register(BMM350_REG_CMD, BMM350_CMD_SOFTRESET)) {
337
continue;
338
}
339
hal.scheduler->delay(24); // Wait 24ms for soft reset complete, it got from offical example
340
341
// Read and verify chip ID
342
uint8_t chip_id;
343
if (!read_bytes(BMM350_REG_CHIP_ID, &chip_id, 1)) {
344
continue;
345
}
346
if (chip_id == BMM350_CHIP_ID) {
347
break;
348
}
349
}
350
351
if (boot_retries == -1) {
352
goto err;
353
}
354
355
// Read out OTP data
356
if (!read_otp_data()) {
357
goto err;
358
}
359
360
// Power off OTP
361
if (!_dev->write_register(BMM350_REG_OTP_CMD, BMM350_OTP_CMD_PWR_OFF_OTP)) {
362
goto err;
363
}
364
365
// Magnetic reset
366
if (!mag_reset_and_wait()) {
367
goto err;
368
}
369
370
// Configure interrupt settings and enable DRDY
371
// Set INT mode as PULSED | active_high polarity | PUSH_PULL | unmap | DRDY interrupt
372
if (!_dev->write_register(BMM350_REG_INT_CTRL, (BMM350_INT_MODE_PULSED |
373
BMM350_INT_POL_ACTIVE_HIGH |
374
BMM350_INT_OD_PUSHPULL |
375
BMM350_INT_OUTPUT_DISABLE |
376
BMM350_INT_DRDY_EN))) {
377
goto err;
378
}
379
380
// Setup ODR and performance. 100Hz ODR and 4 average for lownoise
381
if (!_dev->write_register(BMM350_REG_PMU_CMD_AGGR_SET, (BMM350_AVERAGING_4 | BMM350_ODR_100HZ))) {
382
goto err;
383
}
384
385
// Update ODR and avg parameter
386
if (!_dev->write_register(BMM350_REG_PMU_CMD, BMM350_PMU_CMD_UPD_OAE)) {
387
goto err;
388
}
389
// Wait at most 20ms for update ODR and avg paramter
390
wait_pmu_cmd_ready(BMM350_PMU_CMD_UPD_OAE, 20);
391
392
// Enable measurement of 3 axis
393
if (!_dev->write_register(BMM350_REG_PMU_CMD_AXIS_EN, 0x07)) {
394
goto err;
395
}
396
397
// Switch power mode to normal mode
398
if (!set_power_mode(POWER_MODE_NORMAL)) {
399
goto err;
400
}
401
402
// Lower retries for run
403
_dev->set_retries(3);
404
405
_dev->get_semaphore()->give();
406
407
/* Register the compass instance in the frontend */
408
_dev->set_device_type(DEVTYPE_BMM350);
409
if (!register_compass(_dev->get_bus_id(), _compass_instance)) {
410
return false;
411
}
412
set_dev_id(_compass_instance, _dev->get_bus_id());
413
414
// printf("BMM350: Found at address 0x%x as compass %u\n", _dev->get_bus_address(), _compass_instance);
415
416
set_rotation(_compass_instance, _rotation);
417
418
if (_force_external) {
419
set_external(_compass_instance, true);
420
}
421
422
// Call timer() at 100Hz
423
_dev->register_periodic_callback(1000000U/100U, FUNCTOR_BIND_MEMBER(&AP_Compass_BMM350::timer, void));
424
425
return true;
426
427
err:
428
_dev->get_semaphore()->give();
429
return false;
430
}
431
432
void AP_Compass_BMM350::timer()
433
{
434
struct PACKED {
435
uint8_t magx[3];
436
uint8_t magy[3];
437
uint8_t magz[3];
438
uint8_t temp[3];
439
} data;
440
441
// Read out measurement data
442
if (!read_bytes(BMM350_REG_MAG_X_XLSB, (uint8_t *)&data, sizeof(data))) {
443
return;
444
}
445
446
// Converts 24-bit raw data to signed integer value
447
const int32_t magx_raw = int32_t(((uint32_t)data.magx[0] << 8) + ((uint32_t)data.magx[1] << 16) + ((uint32_t)data.magx[2] << 24)) >> 8;
448
const int32_t magy_raw = int32_t(((uint32_t)data.magy[0] << 8) + ((uint32_t)data.magy[1] << 16) + ((uint32_t)data.magy[2] << 24)) >> 8;
449
const int32_t magz_raw = int32_t(((uint32_t)data.magz[0] << 8) + ((uint32_t)data.magz[1] << 16) + ((uint32_t)data.magz[2] << 24)) >> 8;
450
const int32_t temp_raw = int32_t(((uint32_t)data.temp[0] << 8) + ((uint32_t)data.temp[1] << 16) + ((uint32_t)data.temp[2] << 24)) >> 8;
451
452
// Convert mag lsb to uT and temp lsb to degC
453
float magx = (float)magx_raw * BMM350_XY_SCALE;
454
float magy = (float)magy_raw * BMM350_XY_SCALE;
455
float magz = (float)magz_raw * BMM350_Z_SCALE;
456
float temp = (float)temp_raw * BMM350_TEMP_SCALE;
457
458
if (temp > 0.0f) {
459
temp -= 25.49f;
460
} else if (temp < 0.0f) {
461
temp += 25.49f;
462
}
463
464
// Apply compensation
465
temp = ((1 + _mag_comp.sensit_coef.temp) * temp) + _mag_comp.offset_coef.temp;
466
// Compensate raw magnetic data
467
magx = ((1 + _mag_comp.sensit_coef.x) * magx) + _mag_comp.offset_coef.x + (_mag_comp.tco.x * (temp - _mag_comp.t0_reading));
468
magx /= 1 + _mag_comp.tcs.x * (temp - _mag_comp.t0_reading);
469
470
magy = ((1 + _mag_comp.sensit_coef.y) * magy) + _mag_comp.offset_coef.y + (_mag_comp.tco.y * (temp - _mag_comp.t0_reading));
471
magy /= 1 + _mag_comp.tcs.y * (temp - _mag_comp.t0_reading);
472
473
magz = ((1 + _mag_comp.sensit_coef.z) * magz) + _mag_comp.offset_coef.z + (_mag_comp.tco.z * (temp - _mag_comp.t0_reading));
474
magz /= 1 + _mag_comp.tcs.z * (temp - _mag_comp.t0_reading);
475
476
const float cr_ax_comp_x = (magx - _mag_comp.cross_axis.cross_x_y * magy) / (1 - _mag_comp.cross_axis.cross_y_x * _mag_comp.cross_axis.cross_x_y);
477
const float cr_ax_comp_y = (magy - _mag_comp.cross_axis.cross_y_x * magx) / (1 - _mag_comp.cross_axis.cross_y_x * _mag_comp.cross_axis.cross_x_y);
478
const float cr_ax_comp_z = (magz + (magx * (_mag_comp.cross_axis.cross_y_x * _mag_comp.cross_axis.cross_z_y - _mag_comp.cross_axis.cross_z_x) -
479
magy * (_mag_comp.cross_axis.cross_z_y - _mag_comp.cross_axis.cross_x_y * _mag_comp.cross_axis.cross_z_x)) /
480
(1 - _mag_comp.cross_axis.cross_y_x * _mag_comp.cross_axis.cross_x_y));
481
482
// Store in field vector and convert uT to milligauss
483
Vector3f field { cr_ax_comp_x * 10.0f, cr_ax_comp_y * 10.0f, cr_ax_comp_z * 10.0f };
484
accumulate_sample(field, _compass_instance);
485
}
486
487
void AP_Compass_BMM350::read()
488
{
489
drain_accumulated_samples(_compass_instance);
490
}
491
492
#endif // AP_COMPASS_BMM350_ENABLED
493
494