/* 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 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; } if (!dev->get_semaphore()->take(HAL_SEMAPHORE_BLOCK_FOREVER)) { continue; } // lots of retries during probe dev->set_retries(5); found = read_prom(); if (found) { printf("MS5525: Found sensor on bus %u address 0x%02x\n", get_bus(), addresses[i]); break; } dev->get_semaphore()->give(); } if (!found) { printf("MS5525: no sensor found\n"); return false; } // Send a command to read temperature first uint8_t reg = REG_CONVERT_TEMPERATURE; dev->transfer(®, 1, nullptr, 0); state = 0; command_send_us = AP_HAL::micros(); // drop to 2 retries for runtime dev->set_retries(2); dev->get_semaphore()->give(); // 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) { uint16_t n_rem = 0; uint8_t n_bit; for (uint8_t cnt = 0; cnt < sizeof(prom); cnt++) { /* uneven bytes */ if (cnt & 1) { n_rem ^= (uint8_t)((prom[cnt >> 1]) & 0x00FF); } else { n_rem ^= (uint8_t)(prom[cnt >> 1] >> 8); } for (n_bit = 8; n_bit > 0; n_bit--) { if (n_rem & 0x8000) { n_rem = (n_rem << 1) ^ 0x3000; } else { n_rem = (n_rem << 1); } } } return (n_rem >> 12) & 0xF; } 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; int64_t dT = D2 - int64_t(prom[5]) * (1UL<take(HAL_SEMAPHORE_BLOCK_FOREVER)) { pressure_sum += P_Pa; temperature_sum += Temp_C; press_count++; temp_count++; last_sample_time_ms = AP_HAL::millis(); sem->give(); } } // 80Hz timer void AP_Airspeed_MS5525::timer() { if (AP_HAL::micros() - command_send_us < 10000) { // we should avoid trying to read the ADC too soon after // sending the command return; } uint32_t adc_val = read_adc(); if (adc_val == 0) { // we have either done a read too soon after sending the // request or we have tried to read the same sample twice. We // re-send the command now as we don't know what state the // sensor is in, and we do want to trigger it sending a value, // which we will discard if (dev->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) { if ((AP_HAL::millis() - last_sample_time_ms) > 100) { return false; } if (sem->take(HAL_SEMAPHORE_BLOCK_FOREVER)) { if (press_count > 0) { pressure = pressure_sum / press_count; press_count = 0; pressure_sum = 0; } sem->give(); } _pressure = pressure; return true; } // return the current temperature in degrees C, if available bool AP_Airspeed_MS5525::get_temperature(float &_temperature) { if ((AP_HAL::millis() - last_sample_time_ms) > 100) { return false; } if (sem->take(HAL_SEMAPHORE_BLOCK_FOREVER)) { if (temp_count > 0) { temperature = temperature_sum / temp_count; temp_count = 0; temperature_sum = 0; } sem->give(); } _temperature = temperature; return true; }