Add MAX31865 sensor support, fix MAX31855 sensor (#832)

* Add MAX31865 sensor support, fix MAX31855 sensor.

# MAX31865

Added support for the MAX31865 RTD-to-Digital Converter to measure PT100 and
similar RTDs. Verified with an Adafruit unit (product ID: 3328) and a PT100
probe.

# MAX31855

This was setup for incorrect SPI clock polarity and phase, and would return bad
data due to a race condition measuring on the wrong edge (verified with Saleae
Logic scope). Selecting the correct configuration fixes that problem.

Re-wrote the decode off the datasheet to handle error states better (sends NaN
as an update on failure to read temperature, which shows the value as Unknown
in Home Assistant).

Added the *optional* ability to monitor the internal high-precision temperature
sensor, which can be nice in some applications.

* Tests for MAX31855/MAX38165.

* Update style to match project rules.

Also fix CONF_REFERENCE_RESISTANCE and CONF_REFERENCE_TEMPERATURE being defined
multiple places. Missed this when I added them to const.py.

* Update style to match project rules.

Pylint line limit 101/100 ("missed it by that much").
Also apparently I can't read and patched the wrong line in max31855.cpp.

* Minor string/style cleanup.

There was a copy-paste leftover in max31855.cpp and max31865/sensor.py had
unnecessary whitespace.

* Improve MAX31865 fault detection and logging.

Log levels are more in-line with the documented descriptions.

Fault detection code is improved. A transient fault between reads is still
reported, but now only faults *during* a read cause the sensor to fail and
return NAN ("unknown" in Home Assistant).

* Update style to match project rules.

I just now realized the .clang-format and pylintrc files are included. D'oh!

* MAX31855 & MAX31865 code style alignment.

@OttoWinter caught some style mismatches, updated to match project better.

* Fix a lost '\' in max31865/sensor.py.
This commit is contained in:
DAVe3283
2019-11-06 05:56:43 -07:00
committed by Otto Winter
parent 1ed8e63d59
commit f94e9b6b1e
10 changed files with 397 additions and 51 deletions
+66 -44
View File
@@ -1,10 +1,11 @@
#include "max31855.h"
#include "esphome/core/log.h"
namespace esphome {
namespace max31855 {
static const char *TAG = "max31855";
static const char* TAG = "max31855";
void MAX31855Sensor::update() {
this->enable();
@@ -22,9 +23,15 @@ void MAX31855Sensor::setup() {
this->spi_setup();
}
void MAX31855Sensor::dump_config() {
LOG_SENSOR("", "MAX31855", this);
ESP_LOGCONFIG(TAG, "MAX31855:");
LOG_PIN(" CS Pin: ", this->cs_);
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Thermocouple", this);
if (this->temperature_reference_) {
LOG_SENSOR(" ", "Reference", this->temperature_reference_);
} else {
ESP_LOGCONFIG(TAG, " Reference temperature disabled.");
}
}
float MAX31855Sensor::get_setup_priority() const { return setup_priority::DATA; }
void MAX31855Sensor::read_data_() {
@@ -32,53 +39,68 @@ void MAX31855Sensor::read_data_() {
delay(1);
uint8_t data[4];
this->read_array(data, 4);
// val is 14 bits of signed temperature data followed by 2 bits of status flags
int16_t val = data[1] | data[0] << 8;
// test data from MAX31855 datasheet
// val = 0x6400 // 1600.00°C
// val = 0x3E80 // 1000.00°C
// val = 0x064C // 100.75°C
// val = 0x0190 // 25.00°C
// val = 0x0000 // 0.00°C
// val = 0xFFFC // -0.25°C
// val = 0xFFF0 // -1.00°C
// val = 0xF060 // -250.00°C
this->disable();
if ((data[3] & 0x01) != 0) {
ESP_LOGW(TAG, "Got thermocouple not connected from MAX31855Sensor (0x%04X) (0x%04X)", val, data[3] | data[2] << 8);
this->status_set_warning();
return;
}
if ((data[3] & 0x02) != 0) {
ESP_LOGW(TAG, "Got short circuit to ground from MAX31855Sensor (0x%04X) (0x%04X)", val, data[3] | data[2] << 8);
this->status_set_warning();
return;
}
if ((data[3] & 0x04) != 0) {
ESP_LOGW(TAG, "Got short circuit to power from MAX31855Sensor (0x%04X) (0x%04X)", val, data[3] | data[2] << 8);
this->status_set_warning();
return;
}
if ((data[1] & 0x01) != 0) {
ESP_LOGW(TAG, "Got faulty reading from MAX31855Sensor (0x%04X) (0x%04X)", val, data[3] | data[2] << 8);
this->status_set_warning();
return;
}
if ((val & 0x8000) != 0) {
// Negative value, drop the lower 2 bits and explicitly extend sign bits.
val = 0xE000 | ((val >> 2) & 0x1FFF);
const uint32_t mem = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3] << 0;
// Verify we got data
if (mem != 0 && mem != 0xFFFFFFFF) {
this->status_clear_error();
} else {
// Positive value, just drop the lower 2 bits.
val >>= 2;
ESP_LOGE(TAG, "No data received from MAX31855 (0x%08X). Check wiring!", mem);
this->publish_state(NAN);
if (this->temperature_reference_) {
this->temperature_reference_->publish_state(NAN);
}
this->status_set_error();
return;
}
float temperature = float(val) / 4.0f;
ESP_LOGD(TAG, "'%s': Got temperature=%.1f°C", this->name_.c_str(), temperature);
this->publish_state(temperature);
// Internal reference temperature always works
if (this->temperature_reference_) {
int16_t val = (mem & 0x0000FFF0) >> 4;
if (val & 0x0800) {
val |= 0xF000; // Pad out 2's complement
}
const float t_ref = float(val) * 0.0625f;
ESP_LOGD(TAG, "Got reference temperature: %.4f°C", t_ref);
this->temperature_reference_->publish_state(t_ref);
}
// Check thermocouple faults
if (mem & 0x00000001) {
ESP_LOGW(TAG, "Thermocouple open circuit (not connected) fault from MAX31855 (0x%08X)", mem);
this->publish_state(NAN);
this->status_set_warning();
return;
}
if (mem & 0x00000002) {
ESP_LOGW(TAG, "Thermocouple short circuit to ground fault from MAX31855 (0x%08X)", mem);
this->publish_state(NAN);
this->status_set_warning();
return;
}
if (mem & 0x00000004) {
ESP_LOGW(TAG, "Thermocouple short circuit to VCC fault from MAX31855 (0x%08X)", mem);
this->publish_state(NAN);
this->status_set_warning();
return;
}
if (mem & 0x00010000) {
ESP_LOGW(TAG, "Got faulty reading from MAX31855 (0x%08X)", mem);
this->publish_state(NAN);
this->status_set_warning();
return;
}
// Decode thermocouple temperature
int16_t val = (mem & 0xFFFC0000) >> 18;
if (val & 0x2000) {
val |= 0xC000; // Pad out 2's complement
}
const float t_sense = float(val) * 0.25f;
ESP_LOGD(TAG, "Got thermocouple temperature: %.2f°C", t_sense);
this->publish_state(t_sense);
this->status_clear_warning();
}
+5 -2
View File
@@ -9,9 +9,11 @@ namespace max31855 {
class MAX31855Sensor : public sensor::Sensor,
public PollingComponent,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_4MHZ> {
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH,
spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_4MHZ> {
public:
void set_reference_sensor(sensor::Sensor *temperature_sensor) { temperature_reference_ = temperature_sensor; }
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
@@ -20,6 +22,7 @@ class MAX31855Sensor : public sensor::Sensor,
protected:
void read_data_();
sensor::Sensor *temperature_reference_{nullptr};
};
} // namespace max31855
+6 -1
View File
@@ -1,7 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, spi
from esphome.const import CONF_ID, ICON_THERMOMETER, UNIT_CELSIUS
from esphome.const import CONF_ID, CONF_REFERENCE_TEMPERATURE, ICON_THERMOMETER, UNIT_CELSIUS
max31855_ns = cg.esphome_ns.namespace('max31855')
MAX31855Sensor = max31855_ns.class_('MAX31855Sensor', sensor.Sensor, cg.PollingComponent,
@@ -9,6 +9,8 @@ MAX31855Sensor = max31855_ns.class_('MAX31855Sensor', sensor.Sensor, cg.PollingC
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({
cv.GenerateID(): cv.declare_id(MAX31855Sensor),
cv.Optional(CONF_REFERENCE_TEMPERATURE):
sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 2),
}).extend(cv.polling_component_schema('60s')).extend(spi.SPI_DEVICE_SCHEMA)
@@ -17,3 +19,6 @@ def to_code(config):
yield cg.register_component(var, config)
yield spi.register_spi_device(var, config)
yield sensor.register_sensor(var, config)
if CONF_REFERENCE_TEMPERATURE in config:
tc_ref = yield sensor.new_sensor(config[CONF_REFERENCE_TEMPERATURE])
cg.add(var.set_reference_sensor(tc_ref))