drivers: sensors: Implement MAX31865 sensor

This commit implements the temperature sensor interface for
the Maxim MAX31865 SPI Temperature Sensor.

Signed-off-by: Fin Maaß <fin.maass@haw-hamburg.de>
Co-authored-by: Armin Brauns <armin.brauns@embedded-solutions.at>
This commit is contained in:
Fin Maaß 2022-09-01 16:49:29 +02:00 committed by Anas Nashif
parent 5d240edc17
commit cabc30c725
8 changed files with 475 additions and 0 deletions

View file

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

View file

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

View file

@ -0,0 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources(max31865.c)

View file

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

View file

@ -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 = &reg,
.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 = &reg, .len = 1};
const struct spi_buf_set tx = {.buffers = &tx_buf, .count = 1};
struct spi_buf rx_buf[] = {{
.buf = &reg,
.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)

View file

@ -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 <math.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/util_macro.h>
#include <zephyr/sys/byteorder.h>
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

View file

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

View file

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