nuttx/drivers/analog/ads7828.c

495 lines
14 KiB
C
Raw Normal View History

/****************************************************************************
* drivers/analog/ads7828.c
*
* 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 <errno.h>
#include <assert.h>
#include <debug.h>
#include <endian.h>
#include <nuttx/arch.h>
#include <nuttx/i2c/i2c_master.h>
#include <nuttx/analog/adc.h>
#include <nuttx/analog/ioctl.h>
#include <nuttx/analog/ads7828.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#if defined(CONFIG_ADC_ADS7828)
#define ADS7828_NUM_CHANNELS 8
/* All conversions are started by writing a command byte to the ADS7828.
* Below are the bit/ mask definitions for this byte.
*/
#define ADS7828_CMD_BYTE_MODE (1 << 7)
#define ADS7828_CMD_BYTE_CHANNEL_SHIFT 4
#define ADS7828_CMD_BYTE_CHANNEL_MASK (7 << ADS7828_CMD_BYTE_CHANNEL_SHIFT)
#define ADS7828_CMD_BYTE_REF (1 << 3)
#define ADS7828_CMD_BYTE_PWR_DOWN (1 << 2)
/****************************************************************************
* Private Types
****************************************************************************/
struct ads7828_dev_s
{
FAR struct i2c_master_s *i2c;
FAR const struct adc_callback_s *cb;
uint8_t addr;
/* List of channels to read on every convert trigger.
* Bit position corresponds to channel. i.e. bit0 = 1 to read channel 0.
*/
uint8_t chanstrobed;
/* Current configuration of the ADC. Encoded in its position in the
* command byte that is sent to the ADS7828 to trigger a conversion
*/
uint8_t cmdbyte;
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* ads7828 helpers */
static int ads7828_manage_strobe(FAR struct ads7828_dev_s *priv,
uint8_t channel, bool add_nremove);
static int ads7828_readchannel(FAR struct ads7828_dev_s *priv,
FAR struct adc_msg_s *msg);
/* ADC methods */
static int ads7828_bind(FAR struct adc_dev_s *dev,
FAR const struct adc_callback_s *callback);
static void ads7828_reset(FAR struct adc_dev_s *dev);
static int ads7828_setup(FAR struct adc_dev_s *dev);
static void ads7828_shutdown(FAR struct adc_dev_s *dev);
static void ads7828_rxint(FAR struct adc_dev_s *dev, bool enable);
static int ads7828_ioctl(FAR struct adc_dev_s *dev, int cmd,
unsigned long arg);
/****************************************************************************
* Private Data
****************************************************************************/
static const struct adc_ops_s g_adcops =
{
ads7828_bind, /* ao_bind */
ads7828_reset, /* ao_reset */
ads7828_setup, /* ao_setup */
ads7828_shutdown, /* ao_shutdown */
ads7828_rxint, /* ao_rxint */
ads7828_ioctl /* ao_read */
};
static struct ads7828_dev_s g_adcpriv;
static struct adc_dev_s g_adcdev =
{
&g_adcops, /* ad_ops */
&g_adcpriv /* ad_priv */
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: ads7828_manage_strobe
*
* Description:
* Controls which channels are read on ANIOC_TRIGGER. By default all
* channels are read.
*
* Returned Value:
* 0 on success. Negated errno on failure.
*
****************************************************************************/
static int ads7828_manage_strobe(FAR struct ads7828_dev_s *priv,
uint8_t channel, bool add_nremove)
{
int ret = OK;
if (priv == NULL || channel >= ADS7828_NUM_CHANNELS)
{
ret = -EINVAL;
}
else
{
uint8_t flag = 1U << channel;
if (add_nremove)
{
priv->chanstrobed |= flag;
}
else
{
priv->chanstrobed &= ~flag;
}
}
return ret;
}
/****************************************************************************
* Name: ads7828_readchannel
*
* Description:
* Reads a conversion from the ADC.
*
* Input Parameters:
* msg - msg->am_channel should be set to the channel to be read.
* msg->am_data will store the result of the read.
*
* Returned Value:
* 0 on success. Negated errno on failure.
*
* Assumptions/Limitations:
* NOTE: When used in single-ended mode, msg->am_channel will be converted
* to the corresponding channel selection bits in the command byte.
* In differential mode, msg->am_channel is used as the channel
* selection bits. The corresponding "channels" are as follows:
*
* msg->am_channel Analog Source
* 0 +CH0, -CH1
* 1 +CH2, -CH3
* 2 +CH4, -CH5
* 3 +CH6, -CH7
* 4 +CH1, -CH0
* 5 +CH3, -CH2
* 6 +CH5, -CH4
* 7 +CH7, -CH6
*
****************************************************************************/
static int ads7828_readchannel(FAR struct ads7828_dev_s *priv,
FAR struct adc_msg_s *msg)
{
int ret = OK;
if (priv == NULL || msg == NULL)
{
ret = -EINVAL;
}
else
{
struct i2c_msg_s i2cmsg[2];
uint8_t channel = msg->am_channel;
/* Convert single-ended channels to write encoding. */
if (priv->cmdbyte & ADS7828_CMD_BYTE_MODE)
{
channel = channel / 2;
if (msg->am_channel % 2 != 0)
{
channel += 4;
}
}
uint8_t cmdbyte = channel << ADS7828_CMD_BYTE_CHANNEL_SHIFT;
cmdbyte |= priv->cmdbyte;
i2cmsg[0].frequency = CONFIG_ADS7828_FREQUENCY;
i2cmsg[0].addr = priv->addr;
i2cmsg[0].flags = I2C_M_NOSTOP;
i2cmsg[0].buffer = &cmdbyte;
i2cmsg[0].length = sizeof(cmdbyte);
i2cmsg[1].frequency = CONFIG_ADS7828_FREQUENCY;
i2cmsg[1].addr = priv->addr;
i2cmsg[1].flags = I2C_M_READ;
uint16_t buf;
i2cmsg[1].buffer = (FAR uint8_t *)(&buf);
i2cmsg[1].length = sizeof(buf);
ret = I2C_TRANSFER(priv->i2c, i2cmsg, 2);
if (ret < 0)
{
aerr("ADS7828 I2C transfer failed: %d", ret);
}
msg->am_data = be16toh(buf);
}
return ret;
}
/****************************************************************************
* Name: ads7828_bind
*
* Description:
* Bind the upper-half driver callbacks to the lower-half implementation.
* This must be called early in order to receive ADC event notifications.
*
****************************************************************************/
static int ads7828_bind(FAR struct adc_dev_s *dev,
FAR const struct adc_callback_s *callback)
{
FAR struct ads7828_dev_s *priv = (FAR struct ads7828_dev_s *)dev->ad_priv;
DEBUGASSERT(priv != NULL);
priv->cb = callback;
return OK;
}
/****************************************************************************
* Name: ads7828_reset
*
* Description:
* Reset the ADC device. Called early to initialize the hardware. This
* is called, before ao_setup() and on error conditions.
*
****************************************************************************/
static void ads7828_reset(FAR struct adc_dev_s *dev)
{
FAR struct ads7828_dev_s *priv = (FAR struct ads7828_dev_s *)dev->ad_priv;
priv->cmdbyte = 0;
priv->chanstrobed = 0xffu;
}
/****************************************************************************
* Name: ads7828_setup
*
* Description:
* Configure the ADC. This method is called the first time that the ADC
* device is opened. The ADS7828 is quite simple and nothing special is
* needed to be done.
*
****************************************************************************/
static int ads7828_setup(FAR struct adc_dev_s *dev)
{
return OK;
}
/****************************************************************************
* Name: ads7828_shutdown
*
* Description:
* Disable the ADC. This method is called when the ADC device is closed.
* This method should reverse the operation of the setup method, but as
* the ADS7828 is quite simple does not need to do anything.
*
****************************************************************************/
static void ads7828_shutdown(FAR struct adc_dev_s *dev)
{
}
/****************************************************************************
* Name: ads7828_rxint
*
* Description:
* Needed for ADC upper-half compatibility but conversion interrupts
* are not supported by the ADC7828.
*
****************************************************************************/
static void ads7828_rxint(FAR struct adc_dev_s *dev, bool enable)
{
}
/****************************************************************************
* Name: ads7828_ioctl
*
* Description:
* All ioctl calls will be routed through this method
*
****************************************************************************/
static int ads7828_ioctl(FAR struct adc_dev_s *dev, int cmd,
unsigned long arg)
{
FAR struct ads7828_dev_s *priv = (FAR struct ads7828_dev_s *)dev->ad_priv;
int ret = OK;
switch (cmd)
{
case ANIOC_TRIGGER:
{
struct adc_msg_s msg;
int i;
for (i = 0; (i < ADS7828_NUM_CHANNELS) && (ret == OK); i++)
{
if ((priv->chanstrobed >> i) & 1u)
{
msg.am_channel = i;
ret = ads7828_readchannel(priv, &msg);
if (ret == OK)
{
priv->cb->au_receive(&g_adcdev, i, msg.am_data);
}
}
}
}
break;
/* Add a channel to list of channels read on ANIOC_TRIGGER */
case ANIOC_ADS7828_ADD_CHAN:
{
ret = ads7828_manage_strobe(priv, (uint8_t)arg, true);
}
break;
/* Remove a channel from list of channels read on ANIOC_TRIGGER */
case ANIOC_ADS7828_REMOVE_CHAN:
{
ret = ads7828_manage_strobe(priv, (uint8_t)arg, false);
}
break;
/* Read a single channel from the ADC */
case ANIOC_ADS7828_READ_CHANNEL:
{
FAR struct adc_msg_s *msg = (FAR struct adc_msg_s *)arg;
ret = ads7828_readchannel(priv, msg);
}
break;
/* Set the ADC reference voltage */
case ANIOC_ADS7828_SET_REF:
{
if (arg == ADS7828_REF_INTERNAL)
{
priv->cmdbyte |= ADS7828_CMD_BYTE_REF;
}
else if (arg == ADS7828_REF_EXTERNAL)
{
priv->cmdbyte &= ~ADS7828_CMD_BYTE_REF;
}
else
{
ret = -EINVAL;
}
}
break;
/* Set mode to single-ended or differential-mode */
case ANIOC_ADS7828_MODE:
{
if (arg == ADS7828_SINGLE_ENDED)
{
priv->cmdbyte |= ADS7828_CMD_BYTE_MODE;
}
else if (arg == ADS7828_DIFFERENTIAL)
{
priv->cmdbyte &= ~ADS7828_CMD_BYTE_MODE;
}
else
{
ret = -EINVAL;
}
}
break;
/* Set whether to power down ADC between conversions or not */
case ANIOC_ADS7828_POWER_SAVE:
{
if ((bool)arg)
{
priv->cmdbyte &= ~ADS7828_CMD_BYTE_PWR_DOWN;
}
else
{
priv->cmdbyte |= ADS7828_CMD_BYTE_PWR_DOWN;
}
}
break;
/* Command was not recognized */
default:
ret = -ENOTTY;
aerr("ADS7828 ERROR: Unrecognized cmd: %d\n", cmd);
break;
}
return ret;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: ads7828_initialize
*
* Description:
* Initialize the selected adc
*
* Input Parameters:
* i2c - Pointer to a I2C master struct for the bus the ADC resides on.
* addr - I2C address of the ADC
*
* Returned Value:
* Valid ADC device structure reference on success; a NULL on failure
*
****************************************************************************/
FAR struct adc_dev_s *ads7828_initialize(FAR struct i2c_master_s *i2c,
uint8_t addr)
{
DEBUGASSERT(i2c != NULL);
/* Driver state data */
FAR struct ads7828_dev_s *priv;
priv = (FAR struct ads7828_dev_s *)g_adcdev.ad_priv;
priv->cb = NULL;
priv->i2c = i2c;
priv->addr = addr;
priv->cmdbyte = 0;
priv->chanstrobed = 0xffu;
return &g_adcdev;
}
#endif