LCOV - code coverage report
Current view: top level - AP_Compass - AP_Compass_BMM150.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 129 0.0 %
Date: 2026-09-09 11:02:08 Functions: 0 7 0.0 %

          Line data    Source code
       1             : /*
       2             :  * Copyright (C) 2016  Intel Corporation. All rights reserved.
       3             :  *
       4             :  * This file is free software: you can redistribute it and/or modify it
       5             :  * under the terms of the GNU General Public License as published by the
       6             :  * Free Software Foundation, either version 3 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * This file is distributed in the hope that it will be useful, but
      10             :  * WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
      12             :  * See the GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License along
      15             :  * with this program.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : #include "AP_Compass_BMM150.h"
      18             : 
      19             : #if AP_COMPASS_BMM150_ENABLED
      20             : 
      21             : #include <AP_HAL/AP_HAL.h>
      22             : 
      23             : #include <utility>
      24             : 
      25             : #include <AP_HAL/utility/sparse-endian.h>
      26             : #include <AP_Math/AP_Math.h>
      27             : #include <stdio.h>
      28             : 
      29             : #define CHIP_ID_REG 0x40
      30             : #define CHIP_ID_VAL 0x32
      31             : 
      32             : #define POWER_AND_OPERATIONS_REG 0x4B
      33             : #define POWER_CONTROL_VAL (1 << 0)
      34             : #define SOFT_RESET (1 << 7 | 1 << 1)
      35             : 
      36             : #define OP_MODE_SELF_TEST_ODR_REG 0x4C
      37             : #define NORMAL_MODE (0 << 1)
      38             : #define ODR_30HZ (1 << 3 | 1 << 4 | 1 << 5)
      39             : #define ODR_20HZ (1 << 3 | 0 << 4 | 1 << 5)
      40             : 
      41             : #define DATA_X_LSB_REG 0x42
      42             : 
      43             : #define REPETITIONS_XY_REG 0x51
      44             : #define REPETITIONS_Z_REG 0X52
      45             : 
      46             : /* Trim registers */
      47             : #define DIG_X1_REG 0x5D
      48             : #define DIG_Y1_REG 0x5E
      49             : #define DIG_Z4_LSB_REG 0x62
      50             : #define DIG_Z4_MSB_REG 0x63
      51             : #define DIG_X2_REG 0x64
      52             : #define DIG_Y2_REG 0x65
      53             : #define DIG_Z2_LSB_REG 0x68
      54             : #define DIG_Z2_MSB_REG 0x69
      55             : #define DIG_Z1_LSB_REG 0x6A
      56             : #define DIG_Z1_MSB_REG 0x6B
      57             : #define DIG_XYZ1_LSB_REG 0x6C
      58             : #define DIG_XYZ1_MSB_REG 0x6D
      59             : #define DIG_Z3_LSB_REG 0x6E
      60             : #define DIG_Z3_MSB_REG 0x6F
      61             : #define DIG_XY2_REG 0x70
      62             : #define DIG_XY1_REG 0x71
      63             : 
      64             : #define MEASURE_TIME_USEC 16667
      65             : 
      66             : extern const AP_HAL::HAL &hal;
      67             : 
      68           0 : AP_Compass_Backend *AP_Compass_BMM150::probe(AP_HAL::OwnPtr<AP_HAL::Device> dev, bool force_external, enum Rotation rotation)
      69             : {
      70           0 :     if (!dev) {
      71           0 :         return nullptr;
      72             :     }
      73           0 :     AP_Compass_BMM150 *sensor = NEW_NOTHROW AP_Compass_BMM150(std::move(dev), force_external, rotation);
      74           0 :     if (!sensor || !sensor->init()) {
      75           0 :         delete sensor;
      76           0 :         return nullptr;
      77             :     }
      78             : 
      79           0 :     return sensor;
      80             : }
      81             : 
      82           0 : AP_Compass_BMM150::AP_Compass_BMM150(AP_HAL::OwnPtr<AP_HAL::Device> dev, bool force_external, enum Rotation rotation)
      83           0 :     : _dev(std::move(dev)), _rotation(rotation), _force_external(force_external)
      84             : {
      85           0 : }
      86             : 
      87           0 : bool AP_Compass_BMM150::_load_trim_values()
      88             : {
      89             :     struct {
      90             :         int8_t dig_x1;
      91             :         int8_t dig_y1;
      92             :         uint8_t rsv[3];
      93             :         le16_t dig_z4;
      94             :         int8_t dig_x2;
      95             :         int8_t dig_y2;
      96             :         uint8_t rsv2[2];
      97             :         le16_t dig_z2;
      98             :         le16_t dig_z1;
      99             :         le16_t dig_xyz1;
     100             :         le16_t dig_z3;
     101             :         int8_t dig_xy2;
     102             :         uint8_t dig_xy1;
     103             :     } PACKED trim_registers, trim_registers2;
     104             : 
     105             :     // read the registers twice to confirm we have the right
     106             :     // values. There is no CRC in the registers and these values are
     107             :     // vital to correct operation
     108           0 :     int8_t tries = 4;
     109           0 :     while (tries--) {
     110           0 :         if (!_dev->read_registers(DIG_X1_REG, (uint8_t *)&trim_registers,
     111             :                                   sizeof(trim_registers))) {
     112           0 :             continue;
     113             :         }
     114           0 :         if (!_dev->read_registers(DIG_X1_REG, (uint8_t *)&trim_registers2,
     115             :                                   sizeof(trim_registers))) {
     116           0 :             continue;
     117             :         }
     118           0 :         if (memcmp(&trim_registers, &trim_registers2, sizeof(trim_registers)) == 0) {
     119           0 :             break;
     120             :         }
     121             :     }
     122           0 :     if (-1 == tries) {
     123           0 :         DEV_PRINTF("BMM150: Failed to load trim registers\n");
     124           0 :         return false;
     125             :     }
     126             : 
     127           0 :     _dig.x1 = trim_registers.dig_x1;
     128           0 :     _dig.x2 = trim_registers.dig_x2;
     129           0 :     _dig.xy1 = trim_registers.dig_xy1;
     130           0 :     _dig.xy2 = trim_registers.dig_xy2;
     131           0 :     _dig.xyz1 = le16toh(trim_registers.dig_xyz1);
     132           0 :     _dig.y1 = trim_registers.dig_y1;
     133           0 :     _dig.y2 = trim_registers.dig_y2;
     134           0 :     _dig.z1 = le16toh(trim_registers.dig_z1);
     135           0 :     _dig.z2 = le16toh(trim_registers.dig_z2);
     136           0 :     _dig.z3 = le16toh(trim_registers.dig_z3);
     137           0 :     _dig.z4 = le16toh(trim_registers.dig_z4);
     138             : 
     139           0 :     return true;
     140             : }
     141             : 
     142           0 : bool AP_Compass_BMM150::init()
     143             : {
     144           0 :     uint8_t val = 0;
     145             :     bool ret;
     146             : 
     147           0 :     _dev->get_semaphore()->take_blocking();
     148             : 
     149             :     // 10 retries for init
     150           0 :     _dev->set_retries(10);
     151             : 
     152             :     // use checked registers to cope with bus errors
     153           0 :     _dev->setup_checked_registers(4);
     154             :     
     155           0 :     int8_t boot_tries = 4;
     156           0 :     while (boot_tries--) {
     157             :         /* Do a soft reset */
     158           0 :         ret = _dev->write_register(POWER_AND_OPERATIONS_REG, SOFT_RESET);
     159           0 :         hal.scheduler->delay(2);
     160           0 :         if (!ret) {
     161           0 :             continue;
     162             :         }
     163             : 
     164             :         /* Change power state from suspend mode to sleep mode */
     165           0 :         ret = _dev->write_register(POWER_AND_OPERATIONS_REG, POWER_CONTROL_VAL, true);
     166           0 :         hal.scheduler->delay(2);
     167           0 :         if (!ret) {
     168           0 :             continue;
     169             :         }
     170             : 
     171           0 :         ret = _dev->read_registers(CHIP_ID_REG, &val, 1);
     172           0 :         if (!ret) {
     173           0 :             continue;
     174             :         }
     175           0 :         if (val == CHIP_ID_VAL) {
     176             :             // found it
     177           0 :             break;
     178             :         }
     179           0 :         if (boot_tries == 0) {
     180           0 :             DEV_PRINTF("BMM150: Wrong chip ID 0x%02x should be 0x%02x\n", val, CHIP_ID_VAL);
     181             :         }
     182             :     }
     183           0 :     if (-1 == boot_tries) {
     184           0 :         goto bus_error;
     185             :     }
     186             : 
     187           0 :     ret = _load_trim_values();
     188           0 :     if (!ret) {
     189           0 :         goto bus_error;
     190             :     }
     191             : 
     192             :     /*
     193             :      * Recommended preset for high accuracy:
     194             :      * - Rep X/Y = 47
     195             :      * - Rep Z = 83
     196             :      * - ODR = 20
     197             :      * But we are going to use 30Hz of ODR
     198             :      */
     199           0 :     ret = _dev->write_register(REPETITIONS_XY_REG, (47 - 1) / 2, true);
     200           0 :     if (!ret) {
     201           0 :         goto bus_error;
     202             :     }
     203           0 :     ret = _dev->write_register(REPETITIONS_Z_REG, 83 - 1, true);
     204           0 :     if (!ret) {
     205           0 :         goto bus_error;
     206             :     }
     207             :     /* Change operation mode from sleep to normal and set ODR */
     208           0 :     ret = _dev->write_register(OP_MODE_SELF_TEST_ODR_REG, NORMAL_MODE | ODR_30HZ, true);
     209           0 :     if (!ret) {
     210           0 :         goto bus_error;
     211             :     }
     212             : 
     213           0 :     _dev->get_semaphore()->give();
     214             : 
     215             :     /* register the compass instance in the frontend */
     216           0 :     _dev->set_device_type(DEVTYPE_BMM150);
     217           0 :     if (!register_compass(_dev->get_bus_id())) {
     218           0 :         return false;
     219             :     }
     220             : 
     221           0 :     set_rotation(_rotation);
     222             : 
     223           0 :     if (_force_external) {
     224           0 :         set_external(true);
     225             :     }
     226             : 
     227             :     // 2 retries for run
     228           0 :     _dev->set_retries(2);
     229             :     
     230           0 :     _dev->register_periodic_callback(MEASURE_TIME_USEC,
     231             :             FUNCTOR_BIND_MEMBER(&AP_Compass_BMM150::_update, void));
     232             : 
     233           0 :     _last_read_ms = AP_HAL::millis();
     234             :     
     235           0 :     return true;
     236             : 
     237           0 : bus_error:
     238           0 :     _dev->get_semaphore()->give();
     239           0 :     return false;
     240             : }
     241             : 
     242             : /*
     243             :  * Compensation algorithm got from https://github.com/BoschSensortec/BMM050_driver
     244             :  * this is not explained in datasheet.
     245             :  */
     246           0 : int16_t AP_Compass_BMM150::_compensate_xy(int16_t xy, uint32_t rhall, int32_t txy1, int32_t txy2) const
     247             : {
     248           0 :     int32_t inter = ((int32_t)_dig.xyz1) << 14;
     249           0 :     inter /= rhall;
     250           0 :     inter -= 0x4000;
     251             : 
     252           0 :     int32_t val = _dig.xy2 * ((inter * inter) >> 7);
     253           0 :     val += (inter * (((uint32_t)_dig.xy1) << 7));
     254           0 :     val >>= 9;
     255           0 :     val += 0x100000;
     256           0 :     val *= (txy2 + 0xA0);
     257           0 :     val >>= 12;
     258           0 :     val *= xy;
     259           0 :     val >>= 13;
     260           0 :     val += (txy1 << 3);
     261             : 
     262           0 :     return val;
     263             : }
     264             : 
     265           0 : int16_t AP_Compass_BMM150::_compensate_z(int16_t z, uint32_t rhall) const
     266             : {
     267           0 :     int32_t dividend = int32_t(z - _dig.z4) << 15;
     268           0 :     int32_t dividend2 = dividend - ((_dig.z3 * (int32_t(rhall) - int32_t(_dig.xyz1))) >> 2);
     269             : 
     270           0 :     int32_t divisor = int32_t(_dig.z1) * (rhall << 1);
     271           0 :     divisor += 0x8000;
     272           0 :     divisor >>= 16;
     273           0 :     divisor += _dig.z2;
     274             : 
     275           0 :     int16_t ret = constrain_int32(dividend2 / divisor, -0x8000, 0x8000);
     276             : #if 0
     277             :     static uint8_t counter;
     278             :     if (counter++ == 0) {
     279             :         printf("ret=%d z=%d rhall=%u z1=%d z2=%d z3=%d z4=%d xyz1=%d dividend=%d dividend2=%d divisor=%d\n",
     280             :                ret, z, rhall, _dig.z1, _dig.z2, _dig.z3, _dig.z4, _dig.xyz1, dividend, dividend2, divisor);
     281             :     }
     282             : #endif
     283           0 :     return ret;
     284             : }
     285             : 
     286           0 : void AP_Compass_BMM150::_update()
     287             : {
     288             :     le16_t data[4];
     289           0 :     bool ret = _dev->read_registers(DATA_X_LSB_REG, (uint8_t *) &data, sizeof(data));
     290             : 
     291             :     /* Checking data ready status */
     292           0 :     if (!ret || !(data[3] & 0x1)) {
     293           0 :         _dev->check_next_register();
     294           0 :         uint32_t now = AP_HAL::millis();
     295           0 :         if (now - _last_read_ms > 250) {
     296             :             // cope with power cycle to sensor
     297           0 :             _last_read_ms = now;
     298           0 :             _dev->write_register(POWER_AND_OPERATIONS_REG, SOFT_RESET);
     299           0 :             _dev->write_register(POWER_AND_OPERATIONS_REG, POWER_CONTROL_VAL, true);
     300             :         }
     301           0 :         return;
     302             :     }
     303             : 
     304           0 :     const uint16_t rhall = le16toh(data[3]) >> 2;
     305             : 
     306             :     Vector3f raw_field = Vector3f{
     307           0 :         (float) _compensate_xy(((int16_t)le16toh(data[0])) >> 3,
     308           0 :                                rhall, _dig.x1, _dig.x2),
     309           0 :         (float) _compensate_xy(((int16_t)le16toh(data[1])) >> 3,
     310           0 :                                rhall, _dig.y1, _dig.y2),
     311           0 :         (float) _compensate_z(((int16_t)le16toh(data[2])) >> 1, rhall)};
     312             : 
     313             :     /* apply sensitivity scale 16 LSB/uT */
     314           0 :     raw_field /= 16;
     315             :     /* convert uT to milligauss */
     316           0 :     raw_field *= 10;
     317             : 
     318           0 :     _last_read_ms = AP_HAL::millis();
     319             : 
     320           0 :     accumulate_sample(raw_field);
     321           0 :     _dev->check_next_register();
     322             : }
     323             : 
     324             : #endif  // AP_COMPASS_BMM150_ENABLED

Generated by: LCOV version 1.14