drivers/analog/mcp47x6: Add support for MCP47X6 DAC series

The MCP47X6 series consists of the following single channel DAC chips:
- MCP4706 (8 bit)
- MCP4716 (10 bit)
- MCP4726 (12 bit)

The driver supports the following configurations:
- gain
- power down
- voltage reference

Persistent configuration storage in the internal EEPROM is not
implemented.
This commit is contained in:
Lars Kruse
2025-04-26 13:23:39 +02:00
committed by Alan C. Assis
parent 33a6e70077
commit 9b3914b740
8 changed files with 651 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
===========================
Microchip MCP4706/4716/4726
===========================
Microchip MCP4706/4716/4726 DAC.
The digital-analog-converter operates over I2C.
- ``include/nuttx/analog/mcp47x6.h``. All structures and APIs needed
to work with DAC drivers are provided in this header file.
The following features are configurable via the ``ioctl`` interface of
the device:
- gain
- power down
- voltage reference
Usage Example
-------------
.. code-block:: c
#include <nuttx/analog/dac.h>
#include <nuttx/analog/mcp47x6.h>
struct dac_dev_s *dac;
unsigned int const i2c_bus = 0;
unsigned int const i2c_address = 0x63;
/* create and register device */
dac = mcp47x6_initialize(i2c_bus, i2c_address);
dac_register("/dev/dac0", dac);
/* configure the DAC */
int fd = open("/dev/dac0", O_WRONLY | O_NONBLOCK);
ioctl(fd, ANIOC_MCP47X6_DAC_SET_REFERENCE, MCP47X6_REFERENCE_VREF_BUFFERED);
/* set DAC output value */
struct dac_msg_s dac_message = {
.am_channel = 0,
.am_data = 1234
};
write(fd, &dac_message, sizeof(dac_message));
/* clean up */
close(fd);

View File

@@ -40,6 +40,10 @@ if(CONFIG_DAC)
list(APPEND SRCS dac7554.c)
endif()
if(CONFIG_MCP47X6)
list(APPEND SRCS mcp47x6.c)
endif()
if(CONFIG_MCP48XX)
list(APPEND SRCS mcp48xx.c)
endif()

View File

@@ -411,6 +411,34 @@ config DAC7554
---help---
Enable driver support for the Texas Instruments DAC7554 dac.
config MCP47X6
bool "MCP4706/4716/4726 support"
default n
select I2C
---help---
Enable driver support for the Microchip MCP4706/4716/4726 DAC.
choice
prompt "MCP47X6 variant"
default MCP4706
depends on MCP47X6
config MCP4706
bool "MCP4706 (8-bit) DAC"
config MCP4716
bool "MCP4716 (10-bit) DAC"
config MCP4726
bool "MCP4726 (12-bit) DAC"
endchoice # MCP47X6 variant
config MCP47X6_I2C_FREQUENCY
int "MCP47X6 I2C frequency"
default 400000
depends on MCP47X6
config MCP48XX
bool "MCP4802/4812/4822 support"
default n

View File

@@ -44,6 +44,10 @@ ifeq ($(CONFIG_DAC7554),y)
CSRCS += dac7554.c
endif
ifeq ($(CONFIG_MCP47X6),y)
CSRCS += mcp47x6.c
endif
ifeq ($(CONFIG_MCP48XX),y)
CSRCS += mcp48xx.c
endif

459
drivers/analog/mcp47x6.c Normal file
View File

