/* 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 . */ #include "AP_Baro_ICP201XX.h" #if AP_BARO_ICP201XX_ENABLED #include #include #include #include #include #include #include #include #include #include #include #include extern const AP_HAL::HAL &hal; #define ICP201XX_ID 0x63 #define CONVERSION_INTERVAL 25000 #define REG_EMPTY 0x00 #define REG_TRIM1_MSB 0x05 #define REG_TRIM2_LSB 0x06 #define REG_TRIM2_MSB 0x07 #define REG_DEVICE_ID 0x0C #define REG_OTP_MTP_OTP_CFG1 0xAC #define REG_OTP_MTP_MR_LSB 0xAD #define REG_OTP_MTP_MR_MSB 0xAE #define REG_OTP_MTP_MRA_LSB 0xAF #define REG_OTP_MTP_MRA_MSB 0xB0 #define REG_OTP_MTP_MRB_LSB 0xB1 #define REG_OTP_MTP_MRB_MSB 0xB2 #define REG_OTP_MTP_OTP_ADDR 0xB5 #define REG_OTP_MTP_OTP_CMD 0xB6 #define REG_OTP_MTP_RD_DATA 0xB8 #define REG_OTP_MTP_OTP_STATUS 0xB9 #define REG_OTP_DEBUG2 0xBC #define REG_MASTER_LOCK 0xBE #define REG_OTP_MTP_OTP_STATUS2 0xBF #define REG_MODE_SELECT 0xC0 #define REG_INTERRUPT_STATUS 0xC1 #define REG_INTERRUPT_MASK 0xC2 #define REG_FIFO_CONFIG 0xC3 #define REG_FIFO_FILL 0xC4 #define REG_SPI_MODE 0xC5 #define REG_PRESS_ABS_LSB 0xC7 #define REG_PRESS_ABS_MSB 0xC8 #define REG_PRESS_DELTA_LSB 0xC9 #define REG_PRESS_DELTA_MSB 0xCA #define REG_DEVICE_STATUS 0xCD #define REG_I3C_INFO 0xCE #define REG_VERSION 0xD3 #define REG_FIFO_BASE 0xFA /* constructor */ AP_Baro_ICP201XX::AP_Baro_ICP201XX(AP_Baro &baro, AP_HAL::OwnPtr _dev) : AP_Baro_Backend(baro) , dev(std::move(_dev)) { } AP_Baro_Backend *AP_Baro_ICP201XX::probe(AP_Baro &baro, AP_HAL::OwnPtr dev) { if (!dev) { return nullptr; } AP_Baro_ICP201XX *sensor = new AP_Baro_ICP201XX(baro, std::move(dev)); if (!sensor || !sensor->init()) { delete sensor; return nullptr; } return sensor; } bool AP_Baro_ICP201XX::init() { if (!dev) { return false; } dev->get_semaphore()->take_blocking(); uint8_t id = 0xFF; uint8_t ver = 0xFF; read_reg(REG_DEVICE_ID, &id); read_reg(REG_VERSION, &ver); if (id != ICP201XX_ID) { goto failed; } if (ver != 0x00 && ver != 0xB2) { goto failed; } hal.scheduler->delay(10); soft_reset(); if (!boot_sequence()) { goto failed; } if (!configure()) { goto failed; } wait_read(); dev->set_retries(0); instance = _frontend.register_sensor(); dev->set_device_type(DEVTYPE_BARO_ICP201XX); set_bus_id(instance, dev->get_bus_id()); dev->get_semaphore()->give(); dev->register_periodic_callback(CONVERSION_INTERVAL/2, FUNCTOR_BIND_MEMBER(&AP_Baro_ICP201XX::timer, void)); return true; failed: dev->get_semaphore()->give(); return false; } void AP_Baro_ICP201XX::dummy_reg() { do { uint8_t reg = REG_EMPTY; uint8_t val = 0; dev->transfer(®, 1, &val, 1); } while (0); } bool AP_Baro_ICP201XX::read_reg(uint8_t reg, uint8_t *buf, uint8_t len) { bool ret; ret = dev->transfer(®, 1, buf, len); dummy_reg(); return ret; } bool AP_Baro_ICP201XX::read_reg(uint8_t reg, uint8_t *val) { return read_reg(reg, val, 1); } bool AP_Baro_ICP201XX::write_reg(uint8_t reg, uint8_t val) { bool ret; uint8_t data[2] = { reg, val }; ret = dev->transfer(data, sizeof(data), nullptr, 0); dummy_reg(); return ret; } void AP_Baro_ICP201XX::soft_reset() { /* Stop the measurement */ mode_select(0x00); hal.scheduler->delay(2); /* Flush FIFO */ flush_fifo(); /* Mask all interrupts */ write_reg(REG_FIFO_CONFIG, 0x00); write_reg(REG_INTERRUPT_MASK, 0xFF); } bool AP_Baro_ICP201XX::mode_select(uint8_t mode) { uint8_t mode_sync_status = 0; do { read_reg(REG_DEVICE_STATUS, &mode_sync_status, 1); if (mode_sync_status & 0x01) { break; } hal.scheduler->delay(1); } while (1); return write_reg(REG_MODE_SELECT, mode); } bool AP_Baro_ICP201XX::read_otp_data(uint8_t addr, uint8_t cmd, uint8_t *val) { uint8_t otp_status = 0xFF; /* Write the address content and read command */ if (!write_reg(REG_OTP_MTP_OTP_ADDR, addr)) { return false; } if (!write_reg(REG_OTP_MTP_OTP_CMD, cmd)) { return false; } /* Wait for the OTP read to finish Monitor otp_status */ do { read_reg(REG_OTP_MTP_OTP_STATUS, &otp_status); if (otp_status == 0) { break; } hal.scheduler->delay_microseconds(1); } while (1); /* Read the data from register */ if (!read_reg(REG_OTP_MTP_RD_DATA, val)) { return false; } return true; } bool AP_Baro_ICP201XX::get_sensor_data(float *pressure, float *temperature) { uint8_t fifo_data[96] {0}; uint8_t fifo_packets = 0; int32_t data_temp = 0; int32_t data_press = 0; *pressure = 0; *temperature = 0; if (read_reg(REG_FIFO_FILL, &fifo_packets)) { fifo_packets = (uint8_t)(fifo_packets & 0x1F); if (fifo_packets > 16) { flush_fifo(); return false; } if (fifo_packets > 0 && fifo_packets <= 16 && read_reg(REG_FIFO_BASE, fifo_data, fifo_packets * 2 * 3)) { uint8_t offset = 0; for (uint8_t i = 0; i < fifo_packets; i++) { data_press = (int32_t)(((fifo_data[offset + 2] & 0x0f) << 16) | (fifo_data[offset + 1] << 8) | fifo_data[offset]); if (data_press & 0x080000) { data_press |= 0xFFF00000; } /* P = (POUT/2^17)*40kPa + 70kPa */ *pressure += ((float)(data_press) * 40 / 131072) + 70; offset += 3; data_temp = (int32_t)(((fifo_data[offset + 2] & 0x0f) << 16) | (fifo_data[offset + 1] << 8) | fifo_data[offset]); if (data_temp & 0x080000) { data_temp |= 0xFFF00000; } /* T = (TOUT/2^18)*65C + 25C */ *temperature += ((float)(data_temp) * 65 / 262144) + 25; offset += 3; } *pressure = *pressure * 1000 / fifo_packets; *temperature = *temperature / fifo_packets; return true; } } return false; } bool AP_Baro_ICP201XX::boot_sequence() { uint8_t reg_value = 0; uint8_t offset = 0, gain = 0, Hfosc = 0; uint8_t version = 0; uint8_t bootup_status = 0; int ret = 1; /* read version register */ if (!read_reg(REG_VERSION, &version)) { return false; } if (version == 0xB2) { /* B2 version Asic is detected. Boot up sequence is not required for B2 Asic, so returning */ return true; } /* Read boot up status and avoid re running boot up sequence if it is already done */ if (!read_reg(REG_OTP_MTP_OTP_STATUS2, &bootup_status)) { return false; } if (bootup_status & 0x01) { /* Boot up sequence is already done, not required to repeat boot up sequence */ return true; } /* Bring the ASIC in power mode to activate the OTP power domain and get access to the main registers */ mode_select(0x04); hal.scheduler->delay(4); /* Unlock the main registers */ write_reg(REG_MASTER_LOCK, 0x1F); /* Enable the OTP and the write switch */ read_reg(REG_OTP_MTP_OTP_CFG1, ®_value); reg_value |= 0x03; write_reg(REG_OTP_MTP_OTP_CFG1, reg_value); hal.scheduler->delay_microseconds(10); /* Toggle the OTP reset pin */ read_reg(REG_OTP_DEBUG2, ®_value); reg_value |= 1 << 7; write_reg(REG_OTP_DEBUG2, reg_value); hal.scheduler->delay_microseconds(10); read_reg(REG_OTP_DEBUG2, ®_value); reg_value &= ~(1 << 7); write_reg(REG_OTP_DEBUG2, reg_value); hal.scheduler->delay_microseconds(10); /* Program redundant read */ write_reg(REG_OTP_MTP_MRA_LSB, 0x04); write_reg(REG_OTP_MTP_MRA_MSB, 0x04); write_reg(REG_OTP_MTP_MRB_LSB, 0x21); write_reg(REG_OTP_MTP_MRB_MSB, 0x20); write_reg(REG_OTP_MTP_MR_LSB, 0x10); write_reg(REG_OTP_MTP_MR_MSB, 0x80); /* Read the data from register */ ret &= read_otp_data(0xF8, 0x10, &offset); ret &= read_otp_data(0xF9, 0x10, &gain); ret &= read_otp_data(0xFA, 0x10, &Hfosc); hal.scheduler->delay_microseconds(10); /* Write OTP values to main registers */ ret &= read_reg(REG_TRIM1_MSB, ®_value); if (ret) { reg_value = (reg_value & (~0x3F)) | (offset & 0x3F); ret &= write_reg(REG_TRIM1_MSB, reg_value); } ret &= read_reg(REG_TRIM2_MSB, ®_value); if (ret) { reg_value = (reg_value & (~0x70)) | ((gain & 0x07) << 4); ret &= write_reg(REG_TRIM2_MSB, reg_value); } ret &= read_reg(REG_TRIM2_LSB, ®_value); if (ret) { reg_value = (reg_value & (~0x7F)) | (Hfosc & 0x7F); ret &= write_reg(REG_TRIM2_LSB, reg_value); } hal.scheduler->delay_microseconds(10); /* Update boot up status to 1 */ if (ret) { ret &= read_reg(REG_OTP_MTP_OTP_STATUS2, ®_value); if (!ret) { reg_value |= 0x01; ret &= write_reg(REG_OTP_MTP_OTP_STATUS2, reg_value); } } /* Disable OTP and write switch */ read_reg(REG_OTP_MTP_OTP_CFG1, ®_value); reg_value &= ~0x03; write_reg(REG_OTP_MTP_OTP_CFG1, reg_value); /* Lock the main register */ write_reg(REG_MASTER_LOCK, 0x00); /* Move to standby */ mode_select(0x00); return ret; } bool AP_Baro_ICP201XX::configure() { uint8_t reg_value = 0; /* Initiate Triggered Operation: Stay in Standby mode */ reg_value |= (reg_value & (~0x10)) | ((uint8_t)_forced_meas_trigger << 4); /* Power Mode Selection: Normal Mode */ reg_value |= (reg_value & (~0x04)) | ((uint8_t)_power_mode << 2); /* FIFO Readout Mode Selection: Pressure first. */ reg_value |= (reg_value & (~0x03)) | ((uint8_t)(_fifo_readout_mode)); /* Measurement Configuration: Mode2*/ reg_value |= (reg_value & (~0xE0)) | (((uint8_t)_op_mode) << 5); /* Measurement Mode Selection: Continuous Measurements (duty cycled) */ reg_value |= (reg_value & (~0x08)) | ((uint8_t)_meas_mode << 3); return mode_select(reg_value); } void AP_Baro_ICP201XX::wait_read() { /* * If FIR filter is enabled, it will cause a settling effect on the first 14 pressure values. * Therefore the first 14 pressure output values are discarded. **/ uint8_t fifo_packets = 0; uint8_t fifo_packets_to_skip = 14; do { hal.scheduler->delay(10); read_reg(REG_FIFO_FILL, &fifo_packets); fifo_packets = (uint8_t)(fifo_packets & 0x1F); } while (fifo_packets >= fifo_packets_to_skip); flush_fifo(); fifo_packets = 0; do { hal.scheduler->delay(10); read_reg(REG_FIFO_FILL, &fifo_packets); fifo_packets = (uint8_t)(fifo_packets & 0x1F); } while (fifo_packets == 0); } bool AP_Baro_ICP201XX::flush_fifo() { uint8_t reg_value; if (!read_reg(REG_FIFO_FILL, ®_value)) { return false; } reg_value |= 0x80; if (!write_reg(REG_FIFO_FILL, reg_value)) { return false; } return true; } void AP_Baro_ICP201XX::timer() { float p = 0; float t = 0; if (get_sensor_data(&p, &t)) { WITH_SEMAPHORE(_sem); accum.psum += p; accum.tsum += t; accum.count++; last_measure_us = AP_HAL::micros(); } else { if (AP_HAL::micros() - last_measure_us > CONVERSION_INTERVAL*3) { flush_fifo(); last_measure_us = AP_HAL::micros(); } } } void AP_Baro_ICP201XX::update() { WITH_SEMAPHORE(_sem); if (accum.count > 0) { _copy_to_frontend(instance, accum.psum/accum.count, accum.tsum/accum.count); accum.psum = accum.tsum = 0; accum.count = 0; } } #endif // AP_BARO_ICP201XX_ENABLED