LCOV - code coverage report
Current view: top level - AP_Airspeed - AP_Airspeed_SDP3X.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 143 0.0 %
Date: 2026-09-09 11:02:08 Functions: 0 7 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             : /*
      17             :   backend driver for airspeed from a I2C SDP3X sensor
      18             : 
      19             :   with thanks to https://github.com/PX4/Firmware/blob/master/src/drivers/sdp3x_airspeed
      20             :  */
      21             : #include "AP_Airspeed_SDP3X.h"
      22             : 
      23             : #if AP_AIRSPEED_SDP3X_ENABLED
      24             : 
      25             : #include <GCS_MAVLink/GCS.h>
      26             : #include <AP_Baro/AP_Baro.h>
      27             : 
      28             : #include <stdio.h>
      29             : 
      30             : #define SDP3X_SCALE_TEMPERATURE         200.0f
      31             : 
      32             : #define SDP3XD0_I2C_ADDR                0x21
      33             : #define SDP3XD1_I2C_ADDR                0x22
      34             : #define SDP3XD2_I2C_ADDR                0x23
      35             : 
      36             : #define SDP3X_CONT_MEAS_AVG_MODE        0x3615
      37             : #define SDP3X_CONT_MEAS_STOP            0x3FF9
      38             : 
      39             : #define SDP3X_SCALE_PRESSURE_SDP31      60
      40             : #define SDP3X_SCALE_PRESSURE_SDP32      240
      41             : #define SDP3X_SCALE_PRESSURE_SDP33      20
      42             : 
      43             : extern const AP_HAL::HAL &hal;
      44             : 
      45             : /*
      46             :   send a 16 bit command code
      47             :  */
      48           0 : bool AP_Airspeed_SDP3X::_send_command(uint16_t cmd)
      49             : {
      50           0 :     uint8_t b[2] {uint8_t(cmd >> 8), uint8_t(cmd & 0xFF)};
      51           0 :     return _dev->transfer(b, 2, nullptr, 0);
      52             : }
      53             : 
      54             : // probe and initialise the sensor
      55           0 : __INITFUNC__ bool AP_Airspeed_SDP3X::init()
      56             : {
      57           0 :     const uint8_t addresses[3] = { SDP3XD0_I2C_ADDR,
      58             :                                    SDP3XD1_I2C_ADDR,
      59             :                                    SDP3XD2_I2C_ADDR
      60             :                                  };
      61           0 :     bool found = false;
      62           0 :     bool ret = false;
      63             : 
      64           0 :     for (uint8_t i=0; i<ARRAY_SIZE(addresses) && !found; i++) {
      65           0 :         _dev = hal.i2c_mgr->get_device_ptr(get_bus(), addresses[i]);
      66           0 :         if (!_dev) {
      67           0 :             continue;
      68             :         }
      69           0 :         _dev->get_semaphore()->take_blocking();
      70             : 
      71             :         // lots of retries during probe
      72           0 :         _dev->set_retries(10);
      73             : 
      74             :         // stop continuous average mode
      75           0 :         if (!_send_command(SDP3X_CONT_MEAS_STOP)) {
      76           0 :             _dev->get_semaphore()->give();
      77           0 :             continue;
      78             :         }
      79             : 
      80             :         // these delays are needed for reliable operation
      81           0 :         _dev->get_semaphore()->give();
      82           0 :         hal.scheduler->delay_microseconds(20000);
      83           0 :         _dev->get_semaphore()->take_blocking();
      84             : 
      85             :         // start continuous average mode
      86           0 :         if (!_send_command(SDP3X_CONT_MEAS_AVG_MODE)) {
      87           0 :             _dev->get_semaphore()->give();
      88           0 :             continue;
      89             :         }
      90             : 
      91             :         // these delays are needed for reliable operation
      92           0 :         _dev->get_semaphore()->give();
      93           0 :         hal.scheduler->delay_microseconds(20000);
      94           0 :         _dev->get_semaphore()->take_blocking();
      95             : 
      96             :         // step 3 - get scale
      97             :         uint8_t val[9];
      98           0 :         ret = _dev->transfer(nullptr, 0, &val[0], sizeof(val));
      99           0 :         if (!ret) {
     100           0 :             _dev->get_semaphore()->give();
     101           0 :             continue;
     102             :         }
     103             : 
     104             :         // Check the CRC
     105           0 :         if (!_crc(&val[0], 2, val[2]) || !_crc(&val[3], 2, val[5]) || !_crc(&val[6], 2, val[8])) {
     106           0 :             _dev->get_semaphore()->give();
     107           0 :             continue;
     108             :         }
     109             : 
     110           0 :         _scale = (((uint16_t)val[6]) << 8) | val[7];
     111             : 
     112           0 :         _dev->get_semaphore()->give();
     113             : 
     114           0 :         found = true;
     115             : 
     116             : #if HAL_GCS_ENABLED
     117           0 :         char c = 'X';
     118           0 :         switch (_scale) {
     119           0 :         case SDP3X_SCALE_PRESSURE_SDP31:
     120           0 :             c = '1';
     121           0 :             break;
     122           0 :         case SDP3X_SCALE_PRESSURE_SDP32:
     123           0 :             c = '2';
     124           0 :             break;
     125           0 :         case SDP3X_SCALE_PRESSURE_SDP33:
     126           0 :             c = '3';
     127           0 :             break;
     128             :         }
     129           0 :         GCS_SEND_TEXT(MAV_SEVERITY_INFO, "SDP3%c[%u]: Found bus %u addr 0x%02x scale=%u",
     130             :                       get_instance(),
     131             :                       c, get_bus(), addresses[i], _scale);
     132             : #endif
     133             :     }
     134             : 
     135           0 :     if (!found) {
     136           0 :         return false;
     137             :     }
     138             : 
     139             :     // this sensor uses zero offset
     140           0 :     set_use_zero_offset();
     141             : 
     142           0 :     _dev->set_device_type(uint8_t(DevType::SDP3X));
     143           0 :     set_bus_id(_dev->get_bus_id());
     144             : 
     145             :     // drop to 2 retries for runtime
     146           0 :     _dev->set_retries(2);
     147             : 
     148           0 :     _dev->register_periodic_callback(20000,
     149             :                                      FUNCTOR_BIND_MEMBER(&AP_Airspeed_SDP3X::_timer, void));
     150           0 :     return true;
     151             : }
     152             : 
     153             : // read the values from the sensor. Called at 50Hz
     154           0 : void AP_Airspeed_SDP3X::_timer()
     155             : {
     156             :     // read 6 bytes from the sensor
     157             :     uint8_t val[6];
     158           0 :     int ret = _dev->transfer(nullptr, 0, &val[0], sizeof(val));
     159           0 :     uint32_t now = AP_HAL::millis();
     160           0 :     if (!ret) {
     161           0 :         if (now - _last_sample_time_ms > 200) {
     162             :             // try and re-connect
     163           0 :             _send_command(SDP3X_CONT_MEAS_AVG_MODE);
     164             :         }
     165           0 :         return;
     166             :     }
     167             :     // Check the CRC
     168           0 :     if (!_crc(&val[0], 2, val[2]) || !_crc(&val[3], 2, val[5])) {
     169           0 :         return;
     170             :     }
     171             : 
     172           0 :     int16_t P = (((int16_t)val[0]) << 8) | val[1];
     173           0 :     int16_t temp = (((int16_t)val[3]) << 8) | val[4];
     174             : 
     175           0 :     float diff_press_pa = float(P) / float(_scale);
     176           0 :     float temperature = float(temp) / SDP3X_SCALE_TEMPERATURE;
     177             : 
     178           0 :     WITH_SEMAPHORE(sem);
     179             : 
     180           0 :     _press_sum += diff_press_pa;
     181           0 :     _temp_sum += temperature;
     182           0 :     _press_count++;
     183           0 :     _temp_count++;
     184           0 :     _last_sample_time_ms = now;
     185           0 : }
     186             : 
     187             : /*
     188             :   correct pressure for barometric height
     189             :   With thanks to:
     190             :    https://github.com/PX4/Firmware/blob/master/Tools/models/sdp3x_pitot_model.py
     191             :  */
     192           0 : float AP_Airspeed_SDP3X::_correct_pressure(float press)
     193             : {
     194           0 :     float sign = 1.0f;
     195             :     
     196             :     // fix for tube order
     197           0 :     AP_Airspeed::pitot_tube_order tube_order = get_tube_order();
     198           0 :     switch (tube_order) {
     199           0 :     case AP_Airspeed::PITOT_TUBE_ORDER_NEGATIVE:
     200           0 :         press = -press;
     201           0 :         sign = -1.0f;
     202           0 :         break;
     203           0 :     case AP_Airspeed::PITOT_TUBE_ORDER_POSITIVE:
     204           0 :         break;
     205           0 :     case AP_Airspeed::PITOT_TUBE_ORDER_AUTO:
     206             :     default:
     207           0 :         if (press < 0.0f) {
     208           0 :             sign = -1.0f;
     209           0 :             press = -press;
     210             :         }
     211           0 :         break;
     212             :     }
     213             : 
     214           0 :     if (press <= 0.0f) {
     215           0 :         return 0.0f;
     216             :     }
     217             : 
     218           0 :     AP_Baro *baro = AP_Baro::get_singleton();
     219             : 
     220             :     float baro_pressure;
     221           0 :     if (baro == nullptr || baro->num_instances() == 0) {
     222             :         // with no baro assume sea level
     223           0 :         baro_pressure = SSL_AIR_PRESSURE;
     224             :     } else {
     225           0 :         baro_pressure = baro->get_pressure();
     226             :     }
     227             : 
     228             :     float temperature;
     229           0 :     if (!get_temperature(temperature)) {
     230             :         // assume 25C if no temperature
     231           0 :         temperature = 25;
     232             :     }
     233             : 
     234           0 :     float rho_air = baro_pressure / (ISA_GAS_CONSTANT * C_TO_KELVIN(temperature));
     235           0 :     if (!is_positive(rho_air)) {
     236             :         // bad pressure
     237           0 :         return press;
     238             :     }
     239             : 
     240             :     /*
     241             :       the constants in the code below come from a calibrated test of
     242             :       the drotek pitot tube by Sensiron. They are specific to the droktek pitot tube 
     243             : 
     244             :       At 25m/s, the rough proportions of each pressure correction are:
     245             : 
     246             :        - dp_pitot: 5%
     247             :        - press_correction: 14%
     248             :        - press: 81%
     249             : 
     250             :        dp_tube has been removed from the Sensiron model as it is
     251             :        insignificant (less than 0.02% over the supported speed ranges)
     252             :      */
     253             :     
     254             :     // flow through sensor
     255           0 :     float flow_SDP3X = (300.805f - 300.878f / (0.00344205f * (float)powf(press, 0.68698f) + 1.0f)) * 1.29f / rho_air;
     256           0 :     if (flow_SDP3X < 0.0f) {
     257           0 :         flow_SDP3X = 0.0f;
     258             :     }
     259             : 
     260             :     // differential pressure through pitot tube
     261           0 :     float dp_pitot = 28557670.0f * (1.0f - 1.0f / (1.0f + (float)powf((flow_SDP3X / 5027611.0f), 1.227924f)));
     262             : 
     263             :     // uncorrected pressure
     264           0 :     float press_uncorrected = (press + dp_pitot) / SSL_AIR_DENSITY;
     265             : 
     266             :     // correction for speed at pitot-tube tip due to flow through sensor
     267           0 :     float dv = 0.0331582f * flow_SDP3X;
     268             : 
     269             :     // airspeed ratio
     270           0 :     float ratio = get_airspeed_ratio();
     271           0 :     if (!is_positive(ratio)) {
     272             :         // cope with AP_Periph where ratio is 0
     273           0 :         ratio = 2.0;
     274             :     }
     275             : 
     276             :     // calculate equivalent pressure correction. This formula comes
     277             :     // from turning the dv correction above into an equivalent
     278             :     // pressure correction. We need to do this so the airspeed ratio
     279             :     // calibrator can work, as it operates on pressure values
     280           0 :     float press_correction = sq(sqrtf(press_uncorrected*ratio)+dv)/ratio - press_uncorrected;
     281             : 
     282           0 :     return (press_uncorrected + press_correction) * sign;
     283             : }
     284             : 
     285             : // return the current differential_pressure in Pascal
     286           0 : bool AP_Airspeed_SDP3X::get_differential_pressure(float &pressure)
     287             : {
     288           0 :     WITH_SEMAPHORE(sem);
     289             : 
     290           0 :     if (AP_HAL::millis() - _last_sample_time_ms > 100) {
     291           0 :         return false;
     292             :     }
     293             : 
     294           0 :     if (_press_count > 0) {
     295           0 :         _press = _press_sum / _press_count;
     296           0 :         _press_count = 0;
     297           0 :         _press_sum = 0;
     298             :     }
     299             : 
     300           0 :     pressure = _correct_pressure(_press);
     301           0 :     return true;
     302           0 : }
     303             : 
     304             : // return the current temperature in degrees C, if available
     305           0 : bool AP_Airspeed_SDP3X::get_temperature(float &temperature)
     306             : {
     307           0 :     WITH_SEMAPHORE(sem);
     308             : 
     309           0 :     if ((AP_HAL::millis() - _last_sample_time_ms) > 100) {
     310           0 :         return false;
     311             :     }
     312             : 
     313           0 :     if (_temp_count > 0) {
     314           0 :         _temp = _temp_sum / _temp_count;
     315           0 :         _temp_count = 0;
     316           0 :         _temp_sum = 0;
     317             :     }
     318             : 
     319           0 :     temperature = _temp;
     320           0 :     return true;
     321           0 : }
     322             : 
     323             : /*
     324             :   check CRC for a set of bytes
     325             :  */
     326           0 : bool AP_Airspeed_SDP3X::_crc(const uint8_t data[], uint8_t size, uint8_t checksum)
     327             : {
     328           0 :     uint8_t crc_value = 0xff;
     329             : 
     330             :     // calculate 8-bit checksum with polynomial 0x31 (x^8 + x^5 + x^4 + 1)
     331           0 :     for (uint8_t i = 0; i < size; i++) {
     332           0 :         crc_value ^= data[i];
     333           0 :         for (uint8_t bit = 8; bit > 0; --bit) {
     334           0 :             if (crc_value & 0x80) {
     335           0 :                 crc_value = (crc_value << 1) ^ 0x31;
     336             :             } else {
     337           0 :                 crc_value = (crc_value << 1);
     338             :             }
     339             :         }
     340             :     }
     341             :     // verify checksum
     342           0 :     return (crc_value == checksum);
     343             : }
     344             : 
     345             : #endif  // AP_AIRSPEED_SDP3X_ENABLED

Generated by: LCOV version 1.14