@@ -0,0 +1,459 @@
/****************************************************************************
* drivers/analog/mcp47x6.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdio.h>
#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/arch.h>
#include <nuttx/analog/dac.h>
#include <nuttx/i2c/i2c_master.h>
#include <nuttx/analog/mcp47x6.h>
/****************************************************************************
* Preprocessor definitions
****************************************************************************/
#if !defined(CONFIG_I2C)
# error I2C Support Required.
#endif
#if defined(CONFIG_MCP47X6)
#if defined(CONFIG_MCP4706)
# define MCP47X6_DATA_BITS 8u
# define MCP47X6_DATA_SHIFT 0u
#elif defined(CONFIG_MCP4716)
# define MCP47X6_DATA_BITS 10u
# define MCP47X6_DATA_SHIFT 2u
#elif defined(CONFIG_MCP4726)
# define MCP47X6_DATA_BITS 12u
# define MCP47X6_DATA_SHIFT 0u
#else
# error MCP47x6 variant selection required
#endif
#ifndef CONFIG_MCP47X6_I2C_FREQUENCY
# define CONFIG_MCP47X6_I2C_FREQUENCY 400000
#endif
#define MCP47X6_GAIN_MASK (1u << MCP47X6_GAIN_SHIFT)
#define MCP47X6_GAIN_SHIFT 0u
#define MCP47X6_POWER_DOWN_MASK (3u << MCP47X6_POWER_DOWN_SHIFT)
#define MCP47X6_POWER_DOWN_SHIFT 1u
#define MCP47X6_REFERENCE_MASK (3u << MCP47X6_REFERENCE_SHIFT)
#define MCP47X6_REFERENCE_SHIFT 3u
#define MCP47X6_COMMAND_MASK (7u << MCP47X6_COMMAND_SHIFT)
#define MCP47X6_COMMAND_SHIFT 5u
#define MCP47X6_DATA_MASK ((1u << MCP47X6_DATA_BITS) - 1u)
/****************************************************************************
* Private Types
****************************************************************************/
struct mcp47x6_dev_s
{
FAR struct i2c_master_s *i2c; /* I2C interface */
uint8_t addr; /* I2C address */
uint8_t cmd; /* MCP47x6 current state */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* DAC methods */
static void mcp47x6_reset(FAR struct dac_dev_s *dev);
static int mcp47x6_setup(FAR struct dac_dev_s *dev);
static void mcp47x6_shutdown(FAR struct dac_dev_s *dev);
static void mcp47x6_txint(FAR struct dac_dev_s *dev, bool enable);
static int mcp47x6_send(FAR struct dac_dev_s *dev,
FAR struct dac_msg_s *msg);
static int mcp47x6_ioctl(FAR struct dac_dev_s *dev, int cmd,
unsigned long arg);
/****************************************************************************
* Private Data
****************************************************************************/
static const struct dac_ops_s g_dacops =
{
mcp47x6_reset, /* ao_reset */
mcp47x6_setup, /* ao_setup */
mcp47x6_shutdown, /* ao_shutdown */
mcp47x6_txint, /* ao_txint */
mcp47x6_send, /* ao_send */
mcp47x6_ioctl /* ao_ioctl */
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: mcp47x6_i2c_write
*
* Description:
* Send the raw content of a buffer to the DAC.
*
****************************************************************************/
static int mcp47x6_i2c_write(FAR struct mcp47x6_dev_s *priv,
uint8_t const *source, size_t size)
{
struct i2c_msg_s msg;
int ret;
/* Sanity check */
DEBUGASSERT(priv->i2c != NULL);
/* Setup for the transfer */
msg.frequency = CONFIG_MCP47X6_I2C_FREQUENCY;
msg.addr = priv->addr;
msg.flags = 0;
msg.buffer = (uint8_t *)source; /* discard const qualifier */
msg.length = size;
/* Then perform the transfer. */
ret = I2C_TRANSFER(priv->i2c, &msg, 1);
if (ret < 0)
{
aerr("MCP47X6 I2C write transfer failed: %d", ret);
return ret;
}
return ret;
}
/****************************************************************************
* Name: mcp47x6_i2c_read
*
* Description:
* Read raw content from the DAC
*
* Response bytes:
* - volatile status and configuration
* - volatile data byte
* - volatile data byte (only for MCP4716 and MCP4726)
* - non-volatile status and configuration
* - non-volatile data byte
* - non-volatile data byte (only for MCP4716 and MCP4726)
*
****************************************************************************/
static int mcp47x6_i2c_read(FAR struct mcp47x6_dev_s *priv,
uint8_t *destination, size_t size)
{
struct i2c_msg_s msg;
int ret;
/* Sanity check */
DEBUGASSERT(priv->i2c != NULL);
/* Setup for the transfer */
msg.frequency = CONFIG_MCP47X6_I2C_FREQUENCY;
msg.addr = priv->addr;
msg.flags = I2C_M_READ;
msg.buffer = destination;
msg.length = size;
/* Then perform the transfer. */
ret = I2C_TRANSFER(priv->i2c, &msg, 1);
if (ret < 0)
{
aerr("MCP47X6 I2C read transfer failed: %d", ret);
return ret;
}
return ret;
}
/****************************************************************************
* Name: mcp47x6_reset
*
* Description:
* Reset the DAC device. Called early to initialize the hardware. This
* is called, before ao_setup() and on error conditions.
*
****************************************************************************/
static void mcp47x6_reset(FAR struct dac_dev_s *dev)
{
}
/****************************************************************************
* Name: mcp47x6_setup
*
* Description:
* Configure the DAC. This method is called the first time that the DAC
* device is opened. This will occur when the port is first opened. This
* setup includes configuring and attaching DAC interrupts. Interrupts are
* all disabled upon return.
*
****************************************************************************/
static int mcp47x6_setup(FAR struct dac_dev_s *dev)
{
FAR struct mcp47x6_dev_s *priv = (FAR struct mcp47x6_dev_s *)dev->ad_priv;
uint8_t response;
int ret;
/* Device's default settings after power up. */
uint8_t default_settings = MCP47X6_REFERENCE_VDD_UNBUFFERED
| MCP47X6_POWER_DOWN_DISABLED
| MCP47X6_GAIN_1X;
/* Retrieve the current device setup. */
ret = mcp47x6_i2c_read(priv, &response, 1);
if (ret < 0)
{
aerr("MCP47X6 I2C reading initial configuration failed: %d", ret);
priv->cmd = default_settings;
return ret;
}
/* Store the current setup for future configuration operations. */
priv->cmd = response & (MCP47X6_REFERENCE_MASK
| MCP47X6_POWER_DOWN_MASK
| MCP47X6_GAIN_MASK);
return OK;
}
/****************************************************************************
* Name: mcp47x6_shutdown
*
* Description:
* Disable the DAC. This method is called when the DAC device is closed.
* This method reverses the operation the setup method.
*
****************************************************************************/
static void mcp47x6_shutdown(FAR struct dac_dev_s *dev)
{
}
/****************************************************************************
* Name: mcp47x6_txint
*
* Description:
* Call to enable or disable TX interrupts
*
****************************************************************************/
static void mcp47x6_txint(FAR struct dac_dev_s *dev, bool enable)
{
}
/****************************************************************************
* Name: mcp47x6_send
****************************************************************************/
static int mcp47x6_send(FAR struct dac_dev_s *dev, FAR struct dac_msg_s *msg)
{
FAR struct mcp47x6_dev_s *priv = (FAR struct mcp47x6_dev_s *)dev->ad_priv;
int ret;
/* Set up message to send */
ainfo("value: %08x", (unsigned int)msg->am_data);
uint8_t const BUFFER_SIZE = 2;
uint8_t buffer[BUFFER_SIZE];
uint32_t data;
data = msg->am_data & MCP47X6_DATA_MASK;
data <<= MCP47X6_DATA_SHIFT;
buffer[0] = (uint8_t)(data >> 8);
buffer[1] = (uint8_t)(data);
ret = mcp47x6_i2c_write(priv, buffer, sizeof(buffer));
if (ret < 0)
{
aerr("ERROR: mcp47x6_send failed: %d", ret);
}
dac_txdone(dev);
return ret;
}
/****************************************************************************
* Name: mcp47x6_ioctl
****************************************************************************/
static int mcp47x6_ioctl(FAR struct dac_dev_s *dev, int cmd,
unsigned long arg)
{
FAR struct mcp47x6_dev_s *priv = (FAR struct mcp47x6_dev_s *)dev->ad_priv;
int ret = OK;
bool command_prepared = false;
switch (cmd)
{
case ANIOC_MCP47X6_DAC_SET_GAIN:
{
switch (arg)
{
case MCP47X6_GAIN_1X:
case MCP47X6_GAIN_2X:
priv->cmd &= ~MCP47X6_GAIN_MASK;
priv->cmd |= arg;
command_prepared = true;
break;
default:
ret = -EINVAL;
break;
}
}
break;
case ANIOC_MCP47X6_DAC_SET_POWER_DOWN:
{
switch (arg)
{
case MCP47X6_POWER_DOWN_DISABLED:
case MCP47X6_POWER_DOWN_1K:
case MCP47X6_POWER_DOWN_100K:
case MCP47X6_POWER_DOWN_500K:
priv->cmd &= ~MCP47X6_POWER_DOWN_MASK;
priv->cmd |= arg;
command_prepared = true;
break;
default:
ret = -EINVAL;
break;
}
}
break;
case ANIOC_MCP47X6_DAC_SET_REFERENCE:
{
switch (arg)
{
case MCP47X6_REFERENCE_VDD_UNBUFFERED:
case MCP47X6_REFERENCE_VREF_UNBUFFERED:
case MCP47X6_REFERENCE_VREF_BUFFERED:
priv->cmd &= ~MCP47X6_REFERENCE_MASK;
priv->cmd |= arg;
command_prepared = true;
break;
default:
ret = -EINVAL;
break;
}
}
break;
/* Command was not recognized */
default:
aerr("MCP47X6 ERROR: Unrecognized cmd: %d", cmd);
ret = -ENOTTY;
break;
}
if (command_prepared)
{
ret = mcp47x6_i2c_write(priv, &priv->cmd, sizeof(priv->cmd));
}
return ret;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: mcp47x6_initialize
*
* Description:
* Initialize DAC
*
* Input Parameters:
* i2c - Pointer to a valid I2C master struct.
* addr - I2C device address.
*
* Returned Value:
* Valid MCP47X6 device structure reference on success; a NULL on failure
*
****************************************************************************/
FAR struct dac_dev_s *mcp47x6_initialize(FAR struct i2c_master_s *i2c,
uint8_t addr)
{
FAR struct mcp47x6_dev_s *priv;
FAR struct dac_dev_s *dacdev;
/* Sanity check */
DEBUGASSERT(i2c != NULL);
/* Initialize the MCP47X6 device structure */
priv = kmm_malloc(sizeof(struct mcp47x6_dev_s));
if (priv == NULL)
{
aerr("ERROR: Failed to allocate mcp47x6_dev_s instance\n");
free(priv);
return NULL;
}
dacdev = kmm_malloc(sizeof(struct dac_dev_s));
if (dacdev == NULL)
{
aerr("ERROR: Failed to allocate dac_dev_s instance\n");
return NULL;
}
dacdev->ad_ops = &g_dacops;
dacdev->ad_priv = priv;
priv->i2c = i2c;
priv->addr = addr;
return dacdev;
}
#endif /* CONFIG_MCP47X6 */

View File

@@ -224,6 +224,24 @@ FAR struct dac_dev_s *dac7554_initialize(FAR struct spi_dev_s *spi,
FAR struct dac_dev_s *lmp92001_dac_initialize(FAR struct i2c_master_s *i2c,
uint8_t addr);
/****************************************************************************
* Name: mcp47x6_initialize
*
* Description:
* Initialize DAC
*
* Input Parameters:
* I2C Port number
* Device address
*
* Returned Value:
* Valid MCP47X6 device structure reference on success; a NULL on failure
*
****************************************************************************/
FAR struct dac_dev_s *mcp47x6_initialize(FAR struct i2c_master_s *i2c,
uint8_t addr);
/****************************************************************************
* Name: mcp48xx_initialize
*

View File

@@ -121,6 +121,11 @@
#define AN_ADS1115_FIRST (AN_MCP3008_FIRST + AN_MCP3008_NCMDS)
#define AN_ADS1115_NCMDS 10
/* See include/nuttx/analog/mcp47x6.h */
#define AN_MCP47X6_FIRST (AN_ADS1115_FIRST + AN_ADS1115_NCMDS)
#define AN_MCP47X6_NCMDS 3
/****************************************************************************
* Public Function Prototypes
****************************************************************************/

View File

@@ -0,0 +1,81 @@
/****************************************************************************
* include/nuttx/analog/mcp47x6.h
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
#ifndef __INCLUDE_NUTTX_ANALOG_MCP47X6_H
#define __INCLUDE_NUTTX_ANALOG_MCP47X6_H
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <nuttx/analog/ioctl.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* IOCTL Commands ***********************************************************/
/* Cmd: ANIOC_MCP47X6_DAC_SET_GAIN Arg: mcp47x6_gain_e value
* Cmd: ANIOC_MCP47X6_DAC_SET_POWER_DOWN Arg: mcp47x6_power_down_e value
* Cmd: ANIOC_MCP47X6_DAC_SET_REFERENCE Arg: mcp47x6_reference_e value
*/
#define ANIOC_MCP47X6_DAC_SET_GAIN _ANIOC(AN_MCP47X6_FIRST + 0)
#define ANIOC_MCP47X6_DAC_SET_POWER_DOWN _ANIOC(AN_MCP47X6_FIRST + 1)
#define ANIOC_MCP47X6_DAC_SET_REFERENCE _ANIOC(AN_MCP47X6_FIRST + 2)
/****************************************************************************
* Public Types
****************************************************************************/
enum mcp47x6_command_e
{
MCP47X6_COMMAND_WRITE_DAC = 0 << 5U,
MCP47X6_COMMAND_WRITE_CONFIG = 4 << 5U,
};
enum mcp47x6_reference_e
{
MCP47X6_REFERENCE_VDD_UNBUFFERED = 0 << 3U,
MCP47X6_REFERENCE_VREF_UNBUFFERED = 2 << 3U,
MCP47X6_REFERENCE_VREF_BUFFERED = 3 << 3U,
};
enum mcp47x6_power_down_e
{
MCP47X6_POWER_DOWN_DISABLED = 0 << 1U,
MCP47X6_POWER_DOWN_1K = 1 << 1U,
MCP47X6_POWER_DOWN_100K = 2 << 1U,
MCP47X6_POWER_DOWN_500K = 3 << 1U,
};
/* MCP47X6_GAIN_2X is ignored for MCP47X6_REFERENCE_VDD_UNBUFFERED */
enum mcp47x6_gain_e
{
MCP47X6_GAIN_1X = 0 << 0U,
MCP47X6_GAIN_2X = 1 << 0U
};
#endif /* __INCLUDE_NUTTX_ANALOG_MCP47X6_H */