1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

Merge pull request #10518 from gschorcht/drivers_mcp47xx

drivers: support for Microchip MCP47xx DAC devices added
This commit is contained in:
benpicco 2022-02-26 22:13:41 +01:00 committed by GitHub
commit 6cfbec4f8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1140 additions and 0 deletions

View File

@ -19,6 +19,7 @@ rsource "dynamixel/Kconfig"
rsource "feetech/Kconfig"
rsource "grove_ledbar/Kconfig"
rsource "motor_driver/Kconfig"
rsource "mcp47xx/Kconfig"
rsource "my9221/Kconfig"
rsource "rgbled/Kconfig"
rsource "servo/Kconfig"

349
drivers/include/mcp47xx.h Normal file
View 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 */
/** @} */

14
drivers/mcp47xx/Kconfig Normal file
View File

@ -0,0 +1,14 @@
# 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.
#
menuconfig MODULE_MCP47XX
bool "MCP47xx DAC with I2C interface"
depends on HAS_PERIPH_I2C
select MODULE_PERIPH_I2C
help
Driver for Microchip MCP47xx DAC devices with I2C interfaces. The
driver supports MCP4706, MCP4716, MCP4725, MCP4726 and MCP4728.

1
drivers/mcp47xx/Makefile Normal file
View File

@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base

View File

@ -0,0 +1 @@
FEATURES_REQUIRED += periph_i2c

View File

@ -0,0 +1,2 @@
USEMODULE_INCLUDES_mcp47xx := $(LAST_MAKEFILEDIR)/include
USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_mcp47xx)

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

View File

@ -0,0 +1,43 @@
/*
* 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 MCP47xx adaption to the RIOT actuator/sensor interface
* @author Gunar Schorcht <gunar@schorcht.net>
* @file
*/
#if MODULE_SAUL
#include <string.h>
#include "saul.h"
#include "mcp47xx.h"
extern mcp47xx_t mcp47xx_devs[];
static int set(const void *dev, phydat_t *data)
{
const mcp47xx_saul_dac_params_t *p = (const mcp47xx_saul_dac_params_t *)dev;
mcp47xx_dac_set(&mcp47xx_devs[p->dev], p->channel, (uint16_t)data->val[0]);
return 1;
}
static int get(const void *dev, phydat_t *data)
{
const mcp47xx_saul_dac_params_t *p = (const mcp47xx_saul_dac_params_t *)dev;
mcp47xx_dac_get(&mcp47xx_devs[p->dev], p->channel, (uint16_t*)&data->val[0]);
return 1;
}
const saul_driver_t mcp47xx_dac_saul_driver = {
.read = get,
.write = set,
.type = SAUL_ACT_DIMMER
};
#endif /* MODULE_SAUL */

View File

@ -0,0 +1,90 @@
/*
* 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
* @ingroup sys_auto_init_saul
* @brief Auto initialization of Microchip MCP47xx DAC with I2C interface
* @author Gunar Schorcht <gunar@schorcht.net>
* @file
* @{
*/
#include "assert.h"
#include "log.h"
#include "saul_reg.h"
#include "saul/periph.h"
#include "mcp47xx.h"
#include "mcp47xx_params.h"
/**
* @brief Number of configured MCP47xx DAC devices
*/
#define MCP47XX_NUM (sizeof(mcp47xx_params) / sizeof(mcp47xx_params[0]))
/**
* @brief Number of configured SAUL MCP47xx DAC channels
*/
#define MCP47XX_SAUL_DAC_NUMOF (sizeof(mcp47xx_saul_dac_params) / \
sizeof(mcp47xx_saul_dac_params[0]))
/**
* @brief Number of saul info
*/
#define MCP47XX_INFO_NUM (sizeof(mcp47xx_saul_info) / \
sizeof(mcp47xx_saul_info[0]))
/**
* @brief Allocate the memory for the MCP47xx DAC device descriptors
*/
mcp47xx_t mcp47xx_devs[MCP47XX_NUM];
/**
* @brief Allocate the memory for MCP47xx DAC SAUL registry entries
*/
static saul_reg_t mcp47xx_saul_reg_entries[MCP47XX_SAUL_DAC_NUMOF];
/**
* @brief Reference the MCP47xx DAC input mode driver struct
*/
extern saul_driver_t mcp47xx_dac_in_saul_driver;
/**
* @brief Reference to the MCP47xx DAC output mode driver struct
*/
extern saul_driver_t mcp47xx_dac_saul_driver;
void auto_init_mcp47xx(void)
{
for (unsigned int i = 0; i < MCP47XX_NUM; i++) {
LOG_DEBUG("[auto_init_saul] initializing MCP47xx DAC dev #%u\n", i);
mcp47xx_init(&mcp47xx_devs[i], &mcp47xx_params[i]);
}
for (unsigned int i = 0; i < MCP47XX_SAUL_DAC_NUMOF; i++) {
const mcp47xx_saul_dac_params_t *p = &mcp47xx_saul_dac_params[i];
LOG_DEBUG("[auto_init_saul] initializing MCP47xx DAC channel #%u\n", i);
/* check the MCP47xx device index */
assert(p->dev < MCP47XX_NUM);
mcp47xx_saul_reg_entries[i].dev = (void *)p;
mcp47xx_saul_reg_entries[i].name = p->name;
mcp47xx_saul_reg_entries[i].driver = &mcp47xx_dac_saul_driver;
/* initialize the MCP47xx pin */
mcp47xx_dac_init(&mcp47xx_devs[p->dev], p->channel);
mcp47xx_dac_set(&mcp47xx_devs[p->dev], p->channel, p->initial);
/* add to registry */
saul_reg_add(&(mcp47xx_saul_reg_entries[i]));
}
}

