/* 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 . */ /* backend driver for airspeed from a I2C MS5525D0 sensor */ #include "AP_Airspeed_MS5525.h" #include #include #include #include #include #include #include #include #include extern const AP_HAL::HAL &hal; #define MS5525D0_I2C_ADDR_1 0x76 #define MS5525D0_I2C_ADDR_2 0x77 #define REG_RESET 0x1E #define REG_CONVERT_D1_OSR_256 0x40 #define REG_CONVERT_D1_OSR_512 0x42 #define REG_CONVERT_D1_OSR_1024 0x44 #define REG_CONVERT_D1_OSR_2048 0x46 #define REG_CONVERT_D1_OSR_4096 0x48 #define REG_CONVERT_D2_OSR_256 0x50 #define REG_CONVERT_D2_OSR_512 0x52 #define REG_CONVERT_D2_OSR_1024 0x54 #define REG_CONVERT_D2_OSR_2048 0x56 #define REG_CONVERT_D2_OSR_4096 0x58 #define REG_ADC_READ 0x00 #define REG_PROM_BASE 0xA0 // go for 1024 oversampling. This should be fast enough to reduce // noise but low enough to keep self-heating small #define REG_CONVERT_PRESSURE REG_CONVERT_D1_OSR_1024 #define REG_CONVERT_TEMPERATURE REG_CONVERT_D2_OSR_1024 AP_Airspeed_MS5525::AP_Airspeed_MS5525(AP_Airspeed &_frontend, uint8_t _instance, MS5525_ADDR address) : AP_Airspeed_Backend(_frontend, _instance) { _address = address; } // probe and initialise the sensor bool AP_Airspeed_MS5525::init() { const uint8_t addresses[] = { MS5525D0_I2C_ADDR_1, MS5525D0_I2C_ADDR_2 }; bool found = false; for (uint8_t i=0; iget_device(get_bus(), addresses[i]); if (!dev) { continue; } WITH_SEMAPHORE(dev->get_semaphore()); // lots of retries during probe dev->set_retries(5); found = read_prom(); if (found) { GCS_SEND_TEXT(MAV_SEVERITY_INFO, "MS5525[%u]: Found on bus %u addr 0x%02x", get_instance(), get_bus(), addresses[i]); break; } } if (!found) { GCS_SEND_TEXT(MAV_SEVERITY_ERROR, "MS5525[%u]: no sensor found", get_instance()); return false; } // Send a command to read temperature first WITH_SEMAPHORE(dev->get_semaphore()); uint8_t reg = REG_CONVERT_TEMPERATURE; dev->transfer(®, 1, nullptr, 0); state = 0; command_send_us = AP_HAL::micros(); dev->set_device_type(uint8_t(DevType::MS5525)); set_bus_id(dev->get_bus_id()); // drop to 2 retries for runtime dev->set_retries(2); // read at 80Hz dev->register_periodic_callback(1000000UL/80U, FUNCTOR_BIND_MEMBER(&AP_Airspeed_MS5525::timer, void)); return true; } /** * CRC used by MS pressure devices */ uint16_t AP_Airspeed_MS5525::crc4_prom(void) { return crc_crc4(prom); } bool AP_Airspeed_MS5525::read_prom(void) { // reset the chip to ensure it has correct prom values loaded uint8_t reg = REG_RESET; if (!dev->transfer(®, 1, nullptr, 0)) { return false; } hal.scheduler->delay(5); bool all_zero = true; for (uint8_t i = 0; i < 8; i++) { be16_t val; if (!dev->read_registers(REG_PROM_BASE+i*2, (uint8_t *) &val, sizeof(uint16_t))) { return false; } prom[i] = be16toh(val); if (prom[i] != 0) { all_zero = false; } } if (all_zero) { return false; } /* save the read crc */ const uint16_t crc_read = prom[7] & 0xf; /* remove CRC byte */ prom[7] &= 0xff00; uint16_t crc_calc = crc4_prom(); if (crc_read != crc_calc) { printf("MS5525: CRC mismatch 0x%04x 0x%04x\n", crc_read, crc_calc); } return crc_read == crc_calc; } /* read from the ADC */ int32_t AP_Airspeed_MS5525::read_adc() { uint8_t val[3]; if (!dev->read_registers(REG_ADC_READ, val, 3)) { return 0; } return (val[0] << 16) | (val[1] << 8) | val[2]; } /* calculate pressure and temperature */ void AP_Airspeed_MS5525::calculate(void) { // table for the 001DS part, 1PSI range const uint8_t Q1 = 15; const uint8_t Q2 = 17; const uint8_t Q3 = 7; const uint8_t Q4 = 5; const uint8_t Q5 = 7; const uint8_t Q6 = 21; float dT = float(D2) - int64_t(prom[5]) * (1L<transfer(&cmd_sent, 1, nullptr, 0)) { command_send_us = AP_HAL::micros(); } // when we get adc_val == 0 then then both the current value and // the next value we read from the sensor are invalid ignore_next = true; return; } /* * If read fails, re-initiate a read command for current state or we are * stuck */ if (!ignore_next) { if (cmd_sent == REG_CONVERT_TEMPERATURE) { D2 = adc_val; } else if (cmd_sent == REG_CONVERT_PRESSURE) { D1 = adc_val; calculate(); } } ignore_next = false; cmd_sent = (state == 0) ? REG_CONVERT_TEMPERATURE : REG_CONVERT_PRESSURE; if (!dev->transfer(&cmd_sent, 1, nullptr, 0)) { // we don't know for sure what state the sensor is in when we // fail to send the command, so ignore the next response ignore_next = true; return; } command_send_us = AP_HAL::micros(); state = (state + 1) % 5; } // return the current differential_pressure in Pascal bool AP_Airspeed_MS5525::get_differential_pressure(float &_pressure) { WITH_SEMAPHORE(sem); if ((AP_HAL::millis() - last_sample_time_ms) > 100) { return false; } if (press_count > 0) { pressure = pressure_sum / press_count; press_count = 0; pressure_sum = 0; } _pressure = pressure; return true; } // return the current temperature in degrees C, if available bool AP_Airspeed_MS5525::get_temperature(float &_temperature) { WITH_SEMAPHORE(sem); if ((AP_HAL::millis() - last_sample_time_ms) > 100) { return false; } if (temp_count > 0) { temperature = temperature_sum / temp_count; temp_count = 0; temperature_sum = 0; } _temperature = temperature; return true; }