diff --git a/esphome/components/mlx90614/mlx90614.cpp b/esphome/components/mlx90614/mlx90614.cpp index f681f3cc7e..a62c0e04bb 100644 --- a/esphome/components/mlx90614/mlx90614.cpp +++ b/esphome/components/mlx90614/mlx90614.cpp @@ -38,16 +38,7 @@ void MLX90614Component::setup() { bool MLX90614Component::write_emissivity_() { if (std::isnan(this->emissivity_)) return true; - uint16_t value = (uint16_t) (this->emissivity_ * 65535); - if (!this->write_bytes_(MLX90614_EMISSIVITY, 0)) { - return false; - } - delay(10); - if (!this->write_bytes_(MLX90614_EMISSIVITY, value)) { - return false; - } - delay(10); - return true; + return this->write_register_(MLX90614_EMISSIVITY, this->emissivity_ * 0xFFFF); } uint8_t MLX90614Component::crc8_pec_(const uint8_t *data, uint8_t len) { @@ -65,14 +56,95 @@ uint8_t MLX90614Component::crc8_pec_(const uint8_t *data, uint8_t len) { return crc; } -bool MLX90614Component::write_bytes_(uint8_t reg, uint16_t data) { +i2c::ErrorCode MLX90614Component::write_register_(uint8_t reg, uint16_t data, uint8_t max_try) { uint8_t buf[5]; - buf[0] = this->address_ << 1; - buf[1] = reg; - buf[2] = data & 0xFF; - buf[3] = data >> 8; - buf[4] = this->crc8_pec_(buf, 4); - return this->write_bytes(reg, buf + 2, 3); + i2c::ErrorCode ec = i2c::ERROR_UNKNOWN; + auto init_buffer = [&]() { + buf[0] = this->address_ << 1; + buf[1] = reg; + }; + + // See 8.3.3.1. ERPROM write sequence + // 1. Power up the device + const uint8_t delay_ms = 10; + for (uint8_t i_try = 0; i_try < max_try; ++i_try) { + init_buffer(); + + // 2. Write 0x0000 into the cell of interest (effectively erasing the cell) + buf[2] = buf[3] = 0; + buf[4] = this->crc8_pec_(buf, 4); + if (!this->write_bytes(reg, buf + 2, 3)) { + ESP_LOGW(TAG, "Try %d: Can't clean register %x", i_try, reg); + ec = i2c::ERROR_UNKNOWN; + continue; + } + + // 3. Wait at least 5ms (10ms to be on the safe side) + delay(delay_ms); + + // 4. Write the new value + if (data != 0) { + buf[2] = data & 0xFF; + buf[3] = data >> 8; + buf[4] = this->crc8_pec_(buf, 4); + if (!this->write_bytes(reg, buf + 2, 3)) { + ESP_LOGW(TAG, "Try %d: Can't write register %x", i_try, reg); + ec = i2c::ERROR_UNKNOWN; + continue; + } + + // 5. Wait at least 5ms (10ms to be on the safe side) + delay(delay_ms); + } + + uint8_t read_buf[3]; + // 6. Read back and compare if the write was successful + ec = this->read_register(reg, read_buf, 3, false); + if (ec != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Try %d: Can't check register value %x", i_try, reg); + continue; + } + + if (read_buf[0] != buf[2] || read_buf[1] != buf[3] || read_buf[2] != buf[4]) { + ESP_LOGW(TAG, "Try %d: Read back value is not the same. Expected %x%x%x. Actual %x%x%x", i_try, buf[2], buf[3], + buf[4], read_buf[0], read_buf[1], read_buf[2]); + ec = i2c::ERROR_CRC; + continue; + } + + return i2c::ERROR_OK; + } + + ESP_LOGE(TAG, "Out of tries"); + return ec; +} + +uint16_t MLX90614Component::read_register_(uint8_t reg, i2c::ErrorCode &ec, uint8_t max_try) { + uint8_t buf[6] = { + uint8_t(this->address_ << 1), + reg, + uint8_t(0x01 | (this->address_ << 1)), + }; + for (uint8_t i_try = 0; i_try < max_try; ++i_try) { + ec = this->read_register(reg, buf + 3, 3, false); + + if (ec != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Try %d: i2c read error %d", i_try, ec); + continue; + } + + const auto expected_pec = this->crc8_pec_(buf, 5); + if (buf[5] != expected_pec) { + ESP_LOGW(TAG, "Try %d: i2c CRC error. Expected %x. Actual %x", i_try, expected_pec, buf[4]); + ec = i2c::ERROR_CRC; + continue; + } + + ec = i2c::ERROR_OK; + return encode_uint16(buf[4], buf[3]); + } + + return 0; } void MLX90614Component::dump_config() { @@ -89,33 +161,46 @@ void MLX90614Component::dump_config() { float MLX90614Component::get_setup_priority() const { return setup_priority::DATA; } void MLX90614Component::update() { - uint8_t emissivity[3]; - if (this->read_register(MLX90614_EMISSIVITY, emissivity, 3, false) != i2c::ERROR_OK) { - this->status_set_warning(); - return; - } - uint8_t raw_object[3]; - if (this->read_register(MLX90614_TEMPERATURE_OBJECT_1, raw_object, 3, false) != i2c::ERROR_OK) { - this->status_set_warning(); - return; + bool write_emissivity_status = true; + if (!std::isnan(this->emissivity_)) { + i2c::ErrorCode ec = i2c::ERROR_OK; + const auto read_emissivity = read_register_(MLX90614_EMISSIVITY, ec); + if (ec == i2c::ERROR_OK) { + const auto desired_emissivity = uint16_t(this->emissivity_ * 0xFFFF); + if (read_emissivity != desired_emissivity) { + if (i2c::ERROR_OK != this->write_register_(MLX90614_EMISSIVITY, desired_emissivity)) { + write_emissivity_status = false; + } + } + } else { + write_emissivity_status = false; + } } - uint8_t raw_ambient[3]; - if (this->read_register(MLX90614_TEMPERATURE_AMBIENT, raw_ambient, 3, false) != i2c::ERROR_OK) { + auto publish_sensor = [&](sensor::Sensor *sensor, uint8_t reg) { + if (nullptr == sensor) { + return true; + } + + i2c::ErrorCode ec = i2c::ERROR_OK; + const auto raw = read_register_(reg, ec); + if (ec != i2c::ERROR_OK) { + sensor->publish_state(NAN); + return false; + } + float value = raw & 0x8000 ? NAN : raw * 0.02f - 273.15f; + sensor->publish_state(value); + return true; + }; + + const auto object_updated = publish_sensor(this->object_sensor_, MLX90614_TEMPERATURE_OBJECT_1); + const auto ambient_updated = publish_sensor(this->ambient_sensor_, MLX90614_TEMPERATURE_AMBIENT); + + if (object_updated && ambient_updated && write_emissivity_status) { + this->status_clear_warning(); + } else { this->status_set_warning(); - return; } - - float ambient = raw_ambient[1] & 0x80 ? NAN : encode_uint16(raw_ambient[1], raw_ambient[0]) * 0.02f - 273.15f; - float object = raw_object[1] & 0x80 ? NAN : encode_uint16(raw_object[1], raw_object[0]) * 0.02f - 273.15f; - - ESP_LOGD(TAG, "Got Temperature=%.1f°C Ambient=%.1f°C", object, ambient); - - if (this->ambient_sensor_ != nullptr && !std::isnan(ambient)) - this->ambient_sensor_->publish_state(ambient); - if (this->object_sensor_ != nullptr && !std::isnan(object)) - this->object_sensor_->publish_state(object); - this->status_clear_warning(); } } // namespace mlx90614 diff --git a/esphome/components/mlx90614/mlx90614.h b/esphome/components/mlx90614/mlx90614.h index b6bd44172d..d6ca143fda 100644 --- a/esphome/components/mlx90614/mlx90614.h +++ b/esphome/components/mlx90614/mlx90614.h @@ -23,7 +23,8 @@ class MLX90614Component : public PollingComponent, public i2c::I2CDevice { bool write_emissivity_(); uint8_t crc8_pec_(const uint8_t *data, uint8_t len); - bool write_bytes_(uint8_t reg, uint16_t data); + i2c::ErrorCode write_register_(uint8_t reg, uint16_t data, uint8_t max_try = 2); + uint16_t read_register_(uint8_t reg, i2c::ErrorCode &ec, uint8_t max_try = 2); sensor::Sensor *ambient_sensor_{nullptr}; sensor::Sensor *object_sensor_{nullptr};