From 073d45aa4d0ac23ab36f42e46bfb9f8edb2d5dfb Mon Sep 17 00:00:00 2001 From: Matthew Blue Date: Wed, 4 Apr 2018 16:57:58 -0400 Subject: [PATCH] drivers/ads101x: initial support --- drivers/ads101x/Makefile | 1 + drivers/ads101x/ads101x.c | 216 +++++++++++++++++++++++ drivers/ads101x/ads101x_saul.c | 70 ++++++++ drivers/ads101x/include/ads101x_params.h | 100 +++++++++++ drivers/ads101x/include/ads101x_regs.h | 107 +++++++++++ drivers/include/ads101x.h | 175 ++++++++++++++++++ 6 files changed, 669 insertions(+) create mode 100644 drivers/ads101x/Makefile create mode 100644 drivers/ads101x/ads101x.c create mode 100644 drivers/ads101x/ads101x_saul.c create mode 100644 drivers/ads101x/include/ads101x_params.h create mode 100644 drivers/ads101x/include/ads101x_regs.h create mode 100644 drivers/include/ads101x.h diff --git a/drivers/ads101x/Makefile b/drivers/ads101x/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/drivers/ads101x/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/ads101x/ads101x.c b/drivers/ads101x/ads101x.c new file mode 100644 index 0000000000..1c03a475bf --- /dev/null +++ b/drivers/ads101x/ads101x.c @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2017 OTA keys S.A. + * 2018 Matthew Blue + * + * 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_ads101x + * @{ + * + * @file + * @brief ADS101x/111x ADC device driver + * + * @author Vincent Dupont + * @author Matthew Blue + * @} + */ + +#include "assert.h" +#include "periph/i2c.h" +#include "periph/gpio.h" +#include "xtimer.h" + +#include "ads101x.h" +#include "ads101x_params.h" +#include "ads101x_regs.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#ifndef ADS101X_READ_DELAY +#define ADS101X_READ_DELAY (8 * US_PER_MS) /* Compatible with 128SPS */ +#endif + +#define I2C_SPEED I2C_SPEED_FAST + +#define I2C (dev->params.i2c) +#define ADDR (dev->params.addr) + +static int _ads101x_init_test(i2c_t i2c, uint8_t addr); + +int ads101x_init(ads101x_t *dev, const ads101x_params_t *params) +{ + assert(dev && params); + + dev->params = *params; + + return _ads101x_init_test(I2C, ADDR); +} + +int ads101x_alert_init(ads101x_alert_t *dev, + const ads101x_alert_params_t *params) +{ + assert(dev && params); + + dev->params = *params; + dev->cb = NULL; + dev->arg = NULL; + + /* Set up alerts */ + ads101x_set_alert_parameters(dev, dev->params.low_limit, + dev->params.high_limit); + + return _ads101x_init_test(I2C, ADDR); +} + +int _ads101x_init_test(i2c_t i2c, uint8_t addr) +{ + uint8_t regs[2]; + + /* Acquire test */ + i2c_acquire(i2c); + if (i2c_init_master(i2c, I2C_SPEED) < 0) { + i2c_release(i2c); + DEBUG("[ads101x] init - error: unable to initialize I2C bus\n"); + return ADS101X_NOI2C; + } + + /* Register read/write test */ + i2c_read_regs(i2c, addr, ADS101X_CONF_ADDR, ®s, 2); + regs[1] = (regs[1] & ~ADS101X_DATAR_MASK) | ADS101X_DATAR_3300; + + i2c_write_regs(i2c, addr, ADS101X_CONF_ADDR, ®s, 2); + i2c_read_regs(i2c, addr, ADS101X_CONF_ADDR, ®s, 2); + + i2c_release(i2c); + + /* Write should have actually written the register */ + if ((regs[1] & ADS101X_DATAR_MASK) != ADS101X_DATAR_3300) { + DEBUG("[ads101x] init - error: unable to set reg (reg=%x)\n", regs[1]); + return ADS101X_NODEV; + } + + return ADS101X_OK; +} + +int ads101x_set_mux_gain(const ads101x_t *dev, uint8_t mux_gain) +{ + uint8_t regs[2]; + + i2c_acquire(I2C); + + i2c_read_regs(I2C, ADDR, ADS101X_CONF_ADDR, ®s, 2); + + /* Zero mux and gain */ + regs[0] &= ~ADS101X_MUX_MASK; + regs[0] &= ~ADS101X_PGA_MASK; + + /* Write mux and gain */ + regs[0] |= mux_gain; + + i2c_write_regs(I2C, ADDR, ADS101X_CONF_ADDR, ®s, 2); + + i2c_release(I2C); + + return ADS101X_OK; +} + +int ads101x_read_raw(const ads101x_t *dev, int16_t *raw) +{ + uint8_t regs[2]; + int status; + + i2c_acquire(I2C); + + /* Read control register */ + i2c_read_regs(I2C, ADDR, ADS101X_CONF_ADDR, ®s, 2); + + /* Tell the ADC to aquire a single-shot sample */ + regs[0] |= ADS101X_CONF_OS_CONV; + i2c_write_regs(I2C, ADDR, ADS101X_CONF_ADDR, ®s, 2); + + /* Wait for the sample to be aquired */ + xtimer_usleep(ADS101X_READ_DELAY); + + /* Read the sample */ + status = i2c_read_regs(I2C, ADDR, ADS101X_CONV_RES_ADDR, ®s, 2); + + i2c_release(I2C); + + /* Status should equal bytes asked for (2) */ + if (status != 2) { + return ADS101X_NODATA; + } + + /* If all okay, change raw value */ + *raw = (int16_t)(regs[0] << 8) | (int16_t)(regs[1]); + + return ADS101X_OK; +} + +int ads101x_enable_alert(ads101x_alert_t *dev, + ads101x_alert_cb_t cb, void *arg) +{ + uint8_t regs[2]; + + if (dev->params.alert_pin == GPIO_UNDEF) { + return ADS101X_OK; + } + + /* Read control register */ + i2c_acquire(I2C); + i2c_read_regs(I2C, ADDR, ADS101X_CONF_ADDR, ®s, 2); + + /* Enable alert comparator */ + regs[1] &= ~ADS101X_CONF_COMP_DIS; + i2c_write_regs(I2C, ADDR, ADS101X_CONF_ADDR, ®s, 2); + + i2c_release(I2C); + + /* Enable interrupt */ + dev->arg = arg; + dev->cb = cb; + gpio_init_int(dev->params.alert_pin, GPIO_IN, GPIO_FALLING, cb, arg); + + return ADS101X_OK; +} + +int ads101x_set_alert_parameters(const ads101x_alert_t *dev, + int16_t low_limit, int16_t high_limit) +{ + uint8_t regs[2]; + + i2c_acquire(I2C); + + /* Set up low_limit */ + regs[0] = (uint8_t)(low_limit >> 8); + regs[1] = (uint8_t)low_limit; + i2c_write_regs(I2C, ADDR, ADS101X_LOW_LIMIT_ADDR, ®s, 2); + + /* Set up high_limit */ + regs[0] = (uint8_t)(high_limit >> 8); + regs[1] = (uint8_t)high_limit; + i2c_write_regs(I2C, ADDR, ADS101X_HIGH_LIMIT_ADDR, ®s, 2); + + /* Read control register */ + i2c_read_regs(I2C, ADDR, ADS101X_CONF_ADDR, ®s, 2); + + /* Set up window mode */ + if (low_limit != 0) { + /* Enable window mode */ + regs[1] |= ADS101X_CONF_COMP_MODE_WIND; + } + else { + /* Disable window mode */ + regs[1] &= ~ADS101X_CONF_COMP_MODE_WIND; + } + i2c_write_regs(I2C, ADDR, ADS101X_CONF_ADDR, ®s, 2); + + i2c_release(I2C); + + return ADS101X_OK; +} diff --git a/drivers/ads101x/ads101x_saul.c b/drivers/ads101x/ads101x_saul.c new file mode 100644 index 0000000000..76021bf44f --- /dev/null +++ b/drivers/ads101x/ads101x_saul.c @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2017 OTA keys S.A. + * 2018 Matthew Blue + * + * 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_ads101x + * @{ + * + * @file + * @brief ADS101x/111x adaption to the RIOT actuator/sensor interface + * + * @author Vincent Dupont + * @author Matthew Blue + * + * @} + */ + +#include +#include + +#include "saul.h" +#include "ads101x.h" +#include "ads101x_regs.h" + +/* LSB conversion to power of 10 + * (5/8) * 2^16 = 40960 */ +#define CONV_TO_B10 (40960L) + +static int read_adc(const void *dev, phydat_t *res) +{ + /* Change the mux channel */ + ads101x_set_mux_gain((const ads101x_t *)dev, + ((ads101x_t *)dev)->params.mux_gain); + + /* Read raw value */ + if (ads101x_read_raw((const ads101x_t *)dev, res->val) < 0) { + return ECANCELED; + } + + /* Special case for 2.048V */ + /* (this is the fixed FSR of ADS1013 and ADS1113) */ + if ((((ads101x_t *)dev)->params.mux_gain & ADS101X_PGA_MASK) + == ADS101X_PGA_FSR_2V048) { + + /* LSB == 62.5uV to LSB == 100uV */ + *(res->val) = (int16_t)((CONV_TO_B10 * (int32_t)*(res->val)) >> 16); + + /* 100uV == 2^-4 V */ + res->unit = UNIT_V; + res->scale = -4; + } + else { + /* Otherwise let the user deal with it */ + res->unit = UNIT_NONE; + res->scale = 0; + } + + return 1; +} + +const saul_driver_t ads101x_saul_driver = { + .read = read_adc, + .write = saul_notsup, + .type = SAUL_SENSE_ANALOG, +}; diff --git a/drivers/ads101x/include/ads101x_params.h b/drivers/ads101x/include/ads101x_params.h new file mode 100644 index 0000000000..22f9867500 --- /dev/null +++ b/drivers/ads101x/include/ads101x_params.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2017 OTA keys S.A. + * 2018 Matthew Blue + * + * 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_ads101x + * @{ + * + * @file + * @brief Default configuration for ADS101x/111x devices + * + * @author Vincent Dupont + * @author Matthew Blue + */ + +#ifndef ADS101X_PARAMS_H +#define ADS101X_PARAMS_H + +#include "board.h" +#include "saul_reg.h" +#include "ads101x.h" +#include "ads101x_regs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name Set default configuration parameters for the ADS101x/111x driver + * @{ + */ +#ifndef ADS101X_PARAM_I2C +#define ADS101X_PARAM_I2C (I2C_DEV(0)) +#endif +#ifndef ADS101X_PARAM_ADDR +#define ADS101X_PARAM_ADDR (ADS101X_I2C_ADDRESS) +#endif +#ifndef ADS101X_PARAM_MUX_GAIN +#define ADS101X_PARAM_MUX_GAIN (ADS101X_AIN0_DIFFM_AIN1 \ + | ADS101X_PGA_FSR_2V048) +#endif +#ifndef ADS101X_PARAM_ALERT_PIN +#define ADS101X_PARAM_ALERT_PIN (GPIO_UNDEF) +#endif +#ifndef ADS101X_PARAM_LOW_LIMIT +#define ADS101X_PARAM_LOW_LIMIT (10000U) +#endif +#ifndef ADS101X_PARAM_HIGH_LIMIT +#define ADS101X_PARAM_HIGH_LIMIT (20000U) +#endif + +#ifndef ADS101X_PARAMS +#define ADS101X_PARAMS { .i2c = ADS101X_PARAM_I2C, \ + .addr = ADS101X_PARAM_ADDR, \ + .mux_gain = ADS101X_PARAM_MUX_GAIN } +#endif + +#ifndef ADS101X_ALERT_PARAMS +#define ADS101X_ALERT_PARAMS { .i2c = ADS101X_PARAM_I2C, \ + .addr = ADS101X_PARAM_ADDR, \ + .alert_pin = ADS101X_PARAM_ALERT_PIN, \ + .low_limit = ADS101X_PARAM_LOW_LIMIT, \ + .high_limit = ADS101X_PARAM_HIGH_LIMIT } +#endif +#ifndef ADS101X_SAUL_INFO +#define ADS101X_SAUL_INFO { .name = "ads101x" } +#endif +/** @} */ + +/** + * @brief ADS101X/111x defaults if not defined for a board or application + */ +static const ads101x_params_t ads101x_params[] = +{ + ADS101X_PARAMS +}; +static const ads101x_alert_params_t ads101x_alert_params[] = +{ + ADS101X_ALERT_PARAMS +}; + +/** + * @brief Additional meta information to keep in the SAUL registry + */ +static const saul_reg_info_t ads101x_saul_info[] = +{ + ADS101X_SAUL_INFO +}; + +#ifdef __cplusplus +} +#endif + +#endif /* ADS101X_PARAMS_H */ +/** @} */ diff --git a/drivers/ads101x/include/ads101x_regs.h b/drivers/ads101x/include/ads101x_regs.h new file mode 100644 index 0000000000..128e6da134 --- /dev/null +++ b/drivers/ads101x/include/ads101x_regs.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2017 OTA keys S.A. + * 2018 Matthew Blue + * + * 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_ads101x + * @{ + * + * @file + * @brief Register definition for ADS101x/111x devices + * + * @author Vincent Dupont + * @author Matthew Blue + */ + +#ifndef ADS101X_REGS_H +#define ADS101X_REGS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name ADS101x/111x register addesses + * @{ + */ +#define ADS101X_CONV_RES_ADDR (0) +#define ADS101X_CONF_ADDR (1) +#define ADS101X_LOW_LIMIT_ADDR (2) +#define ADS101X_HIGH_LIMIT_ADDR (3) +/** @} */ + +/** + * @name ADS101x/111x Config flags + * + * Comparator flags have no effect on ADS1013 and ADS1113. + * + * @{ + */ +#define ADS101X_CONF_OS_CONV (1 << 7) +#define ADS101X_CONF_COMP_MODE_WIND (1 << 4) +#define ADS101X_CONF_COMP_DIS ((1 << 1) | (1 << 0)) +/** @} */ + +/** + * @name ADS101x/111x mux settings + * + * Supports both single mode and differential. + * This has no effect on ADS1013-4 and ADS1113-4. + * + * @{ + */ +#define ADS101X_MUX_MASK ((1 << 6) | (1 << 5) | (1 << 4)) +#define ADS101X_AIN0_DIFFM_AIN1 ((0 << 6) | (0 << 5) | (0 << 4)) +#define ADS101X_AIN0_DIFFM_AIN3 ((0 << 6) | (0 << 5) | (1 << 4)) +#define ADS101X_AIN1_DIFFM_AIN3 ((0 << 6) | (1 << 5) | (0 << 4)) +#define ADS101X_AIN2_DIFFM_AIN3 ((0 << 6) | (1 << 5) | (1 << 4)) +#define ADS101X_AIN0_SINGM ((1 << 6) | (0 << 5) | (0 << 4)) +#define ADS101X_AIN1_SINGM ((1 << 6) | (0 << 5) | (1 << 4)) +#define ADS101X_AIN2_SINGM ((1 << 6) | (1 << 5) | (0 << 4)) +#define ADS101X_AIN3_SINGM ((1 << 6) | (1 << 5) | (1 << 4)) +/** @} */ + +/** + * @name ADS101x/111x programmable gain + * + * Sets the full-scale range (max voltage value). + * This has no effect on ADS1013 and ADS1113 (both use 2.048V FSR). + * + * @{ + */ +#define ADS101X_PGA_MASK ((1 << 3) | (1 << 2) | (1 << 1)) +#define ADS101X_PGA_FSR_6V144 ((0 << 3) | (0 << 2) | (0 << 1)) +#define ADS101X_PGA_FSR_4V096 ((0 << 3) | (0 << 2) | (1 << 1)) +#define ADS101X_PGA_FSR_2V048 ((0 << 3) | (1 << 2) | (0 << 1)) +#define ADS101X_PGA_FSR_1V024 ((0 << 3) | (1 << 2) | (1 << 1)) +#define ADS101X_PGA_FSR_0V512 ((1 << 3) | (0 << 2) | (0 << 1)) +#define ADS101X_PGA_FSR_0V256 ((1 << 3) | (0 << 2) | (1 << 1)) +/** @} */ + +/** + * @name ADS101x/111x data rate settings + * + * Determines how quickly samples are taken (even on one-shot mode) + * + * @{ + */ +#define ADS101X_DATAR_MASK ((1 << 7) | (1 << 6) | (1 << 5)) +#define ADS101X_DATAR_128 ((0 << 7) | (0 << 6) | (0 << 5)) +#define ADS101X_DATAR_250 ((0 << 7) | (0 << 6) | (1 << 5)) +#define ADS101X_DATAR_490 ((0 << 7) | (1 << 6) | (0 << 5)) +#define ADS101X_DATAR_920 ((0 << 7) | (1 << 6) | (1 << 5)) +#define ADS101X_DATAR_1600 ((1 << 7) | (0 << 6) | (0 << 5)) +#define ADS101X_DATAR_2400 ((1 << 7) | (0 << 6) | (1 << 5)) +#define ADS101X_DATAR_3300 ((1 << 7) | (1 << 6) | (0 << 5)) + +#ifdef __cplusplus +} +#endif + +#endif /* ADS101X_REGS_H */ +/** @} */ diff --git a/drivers/include/ads101x.h b/drivers/include/ads101x.h new file mode 100644 index 0000000000..7f3e689dc5 --- /dev/null +++ b/drivers/include/ads101x.h @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2017 OTA keys S.A. + * 2018 Matthew Blue + * + * 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_ads101x ADS101x/111x ADC device driver + * @ingroup drivers_sensors + * @brief I2C Analog-to-Digital Converter device driver + * + * This driver works with ADS1013-5 and ADS1113-5. + * @{ + * + * @file + * @brief ADS101x/111x ADC device driver + * + * ADC and alert functionality are separated into two devices to + * prevent wasteful representations on muxed devices. + * + * @author Vincent Dupont + * @author Matthew Blue + */ + +#ifndef ADS101X_H +#define ADS101X_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "periph/i2c.h" +#include "periph/gpio.h" + +/** + * @brief ADS101x/111x default address + * + * Address pin tied to: GND (0x48), Vcc (0x49), SDA (0x50), SCL (0x51) + */ +#ifndef ADS101X_I2C_ADDRESS +#define ADS101X_I2C_ADDRESS (0x48) +#endif + +/** + * @brief Named return values + */ +enum { + ADS101X_OK = 0, /**< everything was fine */ + ADS101X_NOI2C = -1, /**< I2C communication failed */ + ADS101X_NODEV = -2, /**< no ADS101X device found on the bus */ + ADS101X_NODATA = -3 /**< no data available */ +}; + +/** + * @brief ADS101x/111x params + */ +typedef struct ads101x_params { + i2c_t i2c; /**< i2c device */ + uint8_t addr; /**< i2c address */ + uint8_t mux_gain; /**< Mux and gain boolean settings */ +} ads101x_params_t; + +/** + * @brief ADS101x/111x alert params + */ +typedef struct ads101x_alert_params { + i2c_t i2c; /**< i2c device */ + uint8_t addr; /**< i2c address */ + gpio_t alert_pin; /**< alert pin (GPIO_UNDEF if not connected) */ + int16_t low_limit; /**< alert low value */ + int16_t high_limit; /**< alert high value */ +} ads101x_alert_params_t; + +/** + * @brief ADS101x/111x device descriptor + */ +typedef struct ads101x { + ads101x_params_t params; /**< device driver configuration */ +} ads101x_t; + +/** + * @brief ADS101x/111x alert callback + */ +typedef void (*ads101x_alert_cb_t)(void *); + +/** + * @brief ADS101x/111x alert device descriptor + */ +typedef struct ads101x_alert { + ads101x_alert_params_t params; /**< device driver configuration */ + ads101x_alert_cb_t cb; /**< alert callback */ + void *arg; /**< alert callback param */ +} ads101x_alert_t; + +/** + * @brief Initialize an ADS101x/111x ADC device (ADC only) + * + * @param[in,out] dev device descriptor + * @param[in] params device configuration + * + * @return zero on successful initialization, non zero on error + */ +int ads101x_init(ads101x_t *dev, const ads101x_params_t *params); + +/** + * @brief Initialize an ADS101x/111x alert device + * + * @param[in,out] dev device descriptor + * @param[in] params device configuration + * + * @return zero on successful initialization, non zero on error + */ +int ads101x_alert_init(ads101x_alert_t *dev, + const ads101x_alert_params_t *params); + +/** + * @brief Set mux and gain + * + * Mux settings have no effect on ADS1013-4 and ADS1113-4. + * Gain settings have no effect on ADS1013 and ADS1113. + * + * @param[in] dev device descriptor + * @param[in] mux_gain mux and gain boolean values + * + * @return zero on successful read, non zero on error + */ +int ads101x_set_mux_gain(const ads101x_t *dev, uint8_t mux_gain); + +/** + * @brief Read a raw ADC value + * + * @param[in] dev device descriptor + * @param[out] raw read value + * + * @return zero on successful read, non zero on error + */ +int ads101x_read_raw(const ads101x_t *dev, int16_t *raw); + +/** + * @brief Enable alert interrupt + * + * Alert settings have no effect on ADS1013 and ADS1113. + * + * @param[in] dev device descriptor + * @param[in] cb callback called when the alert fires + * @param[in] arg callback argument + * + * @return zero on success, non zero on error + */ +int ads101x_enable_alert(ads101x_alert_t *dev, + ads101x_alert_cb_t cb, void *arg); + +/** + * @brief Set the alert parameters + * + * Alert settings have no effect on ADS1013 and ADS1113. + * + * @param[in,out] dev device descriptor + * @param[in] low_limit alert low limit + * @param[in] high_limit alert high limit + * + * @return zero on success, non zero on error + */ +int ads101x_set_alert_parameters(const ads101x_alert_t *dev, + int16_t low_limit, int16_t high_limit); + +#ifdef __cplusplus +} +#endif + +#endif /* ADS101X_H */ +/** @} */