From 499cbb90627d6e4c486d0a3b90381dfb411f285e Mon Sep 17 00:00:00 2001 From: Gunar Schorcht Date: Mon, 6 Dec 2021 23:49:18 +0100 Subject: [PATCH] drivers/mcp47xx: add driver for MCP47xx DAC devices --- drivers/include/mcp47xx.h | 349 +++++++++++++++++++++++ drivers/mcp47xx/Makefile | 1 + drivers/mcp47xx/Makefile.dep | 1 + drivers/mcp47xx/Makefile.include | 2 + drivers/mcp47xx/include/mcp47xx_params.h | 109 +++++++ drivers/mcp47xx/mcp47xx.c | 298 +++++++++++++++++++ 6 files changed, 760 insertions(+) create mode 100644 drivers/include/mcp47xx.h create mode 100644 drivers/mcp47xx/Makefile create mode 100644 drivers/mcp47xx/Makefile.dep create mode 100644 drivers/mcp47xx/Makefile.include create mode 100644 drivers/mcp47xx/include/mcp47xx_params.h create mode 100644 drivers/mcp47xx/mcp47xx.c diff --git a/drivers/include/mcp47xx.h b/drivers/include/mcp47xx.h new file mode 100644 index 0000000000..2c0165944f --- /dev/null +++ b/drivers/include/mcp47xx.h @@ -0,0 +1,349 @@ +/* + * Copyright (C) 2021 Gunar Schorcht + * + * 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_mcp47xx MCP47xx DAC with I2C interface + * @ingroup drivers_saul + * @ingroup drivers_actuators + * @brief Device driver for Microchip MCP47xx DAC with I2C interface + * + * ## Overview + * + * The driver supports the different Microchip MCP47xx DAC variants. + * + * Expander | Channels | Resolution + * :--------|:--------:|:----------: + * MCP4706 | 1 | 8-bit + * MCP4716 | 1 | 10-bit + * MCP4725 | 1 | 12-bit + * MCP4726 | 1 | 12-bit + * MCP4728 | 4 | 12-bit + * + * @note The following features of MCP47xx DAC devices are not supported at the + * moment: + * - configuring and reading address bits to/from EEPROM + * - writing DAC channel output values to the EEPROM + * - setting the UDAC bit and using the LDAC pin for MCP4728 + * + * ## Usage + * + * Multiple MCP47xx DAC devices and different variants can be used at the + * same time. + * + * The driver interface is kept as compatible as possible with the peripheral + * DAC interface. The only differences are that + * + * - functions have the prefix `mcp47xx_` and + * - functions require an additional parameter, the pointer to the MCP47xx + * device of type #mcp47xx_t. + * + * Please refer the test application in `tests/driver_mcp47xx` for an example + * on how to use the driver. + * + * ## SAUL Capabilities + * + * The driver provides SAUL capabilities that are compatible with SAUL + * actuators of type #SAUL_ACT_DIMMER. + * Each MCP47xx channel can be mapped directly to SAUL by defining an + * according entry in \c MCP47XX_SAUL_DAC_PARAMS. Please refer file + * `$RIOTBASE/drivers/mcp47xx/include/mcp47xx_params.h` for an example. + * + * mcp47xx_saul_dac_params_t mcp47xx_saul_dac_params[] = { + * { + * .name = "DAC00", + * .dev = 0, + * .channel = 0, + * .initial = 32768, + * }, + * }; + * + * For each DAC channel that should be used with SAUL, an entry with a name, + * the device, the channel, and the initial value has to be defined as shown + * above. + * + * ## Using Multiple Devices + * + * It is possible to used multiple devices and different variants of MCP47xx + * DAC devices at the same time. The application has to configure all + * devices by defining the configuration parameter set `mcp47xx_params` + * of type #mcp47xx_params_t. As an example, the default configuration for + * one MCP4725 device is defined in `drivers/mcp47xx/mcp47xx_params.h`. + * + * The application can override it by placing a file `mcp47xx_params.h` in + * the application directory `$(APPDIR)`. For example, the definition of + * the configuration parameter set for the two devices (one MCP4725 and one + * MCP4728) could looks like: + * + * static const mcp47xx_params_t mcp47xx_params[] = { + * { + * .variant = MCP4725, + * .dev = I2C_DEV(0), + * .addr = MCP47XX_BASE_ADDR + 2, + * .gain = MCP47XX_GAIN_1X, + * .vref = MCP47XX_VDD, + * .pd_mode = MCP47XX_PD_LARGE, + * }, + * { + * .variant = MCP4728, + * .dev = I2C_DEV(0), + * .addr = MCP47XX_BASE_ADDR + 3, + * .gain = MCP47XX_GAIN_2X, + * .vref = MCP47XX_VREF_INT, + * .pd_mode = MCP47XX_PD_LARGE, + * }, + * }; + * + * @{ + * + * @author Gunar Schorcht + * @file + */ + +#ifndef MCP47XX_H +#define MCP47XX_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include + +#include "kernel_defines.h" +#include "periph/dac.h" +#include "periph/gpio.h" +#include "periph/i2c.h" + +/** + * @name MCP47xx I2C slave addresses + * + * MCP47xx I2C slave addresses are defined as an offset to a base address, + * which depends on the expander used. The address offset is in the range + * of 0 to 7. + * @{ + */ +#define MCP47XX_BASE_ADDR (0x60) /**< MCP47xx I2C slave base address. + Addresses are then in the range from + 0x60 to 0x67 */ +/** @} */ + +/** + * @name MCP47xx Channel number + * @{ + */ +#define MCP4706_CHN_NUM (1) /**< MCP4706 has 1 channel */ +#define MCP4716_CHN_NUM (1) /**< MCP4716 has 1 channel */ +#define MCP4725_CHN_NUM (1) /**< MCP4725 has 1 channel */ +#define MCP4726_CHN_NUM (1) /**< MCP4726 has 1 channel */ +#define MCP4728_CHN_NUM (4) /**< MCP4728 has 4 channels */ +#define MCP47XX_CHN_NUM_MAX (4) /**< maximum number of channels */ +/** @} */ + +/** + * @brief MCP47xx driver error codes */ +typedef enum { + MCP47XX_OK, /**< success */ + MCP47XX_ERROR_I2C, /**< I2C communication error */ + MCP47XX_ERROR_NOT_AVAIL, /**< device not available */ +} mcp47xx_error_codes_t; + +/** + * @brief Supported MCP47xx variants + * + * It is used in configuration parameters to specify the MCP47xx expander + * used by device. + */ +typedef enum { + MCP4706, /**< 1 channel 8-bit DAC */ + MCP4716, /**< 1 channel 10-bit DAC */ + MCP4725, /**< 1 channel 12-bit DAC */ + MCP4726, /**< 1 channel 12-bit DAC */ + MCP4728, /**< 4 channel 12-bit DAC */ +} mcp47xx_variant_t; + +/** + * @brief MCP47xx gain configuration type + * + * @note Gains are not supported by MCP4725. + */ +typedef enum { + MCP47XX_GAIN_1X = 0, /**< Gain is 1.0, supported by all MCP47xx variants */ + MCP47XX_GAIN_2X = 1, /**< Gain is 2.0, not supported by MCP4725 */ +} mcp47xx_gain_t; + +/** + * @brief MCP47xx V_REF configuration type + * + * @note Different MCP47xx variants allow different V_REF configurations + */ +typedef enum { + MCP47XX_VREF_VDD = 0, /**< V_REF = V_DD, supported by all MCP47xx */ + MCP47XX_VREF_INT = 1, /**< V_REF = internal (2.048 V), MCP4728 only */ + MCP47XX_VREF_PIN = 2, /**< V_REF = VREF pin not buffered, MCP47x6 only */ + MCP47XX_VREF_BUF = 3, /**< V_REF = VREF pin buffered, MCP47x6 only */ +} mcp47xx_vref_t; + +/** + * @brief MCP47xx Power-down mode selection type + * + * Defines the possible power-down modes used for MCP47xx device configuration. + * The mode is used by function #mcp47xx_dac_poweroff to set the DAC into the + * configured power-down mode. + * + * @note #MCP47XX_NORMAL cannot be configured as power-down mode. + */ +typedef enum { + MCP47XX_NORMAL = 0, /**< Normal mode */ + MCP47XX_PD_SMALL = 1, /**< Power down, small resistor 1 kOhm */ + MCP47XX_PD_MEDIUM = 2, /**< Power down, medium resistor, + 125 kOhm for MCP47x6, 100 kOhm otherwise */ + MCP47XX_PD_LARGE = 3, /**< Power down, large resistor, + 640 kOhm for MCP47x6, 125 kOhm otherwise */ +} mcp47xx_pd_mode_t; + +/** + * @brief MCP47xx device configuration parameters + */ +typedef struct { + + i2c_t dev; /**< I2C device */ + uint16_t addr; /**< I2C slave address MCP47XX_BASE_ADDR + [0...7] */ + + mcp47xx_variant_t variant; /**< used variant of MCP47xx */ + mcp47xx_gain_t gain; /**< Gain selection */ + mcp47xx_vref_t vref; /**< Voltage reference selection */ + mcp47xx_pd_mode_t pd_mode; /**< Power-down mode selection */ + +} mcp47xx_params_t; + +/** + * @brief MCP47xx device data structure type + */ +typedef struct { + mcp47xx_params_t params; /**< device configuration parameters */ + uint16_t values[MCP47XX_CHN_NUM_MAX]; /**< contains the last values set + for persistence when device is + powered off */ +} mcp47xx_t; + +#if IS_USED(MODULE_SAUL) || DOXYGEN +/** + * @brief MCP47xx configuration structure for mapping DAC channels to SAUL + */ +typedef struct { + const char *name; /**< name of the MCP47xx device */ + unsigned int dev; /**< index of the MCP47xx device */ + uint8_t channel; /**< channel of the MCP47xx device */ + uint16_t initial; /**< initial value */ +} mcp47xx_saul_dac_params_t; +#endif + +/** + * @brief Initialize the MCP47xx DAC + * + * All expander pins are set to be input and are pulled up. + * + * @param[in] dev descriptor of the MCP47xx DAC device + * @param[in] params configuration parameters, see #mcp47xx_params_t + * + * @retval MCP47XX_OK on success + * @retval MCP47XX_ERROR_* a negative error code on error, + * see #mcp47xx_error_codes_t + */ +int mcp47xx_init(mcp47xx_t *dev, const mcp47xx_params_t *params); + +/** + * @brief Initialize a MCP47xx DAC channel + * + * After the initialization, the DAC channel is active and its output is set + * to 0. + * + * @param[in] dev descriptor of the MCP47xx DAC device + * @param[in] chn channel to initialize + * + * @retval MCP47XX_OK on success + * @retval MCP47XX_ERROR_* a negative error code on error, + * see #mcp47xx_error_codes_t + */ +int mcp47xx_dac_init(mcp47xx_t *dev, uint8_t chn); + +/** + * @brief Write a value to a MCP47xx DAC channel + * + * The value is always given as 16-bit value and is internally scaled to the + * actual resolution that the DAC unit provides, e.g., 12-bit. So to get the + * maximum output voltage, this function has to be called with value set + * to 65535 (UINT16_MAX). + * + * @param[in] dev descriptor of the MCP47xx DAC device + * @param[in] chn channel to set + * @param[in] value value to set line to + * + * @retval none + */ +void mcp47xx_dac_set(mcp47xx_t *dev, uint8_t chn, uint16_t value); + +/** + * @brief Get the current value of a MCP47xx DAC channel + * + * The value is always given as 16-bit value and is internally scaled to the + * actual resolution that the DAC unit provides, e.g., 12-bit. + * + * @param[in] dev descriptor of the MCP47xx DAC device + * @param[in] chn channel to set + * @param[out] value value to set line to + * + * @retval none + */ +void mcp47xx_dac_get(mcp47xx_t *dev, uint8_t chn, uint16_t *value); + +/** + * @brief Enables the MCP47xx DAC device + * + * MCP47xx is enabled and the output is set to the last set value. + * + * @param[in] dev descriptor of the MCP47xx DAC device + * @param[in] chn channel to power on + * @retval none + */ +void mcp47xx_dac_poweron(mcp47xx_t *dev, uint8_t chn); + +/** + * @brief Disables the MCP47xx DAC device + * + * MCP47xx is switched to the power-down mode configured by the configuration + * parameter mcp47xx_params_t::pd_mode. V_OUT is loaded with the configured + * resistor to ground. Most of the channel circuits are powered off. + * + * @note If #MCP47XX_NORMAL is used as power-down mode, the DAC can't be + * powerd off. + * + * @param[in] dev descriptor of the MCP47xx DAC device + * @param[in] chn channel to power on + * @retval none + */ +void mcp47xx_dac_poweroff(mcp47xx_t *dev, uint8_t chn); + +/** + * @brief Returns the number of channels of MCP47xx DAC device + * + * This function returns the number of channels of the device MCP47xx DAC + * device. + * + * @param[in] dev descriptor of the MCP47xx DAC device + * @retval number of channels on success or 0 on error + */ +uint8_t mcp47xx_dac_channels(mcp47xx_t *dev); + +#ifdef __cplusplus +} +#endif + +#endif /* MCP47XX_H */ +/** @} */ diff --git a/drivers/mcp47xx/Makefile b/drivers/mcp47xx/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/drivers/mcp47xx/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/mcp47xx/Makefile.dep b/drivers/mcp47xx/Makefile.dep new file mode 100644 index 0000000000..e67057d463 --- /dev/null +++ b/drivers/mcp47xx/Makefile.dep @@ -0,0 +1 @@ +FEATURES_REQUIRED += periph_i2c diff --git a/drivers/mcp47xx/Makefile.include b/drivers/mcp47xx/Makefile.include new file mode 100644 index 0000000000..fb0f98e52a --- /dev/null +++ b/drivers/mcp47xx/Makefile.include @@ -0,0 +1,2 @@ +USEMODULE_INCLUDES_mcp47xx := $(LAST_MAKEFILEDIR)/include +USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_mcp47xx) diff --git a/drivers/mcp47xx/include/mcp47xx_params.h b/drivers/mcp47xx/include/mcp47xx_params.h new file mode 100644 index 0000000000..4ea008aca5 --- /dev/null +++ b/drivers/mcp47xx/include/mcp47xx_params.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2021 Gunar Schorcht + * + * 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_mcp47xx + * @brief Default configuration for Microchip MCP47xx DAC with I2C interface + * @author Gunar Schorcht + * @file + * @{ + */ + +#ifndef MCP47XX_PARAMS_H +#define MCP47XX_PARAMS_H + +#include "board.h" +#include "mcp47xx.h" +#include "saul_reg.h" +#include "saul/periph.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name Set default configuration parameters + * @{ + */ +#ifndef MCP47XX_PARAM_VARIANT +/** Default MCP47xx variant */ +#define MCP47XX_PARAM_VARIANT (MCP4725) +#endif + +#ifndef MCP47XX_PARAM_DEV +/** Default I2C device */ +#define MCP47XX_PARAM_DEV I2C_DEV(0) +#endif + +#ifndef MCP47XX_PARAM_ADDR +/** Default I2C slave address as offset to MCP47XX_BASE_ADDR */ +#define MCP47XX_PARAM_ADDR (MCP47XX_BASE_ADDR + 2) +#endif + +#ifndef MCP47XX_PARAM_GAIN +/** Default MCP47xx gain selection */ +#define MCP47XX_PARAM_GAIN (MCP47XX_GAIN_1X) +#endif + +#ifndef MCP47XX_PARAM_VREF +/** Default MCP47xx V_REF selection */ +#define MCP47XX_PARAM_VREF (MCP47XX_VREF_VDD) +#endif + +#ifndef MCP47XX_PARAM_PD_MODE +/** Default MCP47xx Power-Down mode selection */ +#define MCP47XX_PARAM_PD_MODE (MCP47XX_PD_LARGE) +#endif + +#ifndef MCP47XX_PARAMS +/** Default MCP47xx configuration parameters */ +#define MCP47XX_PARAMS { \ + .dev = MCP47XX_PARAM_DEV, \ + .addr = MCP47XX_PARAM_ADDR, \ + .variant = MCP47XX_PARAM_VARIANT, \ + .gain = MCP47XX_PARAM_GAIN, \ + .vref = MCP47XX_PARAM_VREF, \ + .pd_mode = MCP47XX_PARAM_PD_MODE, \ + }, +#endif /* MCP47XX_PARAMS */ + +#ifndef MCP47XX_SAUL_DAC_PARAMS +/** Example for mapping DAC channels to SAUL */ +#define MCP47XX_SAUL_DAC_PARAMS { \ + .name = "DAC00", \ + .dev = 0, \ + .channel = 0, \ + .initial = 32768, \ + }, +#endif +/**@}*/ + +/** + * @brief Allocate some memory to store the actual configuration + */ +static const mcp47xx_params_t mcp47xx_params[] = +{ + MCP47XX_PARAMS +}; + +#if IS_USED(MODULE_SAUL) || DOXYGEN +/** + * @brief Additional meta information to keep in the SAUL registry + */ +static const mcp47xx_saul_dac_params_t mcp47xx_saul_dac_params[] = +{ + MCP47XX_SAUL_DAC_PARAMS +}; +#endif /* IS_USED(MODULE_SAUL) || DOXYGEN */ + +#ifdef __cplusplus +} +#endif + +#endif /* MCP47XX_PARAMS_H */ +/** @} */ diff --git a/drivers/mcp47xx/mcp47xx.c b/drivers/mcp47xx/mcp47xx.c new file mode 100644 index 0000000000..66bbc0f188 --- /dev/null +++ b/drivers/mcp47xx/mcp47xx.c @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2021 Gunar Schorcht + * + * 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_mcp47xx + * @brief Device driver for the Microchip MCP47xx DAC with I2C interface + * @author Gunar Schorcht + * @file + * @{ + */ + +#include +#include + +#include "mcp47xx.h" + +#include "irq.h" +#include "log.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#if ENABLE_DEBUG + +#define ASSERT_PARAM(cond) \ + do { \ + if (!(cond)) { \ + DEBUG("[mcp47xx] %s: %s\n", \ + __func__, "parameter condition (" #cond ") not fulfilled"); \ + assert(cond); \ + } \ + } while (0) + +#define DEBUG_DEV(f, d, ...) \ + DEBUG("[mcp47xx] %s i2c dev=%d addr=%02x: " f "\n", \ + __func__, d->params.dev, d->params.addr, ## __VA_ARGS__) + +#else /* ENABLE_DEBUG */ + +#define ASSERT_PARAM(cond) assert(cond) +#define DEBUG_DEV(f, d, ...) + +#endif /* ENABLE_DEBUG */ + +#define ERROR_DEV(f, d, ...) \ + LOG_ERROR("[mcp47xx] %s i2c dev=%d addr=%02x: " f "\n", \ + __func__, d->params.dev, d->params.addr, ## __VA_ARGS__) + +/** Forward declaration of functions for internal use */ + +static int _get(mcp47xx_t *dev, uint8_t chn, uint16_t* value); +static int _set(mcp47xx_t *dev, uint8_t chn, uint16_t value, bool pd); +static int _read(const mcp47xx_t *dev, uint8_t *data, size_t len); +static int _write(const mcp47xx_t *dev, uint8_t* data, size_t len); + +static const uint8_t _mcp47xx_chn_nums[] = { + MCP4706_CHN_NUM, + MCP4716_CHN_NUM, + MCP4725_CHN_NUM, + MCP4726_CHN_NUM, + MCP4728_CHN_NUM +}; + +int mcp47xx_init(mcp47xx_t *dev, const mcp47xx_params_t *params) +{ + /* some parameter sanity checks */ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(params != NULL); + + DEBUG_DEV("params=%p", dev, params); + + ASSERT_PARAM(params->gain <= MCP47XX_GAIN_2X); + ASSERT_PARAM(params->vref <= MCP47XX_VREF_BUF); + ASSERT_PARAM(params->pd_mode <= MCP47XX_PD_LARGE); + ASSERT_PARAM(params->pd_mode != MCP47XX_NORMAL); + + /* MCP4725 supports only MCP47XX_GAIN_1X */ + ASSERT_PARAM(params->variant != MCP4725 || params->gain == MCP47XX_GAIN_1X); + /* MCP4725 supports only MCP47XX_VDD */ + ASSERT_PARAM(params->variant != MCP4725 || params->vref == MCP47XX_VREF_VDD); + + /* MCP47XX_INT is only supported by MCP4728 */ + ASSERT_PARAM(params->vref != MCP47XX_VREF_INT || params->variant == MCP4728); + /* MCP47XX_PIN is only supported by MCP47x6 */ + ASSERT_PARAM((params->vref != MCP47XX_VREF_PIN && + params->vref != MCP47XX_VREF_BUF) || params->variant == MCP4706 + || params->variant == MCP4716 + || params->variant == MCP4726); + /* init sensor data structure */ + dev->params = *params; + + /* check for availability */ + uint8_t bytes[3]; + if (_read(dev, bytes, 3) != MCP47XX_OK) { + DEBUG_DEV("device not available", dev); + return MCP47XX_ERROR_NOT_AVAIL; + } + + /* power_off all channels */ + for (unsigned i = 0; i < _mcp47xx_chn_nums[dev->params.variant]; i++) { + mcp47xx_dac_poweroff(dev, i); + } + + return MCP47XX_OK; +} + +int mcp47xx_dac_init(mcp47xx_t *dev, uint8_t chn) +{ + /* some parameter sanity checks */ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(chn < _mcp47xx_chn_nums[dev->params.variant]); + + DEBUG_DEV("chn=%u", dev, chn); + + /* get the current value */ + uint16_t value; + int res; + if ((res = _get(dev, chn, &value))) { + return res; + } + + /* set the channel value */ + if ((res = _set(dev, chn, 0, false))) { + return res; + } + + return MCP47XX_OK; +} + +void mcp47xx_dac_set(mcp47xx_t *dev, uint8_t chn, uint16_t value) +{ + /* some parameter sanity checks */ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(chn < _mcp47xx_chn_nums[dev->params.variant]); + + DEBUG_DEV("chn=%u val=%u", dev, chn, value); + + dev->values[chn] = value; + + _set(dev, chn, value, false); +} + +void mcp47xx_dac_get(mcp47xx_t *dev, uint8_t chn, uint16_t *value) +{ + /* some parameter sanity checks */ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(value != NULL); + ASSERT_PARAM(chn < _mcp47xx_chn_nums[dev->params.variant]); + + DEBUG_DEV("chn=%u val=%p", dev, chn, value); + + _get(dev, chn, value); + + dev->values[chn] = *value; +} + +void mcp47xx_dac_poweroff(mcp47xx_t *dev, uint8_t chn) +{ + _set(dev, chn, dev->values[chn], true); +} + +void mcp47xx_dac_poweron(mcp47xx_t *dev, uint8_t chn) +{ + _set(dev, chn, dev->values[chn], false); +} + +uint8_t mcp47xx_dac_channels(mcp47xx_t *dev) +{ + /* some parameter sanity checks */ + ASSERT_PARAM(dev != NULL); + + return _mcp47xx_chn_nums[dev->params.variant]; +} + +/** Functions for internal use only */ + +/* Write DAC Register/Volatile Memory command */ +#define MCP47XX_CMD_WRITE_DAC (0x40) + +static int _get(mcp47xx_t *dev, uint8_t chn, uint16_t* value) +{ + /* + * read formats: + * + * MCP4706 BR0VVPPG DDDDDDDD -------- V - Voltage selection VREF1,VREF0 + * MCP4716 BR0VVPPG DDDDDDDD DD------ P - Power Mode seclection PD1,PD0 + * MCP4726 BR0VVPPG DDDDDDDD DDDD---- G - Gain selection + * MCP4725 BR---PP- DDDDDDDD DDDD---- D - Data from MSB to LSB + * MCP4728 BRCC0AAA VPPGDDDD DDDDDDDD C - Channel selection CH1,CH0 + * B - BSY/RDY EEPROM Write status + * R - Power on Reset + */ + int res; + + if (dev->params.variant == MCP4728) { + uint8_t bytes[24]; + res = _read(dev, bytes, 24); + if (res) { + return res; + } + *value = ((uint16_t)bytes[chn * 6 + 1] & 0x000f) << 12; + *value |= bytes[chn * 6 + 2] << 4; + } + else { + uint8_t bytes[3]; + res = _read(dev, bytes, 3); + if (res) { + return res; + } + DEBUG_DEV("read %02x %02x %02x", dev, bytes[0], bytes[1], bytes[2]); + *value = ((uint16_t)bytes[1] << 8) | bytes[2]; + } + + return MCP47XX_OK; +} + +static int _set(mcp47xx_t *dev, uint8_t chn, uint16_t value, bool pd) +{ + /* + * write command formats: + * + * MCP4706 010VVPPG DDDDDDDD -------- V - Voltage selection VREF1,VREF0 + * MCP4716 010VVPPG DDDDDDDD DD------ P - Power Mode seclection PD1,PD0 + * MCP4726 010VVPPG DDDDDDDD DDDD---- G - Gain selection + * MCP4725 010--PP- DDDDDDDD DDDD---- D - Data from MSB to LSB + * MCP4728 01000CCU VPPGDDDD DDDDDDDD C - Channel selection CH1,CH0 + * U - UDAC bit + */ + uint8_t bytes[3] = { }; + + if (dev->params.variant == MCP4728) { + /* U=0 update V_OUT directly */ + bytes[0] = MCP47XX_CMD_WRITE_DAC | (chn << 1); + bytes[1] = (value >> 12) | (dev->params.vref << 7) + | (dev->params.gain << 4) + | (pd ? dev->params.pd_mode << 5 : 0); + bytes[2] = (value >> 4) & 0xff; + } + else { + bytes[0] = MCP47XX_CMD_WRITE_DAC | (dev->params.vref << 3) + | dev->params.gain + | (pd ? dev->params.pd_mode << 1 : 0); + /* + * resolution handling is not required since only the n most + * significant bits are used + */ + bytes[1] = value >> 8; + bytes[2] = value & 0xff; + } + DEBUG_DEV("write %02x %02x %02x", dev, bytes[0], bytes[1], bytes[2]); + + return _write(dev, bytes, 3); +} + +static int _read(const mcp47xx_t *dev, uint8_t *data, size_t len) +{ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(data != NULL); + + DEBUG_DEV("", dev); + + i2c_acquire(dev->params.dev); + int res = i2c_read_bytes(dev->params.dev, dev->params.addr, data, len, 0); + i2c_release(dev->params.dev); + + if (res != 0) { + DEBUG_DEV("could not read data, reason %d (%s)", + dev, res, strerror(res * -1)); + return -MCP47XX_ERROR_I2C; + } + + return res; +} + +static int _write(const mcp47xx_t *dev, uint8_t* data, size_t len) +{ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(data != NULL); + + DEBUG_DEV("", dev); + + i2c_acquire(dev->params.dev); + int res = i2c_write_bytes(dev->params.dev, dev->params.addr, data, len, 0); + i2c_release(dev->params.dev); + + if (res != 0) { + DEBUG_DEV("could not write data, reason %d (%s)", + dev, res, strerror(res * -1)); + return -MCP47XX_ERROR_I2C; + } + + return res; +}