diff --git a/drivers/sensor/CMakeLists.txt b/drivers/sensor/CMakeLists.txt index 9a8a7099eee..fbab21039c8 100644 --- a/drivers/sensor/CMakeLists.txt +++ b/drivers/sensor/CMakeLists.txt @@ -70,6 +70,7 @@ add_subdirectory_ifdef(CONFIG_MAX17055 max17055) add_subdirectory_ifdef(CONFIG_MAX17262 max17262) add_subdirectory_ifdef(CONFIG_MAX30101 max30101) add_subdirectory_ifdef(CONFIG_MAX31855 max31855) +add_subdirectory_ifdef(CONFIG_MAX31865 max31865) add_subdirectory_ifdef(CONFIG_MAX31875 max31875) add_subdirectory_ifdef(CONFIG_MAX44009 max44009) add_subdirectory_ifdef(CONFIG_MAX6675 max6675) diff --git a/drivers/sensor/Kconfig b/drivers/sensor/Kconfig index 95c02209ba8..85612088455 100644 --- a/drivers/sensor/Kconfig +++ b/drivers/sensor/Kconfig @@ -181,6 +181,8 @@ source "drivers/sensor/max30101/Kconfig" source "drivers/sensor/max31855/Kconfig" +source "drivers/sensor/max31865/Kconfig" + source "drivers/sensor/max31875/Kconfig" source "drivers/sensor/max44009/Kconfig" diff --git a/drivers/sensor/max31865/CMakeLists.txt b/drivers/sensor/max31865/CMakeLists.txt new file mode 100644 index 00000000000..4e40673784c --- /dev/null +++ b/drivers/sensor/max31865/CMakeLists.txt @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources(max31865.c) diff --git a/drivers/sensor/max31865/Kconfig b/drivers/sensor/max31865/Kconfig new file mode 100644 index 00000000000..ddbf7af127f --- /dev/null +++ b/drivers/sensor/max31865/Kconfig @@ -0,0 +1,12 @@ +# MAX31865 temperature sensor configuration options + +# Copyright (c) 2022 HAW Hamburg FTZ-DIWIP +# SPDX-License-Identifier: Apache-2.0 + +config MAX31865 + bool "MAX31865 Temperature Sensor" + default y + depends on DT_HAS_MAXIM_MAX31865_ENABLED + select SPI + help + Enable the driver for Maxim MAX31865 SPI Temperature Sensors. diff --git a/drivers/sensor/max31865/max31865.c b/drivers/sensor/max31865/max31865.c new file mode 100644 index 00000000000..d88bd656b5f --- /dev/null +++ b/drivers/sensor/max31865/max31865.c @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2022 HAW Hamburg FTZ-DIWIP + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "max31865.h" + +static int max31865_spi_write(const struct device *dev, uint8_t reg, uint8_t *data, size_t len) +{ + const struct max31865_config *cfg = dev->config; + + const struct spi_buf bufs[] = {{ + .buf = ®, + .len = 1, + }, + {.buf = data, .len = len}}; + + const struct spi_buf_set tx = {.buffers = bufs, .count = 2}; + + return spi_write_dt(&cfg->spi, &tx); +} + +static int max31865_spi_read(const struct device *dev, uint8_t reg, uint8_t *data, size_t len) +{ + const struct max31865_config *cfg = dev->config; + + reg &= 0x7F; + const struct spi_buf tx_buf = {.buf = ®, .len = 1}; + const struct spi_buf_set tx = {.buffers = &tx_buf, .count = 1}; + struct spi_buf rx_buf[] = {{ + .buf = ®, + .len = 1, + }, + {.buf = data, .len = len}}; + const struct spi_buf_set rx = {.buffers = rx_buf, .count = 2}; + + return spi_transceive_dt(&cfg->spi, &tx, &rx); +} + +/** + * @brief Set device configuration register + * + * @param device device instance + * @return 0 if successful, or negative error code from SPI API + */ +static int configure_device(const struct device *dev) +{ + struct max31865_data *data = dev->data; + uint8_t cmd[] = {data->config_control_bits}; + int err = max31865_spi_write(dev, WR(REG_CONFIG), cmd, 1); + + if (err < 0) { + LOG_ERR("Error write SPI%d\n", err); + } + return err; +} + +/** + * @brief Set device fail threshold registers + * + * @param device device instance + * @return 0 if successful, or negative error code from SPI API + */ +static int set_threshold_values(const struct device *dev) +{ + const struct max31865_config *config = dev->config; + uint8_t cmd[] = { + (config->high_threshold >> 7) & 0x00ff, (config->high_threshold << 1) & 0x00ff, + (config->low_threshold >> 7) & 0x00ff, (config->low_threshold << 1) & 0x00ff}; + int err = max31865_spi_write(dev, WR(REG_HIGH_FAULT_THR_MSB), cmd, 4); + + if (err < 0) { + LOG_ERR("Error write SPI%d\n", err); + } + return err; +} + +#ifdef CONFIG_NEWLIB_LIBC + +/** + * Apply the Callendar-Van Dusen equation to convert the RTD resistance + * to temperature: + * Tr = (-A + SQRT(delta) ) / 2*B + * delta = A^2 - 4B*(1-Rt/Ro) + * For under zero, taken from + * https://www.analog.com/media/en/technical-documentation/application-notes/AN709_0.pdf + * @param resistance measured resistance + * @param resistance_0 constant resistance at 0oC + * @return calculated temperature + */ +static double calculate_temperature(double resistance, double resistance_0) +{ + double temperature; + double delta = (RTD_A * RTD_A) - 4 * RTD_B * (1.0 - resistance / resistance_0); + + temperature = (-RTD_A + sqrt(delta)) / (2 * RTD_B); + if (temperature > 0.0) { + return temperature; + } + resistance /= resistance_0; + resistance *= 100.0; + temperature = A[0] + A[1] * resistance + A[2] * pow(resistance, 2) + + A[3] * pow(resistance, 3) + A[4] * pow(resistance, 4) + + A[5] * pow(resistance, 5); + return temperature; +} + +#else + +/** + * Apply a very good linear approximation of the Callendar-Van Dusen equation to convert the RTD + * resistance to temperature: + * @param resistance measured resistance + * @param resistance_0 constant resistance at 0oC + * @return calculated temperature + */ +static double calculate_temperature(double resistance, double resistance_0) +{ + double temperature; + + temperature = (resistance - resistance_0) / (resistance_0 * RTD_A); + return temperature; +} + +#endif + +/** + * @brief Enable/Disable Vbias for MAX31865 + * + * @param device device instance + * @param enable true, turn on vbias, false, turn off vbias + * @return 0 if successful, or negative error code from SPI API + */ +static int max31865_set_vbias(const struct device *dev, bool enable) +{ + struct max31865_data *data = dev->data; + + WRITE_BIT(data->config_control_bits, 7, enable); + return configure_device(dev); +} + +static char *max31865_error_to_string(uint8_t fault_register) +{ + switch (fault_register) { + case 0: + return "No error"; + + case MAX31865_FAULT_VOLTAGE: + return "Over/under voltage fault"; + + case MAX31865_FAULT_RTDIN_FORCE: + return "RTDIN- < 0.85*VBIAS (FORCE- open)"; + + case MAX31865_FAULT_REFIN_FORCE: + return "REFIN- < 0.85*VBIAS (FORCE- open)"; + + case MAX31865_FAULT_REFIN: + return "REFIN- > 0.85*VBIAS"; + + case MAX31865_FAULT_LOW_THRESHOLD: + return "RTD below low threshold"; + + case MAX31865_FAULT_HIGH_THRESHOLD: + return "RTD above high threshold"; + } + return ""; +} + +static int max31865_fault_register(const struct device *dev) +{ + uint8_t fault_register; + + max31865_spi_read(dev, (REG_FAULT_STATUS), &fault_register, 1); + struct max31865_data *data = dev->data; + /*Clear fault register */ + WRITE_BIT(data->config_control_bits, 1, 1); + configure_device(dev); + LOG_ERR("Fault Register: 0x%02x, %s", fault_register, + max31865_error_to_string(fault_register)); + WRITE_BIT(data->config_control_bits, 1, 0); + + return 0; +} + +/** + * @brief Get temperature value in oC for device + * + * @param device device instance + * @param temperature measured temperature + * @return 0 if successful, or negative error code + */ +static int max31865_get_temperature(const struct device *dev) +{ + max31865_set_vbias(dev, true); + union read_reg_u { + uint8_t u8[2]; + uint16_t u16; + } read_reg; + + read_reg.u16 = 0; + /* Waiting Time for Temerature Conversion (Page 3 of the datasheet)*/ + k_sleep(K_MSEC(66)); + /* Read resistance measured value */ + int err = max31865_spi_read(dev, (REG_RTD_MSB), read_reg.u8, 2); + + max31865_set_vbias(dev, false); + + if (err < 0) { + LOG_ERR("SPI read %d\n", err); + return -EIO; + } + read_reg.u16 = sys_be16_to_cpu(read_reg.u16); + + LOG_DBG("RAW: %02X %02X , %04X", read_reg.u8[0], read_reg.u8[1], read_reg.u16); + if (TESTBIT(read_reg.u16, 0)) { + max31865_fault_register(dev); + return -EIO; + } + + const struct max31865_config *config = dev->config; + struct max31865_data *data = dev->data; + + read_reg.u16 = read_reg.u16 >> 1; + double resistance = (double)read_reg.u16; + + resistance /= 32768; + resistance *= config->resistance_reference; + data->temperature = calculate_temperature(resistance, config->resistance_at_zero); + return 0; +} + +static int max31865_init(const struct device *dev) +{ + const struct max31865_config *config = dev->config; + + if (!spi_is_ready_dt(&config->spi)) { + return -ENODEV; + } + struct max31865_data *data = dev->data; + /* Set the confgiuration register */ + data->config_control_bits = 0; + + WRITE_BIT(data->config_control_bits, 6, config->conversion_mode); + WRITE_BIT(data->config_control_bits, 5, config->one_shot); + WRITE_BIT(data->config_control_bits, 4, config->three_wire); + data->config_control_bits |= config->fault_cycle & 0b00001100; + WRITE_BIT(data->config_control_bits, 0, config->filter_50hz); + + configure_device(dev); + set_threshold_values(dev); + max31865_set_vbias(dev, false); + return 0; +} + +static int max31865_sample_fetch(const struct device *dev, enum sensor_channel chan) +{ + if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_AMBIENT_TEMP) { + LOG_ERR("Invalid channel provided"); + return -ENOTSUP; + } + return max31865_get_temperature(dev); +} + +static int max31865_channel_get(const struct device *dev, enum sensor_channel chan, + struct sensor_value *val) +{ + struct max31865_data *data = dev->data; + + switch (chan) { + case SENSOR_CHAN_AMBIENT_TEMP: + return sensor_value_from_double(val, data->temperature); + default: + return -EINVAL; + } +} + +static const struct sensor_driver_api max31865_api_funcs = { + .sample_fetch = max31865_sample_fetch, + .channel_get = max31865_channel_get, +}; + +#define MAX31865_DEFINE(inst) \ + \ + static struct max31865_data max31865_data_##inst; \ + \ + static const struct max31865_config max31865_config_##inst = { \ + .spi = SPI_DT_SPEC_INST_GET(inst, SPI_MODE_CPHA | SPI_WORD_SET(8), 0), \ + .resistance_at_zero = DT_INST_PROP(inst, resistance_at_zero), \ + .resistance_reference = DT_INST_PROP(inst, resistance_reference), \ + .conversion_mode = false, \ + .one_shot = true, \ + .three_wire = DT_INST_PROP(inst, maxim_3_wire), \ + .fault_cycle = MAX31865_FAULT_DETECTION_NONE, \ + .filter_50hz = DT_INST_PROP(inst, filter_50hz), \ + .low_threshold = DT_INST_PROP(inst, low_threshold), \ + .high_threshold = DT_INST_PROP(inst, high_threshold), \ + }; \ + \ + SENSOR_DEVICE_DT_INST_DEFINE(inst, max31865_init, NULL, &max31865_data_##inst, \ + &max31865_config_##inst, POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, \ + &max31865_api_funcs); + +/* Create the struct device for every status "okay" node in the devicetree. */ +DT_INST_FOREACH_STATUS_OKAY(MAX31865_DEFINE) diff --git a/drivers/sensor/max31865/max31865.h b/drivers/sensor/max31865/max31865.h new file mode 100644 index 00000000000..cff6bca9940 --- /dev/null +++ b/drivers/sensor/max31865/max31865.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2022 HAW Hamburg FTZ-DIWIP + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef _MAX31865_H +#define _MAX31865_H + +#define DT_DRV_COMPAT maxim_max31865 + +#include + +#include +#include +#include +#include +#include +#include +LOG_MODULE_REGISTER(MAX31865, CONFIG_SENSOR_LOG_LEVEL); + +#define MAX31865_FAULT_HIGH_THRESHOLD BIT(7) +#define MAX31865_FAULT_LOW_THRESHOLD BIT(6) +#define MAX31865_FAULT_REFIN BIT(5) +#define MAX31865_FAULT_REFIN_FORCE BIT(4) +#define MAX31865_FAULT_RTDIN_FORCE BIT(3) +#define MAX31865_FAULT_VOLTAGE BIT(2) + +#define MAX31865_FAULT_DETECTION_NONE (0x00 << 2) +#define MAX31865_FAULT_DETECTION_AUTO (0x01 << 2) +#define MAX31865_FAULT_DETECTION_MANUAL_1 (0x02 << 2) +#define MAX31865_FAULT_DETECTION_MANUAL_2 (0x03 << 2) + +/* Read Register Address */ +#define REG_CONFIG 0x00 +#define REG_RTD_MSB 0x01 +#define REG_RTD_LSB 0x02 +#define REG_HIGH_FAULT_THR_MSB 0x03 +#define REG_HIGH_FAULT_THR_LSB 0x04 +#define REG_LOW_FAULT_THR_MSB 0x05 +#define REG_LOW_FAULT_THR_LSB 0x06 +#define REG_FAULT_STATUS 0x07 +#define WR(reg) ((reg) | 0x80) + +/** + * RTD data, RTD current, and measurement reference + * voltage. The ITS-90 standard is used; other RTDs + * may have coefficients defined by the DIN 43760 or + * the U.S. Industrial (American) standard. + */ + +#define RTD_A_ITS90 3.9080e-3 +#define RTD_A_USINDUSTRIAL 3.9692e-3 +#define RTD_A_DIN43760 3.9848e-3 +#define RTD_B_ITS90 -5.870e-7 +#define RTD_B_USINDUSTRIAL -5.8495e-7 +#define RTD_B_DIN43760 -5.8019e-7 + +/** + * RTD coefficient C is required only for temperatures + * below 0 deg. C. The selected RTD coefficient set + * is specified below. + */ +#define RTD_A (RTD_A_ITS90) +#define RTD_B (RTD_B_ITS90) + +/* + * For under zero, taken from + * https://www.analog.com/media/en/technical-documentation/application-notes/AN709_0.pdf + */ +static const float A[6] = {-242.02, 2.2228, 2.5859e-3, 4.8260e-6, 2.8183e-8, 1.5243e-10}; + +struct max31865_data { + double temperature; + uint8_t config_control_bits; +}; + +/** + * Configuration struct to the MAX31865. + */ +struct max31865_config { + const struct spi_dt_spec spi; + uint16_t resistance_at_zero; + uint16_t resistance_reference; + bool conversion_mode; + bool one_shot; + bool three_wire; + uint8_t fault_cycle; + bool filter_50hz; + uint16_t low_threshold; + uint16_t high_threshold; +}; + +/* Bit manipulation macros */ +#define TESTBIT(data, pos) ((0u == (data & BIT(pos))) ? 0u : 1u) + +#endif diff --git a/dts/bindings/sensor/maxim,max31865.yaml b/dts/bindings/sensor/maxim,max31865.yaml new file mode 100644 index 00000000000..9eb1ead1971 --- /dev/null +++ b/dts/bindings/sensor/maxim,max31865.yaml @@ -0,0 +1,44 @@ +# Copyright (c) 2022, HAW Hamburg FTZ-DIWIP +# SPDX-License-Identifier: Apache-2.0 + + +description: | + Maxim MAX31865 SPI RTD-to-Digital Converter Temperature Sensor. + Find the datasheet here: + https://datasheets.maximintegrated.com/en/ds/MAX31865.pdf + +compatible: "maxim,max31865" + +include: [sensor-device.yaml, spi-device.yaml] + +properties: + resistance-at-zero: + type: int + required: true + description: Sensor resistance in ohms at 0 Celsius (100 ohms for PT100, 1000 ohms for PT1000) + + resistance-reference: + type: int + required: true + description: Circuit reference resistance in ohms (recommended on MAX31865 datasheet 400 ohms + for PT100, 4000 ohms for PT1000, the Adafruit boards use 430 ohms and 4300 ohms) + + low-threshold: + type: int + default: 0 + description: Low fault threshold (ADC CODE, 15-bit value, unit-free, default value is the + minimum value) + + high-threshold: + type: int + default: 32767 + description: High fault threshold (ADC CODE, 15-bit value, unit-free, default value is the + maximum value) + + maxim,3-wire: + type: boolean + description: 3-wire enabled (@a true) or 2-wire/4-wire (@a false) + + filter-50hz: + type: boolean + description: 50 Hz filter enabled (@a true) or 60 Hz filter enabled (@a false) diff --git a/tests/drivers/build_all/sensor/spi.dtsi b/tests/drivers/build_all/sensor/spi.dtsi index 189d0b6b6d9..c9579d08701 100644 --- a/tests/drivers/build_all/sensor/spi.dtsi +++ b/tests/drivers/build_all/sensor/spi.dtsi @@ -343,3 +343,14 @@ test_spi_max31855: max31855@29 { reg = <0x29>; spi-max-frequency = <0>; }; + +test_spi_max31865: max31865@2a { + compatible = "maxim,max31865"; + reg = <0x2a>; + spi-max-frequency = <125000>; + resistance-at-zero = <100>; + resistance-reference = <430>; + low-threshold = <6579>; + high-threshold = <32767>; + filter-50hz; +};