1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 00:29:46 +01:00

drivers: add max31855

This commit is contained in:
Leandro Lanzieri 2024-06-06 10:45:37 +02:00
parent 350fbf6ba4
commit ebf64133d7
No known key found for this signature in database
GPG Key ID: F4E9A721761C7593
10 changed files with 707 additions and 0 deletions

135
drivers/include/max31855.h Normal file
View File

@ -0,0 +1,135 @@
/*
* Copyright (C) 2024 HAW Hamburg
*
* 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.
*/
/**
* @defgroup drivers_max31855 MAX31855 Thermocouple-to-Digital Converter driver
* @ingroup drivers_sensors
* @brief Driver for the SPI Thermocouple-to-Digital Converter MAX31855.
*
* The MAX31855 performs cold-junction compensation and digitizes
* the signal from a K-, J-, N-, T-, S-, R-, or E-type
* thermocouple. The data is output in a signed 14-bit,
* SPI-compatible, read-only format. This converter resolves
* temperatures to 0.25°C, allows readings as high as +1800°C and
* as low as -270°C, and exhibits thermocouple accuracy of ±2°C
* for temperatures ranging from -200°C to +700°C for K-type
* thermocouples.
*
* @note See the datasheet for more information:
* https://www.analog.com/media/en/technical-documentation/data-sheets/MAX31855.pdf
*
* @note This driver doesn't require a MOSI line, as the MAX31855 is a read-only.
*
* @{
*
* @file
*
* @author Leandro Lanzieri <leandro.lanzieri@haw-hamburg.de>
*/
#ifndef MAX31855_H
#define MAX31855_H
/* Add header includes here */
#ifdef __cplusplus
extern "C" {
#endif
#include "periph/spi.h"
/* Declare the API of the driver */
/**
* @brief Device initialization parameters
*/
typedef struct {
spi_t spi; /**< SPI device */
spi_cs_t cs_pin; /**< Chip select pin */
} max31855_params_t;
/**
* @brief Device descriptor for the driver
*/
typedef struct {
const max31855_params_t *params; /**< device configuration */
} max31855_t;
/**
* @brief Fault status of the MAX31855
*/
typedef enum {
MAX31855_FAULT_VCC_SHORT = 0, /**< VCC short-circuit */
MAX31855_FAULT_GND_SHORT = 1, /**< GND short-circuit */
MAX31855_FAULT_OPEN_CIRCUIT = 2, /**< Open circuit */
MAX31855_FAULT_NO_FAULT = 3 /**< No fault */
} max31855_fault_t;
/**
* @brief Data structure for the MAX31855
*/
typedef struct {
int32_t thermocouple_temperature; /**< Thermocouple temperature in centi degrees C */
int32_t internal_temperature; /**< Internal temperature in centi degrees C */
max31855_fault_t fault; /**< Fault status */
} max31855_data_t;
/**
* @brief Initialize the given device
*
* @param[inout] 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 max31855_init(max31855_t *dev, const max31855_params_t *params);
/**
* @brief Parse the raw data from the MAX31855 to the data structure
*
* @param[in] raw_data Raw data from the MAX31855
* @param[out] data Pointer to the data structure.
*
* @pre @p data must not be NULL
*/
void max31855_raw_to_data(uint32_t raw_data, max31855_data_t *data);
/**
* @brief Read data from the MAX31855. 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] data Pointer to the data structure.
*
* @pre @p dev and @p data must not be NULL
*
* @retval 0 on success
* @retval -EIO if there is an error detected by the MAX31855. For a detailed
* error description, check the fault field of the data structure. On this
* case, temperature fields are not valid.
*/
int max31855_read(max31855_t *dev, max31855_data_t *data);
/**
* @brief Read raw data from the MAX31855
*
* @param[in] dev Device descriptor of the driver
* @param[out] data Pointer where to store the raw data.
*
* @pre @p dev and @p data must not be NULL
*/
void max31855_read_raw(max31855_t *dev, uint32_t *data);
#ifdef __cplusplus
}
#endif
#endif /* MAX31855_H */
/** @} */

View File

@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base

View File

@ -0,0 +1 @@
FEATURES_REQUIRED += periph_spi

View File

@ -0,0 +1,2 @@
USEMODULE_INCLUDES_max31855 := $(LAST_MAKEFILEDIR)/include
USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_max31855)

View File

