diff --git a/drivers/adt7310/Makefile b/drivers/adt7310/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/drivers/adt7310/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/adt7310/adt7310.c b/drivers/adt7310/adt7310.c new file mode 100644 index 0000000000..a9111e3883 --- /dev/null +++ b/drivers/adt7310/adt7310.c @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2015 Eistec AB + * + * 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_adt7310 + * @{ + * + * @file + * @brief Driver for the ADT7310 ±0.5°C Accurate, 16-Bit Digital SPI + * Temperature Sensor from Analog Devices + * + * @author Joakim Gebart + * + * @} + */ + +#include +#include +#include +#include "adt7310.h" +#include "byteorder.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +/* SPI command byte parameters */ +#define ADT7310_CMD_READ (0x40) +#define ADT7310_CMD_WRITE (0x00) +#define ADT7310_CMD_ADDR_SHIFT (3) +#define ADT7310_CMD_CONTINUOUS (0x04) + +/* ****************** * + * Register addresses * + * ****************** */ +#define ADT7310_REG_STATUS (0x00) +#define ADT7310_REG_CONFIG (0x01) +#define ADT7310_REG_VALUE (0x02) +#define ADT7310_REG_ID (0x03) +#define ADT7310_REG_TCRIT (0x04) +#define ADT7310_REG_THYST (0x05) +#define ADT7310_REG_THIGH (0x06) +#define ADT7310_REG_TLOW (0x07) + +/* ************** * + * Register sizes * + * ************** */ +#define ADT7310_REG_SIZE_STATUS (1) +#define ADT7310_REG_SIZE_CONFIG (1) +#define ADT7310_REG_SIZE_VALUE (2) +#define ADT7310_REG_SIZE_ID (1) +#define ADT7310_REG_SIZE_TCRIT (2) +#define ADT7310_REG_SIZE_THYST (1) +#define ADT7310_REG_SIZE_THIGH (2) +#define ADT7310_REG_SIZE_TLOW (2) + +/* Register bit masks */ +/** @brief Manufacturer ID */ +#define ADT7310_REG_ID_MASK_MANUFACTURER_ID (0xF8) +/** @brief Silicon version */ +#define ADT7310_REG_ID_MASK_SILICON_VERSION (0x07) + +/** @brief Expected manufacturer ID */ +#define ADT7310_EXPECTED_MANUF_ID (0b11000000) + +/** @brief 13 bit temperature mask */ +#define ADT7310_REG_VALUE_MASK_13BIT (0xF8) + +/** @brief Number of fractional bits in the raw readings */ +#define ADT7310_VALUE_FRAC_BITS (7) + +/** @brief Scale factor for converting raw temperature readings to degrees + * Celsius, floating point number */ +#define ADT7310_TEMPERATURE_LSB_FLOAT (1.f/((float)((int)1 << ADT7310_VALUE_FRAC_BITS))) + +/** + * @brief Read a register from the sensor + * + * @param[in] dev device descriptor + * @param[in] addr register address + * @param[in] len register size + * @param[out] buf destination buffer + * + * @return 0 on success + * @return -1 on communication errors + */ +static int adt7310_read_reg(const adt7310_t *dev, const uint8_t addr, const uint8_t len, + uint8_t *buf) +{ + int status = 0; + uint8_t command = ADT7310_CMD_READ | (addr << ADT7310_CMD_ADDR_SHIFT); + /* Acquire exclusive access to the bus. */ + spi_acquire(dev->spi); + /* Perform the transaction */ + gpio_clear(dev->cs); + + if (spi_transfer_regs(dev->spi, command, NULL, (char *)buf, len) < len) { + status = -1; + } + + gpio_set(dev->cs); + /* Release the bus for other threads. */ + spi_release(dev->spi); + + return status; +} + +/** + * @brief Write a register value to the sensor + * + * @param[in] dev device descriptor + * @param[in] addr register address + * @param[in] len register size + * @param[in] buf source buffer + * + * @return 0 on success + * @return -1 on communication errors + */ +static int adt7310_write_reg(const adt7310_t *dev, const uint8_t addr, + const uint8_t len, uint8_t *buf) +{ + int status = 0; + uint8_t command = ADT7310_CMD_WRITE | (addr << ADT7310_CMD_ADDR_SHIFT); + /* Acquire exclusive access to the bus. */ + spi_acquire(dev->spi); + /* Perform the transaction */ + gpio_clear(dev->cs); + + if (spi_transfer_regs(dev->spi, command, (char *)buf, NULL, len) < len) { + status = -1; + } + + gpio_set(dev->cs); + /* Release the bus for other threads. */ + spi_release(dev->spi); + + return status; +} + +int adt7310_init(adt7310_t *dev, spi_t spi, gpio_t cs) +{ + int status; + uint8_t reg = 0; + /* write device descriptor */ + dev->spi = spi; + dev->cs = cs; + dev->initialized = false; + dev->high_res = false; + + /* CS */ + gpio_init(dev->cs, GPIO_DIR_OUT, GPIO_NOPULL); + gpio_set(dev->cs); + +#if ENABLE_DEBUG + for (int i = 0; i < 8; ++i) { + uint16_t dbg_reg = 0; + status = adt7310_read_reg(dev, i, sizeof(dbg_reg), (uint8_t *)&dbg_reg); + if (status != 0) { + printf("Error reading address 0x%02x", i); + } + dbg_reg = HTONS(dbg_reg); + printf("%02x: %04" PRIx16 "\n", i, dbg_reg); + } +#endif + + /* Read ID register from device */ + status = adt7310_read_reg(dev, ADT7310_REG_ID, ADT7310_REG_SIZE_ID, ®); + if (status != 0) { + /* SPI bus error */ + return -1; + } + if ((reg & ADT7310_REG_ID_MASK_MANUFACTURER_ID) != ADT7310_EXPECTED_MANUF_ID) { + /* Wrong part ID */ + return -2; + } + + /* Set a configuration, go to shut down mode to save power until the sensor is needed. */ + if (adt7310_set_config(dev, ADT7310_MODE_SHUTDOWN) != 0) { + /* communication error */ + return -3; + } + + dev->initialized = true; + return 0; +} + +int adt7310_set_config(adt7310_t *dev, uint8_t config) +{ + if (config & ADT7310_CONF_RESOLUTION_MASK) { + dev->high_res = true; + } + return adt7310_write_reg(dev, ADT7310_REG_CONFIG, ADT7310_REG_SIZE_CONFIG, &config); +} + +int16_t adt7310_read_raw(adt7310_t *dev) +{ + int status; + int16_t raw; + + /* Read the temperature value register */ + status = adt7310_read_reg(dev, ADT7310_REG_VALUE, ADT7310_REG_SIZE_VALUE, (uint8_t*)&raw); + if (status < 0) { + /* communication error */ + return INT16_MIN; + } + /* The temperature value is sent big endian (network byte order) */ + raw = (int16_t)NTOHS((uint16_t)raw); + return raw; +} + +int32_t adt7310_read(adt7310_t *dev) +{ + int16_t raw = adt7310_read_raw(dev); + if (raw == INT16_MIN) { + return INT32_MIN; + } + if (!dev->high_res) { + /* filter out the flag bits */ + raw &= ADT7310_REG_VALUE_MASK_13BIT; + } + return ((((int32_t)raw) * 1000) >> ADT7310_VALUE_FRAC_BITS); +} + +float adt7310_read_float(adt7310_t *dev) +{ + int16_t raw = adt7310_read_raw(dev); + if (raw == INT16_MIN) { + return (0.0f / 0.0f); /* return NaN */ + } + if (!dev->high_res) { + /* filter out the flag bits */ + raw &= ADT7310_REG_VALUE_MASK_13BIT; + } + return (((float) raw) * ADT7310_TEMPERATURE_LSB_FLOAT); +} diff --git a/drivers/include/adt7310.h b/drivers/include/adt7310.h new file mode 100644 index 0000000000..91ecb51dce --- /dev/null +++ b/drivers/include/adt7310.h @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2015 Eistec AB + * + * 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_adt7310 ADT7310 SPI temperature sensor + * @ingroup drivers + * @brief Driver for the Analog Devices ADT7310 temperature sensor. + * + * ## Description + * + * The ADT7310 is a high accuracy digital temperature sensor + * in a narrow SOIC package. It contains a band gap temperature + * reference and a 13-bit ADC to monitor and digitize the + * temperature to a 0.0625°C resolution. The ADC resolution, + * by default, is set to 13 bits (0.0625 °C). This can be changed + * to 16 bits (0.0078 °C) by setting Bit 7 in the configuration + * register (Register Address 0x01). + * The ADT7310 is guaranteed to operate over supply voltages from + * 2.7 V to 5.5 V. Operating at 3.3 V, the average supply current is + * typically 210 μA. The ADT7310 has a shutdown mode that + * powers down the device and offers a shutdown current of + * typically 2 μA. The ADT7310 is rated for operation over the + * −55°C to +150°C temperature range. + * + * ## Usage + * + * See `tests/driver_adt7310` for an example application using this driver. + * + * ## Caveats + * + * This driver is currently missing support for a number of hardware features: + * + * - Interrupt and compare pins are not handled + * - There is no public API for setting the temperature alarm levels + * - Device SPI reset is not implemented (drive MISO high from the master while clocking SCK) + * + * @{ + * + * @file + * @brief Interface definition for the ADT7310 sensor driver. + * + * @author Joakim Gebart + */ + +#ifndef ADT7310_H_ +#define ADT7310_H_ + +#include +#include +#include "periph/spi.h" +#include "periph/gpio.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Device descriptor for ADT7310 sensors. + */ +typedef struct { + spi_t spi; /**< SPI bus the sensor is connected to */ + gpio_t cs; /**< CS pin GPIO handle */ + bool initialized; /**< sensor status, true if sensor is initialized */ + bool high_res; /**< Sensor resolution, true if configured to 16 bit resolution */ +} adt7310_t; + +/** @name ADT7310 configuration bits */ +/** @{ */ +#define ADT7310_CONF_FAULT_QUEUE_MASK (0x03) +#define ADT7310_CONF_FAULT_QUEUE_SHIFT (0) +#define ADT7310_CONF_FAULT_QUEUE(x) (((x) << ADT7310_CONF_FAULT_QUEUE_SHIFT) & ADT7310_CONF_FAULT_QUEUE_MASK) +#define ADT7310_CONF_CT_POL_MASK (0x04) +#define ADT7310_CONF_CT_POL_SHIFT (2) +#define ADT7310_CONF_CT_POL(x) (((x) << ADT7310_CONF_CT_POL_SHIFT) & ADT7310_CONF_CT_POL_MASK) +#define ADT7310_CONF_INT_POL_MASK (0x08) +#define ADT7310_CONF_INT_POL_SHIFT (3) +#define ADT7310_CONF_INT_POL(x) (((x) << ADT7310_CONF_INT_POL_SHIFT) & ADT7310_CONF_INT_POL_MASK) +#define ADT7310_CONF_INTCT_MODE_MASK (0x10) +#define ADT7310_CONF_INTCT_MODE_SHIFT (4) +#define ADT7310_CONF_INTCT_MODE(x) (((x) << ADT7310_CONF_INTCT_MODE_SHIFT) & ADT7310_CONF_INTCT_MODE_MASK) +#define ADT7310_CONF_OPERATION_MODE_MASK (0x60) +#define ADT7310_CONF_OPERATION_MODE_SHIFT (5) +#define ADT7310_CONF_OPERATION_MODE(x) (((x) << ADT7310_CONF_OPERATION_MODE_SHIFT) & ADT7310_CONF_OPERATION_MODE_MASK) +#define ADT7310_CONF_RESOLUTION_MASK (0x80) +#define ADT7310_CONF_RESOLUTION_SHIFT (7) +#define ADT7310_CONF_RESOLUTION(x) (((x) << ADT7310_CONF_RESOLUTION_SHIFT) & ADT7310_CONF_RESOLUTION_MASK) + +/** @brief Continuous operation mode */ +#define ADT7310_MODE_CONTINUOUS (ADT7310_CONF_OPERATION_MODE(0)) +/** @brief One shot */ +#define ADT7310_MODE_ONE_SHOT (ADT7310_CONF_OPERATION_MODE(1)) +/** @brief 1 sample per second */ +#define ADT7310_MODE_1SPS (ADT7310_CONF_OPERATION_MODE(2)) +/** @brief Shut down (powersave) */ +#define ADT7310_MODE_SHUTDOWN (ADT7310_CONF_OPERATION_MODE(3)) +/** @} */ + +/** + * @brief Set configuration register of an ADT7310 sensor + * + * @param[in] dev pointer to sensor device descriptor + * @param[in] config configuration byte, see macros in adt7310.h + * + * @return 0 on success + * @return -1 on error + */ +int adt7310_set_config(adt7310_t *dev, uint8_t config); + +/** + * @brief Initialize the ADT7310 sensor driver. + * + * @note The SPI bus is expected to have been initialized when adt7310_init is called. + * + * @param[in] dev pointer to sensor device descriptor + * @param[in] spi SPI bus the sensor is connected to + * @param[in] cs GPIO pin the chip select signal is connected to + * + * @return 0 on success + * @return <0 on error + */ +int adt7310_init(adt7310_t *dev, spi_t spi, gpio_t cs); + +/** + * @brief Read raw temperature register value + * + * @note The three least-significant bits of the value register are used for + * flags if the sensor is configured for 13 bit mode. + * + * @param[in] dev pointer to sensor device descriptor + * + * @return raw sensor value on success + * @return INT16_MIN on error + */ +int16_t adt7310_read_raw(adt7310_t *dev); + +/** + * @brief Read temperature value from sensor and convert to milli-degrees Celsius. + * + * Divide the returned value by 1000 to get integer degrees. + * + * @param[in] dev pointer to sensor device descriptor + * + * @return temperature in milli-degrees Celsius + * @return INT32_MIN on errors + */ +int32_t adt7310_read(adt7310_t *dev); + +/** + * @brief Read temperature value from sensor and convert to degrees Celsius. + * + * @param[in] dev pointer to sensor device descriptor + * + * @return floating point representation of temperature in degrees Celsius + * @return NaN on errors + */ +float adt7310_read_float(adt7310_t *dev); + +#ifdef __cplusplus +} +#endif + +#endif /* ADT7310_H_ */ +/** @} */