mirror of
https://github.com/esphome/esphome.git
synced 2026-09-11 15:27:33 +00:00
[mlx90614] pec validation (#6689)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
co-authored by
Jonathan Swoboda
Jesse Hills
parent
ddbd89dd2a
commit
05f7d5e4f1
@@ -26,44 +26,129 @@ static const uint8_t MLX90614_ID4 = 0x3F;
|
||||
|
||||
static const char *const TAG = "mlx90614";
|
||||
|
||||
// The EEPROM cell has a limited number of write cycles, so stop retrying after a few failures
|
||||
static constexpr uint8_t EMISSIVITY_WRITE_ATTEMPTS = 3;
|
||||
|
||||
// SMBus packet error code: CRC-8 with polynomial 0x07, MSB first
|
||||
static uint8_t crc8_pec(const uint8_t *data, uint8_t len) { return crc8(data, len, 0x00, 0x07, true); }
|
||||
|
||||
void MLX90614Component::setup() {
|
||||
if (!this->write_emissivity_()) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
this->mark_failed();
|
||||
if (std::isnan(this->emissivity_)) {
|
||||
return;
|
||||
}
|
||||
this->emissivity_write_attempts_ = EMISSIVITY_WRITE_ATTEMPTS;
|
||||
this->try_write_emissivity_();
|
||||
if (this->emissivity_write_attempts_ != 0) {
|
||||
this->status_set_warning(LOG_STR("Failed to write emissivity, will retry"));
|
||||
}
|
||||
}
|
||||
|
||||
void MLX90614Component::try_write_emissivity_() {
|
||||
if (this->emissivity_write_attempts_ == 0) {
|
||||
return;
|
||||
}
|
||||
if (this->write_emissivity_()) {
|
||||
this->emissivity_write_attempts_ = 0;
|
||||
return;
|
||||
}
|
||||
if (--this->emissivity_write_attempts_ == 0) {
|
||||
ESP_LOGE(TAG, "Giving up on writing emissivity after %u attempts", EMISSIVITY_WRITE_ATTEMPTS);
|
||||
this->emissivity_write_failed_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool MLX90614Component::write_emissivity_() {
|
||||
if (std::isnan(this->emissivity_))
|
||||
// Skip the write when the EEPROM already holds the desired value to save write cycles
|
||||
uint16_t current_emissivity;
|
||||
if (this->read_register_(MLX90614_EMISSIVITY, current_emissivity) != i2c::ERROR_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto desired_emissivity = static_cast<uint16_t>(this->emissivity_ * 0xFFFF);
|
||||
if (current_emissivity == desired_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, desired_emissivity);
|
||||
}
|
||||
|
||||
bool MLX90614Component::write_bytes_(uint8_t reg, uint16_t data) {
|
||||
bool MLX90614Component::write_register_(uint8_t reg, uint16_t data) {
|
||||
// The PEC covers the whole write transaction: SLA+W, command, data low, data high
|
||||
uint8_t buf[5];
|
||||
buf[0] = this->address_ << 1;
|
||||
buf[1] = reg;
|
||||
buf[2] = data & 0xFF;
|
||||
buf[3] = data >> 8;
|
||||
buf[4] = crc8(buf, 4, 0x00, 0x07, true);
|
||||
return this->write_bytes(reg, buf + 2, 3);
|
||||
|
||||
// See datasheet 8.3.3.1 EEPROM write sequence
|
||||
// 1. Write 0x0000 into the cell of interest (erases the cell)
|
||||
buf[2] = buf[3] = 0;
|
||||
buf[4] = crc8_pec(buf, 4);
|
||||
auto ec = this->write_register(reg, buf + 2, 3);
|
||||
if (ec != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Can't erase register 0x%02X, error %d", reg, ec);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Wait at least 5ms
|
||||
delay(10);
|
||||
|
||||
// 3. Write the new value
|
||||
if (data != 0) {
|
||||
buf[2] = data & 0xFF;
|
||||
buf[3] = data >> 8;
|
||||
buf[4] = crc8_pec(buf, 4);
|
||||
ec = this->write_register(reg, buf + 2, 3);
|
||||
if (ec != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Can't write register 0x%02X, error %d", reg, ec);
|
||||
return false;
|
||||
}
|
||||
// 4. Wait at least 5ms
|
||||
delay(10);
|
||||
}
|
||||
|
||||
// 5. Read back to confirm the value was stored
|
||||
uint16_t read_back;
|
||||
ec = this->read_register_(reg, read_back);
|
||||
if (ec != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Can't check register 0x%02X value, error %d", reg, ec);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (read_back != data) {
|
||||
ESP_LOGW(TAG, "Read back mismatch on register 0x%02X. Expected 0x%04X, got 0x%04X", reg, data, read_back);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
i2c::ErrorCode MLX90614Component::read_register_(uint8_t reg, uint16_t &data) {
|
||||
// The PEC covers the whole read transaction: SLA+W, command, SLA+R, data low, data high
|
||||
uint8_t buf[6];
|
||||
buf[0] = this->address_ << 1;
|
||||
buf[1] = reg;
|
||||
buf[2] = (this->address_ << 1) | 0x01;
|
||||
|
||||
const auto ec = this->read_register(reg, buf + 3, 3);
|
||||
if (ec != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "i2c read error %d", ec);
|
||||
return ec;
|
||||
}
|
||||
|
||||
const auto expected_pec = crc8_pec(buf, 5);
|
||||
if (buf[5] != expected_pec) {
|
||||
ESP_LOGW(TAG, "i2c CRC error. Expected 0x%02X, got 0x%02X", expected_pec, buf[5]);
|
||||
return i2c::ERROR_CRC;
|
||||
}
|
||||
|
||||
data = encode_uint16(buf[4], buf[3]);
|
||||
return i2c::ERROR_OK;
|
||||
}
|
||||
|
||||
void MLX90614Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "MLX90614:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
if (this->emissivity_write_attempts_ != 0) {
|
||||
ESP_LOGW(TAG, " Emissivity not written yet, will retry");
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Ambient", this->ambient_sensor_);
|
||||
@@ -71,33 +156,41 @@ void MLX90614Component::dump_config() {
|
||||
}
|
||||
|
||||
void MLX90614Component::update() {
|
||||
uint8_t emissivity[3];
|
||||
if (this->read_register(MLX90614_EMISSIVITY, emissivity, 3) != i2c::ERROR_OK) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
// Temperature reads run regardless of the emissivity state so a failure still shows up as NAN
|
||||
this->try_write_emissivity_();
|
||||
|
||||
// Publishes NAN on a bus or CRC failure so a stuck reading is visible instead of silently stale
|
||||
auto publish_sensor = [this](sensor::Sensor *sensor, uint8_t reg) {
|
||||
if (sensor == nullptr) {
|
||||
return i2c::ERROR_OK;
|
||||
}
|
||||
|
||||
uint16_t raw;
|
||||
const auto ec = this->read_register_(reg, raw);
|
||||
if (ec != i2c::ERROR_OK) {
|
||||
sensor->publish_state(NAN);
|
||||
return ec;
|
||||
}
|
||||
|
||||
// Bit 15 set means the device flagged the reading as invalid
|
||||
const float temperature = (raw & 0x8000) ? NAN : raw * 0.02f - 273.15f;
|
||||
ESP_LOGD(TAG, "'%s': Got temperature=%.1f°C", sensor->get_name().c_str(), temperature);
|
||||
sensor->publish_state(temperature);
|
||||
return ec;
|
||||
};
|
||||
|
||||
const auto object_ec = publish_sensor(this->object_sensor_, MLX90614_TEMPERATURE_OBJECT_1);
|
||||
const auto ambient_ec = publish_sensor(this->ambient_sensor_, MLX90614_TEMPERATURE_AMBIENT);
|
||||
|
||||
if (object_ec != i2c::ERROR_OK || ambient_ec != i2c::ERROR_OK) {
|
||||
this->status_set_warning(LOG_STR("Failed to read some sensors"));
|
||||
} else if (this->emissivity_write_failed_) {
|
||||
this->status_set_warning(LOG_STR("Failed to write emissivity"));
|
||||
} else if (this->emissivity_write_attempts_ != 0) {
|
||||
this->status_set_warning(LOG_STR("Failed to write emissivity, will retry"));
|
||||
} else {
|
||||
this->status_clear_warning();
|
||||
}
|
||||
uint8_t raw_object[3];
|
||||
if (this->read_register(MLX90614_TEMPERATURE_OBJECT_1, raw_object, 3) != i2c::ERROR_OK) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t raw_ambient[3];
|
||||
if (this->read_register(MLX90614_TEMPERATURE_AMBIENT, raw_ambient, 3) != i2c::ERROR_OK) {
|
||||
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 esphome::mlx90614
|
||||
|
||||
@@ -18,13 +18,18 @@ class MLX90614Component final : public PollingComponent, public i2c::I2CDevice {
|
||||
void set_emissivity(float emissivity) { emissivity_ = emissivity; }
|
||||
|
||||
protected:
|
||||
void try_write_emissivity_();
|
||||
bool write_emissivity_();
|
||||
|
||||
bool write_bytes_(uint8_t reg, uint16_t data);
|
||||
bool write_register_(uint8_t reg, uint16_t data);
|
||||
i2c::ErrorCode read_register_(uint8_t reg, uint16_t &data);
|
||||
|
||||
sensor::Sensor *ambient_sensor_{nullptr};
|
||||
sensor::Sensor *object_sensor_{nullptr};
|
||||
|
||||
float emissivity_{NAN};
|
||||
// Remaining attempts to program the emissivity EEPROM cell, bounded to limit cell wear
|
||||
uint8_t emissivity_write_attempts_{0};
|
||||
bool emissivity_write_failed_{false};
|
||||
};
|
||||
} // namespace esphome::mlx90614
|
||||
|
||||
Reference in New Issue
Block a user