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

          Line data    Source code
       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             : /*
      16             :   Driver by Andrew Tridgell, Nov 2016
      17             :  */
      18             : #include "AP_Compass_AK09916.h"
      19             : 
      20             : #if AP_COMPASS_AK09916_ENABLED
      21             : 
      22             : #include <assert.h>
      23             : #include <AP_HAL/AP_HAL.h>
      24             : #include <utility>
      25             : #include <AP_Math/AP_Math.h>
      26             : #include <stdio.h>
      27             : #include <AP_InertialSensor/AP_InertialSensor_Invensensev2.h>
      28             : #include <GCS_MAVLink/GCS.h>
      29             : #include <AP_BoardConfig/AP_BoardConfig.h>
      30             : 
      31             : extern const AP_HAL::HAL &hal;
      32             : 
      33             : #define REG_COMPANY_ID      0x00
      34             : #define REG_DEVICE_ID       0x01
      35             : #define REG_ST1             0x10
      36             : #define REG_HXL             0x11
      37             : #define REG_HXH             0x12
      38             : #define REG_HYL             0x13
      39             : #define REG_HYH             0x14
      40             : #define REG_HZL             0x15
      41             : #define REG_HZH             0x16
      42             : #define REG_TMPS            0x17
      43             : #define REG_ST2             0x18
      44             : #define REG_CNTL1           0x30
      45             : #define REG_CNTL2           0x31
      46             : #define REG_CNTL3           0x32
      47             : 
      48             : #define REG_ICM_WHOAMI      0x00
      49             : #define REG_ICM_PWR_MGMT_1  0x06
      50             : #define REG_ICM_INT_PIN_CFG 0x0f
      51             : 
      52             : #define ICM_WHOAMI_VAL      0xEA
      53             : 
      54             : #define AK09915_Device_ID   0x10
      55             : #define AK09916_Device_ID   0x09
      56             : #define AK09918_Device_ID   0x0c
      57             : #define AK09916_MILLIGAUSS_SCALE 10.0f
      58             : 
      59             : extern const AP_HAL::HAL &hal;
      60             : 
      61           0 : AP_Compass_AK09916::AP_Compass_AK09916(AP_AK09916_BusDriver *bus,
      62             :                                         bool force_external,
      63           0 :                                         enum Rotation rotation)
      64           0 :     : _bus(bus)
      65           0 :     , _force_external(force_external)
      66           0 :     , _rotation(rotation)
      67             : {
      68           0 : }
      69             : 
      70           0 : AP_Compass_AK09916::~AP_Compass_AK09916()
      71             : {
      72           0 :     delete _bus;
      73           0 : }
      74             : 
      75           0 : AP_Compass_Backend *AP_Compass_AK09916::probe(AP_HAL::OwnPtr<AP_HAL::Device> dev,
      76             :                                              bool force_external,
      77             :                                              enum Rotation rotation)
      78             : {
      79           0 :     if (!dev) {
      80           0 :         return nullptr;
      81             :     }
      82           0 :     AP_AK09916_BusDriver *bus = NEW_NOTHROW AP_AK09916_BusDriver_HALDevice(std::move(dev));
      83           0 :     if (!bus) {
      84           0 :         return nullptr;
      85             :     }
      86             : 
      87           0 :     AP_Compass_AK09916 *sensor = NEW_NOTHROW AP_Compass_AK09916(bus, force_external, rotation);
      88           0 :     if (!sensor || !sensor->init()) {
      89           0 :         delete sensor;
      90           0 :         return nullptr;
      91             :     }
      92             : 
      93           0 :     return sensor;
      94             : }
      95             : 
      96             : #if AP_COMPASS_ICM20948_ENABLED
      97           0 : AP_Compass_Backend *AP_Compass_AK09916::probe_ICM20948(AP_HAL::OwnPtr<AP_HAL::Device> dev,
      98             :                                                      AP_HAL::OwnPtr<AP_HAL::Device> dev_icm,
      99             :                                                      bool force_external,
     100             :                                                      enum Rotation rotation)
     101             : {
     102           0 :     if (!dev || !dev_icm) {
     103           0 :         return nullptr;
     104             :     }
     105             : 
     106           0 :     dev->get_semaphore()->take_blocking();
     107             : 
     108             :     /* Allow ICM20x48 to shortcut auxiliary bus and host bus */
     109             :     uint8_t rval;
     110             :     uint16_t whoami;
     111           0 :     uint8_t retries = 5;
     112           0 :     if (!dev_icm->read_registers(REG_ICM_WHOAMI, &rval, 1) ||
     113           0 :         rval != ICM_WHOAMI_VAL) {
     114             :         // not an ICM_WHOAMI
     115           0 :         goto fail;
     116             :     }
     117             :     do {
     118             :         // reset then bring sensor out of sleep mode
     119           0 :         if (!dev_icm->write_register(REG_ICM_PWR_MGMT_1, 0x80)) {
     120           0 :             goto fail;
     121             :         }
     122           0 :         hal.scheduler->delay(10);
     123             : 
     124           0 :         if (!dev_icm->write_register(REG_ICM_PWR_MGMT_1, 0x00)) {
     125           0 :             goto fail;
     126             :         }
     127           0 :         hal.scheduler->delay(10);
     128             :         
     129             :         // see if ICM20948 is sleeping
     130           0 :         if (!dev_icm->read_registers(REG_ICM_PWR_MGMT_1, &rval, 1)) {
     131           0 :             goto fail;
     132             :         }
     133           0 :         if ((rval & 0x40) == 0) {
     134           0 :             break;
     135             :         }
     136           0 :     } while (retries--);
     137             :     
     138           0 :     if (rval & 0x40) {
     139             :         // it didn't come out of sleep
     140           0 :         goto fail;
     141             :     }
     142             : 
     143             :     // initially force i2c bypass off
     144           0 :     dev_icm->write_register(REG_ICM_INT_PIN_CFG, 0x00);
     145           0 :     hal.scheduler->delay(1);
     146             : 
     147             :     // now check if a AK09916 shows up on the bus. If it does then
     148             :     // we have both a real AK09916 and a ICM20948 with an embedded
     149             :     // AK09916. In that case we will fail the driver load and use
     150             :     // the main AK09916 driver
     151           0 :     if (dev->read_registers(REG_COMPANY_ID, (uint8_t *)&whoami, 2)) {
     152             :         // a device is replying on the AK09916 I2C address, don't
     153             :         // load the ICM20948
     154           0 :         DEV_PRINTF("ICM20948: AK09916 bus conflict\n");
     155           0 :         goto fail;
     156             :     }
     157             : 
     158             :     // now force bypass on
     159           0 :     dev_icm->write_register(REG_ICM_INT_PIN_CFG, 0x02);
     160           0 :     hal.scheduler->delay(1);
     161           0 :     dev->get_semaphore()->give();
     162           0 :     return probe(std::move(dev), force_external, rotation);
     163           0 : fail:
     164           0 :     dev->get_semaphore()->give();
     165           0 :     return nullptr;
     166             : }
     167             : 
     168             : // un-named, assume SPI for compat
     169           0 : AP_Compass_Backend *AP_Compass_AK09916::probe_ICM20948(uint8_t inv2_instance,
     170             :                                                      enum Rotation rotation)
     171             : {  
     172           0 :     return probe_ICM20948_SPI(inv2_instance,rotation);
     173             : }
     174             : 
     175           0 : AP_Compass_Backend *AP_Compass_AK09916::probe_ICM20948_SPI(uint8_t inv2_instance,
     176             :                                                      enum Rotation rotation)
     177             : {
     178           0 :     AP_InertialSensor &ins = AP::ins();
     179             : 
     180             :     AP_AK09916_BusDriver *bus =
     181           0 :         NEW_NOTHROW AP_AK09916_BusDriver_Auxiliary(ins, HAL_INS_INV2_SPI, inv2_instance, HAL_COMPASS_AK09916_I2C_ADDR);
     182           0 :     if (!bus) {
     183           0 :         return nullptr;
     184             :     }
     185             : 
     186           0 :     AP_Compass_AK09916 *sensor = NEW_NOTHROW AP_Compass_AK09916(bus, false, rotation);
     187           0 :     if (!sensor || !sensor->init()) {
     188           0 :         delete sensor;
     189           0 :         return nullptr;
     190             :     }
     191             : 
     192           0 :     return sensor;
     193             : }
     194             : 
     195           0 : AP_Compass_Backend *AP_Compass_AK09916::probe_ICM20948_I2C(uint8_t inv2_instance,
     196             :                                                      enum Rotation rotation)
     197             : {
     198           0 :     AP_InertialSensor &ins = AP::ins();
     199             : 
     200             :     AP_AK09916_BusDriver *bus =
     201           0 :         NEW_NOTHROW AP_AK09916_BusDriver_Auxiliary(ins, HAL_INS_INV2_I2C, inv2_instance, HAL_COMPASS_AK09916_I2C_ADDR);
     202           0 :     if (!bus) {
     203           0 :         return nullptr;
     204             :     }
     205             : 
     206           0 :     AP_Compass_AK09916 *sensor = NEW_NOTHROW AP_Compass_AK09916(bus, false, rotation);
     207           0 :     if (!sensor || !sensor->init()) {
     208           0 :         delete sensor;
     209           0 :         return nullptr;
     210             :     }
     211             : 
     212           0 :     return sensor;
     213             : }
     214             : #endif  // AP_COMPASS_ICM20948_ENABLED
     215             : 
     216           0 : bool AP_Compass_AK09916::init()
     217             : {
     218           0 :     AP_HAL::Semaphore *bus_sem = _bus->get_semaphore();
     219             : 
     220           0 :     if (!bus_sem) {
     221           0 :         return false;
     222             :     }
     223           0 :     _bus->get_semaphore()->take_blocking();
     224             : 
     225           0 :     if (!_bus->configure()) {
     226           0 :         DEV_PRINTF("AK09916: Could not configure the bus\n");
     227           0 :         goto fail;
     228             :     }
     229             : 
     230           0 :     if (!_reset()) {
     231           0 :         goto fail;
     232             :     }
     233             : 
     234           0 :     if (!_check_id()) {
     235           0 :         goto fail;
     236             :     }
     237             : 
     238             :     // one checked register for mode
     239           0 :     _bus->setup_checked_registers(1);
     240             : 
     241           0 :     if (!_setup_mode()) {
     242           0 :         DEV_PRINTF("AK09916: Could not setup mode\n");
     243           0 :         goto fail;
     244             :     }
     245             : 
     246           0 :     if (!_bus->start_measurements()) {
     247           0 :         DEV_PRINTF("AK09916: Could not start measurements\n");
     248           0 :         goto fail;
     249             :     }
     250             : 
     251           0 :     _initialized = true;
     252             : 
     253             :     /* register the compass instance in the frontend */
     254           0 :     _bus->set_device_type(_devtype);
     255           0 :     if (!register_compass(_bus->get_bus_id())) {
     256           0 :         goto fail;
     257             :     }
     258             : 
     259           0 :     if (_force_external) {
     260           0 :         set_external(true);
     261             :     }
     262             : 
     263           0 :     set_rotation(_rotation);
     264             :     
     265           0 :     bus_sem->give();
     266             : 
     267           0 :     _bus->register_periodic_callback(10000, FUNCTOR_BIND_MEMBER(&AP_Compass_AK09916::_update, void));
     268             : 
     269           0 :     return true;
     270             : 
     271           0 : fail:
     272           0 :     bus_sem->give();
     273           0 :     return false;
     274             : }
     275             : 
     276           0 : void AP_Compass_AK09916::read()
     277             : {
     278           0 :     if (!_initialized) {
     279           0 :         return;
     280             :     }
     281             : 
     282           0 :     drain_accumulated_samples();
     283             : }
     284             : 
     285           0 : void AP_Compass_AK09916::_make_adc_sensitivity_adjustment(Vector3f& field) const
     286             : {
     287             :     static const float ADC_16BIT_RESOLUTION = 0.15f;
     288             : 
     289           0 :     field *= ADC_16BIT_RESOLUTION;
     290           0 : }
     291             : 
     292           0 : void AP_Compass_AK09916::_update()
     293             : {
     294           0 :     struct sample_regs regs = {0};
     295           0 :     Vector3f raw_field;
     296             : 
     297           0 :     if (!_bus->block_read(REG_ST1, (uint8_t *) &regs, sizeof(regs))) {
     298           0 :         goto check_registers;
     299             :     }
     300             : 
     301           0 :     if (!(regs.st1 & 0x01)) {
     302           0 :         no_data++;
     303           0 :         if (no_data == 5) {
     304           0 :             _reset();
     305           0 :             _setup_mode();
     306           0 :             no_data = 0;
     307             :         }
     308           0 :         goto check_registers;
     309             :     }
     310           0 :     no_data = 0;
     311             : 
     312             :     /* Check for overflow. See AK09916's datasheet*/
     313           0 :     if ((regs.st2 & 0x08)) {
     314           0 :         goto check_registers;
     315             :     }
     316             : 
     317           0 :     raw_field = Vector3f(regs.val[0], regs.val[1], regs.val[2]);
     318             : 
     319           0 :     if (is_zero(raw_field.x) && is_zero(raw_field.y) && is_zero(raw_field.z)) {
     320           0 :         goto check_registers;
     321             :     }
     322             : 
     323           0 :     _make_adc_sensitivity_adjustment(raw_field);
     324           0 :     raw_field *= AK09916_MILLIGAUSS_SCALE;
     325             : 
     326           0 :     accumulate_sample(raw_field, 10);
     327             : 
     328           0 : check_registers:
     329           0 :     _bus->check_next_register();
     330           0 : }
     331             : 
     332           0 : bool AP_Compass_AK09916::_check_id()
     333             : {
     334           0 :     for (int i = 0; i < 5; i++) {
     335           0 :         uint8_t deviceid = 0;
     336             : 
     337             :         /* Read AK09916's id */
     338           0 :         if (_bus->register_read(REG_DEVICE_ID, &deviceid)) {
     339           0 :             switch (deviceid) {
     340           0 :             case AK09915_Device_ID:
     341           0 :                 _devtype = DEVTYPE_AK09915;
     342           0 :                 return true;
     343           0 :             case AK09916_Device_ID:
     344           0 :                 _devtype = DEVTYPE_AK09916;
     345           0 :                 return true;
     346           0 :             case AK09918_Device_ID:
     347           0 :                 _devtype = DEVTYPE_AK09918;
     348           0 :                 return true;
     349             :             }
     350             :         }
     351             :     }
     352             : 
     353           0 :     return false;
     354             : }
     355             : 
     356           0 : bool AP_Compass_AK09916::_setup_mode() {
     357           0 :     return _bus->register_write(REG_CNTL2, 0x08, true); //Continuous Mode 2
     358             : }
     359             : 
     360           0 : bool AP_Compass_AK09916::_reset()
     361             : {
     362           0 :     return _bus->register_write(REG_CNTL3, 0x01); //Soft Reset
     363             : }
     364             : 
     365             : /* AP_HAL::Device implementation of the AK09916 */
     366           0 : AP_AK09916_BusDriver_HALDevice::AP_AK09916_BusDriver_HALDevice(AP_HAL::OwnPtr<AP_HAL::Device> dev)
     367           0 :     : _dev(std::move(dev))
     368             : {
     369           0 : }
     370             : 
     371           0 : bool AP_AK09916_BusDriver_HALDevice::block_read(uint8_t reg, uint8_t *buf, uint32_t size)
     372             : {
     373           0 :     return _dev->read_registers(reg, buf, size);
     374             : }
     375             : 
     376           0 : bool AP_AK09916_BusDriver_HALDevice::register_read(uint8_t reg, uint8_t *val)
     377             : {
     378           0 :     return _dev->read_registers(reg, val, 1);
     379             : }
     380             : 
     381           0 : bool AP_AK09916_BusDriver_HALDevice::register_write(uint8_t reg, uint8_t val, bool checked)
     382             : {
     383           0 :     return _dev->write_register(reg, val, checked);
     384             : }
     385             : 
     386           0 : AP_HAL::Semaphore *AP_AK09916_BusDriver_HALDevice::get_semaphore()
     387             : {
     388           0 :     return _dev->get_semaphore();
     389             : }
     390             : 
     391           0 : AP_HAL::Device::PeriodicHandle AP_AK09916_BusDriver_HALDevice::register_periodic_callback(uint32_t period_usec, AP_HAL::Device::PeriodicCb cb)
     392             : {
     393           0 :     return _dev->register_periodic_callback(period_usec, cb);
     394             : }
     395             : 
     396             : /* AK09916 on an auxiliary bus of IMU driver */
     397           0 : AP_AK09916_BusDriver_Auxiliary::AP_AK09916_BusDriver_Auxiliary(AP_InertialSensor &ins, uint8_t backend_id,
     398           0 :                                                              uint8_t backend_instance, uint8_t addr)
     399             : {
     400             :     /*
     401             :      * Only initialize members. Fails are handled by configure or while
     402             :      * getting the semaphore
     403             :      */
     404             : #if AP_INERTIALSENSOR_ENABLED
     405           0 :     _bus = ins.get_auxiliary_bus(backend_id, backend_instance);
     406           0 :     if (!_bus) {
     407           0 :         return;
     408             :     }
     409             : 
     410           0 :     _slave = _bus->request_next_slave(addr);
     411             : #endif
     412             : }
     413             : 
     414           0 : AP_AK09916_BusDriver_Auxiliary::~AP_AK09916_BusDriver_Auxiliary()
     415             : {
     416             :     /* After started it's owned by AuxiliaryBus */
     417           0 :     if (!_started) {
     418           0 :         delete _slave;
     419             :     }
     420           0 : }
     421             : 
     422           0 : bool AP_AK09916_BusDriver_Auxiliary::block_read(uint8_t reg, uint8_t *buf, uint32_t size)
     423             : {
     424           0 :     if (_started) {
     425             :         /*
     426             :          * We can only read a block when reading the block of sample values -
     427             :          * calling with any other value is a mistake
     428             :          */
     429           0 :         if (reg != REG_ST1) {
     430           0 :             return false;
     431             :         }
     432             : 
     433           0 :         int n = _slave->read(buf);
     434           0 :         return n == static_cast<int>(size);
     435             :     }
     436             : 
     437           0 :     int r = _slave->passthrough_read(reg, buf, size);
     438             : 
     439           0 :     return r > 0 && static_cast<uint32_t>(r) == size;
     440             : }
     441             : 
     442           0 : bool AP_AK09916_BusDriver_Auxiliary::register_read(uint8_t reg, uint8_t *val)
     443             : {
     444           0 :     return _slave->passthrough_read(reg, val, 1) == 1;
     445             : }
     446             : 
     447           0 : bool AP_AK09916_BusDriver_Auxiliary::register_write(uint8_t reg, uint8_t val, bool checked)
     448             : {
     449             :     (void)checked;
     450           0 :     return _slave->passthrough_write(reg, val) == 1;
     451             : }
     452             : 
     453           0 : AP_HAL::Semaphore *AP_AK09916_BusDriver_Auxiliary::get_semaphore()
     454             : {
     455           0 :     return _bus ? _bus->get_semaphore() : nullptr;
     456             : }
     457             : 
     458           0 : bool AP_AK09916_BusDriver_Auxiliary::configure()
     459             : {
     460           0 :     if (!_bus || !_slave) {
     461           0 :         return false;
     462             :     }
     463           0 :     return true;
     464             : }
     465             : 
     466           0 : bool AP_AK09916_BusDriver_Auxiliary::start_measurements()
     467             : {
     468           0 :     if (_bus->register_periodic_read(_slave, REG_ST1, sizeof(AP_Compass_AK09916::sample_regs)) < 0) {
     469           0 :         return false;
     470             :     }
     471             : 
     472           0 :     _started = true;
     473             : 
     474           0 :     return true;
     475             : }
     476             : 
     477           0 : AP_HAL::Device::PeriodicHandle AP_AK09916_BusDriver_Auxiliary::register_periodic_callback(uint32_t period_usec, AP_HAL::Device::PeriodicCb cb)
     478             : {
     479           0 :     return _bus->register_periodic_callback(period_usec, cb);
     480             : }
     481             : 
     482             : // set device type within a device class
     483           0 : void AP_AK09916_BusDriver_Auxiliary::set_device_type(uint8_t devtype)
     484             : {
     485           0 :     _bus->set_device_type(devtype);
     486           0 : }
     487             : 
     488             : // return 24 bit bus identifier
     489           0 : uint32_t AP_AK09916_BusDriver_Auxiliary::get_bus_id(void) const
     490             : {
     491           0 :     return _bus->get_bus_id();
     492             : }
     493             : 
     494             : #endif  // AP_COMPASS_AK09916_ENABLED

Generated by: LCOV version 1.14