Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
275 changes: 275 additions & 0 deletions drivers/include/max31865.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
/*
* Copyright (C) 2025 David Picard
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/

#pragma once

/**
* @defgroup drivers_max31865 MAX31865 RTD-to-Digital converter driver
* @ingroup drivers_sensors
* @ingroup drivers_saul
* @brief Driver for the SPI RTD-to-Digital converter MAX31865.
*
* \section sec_max31865_ovrvw Overview

* The MAX31865 is a resistance-to-digital
* converter optimized for platinum resistance temperature
* detectors (RTDs). An external resistor sets the sensitivity
* for the RTD being used and a precision delta-sigma ADC
* converts the ratio of the RTD resistance to the reference
* resistance on the board, to a 15-bit word.
*
* - Compatible with 2-, 3-, and 4-Wire Sensor Connections
* - 15-Bit ADC resolution; resolution 0.03125°C
* - Total accuracy over all operating conditions: 0.5°C (0.05% FS) max
* - 21 ms max conversion time
*
* The RTD register and the threshold registers represent the ratio
* of the RTD resistance to the reference resistance.
*
* @note See the
* <a href="https://www.analog.com/media/en/technical-documentation/data-sheets/MAX31865.pdf">
* datasheet</a> for more information.
*
* \section sec_max31865_lut Lookup table
*
* In order to convert the ADC code to temperature with a decent precision
* and low CPU usage by using fixed point computation, this driver uses a lookup table.
* The values of the lookup table depend on the type of RTD, the reference
* resistance on the board and the temperature range.
* A default lookup table for a Pt100 sensor, a reference resistance of 330&Omega;
* and a temperature range of -200..+650°C is provided.
* With this lookup table, the standard deviation of the computation
* error over the -200..+650°C range is 0.011°C and the maximum error
* is 0.039°C.
*
* If the default lookup doesn't fit the application, the user can generate
* a custom one with the Python script `drivers/max31865/dist/genlut.py`.
* Type `genlut.py -h` for help.
* Move the generated header file to the application project directory.
* In the application code, include `max31865_lut.h`.
* The header files must be included in the following order:
*
* @code
* #include "max31865.h"
* #include "max31865_lut.h"
* #include "max31865_params.h"
* @endcode
*
* The lookup table in the application directory will override the default one.
*
* \section sec_max31865_usage Usage
*
* The default configuration is set in \ref max31865_params.h as a
* \ref max31865_params_t structure.
* Most of them can be changed using the preprocessor definitions either
* in the board definition in `board.h` or in the Makefile of the application
* using the `CFLAGS` variable.
* In particular, let the SPI device and chip select pin fit the board.
*
* @code
* define MAX31865_PARAM_SPI (SPI_DEV(0))
* define MAX31865_PARAM_CS_PIN (GPIO_PIN(0, 5))
* @endcode
*
* The easiest way to read the temperature is to call #max31865_read().
* In critical applications, where a sensor failure may cause damage to the
* system, calling #max31865_read_raw(),
* #max31865_raw_to_data() and #max31865_detect_fault() independently
* provides a better control on fault detection.
*
* @{
*
* @file
*
* @author David Picard
*/

/* Add header includes here */

