mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
drivers/mcp47xx: add driver for MCP47xx DAC devices
This commit is contained in:
parent
6bb78e544e
commit
499cbb9062
349
drivers/include/mcp47xx.h
Normal file
349
drivers/include/mcp47xx.h
Normal file
@ -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
drivers/mcp47xx/Makefile
Normal file
1
drivers/mcp47xx/Makefile
Normal file
@ -0,0 +1 @@
|
||||
include $(RIOTBASE)/Makefile.base
|
1
drivers/mcp47xx/Makefile.dep
Normal file
1
drivers/mcp47xx/Makefile.dep
Normal file
@ -0,0 +1 @@
|
||||
FEATURES_REQUIRED += periph_i2c
|
2
drivers/mcp47xx/Makefile.include
Normal file
2
drivers/mcp47xx/Makefile.include
Normal file
@ -0,0 +1,2 @@
|
||||
USEMODULE_INCLUDES_mcp47xx := $(LAST_MAKEFILEDIR)/include
|
||||
USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_mcp47xx)
|
109
drivers/mcp47xx/include/mcp47xx_params.h
Normal file
109
drivers/mcp47xx/include/mcp47xx_params.h
Normal file
@ -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
drivers/mcp47xx/mcp47xx.c
Normal file
298
drivers/mcp47xx/mcp47xx.c
Normal file
@ -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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user