@ -0,0 +1,173 @@
/*
* Copyright (C) 2024 HAW Hamburg
*
* 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.
*/
/**
* @ingroup drivers_max31855
* @{
*
* @file
* @brief Internal addresses, registers and constants
*
* @author Leandro Lanzieri <leandro.lanzieri@haw-hamburg.de>
*/
#ifndef MAX31855_CONSTANTS_H
#define MAX31855_CONSTANTS_H
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Size of the data read from the MAX31855 in bytes.
*/
#define MAX31855_DATA_SIZE (4)
/**
* @brief Shift value for the thermocouple sign bit.
*/
#define MAX31855_THERMOCOUPLE_SIGN_SHIFT (31)
/**
@brief Mask value for the thermocouple sign bit.
*/
#define MAX31855_THERMOCOUPLE_SIGN_MASK (1UL << MAX31855_THERMOCOUPLE_SIGN_SHIFT)
/**
* @brief Shift value for the thermocouple integer bits.
*/
#define MAX31855_THERMOCOUPLE_INTEGER_SHIFT (20)
/**
* @brief Mask value for the thermocouple integer bits.
*/
#define MAX31855_THERMOCOUPLE_INTEGER_MASK (0x7FFUL << MAX31855_THERMOCOUPLE_INTEGER_SHIFT)
/**
* @brief Shift value for the thermocouple fractional half degree bit.
*/
#define MAX31855_THERMOCOUPLE_FRACTIONAL_HALF_SHIFT (19)
/**
* @brief Mask value for the thermocouple fractional half degree bit.
*/
#define MAX31855_THERMOCOUPLE_FRACTIONAL_HALF_MASK (1UL << MAX31855_THERMOCOUPLE_FRACTIONAL_HALF_SHIFT)
/**
* @brief Shift value for the thermocouple fractional quarter degree bit.
*/
#define MAX31855_THERMOCOUPLE_FRACTIONAL_QUARTER_SHIFT (18)
/**
* @brief Mask value for the thermocouple fractional quarter degree bit.
*/
#define MAX31855_THERMOCOUPLE_FRACTIONAL_QUARTER_MASK (1UL << MAX31855_THERMOCOUPLE_FRACTIONAL_QUARTER_SHIFT)
/**
* @brief Shift value for the internal sign bit.
*/
#define MAX31855_INTERNAL_SIGN_SHIFT (15)
/**
@brief Mask value for the internal sign bit.
*/
#define MAX31855_INTERNAL_SIGN_MASK (1UL << MAX31855_INTERNAL_SIGN_SHIFT)
/**
* @brief Shift value for the internal integer bits.
*/
#define MAX31855_INTERNAL_INTEGER_SHIFT (8)
/**
* @brief Mask value for the internal integer bits.
*/
#define MAX31855_INTERNAL_INTEGER_MASK (0x7FUL << MAX31855_INTERNAL_INTEGER_SHIFT)
/**
* @brief Shift value for the internal fractional half degree bit.
*/
#define MAX31855_INTERNAL_FRACTIONAL_HALF_SHIFT (7)
/**
* @brief Mask value for the internal fractional half degree bit.
*/
#define MAX31855_INTERNAL_FRACTIONAL_HALF_MASK (1UL << MAX31855_INTERNAL_FRACTIONAL_HALF_SHIFT)
/**
* @brief Shift value for the internal fractional quarter degree bit.
*/
#define MAX31855_INTERNAL_FRACTIONAL_QUARTER_SHIFT (6)
/**
* @brief Mask value for the internal fractional quarter degree bit.
*/
#define MAX31855_INTERNAL_FRACTIONAL_QUARTER_MASK (1UL << MAX31855_INTERNAL_FRACTIONAL_QUARTER_SHIFT)
/**
* @brief Shift value for the internal fractional eighth degree bit.
*/
#define MAX31855_INTERNAL_FRACTIONAL_EIGHTH_SHIFT (5)
/**
* @brief Shift value for the internal fractional eighth degree bit.
*/
#define MAX31855_INTERNAL_FRACTIONAL_EIGHTH_MASK (1UL << MAX31855_INTERNAL_FRACTIONAL_EIGHTH_SHIFT)
/**
* @brief Shift value for the internal fractional sixteenth degree bit.
*/
#define MAX31855_INTERNAL_FRACTIONAL_SIXTEENTH_SHIFT (4)
/**
* @brief Shift value for the internal fractional sixteenth degree bit.
*/
#define MAX31855_INTERNAL_FRACTIONAL_SIXTEENTH_MASK (1UL << MAX31855_INTERNAL_FRACTIONAL_SIXTEENTH_SHIFT)
/**
* @brief Shift value for the fault bit indicating a VCC short.
*/
#define MAX31855_FAULT_VCC_SHORT_SHIFT (2)
/**
* @brief Mask value for the fault bit indicating a VCC short.
*/
#define MAX31855_FAULT_VCC_SHORT_MASK (1UL << MAX31855_FAULT_VCC_SHORT_SHIFT)
/**
* @brief Shift value for the fault bit indicating a GND short.
*/
#define MAX31855_FAULT_GND_SHORT_SHIFT (1)
/**
* @brief Mask value for the fault bit indicating a GND short.
*/
#define MAX31855_FAULT_GND_SHORT_MASK (1UL << MAX31855_FAULT_GND_SHORT_SHIFT)
/**
* @brief Shift value for the fault bit indicating an open circuit.
*/
#define MAX31855_FAULT_OPEN_CIRCUIT_SHIFT (0)
/**
* @brief Mask value for the fault bit indicating an open circuit.
*/
#define MAX31855_FAULT_OPEN_CIRCUIT_MASK (1UL << MAX31855_FAULT_OPEN_CIRCUIT_SHIFT)
/**
* @brief Mask value for the fault bits.
*/
#define MAX31855_FAULT_MASK (MAX31855_FAULT_VCC_SHORT_MASK | \
MAX31855_FAULT_GND_SHORT_MASK | \
MAX31855_FAULT_OPEN_CIRCUIT_MASK)
#ifdef __cplusplus
}
#endif
#endif /* MAX31855_CONSTANTS_H */
/** @} */