View File

@ -199,6 +199,10 @@ void saul_init_devs(void)
extern void auto_init_mag3110(void);
auto_init_mag3110();
}
if (IS_USED(MODULE_MCP47XX)) {
extern void auto_init_mcp47xx(void);
auto_init_mcp47xx();
}
if (IS_USED(MODULE_MHZ19)) {
extern void auto_init_mhz19(void);
auto_init_mhz19();

View File

@ -0,0 +1,7 @@
include ../Makefile.tests_common
USEMODULE += mcp47xx
USEMODULE += shell
USEMODULE += benchmark
include $(RIOTBASE)/Makefile.include

View File

@ -0,0 +1,8 @@
BOARD_INSUFFICIENT_MEMORY := \
arduino-duemilanove \
arduino-leonardo \
arduino-nano \
arduino-uno \
atmega328p \
atmega328p-xplained-mini \
#

View File

@ -0,0 +1,5 @@
# this file enables modules defined in Kconfig. Do not use this file for
# application configuration. This is only needed during migration.
CONFIG_MODULE_MCP47XX=y
CONFIG_MODULE_SHELL=y
CONFIG_MODULE_BENCHMARK=y

208
tests/driver_mcp47xx/main.c Normal file
View File

@ -0,0 +1,208 @@
/*
* 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 tests
* @brief Test application for Microchip MCP47xx DAC with I2C interface
* @author Gunar Schorcht <gunar@schorcht.net>
* @file
*
* This test appliation demonstrates the usage of the MCP47xx driver.
* It can be used to test each MCP47xx DAC channel with shell commands.
*
* Functions `init`, `set`, `poweron`, `poweroff` demonstrate the usage of
* the driver API for one channel of one device using the driver API.
* The `setall` function shows the iteration over all channels of all
* devices for setting a value.
*/
#include <stdio.h>
#include <stdlib.h>
#include "mcp47xx.h"
#include "mcp47xx_params.h"
#include "irq.h"
#include "shell.h"
#include "benchmark.h"
#define BENCH_RUNS_DEFAULT (100UL * 100)
/* Number of configured MCP47xx I/O expander devices */
#define MCP47XX_NUM (sizeof(mcp47xx_params) / sizeof(mcp47xx_params[0]))
/* MCP47xx devices allocation */
mcp47xx_t mcp47xx_dev[MCP47XX_NUM];
static int init(int argc, char **argv)
{
if (argc < 3) {
printf("usage: %s <dev> <channel>\n", argv[0]);
return 1;
}
int dev = atoi(argv[1]);
int chn = atoi(argv[2]);
if (mcp47xx_dac_init(&mcp47xx_dev[dev], chn) < 0) {
printf("error: init MCP47xx pin (dev %i, chn %i) failed\n", dev, chn);
return 1;
}
return 0;
}
static int set(int argc, char **argv)
{
if (argc < 4) {
printf("usage: %s <dev> <channel> <value>\n", argv[0]);
return 1;
}
mcp47xx_dac_set(&mcp47xx_dev[atoi(argv[1])], atoi(argv[2]),
atoi(argv[3]));
return 0;
}
static int setall(int argc, char **argv)
{
if (argc < 2) {
printf("usage: %s <value>\n", argv[0]);
return 1;
}
uint16_t value = atoi(argv[1]);
for (unsigned dev = 0; dev < MCP47XX_NUM; dev++) {
uint8_t num = mcp47xx_dac_channels(&mcp47xx_dev[dev]);
for (uint8_t chn = 0; chn < num; chn++) {
mcp47xx_dac_set(&mcp47xx_dev[dev], chn, value);
}
}
return 0;
}
static int poweron(int argc, char **argv)
{
if (argc < 3) {
printf("usage: %s <dev> <channel>\n", argv[0]);
return 1;
}
mcp47xx_dac_poweron(&mcp47xx_dev[atoi(argv[1])], atoi(argv[2]));
return 0;
}
static int poweroff(int argc, char **argv)
{
if (argc < 3) {
printf("usage: %s <dev> <channel>\n", argv[0]);
return 1;
}
mcp47xx_dac_poweroff(&mcp47xx_dev[atoi(argv[1])], atoi(argv[2]));
return 0;
}
static int channels(int argc, char **argv)
{
if (argc < 2) {
printf("usage: %s <dev>\n", argv[0]);
return 1;
}
uint8_t chn = mcp47xx_dac_channels(&mcp47xx_dev[atoi(argv[1])]);
printf("channel_num: %u\n", chn);
return 0;
}
static int saw(int argc, char **argv)
{
if (argc < 4) {
printf("usage: %s <dev> <channel> <number>\n", argv[0]);
return 1;
}
int dev = atoi(argv[1]);
int chn = atoi(argv[2]);
unsigned num = atoi(argv[3]);
for (unsigned i = 0; i < num; i++) {
for (uint16_t j = 0; j < UINT16_MAX; j ++) {
mcp47xx_dac_set(&mcp47xx_dev[dev], chn, j);
}
}
return 0;
}
static int bench(int argc, char **argv)
{
if (argc < 3) {
printf("usage: %s <dev> <channel> [# of runs]\n", argv[0]);
return 1;
}
unsigned long runs = BENCH_RUNS_DEFAULT;
if (argc > 3) {
runs = (unsigned long)atol(argv[3]);
}
puts("\nDAC driver run-time performance benchmark\n");
int dev = atoi(argv[1]);
int chn = atoi(argv[2]);
BENCHMARK_FUNC("nop loop", runs, __asm__ volatile("nop"));
mcp47xx_dac_init(&mcp47xx_dev[dev], chn);
BENCHMARK_FUNC("dac_set", runs, mcp47xx_dac_set(&mcp47xx_dev[dev], chn, 0));
puts("\n --- DONE ---");
return 0;
}
static const shell_command_t shell_commands[] = {
{ "init", "init one channel of a device", init },
{ "set", "set the output of one channel of a device", set },
{ "setall", "set the outputs of all channels of all devices", setall },
{ "poweron", "power on the output of one channel of a device", poweron },
{ "poweroff", "power off the output of one channel of a device", poweroff },
{ "channels", "number of channels of a device", channels },
{ "saw", "generate a saw signal on one channel of a device", saw },
{ "bench", "run a set of predefined benchmarks", bench },
{ NULL, NULL, NULL }
};
int main(void)
{
puts("MCP47xx DAC peripheral driver test\n");
puts("Initializing MCP47xx");
/* initialize configured MCP47xx devices */
for (unsigned i = 0; i < MCP47XX_NUM; i++) {
if (mcp47xx_init(&mcp47xx_dev[i], &mcp47xx_params[i]) != MCP47XX_OK) {
puts("[Failed]");
return 1;
}
}
puts("[OK]\n");
puts("In this test, DAC lines are specified by device and channel numbers.\n"
"NOTE: make sure the values exist! The\n"
" behavior for not existing devices/channels is not defined!");
/* start the shell */
char line_buf[SHELL_DEFAULT_BUFSIZE];
shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE);
return 0;
}