#ifdef __cplusplus
extern "C" {
#endif

#include "periph/spi.h"

/**
* @brief Lookup table column indexes
*
* The lookup table is a 2D array.
* This enum defines the indexes of the columns of the table.
* The conversion from ADC code to temperature is done with a linear interpolation
* between two lines of the table.
* The coefficients at line n are valid for the temperature range between line n
* and line n+1.
*
* T = a<sub>0</sub> + a<sub>1</sub> &times; code
*/
typedef enum {
MAX31865_LUTCOL_CODE = 0, /**< ADC code column index */
MAX31865_LUTCOL_TEMP = 1, /**< Temperature column index (µ°C) */
MAX31865_LUTCOL_A0 = 2, /**< a<sub>0</sub> coefficient column index */
MAX31865_LUTCOL_A1 = 3, /**< a<sub>1</sub> coefficient column index */
MAX31865_LUTCOL_NUMOF /**< Number of columns in the lookup table */
} max31865_lutcols_t;

/**
* @brief Device initialization parameters
*
* - Use the bits defined in \ref drivers_max31865_constants_regcfg to build the
* configuration byte #max31865_params_t.cfg_byte.
* - The high and low thresholds must be set in units of 0.01 °C, e.g. `12000` for 120 °C.
* - #max31865_params_t.lut_numlines is defined in the header file generated
* by the Python script `drivers/max31865/dist/genlut.py`.
*/
typedef struct {
spi_t spi; /**< SPI device */
spi_cs_t cs_pin; /**< Chip select pin */
uint8_t cfg_byte; /**< Initial value of the configuration register */
int32_t temp_low_threshold; /**< Low threshold temperature (c°C) */
int32_t temp_high_threshold; /**< High threshold temperature (c°C) */
const int32_t (*lut)[][MAX31865_LUTCOL_NUMOF]; /**< Lookup table */
const int lut_numlines; /**< Number of lines in the lookup table */
} max31865_params_t;

/**
* @brief Device descriptor for the driver
*/
typedef struct {
const max31865_params_t *params; /**< device configuration */
} max31865_t;

/**
* @brief Fault status of the MAX31865
*/
typedef enum {
MAX31865_FAULT_NO_FAULT = 0, /**< No fault */
MAX31865_FAULT_RTD_HIGH = 1, /**< The RTD value is too high */
MAX31865_FAULT_RTD_LOW = 2, /**< The RTD value is too small */
MAX31865_FAULT_CIRCUIT = 3, /**< Open or shorted circuit */
MAX31865_FAULT_VOLTAGE = 4 /**< Overvoltage or undervoltage on the FORCE or RTD pin */
} max31865_fault_t;

/**
* @brief Initialize the given device
*
* @param[in,out] dev Device descriptor of the driver
* @param[in] params Initialization parameters
*
* @retval 0 on success
* @retval -ENXIO invalid SPI device
* @retval -EINVAL invalid SPI CS pin/line
*/
int max31865_init(max31865_t *dev, const max31865_params_t *params);

/**
* @brief Clear the fault flag
*
* @param[in] dev Device descriptor of the driver
* @param[out] config If not NULL, set to the value of the config register
*
* Call this function if after #max31865_read() or #max31865_detect_fault() if
* either of them reports a fault.
*/
void max31865_clear_fault(const max31865_t *dev, uint8_t *config);

/**
* @brief Read data from the MAX31865. This is a shortcut to read raw data
* and parse it to the data structure.
*
* @param[in] dev Device descriptor of the driver
* @param[out] rtd_temperature_cdegc Temperature in centi-degrees Celsius (0.01°C)
*
* @pre \a dev and \a data must not be NULL
*
* This function does a minimal error check.
* To get more details on the fault, call
* #max31865_detect_fault().
*
* @retval 0 on success
* @retval -EIO if an error was detected by the MAX31865
*/
int max31865_read(const max31865_t *dev, int32_t *rtd_temperature_cdegc);

/**
* @brief Read raw data from the MAX31865
* @param[in] dev Device descriptor of the driver
* @param[out] raw_data Value of the RTD registers
*
* \a raw_data is a left-justified 15-bit word, representing the ratio
* of the RTD resistance to the reference resistance.
* Bit 0 is a status bit.
*
* @retval 0 on success.
* @retval -EIO if an error was detected by the MAX31865.
* In this case, the temperature is not valid.
* Call #max31865_detect_fault() for more details.
*/
int max31865_read_raw(const max31865_t *dev, uint16_t *raw_data);

/**
* @brief Convert the raw data from the MAX31865 temperature
*
* @param[in] dev Device descriptor of the driver
* @param[in] raw_data Raw data from the MAX31865
* @param[out] rtd_temperature_cdegc Temperature in centi-degrees Celsius (0.01°C)
*
* @pre @p data must not be NULL
*
* @retval 0 on success
* @retval -EINVAL on error
*/
int max31865_raw_to_data(const max31865_t *dev, uint16_t raw_data, int32_t *rtd_temperature_cdegc);

/**
* @brief Run an automatic fault-detection cycle
* @param[in] dev Device descriptor of the driver
* @param[out] flt_code Fault code
*
* Run a full fault-detection cycle, in automatic mode.
* The automatic mode implies that the input filter of the ADC has
* a time constant smaller than 100µs.
* This is the case if the capacitor across the RTD has the
* value which is recommended in the datasheet.
*
* The execution time of this function is at least 100µs, until the
* fault-detection cycle completes.
* It polls the configuration register to check the completion of the cycle.
*
* @pre V<sub>BIAS</sub> must be on for at least 5 time constants.
*
* @retval 0 on success.
* @retval -EIO on error.
*/
int max31865_detect_fault(const max31865_t *dev, max31865_fault_t *flt_code);

/**
* @brief Switch V<sub>BIAS</sub> on or off
* @param[in] dev Device descriptor of the driver
* @param[in] enable Set to \c true to switch on, or to \c false to switch off V<sub>BIAS</sub>.
*
* The bias current of the RTD can be switched off to save power.
* After switching it on, wait for at least 10 time constants -- 10 &times; 100µs,
* if the capacitance across the RTD is as recommended in the datasheet --
* before measuring a valid temperature.
*/
void max31865_switch_vbias(const max31865_t *dev, bool enable);

/**
* @brief Start a one-shot conversion
* @param[in] dev Device descriptor of the driver
*
* The user must wait at least 52ms in 60Hz or 62.5ms in 50Hz filter mode
* for the conversion to complete, before reading the conversion result.
*/
void max31865_oneshot(const max31865_t *dev);

#ifdef __cplusplus
}
#endif

/** @} */
1 change: 1 addition & 0 deletions drivers/max31865/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include $(RIOTMAKE)/driver_with_saul.mk
4 changes: 4 additions & 0 deletions drivers/max31865/Makefile.dep
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FEATURES_REQUIRED += periph_spi

USEMODULE += ztimer
USEMODULE += ztimer_usec
2 changes: 2 additions & 0 deletions drivers/max31865/Makefile.include
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
USEMODULE_INCLUDES_max31865 := $(LAST_MAKEFILEDIR)/include
USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_max31865)
Loading