View File

@ -0,0 +1,86 @@
/*
* Copyright (C) 2024 HAW Hamburg
*
* 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.
*/
/**
* @ingroup drivers_max31855
*
* @{
* @file
* @brief Default configuration for the MAX31855 driver
*
* @author Leandro Lanzieri <leandro.lanzieri@haw-hamburg.de>
*/
#ifndef MAX31855_PARAMS_H
#define MAX31855_PARAMS_H
#include "board.h"
#include "saul_reg.h"
#include "max31855.h"
#include "max31855_constants.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @name Default configuration for the MAX31855 driver
* @{
*/
/**
* @brief Default SPI bus for the MAX31855 driver
*/
#ifndef MAX31855_PARAM_PARAM_SPI
#define MAX31855_PARAM_PARAM_SPI (SPI_DEV(0))
#endif
/**
* @brief Default CS pin for the MAX31855 driver
*/
#ifndef MAX31855_PARAM_PARAM_CS_PIN
#define MAX31855_PARAM_PARAM_CS_PIN (GPIO_PIN(0, 4))
#endif
/**
* @brief Default parameters for the MAX31855 driver
*/
#ifndef MAX31855_PARAMS
#define MAX31855_PARAMS { \
.spi = MAX31855_PARAM_PARAM_SPI, \
.cs_pin = MAX31855_PARAM_PARAM_CS_PIN, \
}
#endif
/**
* @brief Default SAUL info for the MAX31855 driver
*/
#ifndef MAX31855_SAULINFO
#define MAX31855_SAULINFO { .name = "max31855_thermo" },{ .name = "max31855_internal" }
#endif
/**@}*/
/**
* @brief Configuration structs for the MAX31855 driver
*/
static const max31855_params_t max31855_params[] =
{
MAX31855_PARAMS
};
/**
* @brief Allocate and configure entries to the SAUL registry
*/
static const saul_reg_info_t max31855_saul_info[] =
{
MAX31855_SAULINFO
};
#ifdef __cplusplus
}
#endif
#endif /* MAX31855_PARAMS_H */
/** @} */

156
drivers/max31855/max31855.c Normal file
View File

