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

          Line data    Source code
       1             : /*
       2             :    This program is free software: you can redistribute it and/or modify
       3             :    it under the terms of the GNU General Public License as published by
       4             :    the Free Software Foundation, either version 3 of the License, or
       5             :    (at your option) any later version.
       6             : 
       7             :    This program is distributed in the hope that it will be useful,
       8             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
       9             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      10             :    GNU General Public License for more details.
      11             : 
      12             :    You should have received a copy of the GNU General Public License
      13             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      14             :  */
      15             : 
      16             : #include "AP_Baro_KellerLD.h"
      17             : 
      18             : #if AP_BARO_KELLERLD_ENABLED
      19             : 
      20             : #include <stdio.h>
      21             : 
      22             : #include <AP_Math/AP_Math.h>
      23             : 
      24             : #define KELLER_DEBUG 0
      25             : 
      26             : #if KELLER_DEBUG
      27             : # define Debug(fmt, args ...)  do {printf(fmt "\n", ## args);} while(0)
      28             : #else
      29             : # define Debug(fmt, args ...)
      30             : #endif
      31             : 
      32             : extern const AP_HAL::HAL &hal;
      33             : 
      34             : // sensor metadata register
      35             : static const uint8_t CMD_METADATA_PMODE = 0x12;
      36             : // Measurement range registers
      37             : static const uint8_t CMD_PRANGE_MIN_MSB = 0x13;
      38             : static const uint8_t CMD_PRANGE_MIN_LSB = 0x14;
      39             : static const uint8_t CMD_PRANGE_MAX_MSB = 0x15;
      40             : static const uint8_t CMD_PRANGE_MAX_LSB = 0x16;
      41             : 
      42             : // write to this address to start pressure measurement
      43             : static const uint8_t CMD_REQUEST_MEASUREMENT = 0xAC;
      44             : 
      45             : // Status byte handling, per Keller LD "Communication Protocol 4LD-9LD" (v2.6, pg 7)
      46             : //   bits 7-6 : reserved, must be 01
      47             : //   bit 5    : ignored, known to be unreliable in block reads
      48             : //   bits 4-3 : operating mode (00 = normal measurement)
      49             : //   bit 2    : checksum error? (0 = ok, 1 = error)
      50             : //   bits 1-0 : don't care
      51             : static const uint8_t STATUS_REQUIRED_MASK = 0b11011100;
      52             : static const uint8_t STATUS_REQUIRED_BITS = 0b01000000;
      53             : 
      54           0 : AP_Baro_KellerLD::AP_Baro_KellerLD(AP_Baro &baro, AP_HAL::Device &dev)
      55             :     : AP_Baro_Backend(baro)
      56           0 :     , _dev(&dev)
      57             : {
      58           0 : }
      59             : 
      60             : // Look for the device on the bus and see if it responds appropriately
      61           0 : AP_Baro_Backend *AP_Baro_KellerLD::probe(AP_Baro &baro, AP_HAL::Device &dev)
      62             : {
      63           0 :     AP_Baro_KellerLD *sensor = NEW_NOTHROW AP_Baro_KellerLD(baro, dev);
      64           0 :     if (!sensor || !sensor->_init()) {
      65           0 :         delete sensor;
      66           0 :         return nullptr;
      67             :     }
      68           0 :     return sensor;
      69             : }
      70             : 
      71             : 
      72             : // convenience function to work around device transfer oddities
      73           0 : bool AP_Baro_KellerLD::transfer_with_delays(uint8_t *send, uint8_t sendlen, uint8_t *recv, uint8_t recvlen)
      74             : {
      75           0 :     if (!_dev->transfer(send, sendlen, nullptr, 0)) {
      76           0 :         return false;
      77             :     }
      78           0 :     hal.scheduler->delay(1);
      79             : 
      80           0 :     if(!_dev->transfer(nullptr, 0, recv, recvlen)) {
      81           0 :         return false;
      82             :     }
      83           0 :     hal.scheduler->delay(1);
      84           0 :     return true;
      85             : }
      86             : 
      87             : // This device has some undocumented finicky quirks and requires
      88             : // delays when reading out the measurement range, but for some reason
      89             : // this isn't an issue when requesting measurements.  This is why we
      90             : // need to split the transfers with delays like this.  (Using
      91             : // AP_HAL::I2CDevice::set_split_transfers will not work with these
      92             : // sensors)
      93           0 : bool AP_Baro_KellerLD::read_measurement_limit(float *limit, uint8_t msb_addr, uint8_t lsb_addr)
      94             : {
      95             :     uint8_t data[3];
      96             : 
      97           0 :     if (!transfer_with_delays(&msb_addr, 1, data, ARRAY_SIZE(data))) {
      98           0 :         return false;
      99             :     }
     100             : 
     101           0 :     const uint16_t ms_word = (data[1] << 8) | data[2];
     102             :     Debug("0x%02x: %d [%d, %d, %d]", msb_addr, ms_word, data[0], data[1], data[2]);
     103             : 
     104           0 :     if (!transfer_with_delays(&lsb_addr, 1, data, ARRAY_SIZE(data))) {
     105           0 :         return false;
     106             :     }
     107             : 
     108           0 :     const uint16_t ls_word = (data[1] << 8) | data[2];
     109             :     Debug("0x%02x: %d [%d, %d, %d]", lsb_addr, ls_word, data[0], data[1], data[2]);
     110             : 
     111           0 :     const uint32_t cal_data = (ms_word << 16) | ls_word;
     112           0 :     memcpy(limit, &cal_data, sizeof(*limit));
     113             : 
     114           0 :     if (isinf(*limit) || isnan(*limit)) {
     115           0 :         return false;
     116             :     }
     117             : 
     118             :     Debug("data: %d, float: %.2f", cal_data, _p_min);
     119           0 :     return true;
     120             : }
     121             : 
     122           0 : bool AP_Baro_KellerLD::read_cal()
     123             : {
     124             :     // Read out pressure measurement range
     125           0 :     if (!read_measurement_limit(&_p_min, CMD_PRANGE_MIN_MSB, CMD_PRANGE_MIN_LSB)) {
     126           0 :         return false;
     127             :     }
     128             : 
     129           0 :     if (!read_measurement_limit(&_p_max, CMD_PRANGE_MAX_MSB, CMD_PRANGE_MAX_LSB)) {
     130           0 :         return false;
     131             :     }
     132             : 
     133           0 :     if (_p_max <= _p_min) {
     134           0 :         return false;
     135             :     }
     136             : 
     137           0 :     return true;
     138             : }
     139             : 
     140             : // Read sensor P-Mode type and set pressure reference offset
     141             : // This determines the pressure offset based on the type of sensor
     142             : // vented to atmosphere, gauged to vacuum, or gauged to standard sea-level pressure
     143           0 : bool AP_Baro_KellerLD::read_mode_type()
     144             : {
     145           0 :     uint8_t cmd = CMD_METADATA_PMODE;
     146             :     uint8_t data[3];
     147           0 :     if (!transfer_with_delays(&cmd, 1, data, ARRAY_SIZE(data))) {
     148           0 :         return false;
     149             :     }
     150             : 
     151             :     // Byte 3, Bit 0 & 1: Represents P-Mode
     152             :     // "Communication Protocol 4 LD…9 LD", Version 2.6 pg 12 of 25
     153             :     // https://keller-druck.com/?d=VeMYAQBxgoSNjUSHbdnBTU
     154           0 :     _p_mode = (SensorMode)(data[2] & 0b11);
     155             : 
     156             :     // update pressure offset based on P-Mode
     157           0 :     switch (_p_mode) {
     158           0 :         case SensorMode::PR_MODE:
     159             :             // PR-Mode vented gauge sensor
     160             :             // pressure reads zero when the pressure outside is equal to the pressure inside the enclosure
     161           0 :             _p_mode_offset = _frontend.get_pressure(0);
     162           0 :             break;
     163           0 :         case SensorMode::PA_MODE:
     164             :             // PA-Mode sealed gauge sensor
     165             :             // pressure reads zero when the pressure outside is equal to 1.0 bar
     166             :             // i.e., the pressure at which the vent is sealed
     167           0 :             _p_mode_offset = 1.0;
     168           0 :             break;
     169           0 :         case SensorMode::PAA_MODE:
     170             :             // PAA-mode Absolute sensor (zero at vacuum)
     171           0 :             _p_mode_offset = 0.0;
     172           0 :             break;
     173           0 :         case SensorMode::UNDEFINED:
     174             :             // we should give an error here
     175           0 :             printf("KellerLD Device Mode UNDEFINED\n");
     176           0 :             return false;
     177             :     }
     178             : 
     179           0 :     return true;
     180             : }
     181             : 
     182             : // We read out the measurement range to be used in raw value conversions
     183           0 : bool AP_Baro_KellerLD::_init()
     184             : {
     185           0 :     if (!_dev) {
     186           0 :         return false;
     187             :     }
     188             : 
     189           0 :     WITH_SEMAPHORE(_dev->get_semaphore());
     190             : 
     191             :     // high retries for init
     192           0 :     _dev->set_retries(10);
     193             : 
     194           0 :     if (!read_cal()) {
     195           0 :         printf("Cal read bad!\n");
     196           0 :         return false;
     197             :     }
     198             : 
     199           0 :     if (!read_mode_type()) {
     200           0 :         printf("Mode_Type read bad!\n");
     201           0 :         return false;
     202             :     }
     203             : 
     204           0 :     printf("Keller LD found on bus %u address 0x%02x\n", _dev->bus_num(), _dev->get_bus_address());
     205             : 
     206             :     // Send a command to take a measurement
     207           0 :     _dev->transfer(&CMD_REQUEST_MEASUREMENT, 1, nullptr, 0);
     208             : 
     209           0 :     memset(&_accum, 0, sizeof(_accum));
     210             : 
     211           0 :     _instance = _frontend.register_sensor();
     212             : 
     213           0 :     _dev->set_device_type(DEVTYPE_BARO_KELLERLD);
     214           0 :     set_bus_id(_instance, _dev->get_bus_id());
     215             :     
     216           0 :     _frontend.set_type(_instance, AP_Baro::BARO_TYPE_WATER);
     217             : 
     218             :     // lower retries for run
     219           0 :     _dev->set_retries(3);
     220             : 
     221             :     // The sensor needs time to take a deep breath after reading out the calibration...
     222           0 :     hal.scheduler->delay(150);
     223             : 
     224             :     // Request 50Hz update
     225             :     // The sensor really struggles with any jitter in timing at 100Hz, and will sometimes start reading out all zeros
     226           0 :     _dev->register_periodic_callback(20 * AP_USEC_PER_MSEC,
     227             :                                      FUNCTOR_BIND_MEMBER(&AP_Baro_KellerLD::_timer, void));
     228           0 :     return true;
     229           0 : }
     230             : 
     231             : // Read out most recent measurement from sensor hw
     232           0 : bool AP_Baro_KellerLD::_read()
     233             : {
     234             :     uint8_t data[5];
     235           0 :     if (!_dev->transfer(nullptr, 0, data, sizeof(data))) {
     236             :         Debug("Keller LD read failed!");
     237           0 :         return false;
     238             :     }
     239             : 
     240           0 :     const uint8_t status = data[0];
     241           0 :     uint16_t pressure_raw = (data[1] << 8) | data[2];
     242           0 :     uint16_t temperature_raw = (data[3] << 8) | data[4];
     243             : 
     244             : #if KELLER_DEBUG
     245             :     static uint8_t samples = 0;
     246             :     if (samples < 3) {
     247             :         samples++;
     248             :         Debug("data: [%d, %d, %d, %d, %d]", data[0], data[1], data[2], data[3], data[4]);
     249             :         Debug("pressure_raw: %d\ttemperature_raw: %d", pressure_raw, temperature_raw);
     250             :     }
     251             : #endif
     252             : 
     253             :     // Reject if the sensor is not in the normal measurement state, or is reporting an error.
     254           0 :     if ((status & STATUS_REQUIRED_MASK) != STATUS_REQUIRED_BITS) {
     255             :         Debug("Keller: bad status 0x%02x", status);
     256           0 :         return false;
     257             :     }
     258             : 
     259           0 :     if (pressure_raw == 0 || temperature_raw == 0) {
     260             :         Debug("Keller: bad read");
     261           0 :         return false;
     262             :     }
     263             : 
     264           0 :     if (!pressure_ok(pressure_raw)) {
     265           0 :         return false;
     266             :     }
     267             :     
     268           0 :     WITH_SEMAPHORE(_sem);
     269             :     
     270           0 :     _update_and_wrap_accumulator(pressure_raw, temperature_raw, 128);
     271             :     
     272           0 :     return true;
     273           0 : }
     274             : 
     275             : // Periodic callback, regular update at 50Hz
     276             : // Read out most recent measurement, and request another
     277             : // Max conversion time according to datasheet is ~8ms, so
     278             : // max update rate is ~125Hz, yet we struggle to get consistent
     279             : // performance/data at 100Hz
     280           0 : void AP_Baro_KellerLD::_timer(void)
     281             : {
     282           0 :     _read();
     283           0 :     _dev->transfer(&CMD_REQUEST_MEASUREMENT, 1, nullptr, 0);
     284           0 : }
     285             : 
     286             : // Accumulate a reading, shrink if necessary to prevent overflow
     287           0 : void AP_Baro_KellerLD::_update_and_wrap_accumulator(uint16_t pressure, uint16_t temperature, uint8_t max_count)
     288             : {
     289           0 :     _accum.sum_pressure += pressure;
     290           0 :     _accum.sum_temperature += temperature;
     291           0 :     _accum.num_samples += 1;
     292             : 
     293           0 :     if (_accum.num_samples == max_count) {
     294           0 :         _accum.sum_pressure /= 2;
     295           0 :         _accum.sum_temperature /= 2;
     296           0 :         _accum.num_samples /= 2;
     297             :     }
     298           0 : }
     299             : 
     300             : // Take the average of accumulated values and push to frontend
     301           0 : void AP_Baro_KellerLD::update()
     302             : {
     303             :     float sum_pressure, sum_temperature;
     304             :     float num_samples;
     305             : 
     306             :     // update _p_mode_offset if vented guage 
     307           0 :     if (_p_mode == SensorMode::PR_MODE) {
     308             :         // we need to get the pressure from on-board barometer
     309           0 :         _p_mode_offset = _frontend.get_pressure(0);
     310             :     }
     311             : 
     312             :     {
     313           0 :         WITH_SEMAPHORE(_sem);
     314             : 
     315           0 :         if (_accum.num_samples == 0) {
     316           0 :             return;
     317             :         }
     318             : 
     319           0 :         sum_pressure = _accum.sum_pressure;
     320           0 :         sum_temperature = _accum.sum_temperature;
     321           0 :         num_samples = _accum.num_samples;
     322           0 :         memset(&_accum, 0, sizeof(_accum));
     323           0 :     }
     324             : 
     325           0 :     uint16_t raw_pressure_avg = sum_pressure / num_samples;
     326           0 :     uint16_t raw_temperature_avg = sum_temperature / num_samples;
     327             : 
     328             :     // per datasheet
     329           0 :     float pressure = (raw_pressure_avg - 16384) * (_p_max - _p_min) / 32768 + _p_min + _p_mode_offset;
     330           0 :     pressure *= 100000; // bar -> Pascal
     331           0 :     float temperature = ((raw_temperature_avg >> 4) - 24) * 0.05f - 50;
     332             : 
     333           0 :     _copy_to_frontend(_instance, pressure, temperature);
     334             : }
     335             : 
     336             : #endif  // AP_BARO_KELLERLD_ENABLED

Generated by: LCOV version 1.14