mlx90614 bugfix

* Check PEC (CRC) reading registers
* Optional read reties. Defaults to 2
* Write emissivity back to device if changed
This commit is contained in:
Anton Sergunov 2024-05-07 07:47:43 +06:00
parent f2caaf85c8
commit b2c5c5ef3b
2 changed files with 127 additions and 41 deletions

View file

@ -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

View file

@ -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};