@ -0,0 +1,156 @@
/*
* Copyright (C) 2024 HAW Hamburg
*
* 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.
*/
/**
* @ingroup drivers_max31855
* @{
*
* @file
* @brief Device driver implementation for the drivers_sensors
*
* @author Leandro Lanzieri <leandro.lanzieri@haw-hamburg.de>
*
* @}
*/
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include "byteorder.h"
#include "log.h"
#include "max31855.h"
#include "max31855_constants.h"
#include "max31855_params.h"
void _raw_data_to_thermocouple_temperature(uint32_t raw_data, int32_t *temperature)
{
assert(temperature);
int32_t is_negative = raw_data & MAX31855_THERMOCOUPLE_SIGN_MASK;
if (is_negative) {
raw_data = ~raw_data + 1;
}
*temperature = (raw_data & MAX31855_THERMOCOUPLE_INTEGER_MASK) >>
MAX31855_THERMOCOUPLE_INTEGER_SHIFT;
/* convert to centi degC */
*temperature = *temperature * 100;
/* add fractional parts */
*temperature += (raw_data & MAX31855_THERMOCOUPLE_FRACTIONAL_HALF_MASK) ? 50 : 0;
*temperature += (raw_data & MAX31855_THERMOCOUPLE_FRACTIONAL_QUARTER_MASK) ? 25 : 0;
if (is_negative) {
*temperature *= -1;
}
}
void _raw_data_to_internal_temperature(uint32_t raw_data, int32_t *temperature)
{
assert(temperature);
int32_t is_negative = raw_data & MAX31855_INTERNAL_SIGN_MASK;
if (is_negative) {
raw_data = ~raw_data + 1;
}
*temperature = (raw_data & MAX31855_INTERNAL_INTEGER_MASK) >> MAX31855_INTERNAL_INTEGER_SHIFT;
/* convert to centi degC */
*temperature = *temperature * 100;
/* add fractional parts */
*temperature += (raw_data & MAX31855_INTERNAL_FRACTIONAL_HALF_MASK) ? 50 : 0;
*temperature += (raw_data & MAX31855_INTERNAL_FRACTIONAL_QUARTER_MASK) ? 25 : 0;
*temperature += (raw_data & MAX31855_INTERNAL_FRACTIONAL_EIGHTH_MASK) ? 12 : 0;
*temperature += (raw_data & MAX31855_INTERNAL_FRACTIONAL_SIXTEENTH_MASK) ? 6 : 0;
if (is_negative) {
*temperature *= -1;
}
}
void _raw_data_to_fault(uint32_t raw_data, max31855_fault_t *fault)
{
assert(fault);
switch (raw_data & MAX31855_FAULT_MASK) {
case MAX31855_FAULT_VCC_SHORT_MASK:
*fault = MAX31855_FAULT_VCC_SHORT;
break;
case MAX31855_FAULT_GND_SHORT_MASK:
*fault = MAX31855_FAULT_GND_SHORT;
break;
case MAX31855_FAULT_OPEN_CIRCUIT_MASK:
*fault = MAX31855_FAULT_OPEN_CIRCUIT;
break;
default:
*fault = MAX31855_FAULT_NO_FAULT;
break;
}
}
int max31855_init(max31855_t *dev, const max31855_params_t *params)
{
assert(dev);
assert(params);
dev->params = params;
int ret = spi_init_cs(dev->params->spi, dev->params->cs_pin);
if (ret < 0) {
LOG_ERROR("Failed to initialize MAX31855\n");
return ret;
}
return 0;
}
void max31855_read_raw(max31855_t *dev, uint32_t *data)
{
assert(dev);
assert(data);
uint8_t buffer[MAX31855_DATA_SIZE] = { 0 };
spi_acquire(dev->params->spi, dev->params->cs_pin, SPI_MODE_0, SPI_CLK_5MHZ);
spi_transfer_bytes(dev->params->spi, dev->params->cs_pin, false, NULL, buffer, sizeof(buffer));
spi_release(dev->params->spi);
*data = byteorder_bebuftohl(buffer);
}
void max31855_raw_to_data(uint32_t raw_data, max31855_data_t *data)
{
assert(data);
_raw_data_to_thermocouple_temperature(raw_data, &data->thermocouple_temperature);
_raw_data_to_internal_temperature(raw_data, &data->internal_temperature);
_raw_data_to_fault(raw_data, &data->fault);
}
int max31855_read(max31855_t *dev, max31855_data_t *data)
{
assert(dev && data);
uint32_t raw_data;
max31855_read_raw(dev, &raw_data);
max31855_raw_to_data(raw_data, data);
if (data->fault != MAX31855_FAULT_NO_FAULT) {
LOG_ERROR("MAX31855 fault: %d\n", data->fault);
return -EIO;
}
return 0;
}

View File

