Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

drivers: support for Microchip MCP47xx DAC devices added #10518

Merged
merged 4 commits into from
Feb 26, 2022
Merged
Changes from 1 commit
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
349 changes: 349 additions & 0 deletions drivers/include/mcp47xx.h
Original file line number Diff line number Diff line change
@@ -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 <gunar@schorcht.net>
* @file
*/

#ifndef MCP47XX_H
#define MCP47XX_H

#ifdef __cplusplus
extern "C"
{
#endif

#include <stdbool.h>
#include <stdint.h>

#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 */
/** @} */
1 change: 1 addition & 0 deletions drivers/mcp47xx/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base
1 change: 1 addition & 0 deletions drivers/mcp47xx/Makefile.dep
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FEATURES_REQUIRED += periph_i2c
2 changes: 2 additions & 0 deletions drivers/mcp47xx/Makefile.include
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
USEMODULE_INCLUDES_mcp47xx := $(LAST_MAKEFILEDIR)/include
USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_mcp47xx)
109 changes: 109 additions & 0 deletions drivers/mcp47xx/include/mcp47xx_params.h
Original file line number Diff line number Diff line change
@@ -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 <gunar@schorcht.net>
* @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 */
/** @} */
298 changes: 298 additions & 0 deletions drivers/mcp47xx/mcp47xx.c
Original file line number Diff line number Diff line change
@@ -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 <gunar@schorcht.net>
* @file
* @{
*/

#include <string.h>
#include <stdlib.h>

#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;
}