mirror of
https://github.com/ArduPilot/ardupilot.git
synced 2026-10-06 19:00:27 +08:00
The 5-byte measurement read from the Keller 4LD...9LD family starts with a status byte whose layout is defined by the "Communication Protocol 4 LD...9 LD" spec. Until now the driver read that byte and immediately threw it away, so the only rejection criterion on a measurement was that neither the raw pressure nor the raw temperature were exactly zero. On an I2C bus with jitter, EMI on long tether cables, or contention with other devices, this lets through single- sample glitches that propagate straight into SCALED_PRESSURE2 and from there into the EKF as apparent altitude/depth spikes. Honor the status byte: a healthy sample has bit 7 = 0, bit 6 = 1, operating mode bits (4-3) = 0, and the memory checksum error bit (2) = 0. Any other pattern is rejected as an invalid read. BUSY (bit 5) is intentionally not checked here; the BlueRobotics reference implementation found it unreliable when returned as part of the 5-byte block read, and polling it cleanly would require a separate 1-byte transaction that is out of scope for this change. This is a minimal behavioural fix; no API, parameter, or other driver changes. Made-with: Cursor
337 lines
10 KiB
C++
337 lines
10 KiB
C++
/*
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "AP_Baro_KellerLD.h"
|
|
|
|
#if AP_BARO_KELLERLD_ENABLED
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <AP_Math/AP_Math.h>
|
|
|
|
#define KELLER_DEBUG 0
|
|
|
|
#if KELLER_DEBUG
|
|
# define Debug(fmt, args ...) do {printf(fmt "\n", ## args);} while(0)
|
|
#else
|
|
# define Debug(fmt, args ...)
|
|
#endif
|
|
|
|
extern const AP_HAL::HAL &hal;
|
|
|
|
// sensor metadata register
|
|
static const uint8_t CMD_METADATA_PMODE = 0x12;
|
|
// Measurement range registers
|
|
static const uint8_t CMD_PRANGE_MIN_MSB = 0x13;
|
|
static const uint8_t CMD_PRANGE_MIN_LSB = 0x14;
|
|
static const uint8_t CMD_PRANGE_MAX_MSB = 0x15;
|
|
static const uint8_t CMD_PRANGE_MAX_LSB = 0x16;
|
|
|
|
// write to this address to start pressure measurement
|
|
static const uint8_t CMD_REQUEST_MEASUREMENT = 0xAC;
|
|
|
|
// Status byte handling, per Keller LD "Communication Protocol 4LD-9LD" (v2.6, pg 7)
|
|
// bits 7-6 : reserved, must be 01
|
|
// bit 5 : ignored, known to be unreliable in block reads
|
|
// bits 4-3 : operating mode (00 = normal measurement)
|
|
// bit 2 : checksum error? (0 = ok, 1 = error)
|
|
// bits 1-0 : don't care
|
|
static const uint8_t STATUS_REQUIRED_MASK = 0b11011100;
|
|
static const uint8_t STATUS_REQUIRED_BITS = 0b01000000;
|
|
|
|
AP_Baro_KellerLD::AP_Baro_KellerLD(AP_Baro &baro, AP_HAL::Device &dev)
|
|
: AP_Baro_Backend(baro)
|
|
, _dev(&dev)
|
|
{
|
|
}
|
|
|
|
// Look for the device on the bus and see if it responds appropriately
|
|
AP_Baro_Backend *AP_Baro_KellerLD::probe(AP_Baro &baro, AP_HAL::Device &dev)
|
|
{
|
|
AP_Baro_KellerLD *sensor = NEW_NOTHROW AP_Baro_KellerLD(baro, dev);
|
|
if (!sensor || !sensor->_init()) {
|
|
delete sensor;
|
|
return nullptr;
|
|
}
|
|
return sensor;
|
|
}
|
|
|
|
|
|
// convenience function to work around device transfer oddities
|
|
bool AP_Baro_KellerLD::transfer_with_delays(uint8_t *send, uint8_t sendlen, uint8_t *recv, uint8_t recvlen)
|
|
{
|
|
if (!_dev->transfer(send, sendlen, nullptr, 0)) {
|
|
return false;
|
|
}
|
|
hal.scheduler->delay(1);
|
|
|
|
if(!_dev->transfer(nullptr, 0, recv, recvlen)) {
|
|
return false;
|
|
}
|
|
hal.scheduler->delay(1);
|
|
return true;
|
|
}
|
|
|
|
// This device has some undocumented finicky quirks and requires
|
|
// delays when reading out the measurement range, but for some reason
|
|
// this isn't an issue when requesting measurements. This is why we
|
|
// need to split the transfers with delays like this. (Using
|
|
// AP_HAL::I2CDevice::set_split_transfers will not work with these
|
|
// sensors)
|
|
bool AP_Baro_KellerLD::read_measurement_limit(float *limit, uint8_t msb_addr, uint8_t lsb_addr)
|
|
{
|
|
uint8_t data[3];
|
|
|
|
if (!transfer_with_delays(&msb_addr, 1, data, ARRAY_SIZE(data))) {
|
|
return false;
|
|
}
|
|
|
|
const uint16_t ms_word = (data[1] << 8) | data[2];
|
|
Debug("0x%02x: %d [%d, %d, %d]", msb_addr, ms_word, data[0], data[1], data[2]);
|
|
|
|
if (!transfer_with_delays(&lsb_addr, 1, data, ARRAY_SIZE(data))) {
|
|
return false;
|
|
}
|
|
|
|
const uint16_t ls_word = (data[1] << 8) | data[2];
|
|
Debug("0x%02x: %d [%d, %d, %d]", lsb_addr, ls_word, data[0], data[1], data[2]);
|
|
|
|
const uint32_t cal_data = (ms_word << 16) | ls_word;
|
|
memcpy(limit, &cal_data, sizeof(*limit));
|
|
|
|
if (isinf(*limit) || isnan(*limit)) {
|
|
return false;
|
|
}
|
|
|
|
Debug("data: %d, float: %.2f", cal_data, _p_min);
|
|
return true;
|
|
}
|
|
|
|
bool AP_Baro_KellerLD::read_cal()
|
|
{
|
|
// Read out pressure measurement range
|
|
if (!read_measurement_limit(&_p_min, CMD_PRANGE_MIN_MSB, CMD_PRANGE_MIN_LSB)) {
|
|
return false;
|
|
}
|
|
|
|
if (!read_measurement_limit(&_p_max, CMD_PRANGE_MAX_MSB, CMD_PRANGE_MAX_LSB)) {
|
|
return false;
|
|
}
|
|
|
|
if (_p_max <= _p_min) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Read sensor P-Mode type and set pressure reference offset
|
|
// This determines the pressure offset based on the type of sensor
|
|
// vented to atmosphere, gauged to vacuum, or gauged to standard sea-level pressure
|
|
bool AP_Baro_KellerLD::read_mode_type()
|
|
{
|
|
uint8_t cmd = CMD_METADATA_PMODE;
|
|
uint8_t data[3];
|
|
if (!transfer_with_delays(&cmd, 1, data, ARRAY_SIZE(data))) {
|
|
return false;
|
|
}
|
|
|
|
// Byte 3, Bit 0 & 1: Represents P-Mode
|
|
// "Communication Protocol 4 LD…9 LD", Version 2.6 pg 12 of 25
|
|
// https://keller-druck.com/?d=VeMYAQBxgoSNjUSHbdnBTU
|
|
_p_mode = (SensorMode)(data[2] & 0b11);
|
|
|
|
// update pressure offset based on P-Mode
|
|
switch (_p_mode) {
|
|
case SensorMode::PR_MODE:
|
|
// PR-Mode vented gauge sensor
|
|
// pressure reads zero when the pressure outside is equal to the pressure inside the enclosure
|
|
_p_mode_offset = _frontend.get_pressure(0);
|
|
break;
|
|
case SensorMode::PA_MODE:
|
|
// PA-Mode sealed gauge sensor
|
|
// pressure reads zero when the pressure outside is equal to 1.0 bar
|
|
// i.e., the pressure at which the vent is sealed
|
|
_p_mode_offset = 1.0;
|
|
break;
|
|
case SensorMode::PAA_MODE:
|
|
// PAA-mode Absolute sensor (zero at vacuum)
|
|
_p_mode_offset = 0.0;
|
|
break;
|
|
case SensorMode::UNDEFINED:
|
|
// we should give an error here
|
|
printf("KellerLD Device Mode UNDEFINED\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// We read out the measurement range to be used in raw value conversions
|
|
bool AP_Baro_KellerLD::_init()
|
|
{
|
|
if (!_dev) {
|
|
return false;
|
|
}
|
|
|
|
WITH_SEMAPHORE(_dev->get_semaphore());
|
|
|
|
// high retries for init
|
|
_dev->set_retries(10);
|
|
|
|
if (!read_cal()) {
|
|
printf("Cal read bad!\n");
|
|
return false;
|
|
}
|
|
|
|
if (!read_mode_type()) {
|
|
printf("Mode_Type read bad!\n");
|
|
return false;
|
|
}
|
|
|
|
printf("Keller LD found on bus %u address 0x%02x\n", _dev->bus_num(), _dev->get_bus_address());
|
|
|
|
// Send a command to take a measurement
|
|
_dev->transfer(&CMD_REQUEST_MEASUREMENT, 1, nullptr, 0);
|
|
|
|
memset(&_accum, 0, sizeof(_accum));
|
|
|
|
_instance = _frontend.register_sensor();
|
|
|
|
_dev->set_device_type(DEVTYPE_BARO_KELLERLD);
|
|
set_bus_id(_instance, _dev->get_bus_id());
|
|
|
|
_frontend.set_type(_instance, AP_Baro::BARO_TYPE_WATER);
|
|
|
|
// lower retries for run
|
|
_dev->set_retries(3);
|
|
|
|
// The sensor needs time to take a deep breath after reading out the calibration...
|
|
hal.scheduler->delay(150);
|
|
|
|
// Request 50Hz update
|
|
// The sensor really struggles with any jitter in timing at 100Hz, and will sometimes start reading out all zeros
|
|
_dev->register_periodic_callback(20 * AP_USEC_PER_MSEC,
|
|
FUNCTOR_BIND_MEMBER(&AP_Baro_KellerLD::_timer, void));
|
|
return true;
|
|
}
|
|
|
|
// Read out most recent measurement from sensor hw
|
|
bool AP_Baro_KellerLD::_read()
|
|
{
|
|
uint8_t data[5];
|
|
if (!_dev->transfer(nullptr, 0, data, sizeof(data))) {
|
|
Debug("Keller LD read failed!");
|
|
return false;
|
|
}
|
|
|
|
const uint8_t status = data[0];
|
|
uint16_t pressure_raw = (data[1] << 8) | data[2];
|
|
uint16_t temperature_raw = (data[3] << 8) | data[4];
|
|
|
|
#if KELLER_DEBUG
|
|
static uint8_t samples = 0;
|
|
if (samples < 3) {
|
|
samples++;
|
|
Debug("data: [%d, %d, %d, %d, %d]", data[0], data[1], data[2], data[3], data[4]);
|
|
Debug("pressure_raw: %d\ttemperature_raw: %d", pressure_raw, temperature_raw);
|
|
}
|
|
#endif
|
|
|
|
// Reject if the sensor is not in the normal measurement state, or is reporting an error.
|
|
if ((status & STATUS_REQUIRED_MASK) != STATUS_REQUIRED_BITS) {
|
|
Debug("Keller: bad status 0x%02x", status);
|
|
return false;
|
|
}
|
|
|
|
if (pressure_raw == 0 || temperature_raw == 0) {
|
|
Debug("Keller: bad read");
|
|
return false;
|
|
}
|
|
|
|
if (!pressure_ok(pressure_raw)) {
|
|
return false;
|
|
}
|
|
|
|
WITH_SEMAPHORE(_sem);
|
|
|
|
_update_and_wrap_accumulator(pressure_raw, temperature_raw, 128);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Periodic callback, regular update at 50Hz
|
|
// Read out most recent measurement, and request another
|
|
// Max conversion time according to datasheet is ~8ms, so
|
|
// max update rate is ~125Hz, yet we struggle to get consistent
|
|
// performance/data at 100Hz
|
|
void AP_Baro_KellerLD::_timer(void)
|
|
{
|
|
_read();
|
|
_dev->transfer(&CMD_REQUEST_MEASUREMENT, 1, nullptr, 0);
|
|
}
|
|
|
|
// Accumulate a reading, shrink if necessary to prevent overflow
|
|
void AP_Baro_KellerLD::_update_and_wrap_accumulator(uint16_t pressure, uint16_t temperature, uint8_t max_count)
|
|
{
|
|
_accum.sum_pressure += pressure;
|
|
_accum.sum_temperature += temperature;
|
|
_accum.num_samples += 1;
|
|
|
|
if (_accum.num_samples == max_count) {
|
|
_accum.sum_pressure /= 2;
|
|
_accum.sum_temperature /= 2;
|
|
_accum.num_samples /= 2;
|
|
}
|
|
}
|
|
|
|
// Take the average of accumulated values and push to frontend
|
|
void AP_Baro_KellerLD::update()
|
|
{
|
|
float sum_pressure, sum_temperature;
|
|
float num_samples;
|
|
|
|
// update _p_mode_offset if vented guage
|
|
if (_p_mode == SensorMode::PR_MODE) {
|
|
// we need to get the pressure from on-board barometer
|
|
_p_mode_offset = _frontend.get_pressure(0);
|
|
}
|
|
|
|
{
|
|
WITH_SEMAPHORE(_sem);
|
|
|
|
if (_accum.num_samples == 0) {
|
|
return;
|
|
}
|
|
|
|
sum_pressure = _accum.sum_pressure;
|
|
sum_temperature = _accum.sum_temperature;
|
|
num_samples = _accum.num_samples;
|
|
memset(&_accum, 0, sizeof(_accum));
|
|
}
|
|
|
|
uint16_t raw_pressure_avg = sum_pressure / num_samples;
|
|
uint16_t raw_temperature_avg = sum_temperature / num_samples;
|
|
|
|
// per datasheet
|
|
float pressure = (raw_pressure_avg - 16384) * (_p_max - _p_min) / 32768 + _p_min + _p_mode_offset;
|
|
pressure *= 100000; // bar -> Pascal
|
|
float temperature = ((raw_temperature_avg >> 4) - 24) * 0.05f - 50;
|
|
|
|
_copy_to_frontend(_instance, pressure, temperature);
|
|
}
|
|
|
|
#endif // AP_BARO_KELLERLD_ENABLED
|