@ -0,0 +1,69 @@
/*
* Copyright (C) 2024 HAW Hamburg
*
* 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.
*/
/**
* @ingroup drivers_max31855
* @{
*
* @file
* @brief SAUL adaption for MAX31855 devices
*
* @author Leandro Lanzieri <leandro.lanzieri@haw-hamburg.de>
*
* @}
*/
#include <string.h>
#include "saul.h"
#include "phydat.h"
#include "max31855.h"
static int read_temp(const void *dev, phydat_t *res, bool thermocouple)
{
max31855_data_t data;
max31855_read((max31855_t *)dev, &data);
if (data.fault != MAX31855_FAULT_NO_FAULT) {
return -ECANCELED;
}
res->unit = UNIT_TEMP_C;
res->scale = -2;
if (thermocouple) {
phydat_fit(res, &data.thermocouple_temperature, 1);
}
else {
phydat_fit(res, &data.internal_temperature, 1);
}
return 1;
}
static int read_thermo_temp(const void *dev, phydat_t *res)
{
return read_temp(dev, res, true);
}
static int read_internal_temp(const void *dev, phydat_t *res)
{
return read_temp(dev, res, false);
}
const saul_driver_t max31855_thermo_temp_saul_driver = {
.read = read_thermo_temp,
.write = saul_write_notsup,
.type = SAUL_SENSE_TEMP
};
const saul_driver_t max31855_internal_temp_saul_driver = {
.read = read_internal_temp,
.write = saul_write_notsup,
.type = SAUL_SENSE_TEMP
};

View File

@ -0,0 +1,80 @@
/*
* Copyright (C) 2024 HAW Hamburg
*
* 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.
*
*/
/*
* @ingroup sys_auto_init_saul
* @{
*
* @file
* @brief Auto initialization for MAX31855 thermocouple-to-digital converters
*
* @author Leandro Lanzieri <leandro.lanzieri@haw-hamburg.de>
*
* @}
*/
#include "assert.h"
#include "log.h"
#include "saul_reg.h"
#include "max31855_params.h"
#include "max31855.h"
/**
* @brief Define the number of configured devices
*/
#define MAX31855_NUM ARRAY_SIZE(max31855_params)
/**
* @brief Allocate memory for the device descriptors
*/
static max31855_t max31855_devs[MAX31855_NUM];
/**
* @brief Memory for the SAUL registry entries
*/
static saul_reg_t saul_entries[MAX31855_NUM * 2];
/**
* @brief Define the number of saul info
*/
#define MAX31855_INFO_NUM ARRAY_SIZE(max31855_saul_info)
/**
* @name Import SAUL endpoints
* @{
*/
extern const saul_driver_t max31855_thermo_temp_saul_driver;
extern const saul_driver_t max31855_internal_temp_saul_driver;
/** @} */
void auto_init_max31855(void)
{
/* each device has thermocouple and internal temperature, needs 2 infos */
assert(MAX31855_INFO_NUM == 2 * MAX31855_NUM);
for (unsigned int i = 0; i < MAX31855_NUM; i++) {
LOG_DEBUG("[auto_init_saul] initializing MAX31855 #%u\n", i);
if (max31855_init(&max31855_devs[i], &max31855_params[i]) != 0) {
LOG_ERROR("[auto_init_saul] error initializing MAX31855 #%u\n", i);
continue;
}
saul_entries[(i * 2)].dev = &(max31855_devs[i]);
saul_entries[(i * 2)].name = max31855_saul_info[(i * 2)].name;
saul_entries[(i * 2)].driver = &max31855_thermo_temp_saul_driver;
saul_entries[(i * 2) + 1].dev = &(max31855_devs[i]);
saul_entries[(i * 2) + 1].name = max31855_saul_info[(i * 2) + 1].name;
saul_entries[(i * 2) + 1].driver = &max31855_internal_temp_saul_driver;
saul_reg_add(&(saul_entries[(i * 2)]));
saul_reg_add(&(saul_entries[(i * 2) + 1]));
}
}

View File

@ -207,6 +207,10 @@ void saul_init_devs(void)
extern void auto_init_mag3110(void);
auto_init_mag3110();
}
if (IS_USED(MODULE_MAX31855)) {
extern void auto_init_max31855(void);
auto_init_max31855();
}
if (IS_USED(MODULE_MCP47XX)) {
extern void auto_init_mcp47xx(void);
auto_init_mcp47xx();