#include "spa06_base.h" #include #include "esphome/core/helpers.h" namespace esphome::spa06_base { static const char *const TAG = "spa06"; // Sign extension function for <=16 bit types inline int16_t decode16(uint8_t msb, uint8_t lsb, size_t bits, size_t head = 0) { return static_cast(encode_uint16(msb, lsb) << head) >> (16 - bits); } // Sign extension function for <=32 bit types inline int32_t decode32(uint8_t xmsb, uint8_t msb, uint8_t lsb, uint8_t xlsb, size_t bits, size_t head = 0) { return static_cast(encode_uint32(xmsb, msb, lsb, xlsb) << head) >> (32 - bits); } void SPA06Component::dump_config() { ESP_LOGCONFIG(TAG, "SPA06:"); LOG_UPDATE_INTERVAL(this); ESP_LOGCONFIG(TAG, " Measurement conversion time: %ums", this->conversion_time_); if (this->temperature_sensor_) { LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); ESP_LOGCONFIG(TAG, " Oversampling: %s\n" " Rate: %s", LOG_STR_ARG(oversampling_to_str(this->temperature_oversampling_)), LOG_STR_ARG(meas_rate_to_str(this->temperature_rate_))); } if (this->pressure_sensor_) { LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); ESP_LOGCONFIG(TAG, " Oversampling: %s\n" " Rate: %s", LOG_STR_ARG(oversampling_to_str(this->pressure_oversampling_)), LOG_STR_ARG(meas_rate_to_str(this->pressure_rate_))); } } void SPA06Component::setup() { // Startup sequence for SPA06 (Pg. 16, Figure 4.6.4): // 1. Perform a soft reset // 2. Verify sensor chip ID matches // 3. Verify coefficients are ready // 4. Read coefficients // 5. Configure temperature and pressure sensors // 6. Write communication settings // 7. Write measurement settings (background measurement mode) // 1. Soft reset if (!this->soft_reset_()) { this->mark_failed(LOG_STR("Reset failed")); return; } // soft_reset_() internally delays by 3ms to make sure that // the sensor is in a ready state and coefficients are ready. // 2. Read chip ID // TODO: check ID for consistency? if (!spa_read_byte(SPA06_ID, &this->prod_id_.reg)) { this->mark_failed(LOG_STR("Chip ID read failure")); return; } ESP_LOGV(TAG, "Product Info:\n" " Prod ID: %u\n" " Rev ID: %u", this->prod_id_.bit.prod_id, this->prod_id_.bit.rev_id); // 3. Read chip readiness from MEAS_CFG // First check if the sensor reports ready if (!spa_read_byte(SPA06_MEAS_CFG, &this->meas_.reg)) { this->mark_failed(LOG_STR("Sensor status read failure")); return; } // Check if the sensor reports coefficients are ready if (!meas_.bit.coef_ready) { this->mark_failed(LOG_STR("Coefficients not ready")); return; } // 4. Read coefficients if (!this->read_coefficients_()) { this->mark_failed(LOG_STR("Coefficients read error")); return; } // 5. Configure temperature and pressure sensors // Default to measuring both temperature and pressure // Temperature must be read regardless of configuration to compute pressure // If temperature is not configured in config: // - No oversampling is used // - Lowest possible rate is configured if (!this->temperature_sensor_) { this->temperature_rate_ = SAMPLE_RATE_1; this->temperature_oversampling_ = OVERSAMPLING_NONE; this->kt_ = oversampling_to_scale_factor(OVERSAMPLING_NONE); } // If pressure is not configured in config // - No oversampling is used // - Lowest possible rate is configured if (!this->pressure_sensor_) { this->pressure_rate_ = SAMPLE_RATE_1; this->pressure_oversampling_ = OVERSAMPLING_NONE; this->kp_ = oversampling_to_scale_factor(OVERSAMPLING_NONE); } // Write temperature settings if (!write_temperature_settings_(this->temperature_oversampling_, this->temperature_rate_)) { this->mark_failed(LOG_STR("Temperature settings write fail")); return; } // Write pressure settings if (!write_pressure_settings_(this->pressure_oversampling_, this->pressure_rate_)) { this->mark_failed(LOG_STR("Pressure settings write fail")); return; } // 6. Write communication settings // This call sets the bit shifts for pressure and temperature if // their respective oversampling config is > X8 // This call also disables interrupts, FIFO, and specifies SPI 4-wire if (!write_communication_settings_(this->pressure_oversampling_ > OVERSAMPLING_X8, this->temperature_oversampling_ > OVERSAMPLING_X8)) { this->mark_failed(LOG_STR("Comm settings write fail")); return; } // 7. Write measurement settings // This function sets background measurement mode without FIFO if (!write_measurement_settings_(this->pressure_sensor_ ? MeasCrtl::MEASCRTL_BG_BOTH : MeasCrtl::MEASCRTL_BG_TEMP)) { this->mark_failed(LOG_STR("Measurement settings write fail")); return; } } bool SPA06Component::write_temperature_settings_(Oversampling oversampling, SampleRate rate) { return this->write_sensor_settings_(oversampling, rate, SPA06_TMP_CFG); } bool SPA06Component::write_pressure_settings_(Oversampling oversampling, SampleRate rate) { return this->write_sensor_settings_(oversampling, rate, SPA06_PSR_CFG); } bool SPA06Component::write_sensor_settings_(Oversampling oversampling, SampleRate rate, uint8_t reg) { if (reg != SPA06_PSR_CFG && reg != SPA06_TMP_CFG) { return false; } this->pt_meas_cfg_.bit.rate = rate; this->pt_meas_cfg_.bit.prc = oversampling; ESP_LOGD(TAG, "Config write: %02x", this->pt_meas_cfg_.reg); return spa_write_byte(reg, this->pt_meas_cfg_.reg); } bool SPA06Component::write_measurement_settings_(MeasCrtl crtl) { this->meas_.bit.meas_crtl = crtl; return spa_write_byte(SPA06_MEAS_CFG, this->meas_.reg); } bool SPA06Component::write_communication_settings_(bool pressure_shift, bool temperature_shift, bool interrupt_hl, bool interrupt_fifo, bool interrupt_tmp, bool interrupt_prs, bool enable_fifo, bool spi_3wire) { this->cfg_.bit.p_shift = pressure_shift; this->cfg_.bit.t_shift = temperature_shift; this->cfg_.bit.int_hl = interrupt_hl; this->cfg_.bit.int_fifo = interrupt_fifo; this->cfg_.bit.int_tmp = interrupt_tmp; this->cfg_.bit.int_prs = interrupt_prs; this->cfg_.bit.fifo_en = enable_fifo; this->cfg_.bit.spi_3wire = spi_3wire; return spa_write_byte(SPA06_CFG_REG, this->cfg_.reg); } bool SPA06Component::read_coefficients_() { uint8_t coef[SPA06_COEF_LEN]; if (!spa_read_bytes(SPA06_COEF, coef, SPA06_COEF_LEN)) { return false; } this->c0_ = decode16(coef[0], coef[1], 12); this->c1_ = decode16(coef[1], coef[2], 12, 4); this->c00_ = decode32(coef[3], coef[4], coef[5], 0, 20); this->c10_ = decode32(coef[5], coef[6], coef[7], 0, 20, 4); this->c01_ = decode16(coef[8], coef[9], 16); this->c11_ = decode16(coef[10], coef[11], 16); this->c20_ = decode16(coef[12], coef[13], 16); this->c21_ = decode16(coef[14], coef[15], 16); this->c30_ = decode16(coef[16], coef[17], 16); this->c31_ = decode16(coef[18], coef[19], 12); this->c40_ = decode16(coef[19], coef[20], 12, 4); ESP_LOGV(TAG, "Coefficients:\n" " c0: %i, c1: %i,\n" " c00: %" PRIi32 ", c10: %" PRIi32 ", c20: %i, c30: %i, c40: %i,\n" " c01: %i, c11: %i, c21: %i, c31: %i", this->c0_, this->c1_, this->c00_, this->c10_, this->c20_, this->c30_, this->c40_, this->c01_, this->c11_, this->c21_, this->c31_); return true; } bool SPA06Component::soft_reset_() { // Setup steps for SPA06: // 1. Perform a protocol reset (required to write command for SPI code, noop for I2C) this->protocol_reset(); // 2. Perform the actual reset this->reset_.bit.fifo_flush = true; this->reset_.bit.soft_rst = SPA06_SOFT_RESET; if (!this->spa_write_byte(SPA06_RESET, this->reset_.reg)) { return false; } // 3. Wait for chip to become ready. Datasheet specifies 2 ms; wait 3 delay(3); // 4. Perform another protocol reset (required for SPI code, noop for I2C) this->protocol_reset(); return true; } // Temperature conversion formula. See datasheet pg. 14 float SPA06Component::convert_temperature_(const float &t_raw_sc) { return this->c0_ * 0.5 + this->c1_ * t_raw_sc; } // Pressure conversion formula. See datasheet pg. 14 float SPA06Component::convert_pressure_(const float &p_raw_sc, const float &t_raw_sc) { float p2_raw_sc = p_raw_sc * p_raw_sc; float p3_raw_sc = p2_raw_sc * p_raw_sc; float p4_raw_sc = p3_raw_sc * p_raw_sc; return this->c00_ + (float) this->c10_ * p_raw_sc + (float) this->c20_ * p2_raw_sc + (float) this->c30_ * p3_raw_sc + (float) this->c40_ * p4_raw_sc + t_raw_sc * ((float) this->c01_ + (float) this->c11_ * p_raw_sc + (float) this->c21_ * p2_raw_sc + (float) this->c31_ * p3_raw_sc); } void SPA06Component::update() { // Verify either a temperature or pressure sensor is defined before proceeding if ((!this->temperature_sensor_) && (!this->pressure_sensor_)) { return; } // Queue a background task for retrieving the measurement this->set_timeout("measurement", this->conversion_time_, [this]() { float raw_temperature; float temperature = 0.0; float pressure = 0.0; // Check measurement register for readiness if (!this->spa_read_byte(SPA06_MEAS_CFG, &this->meas_.reg)) { ESP_LOGW(TAG, "Cannot read meas config"); this->status_set_warning(); return; } if (this->pressure_sensor_) { if (!this->meas_.bit.prs_ready || !this->meas_.bit.tmp_ready) { ESP_LOGW(TAG, "Temperature and pressure not ready"); this->status_set_warning(); return; } if (!this->read_temperature_and_pressure_(temperature, pressure, raw_temperature)) { ESP_LOGW(TAG, "Temperature and pressure read failure"); this->status_set_warning(); return; } } else { if (!this->meas_.bit.tmp_ready) { ESP_LOGW(TAG, "Temperature not ready"); this->status_set_warning(); return; } if (!this->read_temperature_(temperature, raw_temperature)) { ESP_LOGW(TAG, "Temperature read fail"); this->status_set_warning(); return; } } if (this->temperature_sensor_) { this->temperature_sensor_->publish_state(temperature); } else { ESP_LOGV(TAG, "No temperature sensor configured"); } if (this->pressure_sensor_) { this->pressure_sensor_->publish_state(pressure); } else { ESP_LOGV(TAG, "No pressure sensor configured"); } this->status_clear_warning(); }); } bool SPA06Component::read_temperature_and_pressure_(float &temperature, float &pressure, float &t_raw_sc) { // Temperature read and decode if (!this->read_temperature_(temperature, t_raw_sc)) { return false; } // Read raw pressure from device uint8_t buf[3]; if (!this->spa_read_bytes(SPA06_PSR, buf, 3)) { return false; } // Calculate raw scaled pressure value float p_raw_sc = (float) decode32(buf[0], buf[1], buf[2], 0, 24) / (float) this->kp_; // Calculate full pressure values pressure = this->convert_pressure_(p_raw_sc, t_raw_sc); return true; } bool SPA06Component::read_temperature_(float &temperature, float &t_raw_sc) { uint8_t buf[3]; if (!this->spa_read_bytes(SPA06_TMP, buf, 3)) { return false; } t_raw_sc = (float) decode32(buf[0], buf[1], buf[2], 0, 24) / (float) this->kt_; temperature = this->convert_temperature_(t_raw_sc); return true; } } // namespace esphome::spa06_base