/**************************************************************************** * drivers/analog/ads1115.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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include /**************************************************************************** * Preprocessor definitions ****************************************************************************/ #define ADS1115_NUM_CHANNELS 8 #define ADS1115_OS_SHIFT (1 << 15) #define ADS1115_MUX_SHIFT (12) #define ADS1115_MUX_MASK (7 << ADS1115_MUX_SHIFT) #define ADS1115_PGA_SHIFT (9) #define ADS1115_PGA_MASK (7 << ADS1115_PGA_SHIFT) #define ADS1115_MODE_MASK (1 << 8) #define ADS1115_DR_SHIFT (5) #define ADS1115_DR_MASK (7 << ADS1115_DR_SHIFT) #define ADS1115_COMP_MODE_MASK (1 << 4) #define ADS1115_COMP_POL_MASK (1 << 3) #define ADS1115_COMP_LAT_MASK (1 << 2) #define ADS1115_COMP_QUE_SHIFT (0) #define ADS1115_COMP_QUE_MASK (3 << ADS1115_COMP_QUE_SHIFT) /* Helper macros for checking modes */ #define ADS1115_ONESHOT_MODE(priv) \ (((priv->cmdbyte & ADS1115_MODE_MASK) == ADS1115_MODE_MASK)) #define ADS1115_CHANNEL_BITS(am_channel) (am_channel << ADS1115_MUX_SHIFT) /* The conversion time is based on the data rate */ #define ADS1115_CONVERSION_TIME (1000000 / (1 << (CONFIG_ADC_ADS1115_DR + 3))) /**************************************************************************** * Private Types ****************************************************************************/ struct ads1115_dev_s { FAR struct i2c_master_s *i2c; FAR const struct adc_callback_s *cb; uint8_t addr; /* Current configuration of ADC. */ uint16_t cmdbyte; }; enum ads1115_registers_e { ADS1115_CONVERSION_REGISTER = 0x00, ADS1115_CONFIG_REGISTER = 0x01, ADS1115_LO_THRESH_REGISTER = 0x02, ADS1115_HI_THRESH_REGISTER = 0x03, }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static int ads1115_bind(FAR struct adc_dev_s *dev, FAR const struct adc_callback_s *callback); static void ads1115_reset(FAR struct adc_dev_s *dev); static int ads1115_setup(FAR struct adc_dev_s *dev); static void ads1115_shutdown(FAR struct adc_dev_s *dev); static void ads1115_rxint(FAR struct adc_dev_s *dev, bool enable); static int ads1115_ioctl(FAR struct adc_dev_s *dev, int cmd, unsigned long arg); /**************************************************************************** * Private Data ****************************************************************************/ static const struct adc_ops_s g_ads1115ops = { .ao_bind = ads1115_bind, .ao_reset = ads1115_reset, .ao_setup = ads1115_setup, .ao_shutdown = ads1115_shutdown, .ao_rxint = ads1115_rxint, .ao_ioctl = ads1115_ioctl, }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: ads1115_write_register * * Description: * Writes a value to a register in the ADS1115. * * Input Parameters: * priv - An ADS1115 device structure * reg - The register to write to. * value - The value to write to the register, in big endian. * ****************************************************************************/ static int ads1115_write_register(FAR struct ads1115_dev_s *priv, enum ads1115_registers_e reg, uint16_t value) { int ret = OK; struct i2c_msg_s i2cmsg[2]; DEBUGASSERT(priv != NULL); /* Write the register into the address pointer register. */ i2cmsg[0].frequency = CONFIG_ADC_ADS1115_I2C_FREQUENCY; i2cmsg[0].addr = priv->addr; i2cmsg[0].flags = 0; i2cmsg[0].buffer = ® i2cmsg[0].length = sizeof(reg); /* Write the value into the register. */ i2cmsg[1].frequency = CONFIG_ADC_ADS1115_I2C_FREQUENCY; i2cmsg[1].addr = priv->addr; i2cmsg[1].flags = I2C_M_NOSTART; i2cmsg[1].buffer = (FAR uint8_t *)&value; i2cmsg[1].length = sizeof(value); ret = I2C_TRANSFER(priv->i2c, i2cmsg, 2); if (ret < 0) { aerr("ADS1115 I2C transfer failed: %d\n", ret); } return ret; } /**************************************************************************** * Name: ads1115_read_register * * Description: * Reads a value from a register in the ADS1115 in big endian. * * Input Parameters: * priv - An ADS1115 device structure * reg - The register to read from. * buf - A pointer to a buffer to store the value read from the register. * ****************************************************************************/ static int ads1115_read_register(FAR struct ads1115_dev_s *priv, uint8_t reg, uint16_t *buf) { int ret = OK; struct i2c_msg_s i2cmsg[2]; DEBUGASSERT(priv != NULL); DEBUGASSERT(buf != NULL); /* Write the register into the address pointer register. */ i2cmsg[0].frequency = CONFIG_ADC_ADS1115_I2C_FREQUENCY; i2cmsg[0].addr = priv->addr; i2cmsg[0].flags = 0; i2cmsg[0].buffer = (FAR uint8_t *)(®); i2cmsg[0].length = sizeof(reg); /* Read from the register. */ i2cmsg[1].frequency = CONFIG_ADC_ADS1115_I2C_FREQUENCY; i2cmsg[1].addr = priv->addr; i2cmsg[1].flags = I2C_M_READ; i2cmsg[1].buffer = (FAR uint8_t *)(buf); i2cmsg[1].length = sizeof(*buf); ret = I2C_TRANSFER(priv->i2c, i2cmsg, 2); if (ret < 0) { aerr("ADS1115 I2C transfer failed: %d\n", ret); } return ret; } /**************************************************************************** * Name: ads1115_read_current_register * * Description: * Reads from the current register in the ADS1115 in big endian. * * Input Parameters: * priv - An ADS1115 device structure * buf - A pointer to a buffer to store the value read from the register. * ****************************************************************************/ static int ads1115_read_current_register(FAR struct ads1115_dev_s *priv, uint16_t *buf) { int ret = OK; struct i2c_msg_s i2cmsg[1]; DEBUGASSERT(priv != NULL); DEBUGASSERT(buf != NULL); i2cmsg[0].frequency = CONFIG_ADC_ADS1115_I2C_FREQUENCY; i2cmsg[0].addr = priv->addr; i2cmsg[0].flags = I2C_M_READ; i2cmsg[0].buffer = (FAR uint8_t *)(buf); i2cmsg[0].length = sizeof(*buf); ret = I2C_TRANSFER(priv->i2c, i2cmsg, 1); if (ret < 0) { aerr("ADS1115 I2C transfer failed: %d\n", ret); } return ret; } /**************************************************************************** * Name: cmdbyte_init * * Description: * Initializes the command byte with the default settings, and writes to the * high and low threshold registers. * * Input Parameters: * priv - An ADS1115 device structure * ****************************************************************************/ static int cmdbyte_init(FAR struct ads1115_dev_s *priv) { int ret = OK; /* Set the default channel. */ priv->cmdbyte = CONFIG_ADC_ADS1115_CHANNEL << ADS1115_MUX_SHIFT; /* Set the default pga setting. */ priv->cmdbyte |= CONFIG_ADC_ADS1115_PGA << ADS1115_PGA_SHIFT; /* Set the default mode. */ #ifndef CONFIG_ADC_ADS1115_CONTINOUS priv->cmdbyte |= ADS1115_MODE_MASK; #endif /* Set the default data rate. */ priv->cmdbyte |= CONFIG_ADC_ADS1115_DR << ADS1115_DR_SHIFT; /* Set the default comparator mode. */ #ifdef CONFIG_ADC_ADS1115_COMP_MODE priv->cmdbyte |= ADS1115_COMP_MODE_MASK; #endif /* Set the default comparator polarity. */ #ifdef CONFIG_ADC_ADS1115_COMP_POL priv->cmdbyte |= ADS1115_COMP_POL_MASK; #endif /* Set the default comparator latching. */ #ifdef CONFIG_ADC_ADS1115_COMP_LAT priv->cmdbyte |= ADS1115_COMP_LAT_MASK; #endif /* Set the comparator queue. */ priv->cmdbyte |= CONFIG_ADC_ADS1115_COMP_QUE << ADS1115_COMP_QUE_SHIFT; ret = ads1115_write_register(priv, ADS1115_HI_THRESH_REGISTER, htobe16(CONFIG_ADC_ADS1115_HI_THRESH)); if (ret != OK) { aerr("Failed to write high threshold register\n"); return ret; } return ads1115_write_register(priv, ADS1115_LO_THRESH_REGISTER, htobe16(CONFIG_ADC_ADS1115_LO_THRESH)); } /**************************************************************************** * Name: ads1115_trigger_conversion * * Description: * Triggers a conversion on the specified channel. * * Input Parameters: * priv - An ADS1115 device structure * channel - The channel number to trigger conversion on (0-7) * * Return Value: * OK on success; a negated errno on failure * * NOTE: * Each channel corresponds to a differing mux configuration as defined in * the datasheet. * * channel MUX Configuration * 0 AINP = AIN0, AINN = AIN1 * 1 AINP = AIN0, AINN = AIN3 * 2 AINP = AIN1, AINN = AIN3 * 3 AINP = AIN2, AINN = AIN3 * 4 AINP = AIN0, AINN = GND * 5 AINP = AIN0, AINN = GND * 6 AINP = AIN1, AINN = GND * 7 AINP = AIN2, AINN = GND * 8 AINP = AIN3, AINN = GND ****************************************************************************/ static int ads1115_trigger_conversion(FAR struct ads1115_dev_s *priv, FAR struct adc_msg_s *msg) { int ret = OK; uint16_t channel_bits; bool new_channel; DEBUGASSERT(priv != NULL); DEBUGASSERT(msg != NULL); DEBUGASSERT(msg->am_channel <= ADS1115_NUM_CHANNELS); channel_bits = ADS1115_CHANNEL_BITS(msg->am_channel); new_channel = ((priv->cmdbyte & ADS1115_MUX_MASK) != channel_bits); /* Check if our current channel is the same as the one that * is set or if we are in one shot mode. */ if (new_channel || ADS1115_ONESHOT_MODE(priv)) { /* Clear the current mux bits. */ priv->cmdbyte &= ~(ADS1115_MUX_MASK); /* Set the new mux bits. */ priv->cmdbyte |= channel_bits; /* Set the OS bit to start conversion. */ priv->cmdbyte |= ADS1115_OS_SHIFT; /* Write to the configuration register. */ ret = ads1115_write_register(priv, ADS1115_CONFIG_REGISTER, htobe16(priv->cmdbyte)); } return ret; } /**************************************************************************** * Name: ads1115_read_conversion * * Description: * Reads the conversion result from the ADC. * * Input Parameters: * priv - An ADS1115 device structure * value - Pointer to store the conversion result * * Return Value: * OK on success; a negated errno on failure ****************************************************************************/ static int ads1115_read_conversion(FAR struct ads1115_dev_s *priv, FAR struct adc_msg_s *msg) { int ret = OK; uint16_t buf; int count = 0; /* Read the configuration register until OS has been set to 1, indicating * that the conversion value is ready. */ ret = ads1115_read_register(priv, ADS1115_CONFIG_REGISTER, &buf); while (!(be16toh(buf) & ADS1115_OS_SHIFT) && count < 3) { up_udelay(ADS1115_CONVERSION_TIME); ret = ads1115_read_register(priv, ADS1115_CONFIG_REGISTER, &buf); count++; } /* If we are here, either the conversion completed or we encountered some * error. */ if (ret != OK) { return ret; } if (count >= 3) { aerr("ADS1115 timed out waiting for conversion to finish\n"); return -ETIMEDOUT; } /* Read the conversion register. */ ret = ads1115_read_register(priv, ADS1115_CONVERSION_REGISTER, &buf); msg->am_data = (uint32_t)betoh16(buf); priv->cmdbyte &= ~(ADS1115_OS_SHIFT); /* Clear the OS bit. */ return ret; } /**************************************************************************** * Name: ads1115_readchannel * * Description: * Reads a conversion from the ADC. * * Input Parameters: * priv - An ADS1115 device structure * msg - An ADC message struct where the am_channel member contains the * channel number to be read, and where the am_data member is where the * reading is stored. * * NOTE: * When switching between channels in continuous mode, old data may be * read. Using the ALERT/RDY pin can avoid this. * * Each channel corresponds to a differing mux configuration as defined in * the datasheet. * * msg->am_channel MUX Configuration * 0 AINP = AIN0, AINN = AIN1 * 1 AINP = AIN0, AINN = AIN3 * 2 AINP = AIN1, AINN = AIN3 * 3 AINP = AIN2, AINN = AIN3 * 4 AINP = AIN0, AINN = GND * 5 AINP = AIN0, AINN = GND * 6 AINP = AIN1, AINN = GND * 7 AINP = AIN2, AINN = GND * 8 AINP = AIN3, AINN = GND ****************************************************************************/ static int ads1115_readchannel(FAR struct ads1115_dev_s *priv, FAR struct adc_msg_s *msg) { int ret = OK; DEBUGASSERT(priv != NULL); DEBUGASSERT(msg != NULL); DEBUGASSERT(msg->am_channel <= ADS1115_NUM_CHANNELS); /* Trigger a conversion on the requested channel */ ret = ads1115_trigger_conversion(priv, msg); if (ret) { return ret; } return ads1115_read_conversion(priv, msg); } /**************************************************************************** * Name: ads1115_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 ads1115_bind(FAR struct adc_dev_s *dev, FAR const struct adc_callback_s *callback) { FAR struct ads1115_dev_s *priv = (FAR struct ads1115_dev_s *)dev->ad_priv; DEBUGASSERT(priv != NULL); priv->cb = callback; return 0; } /**************************************************************************** * Name: ads1115_reset * * Description: * Reset the ADC device. Called early to initialize the hardware. This * is called, before ao_setup() and on error conditions. * ****************************************************************************/ static void ads1115_reset(FAR struct adc_dev_s *dev) { FAR struct ads1115_dev_s *priv = (FAR struct ads1115_dev_s *)dev->ad_priv; cmdbyte_init(priv); } /**************************************************************************** * Name: ads1115_setup * * Description: * Configure the ADC. This method is called the first time that the ADC * device is opened. This method will write the default configuration into * the configuration register. * ****************************************************************************/ static int ads1115_setup(FAR struct adc_dev_s *dev) { FAR struct ads1115_dev_s *priv = (FAR struct ads1115_dev_s *)dev->ad_priv; return ads1115_write_register(priv, ADS1115_CONFIG_REGISTER, htobe16(priv->cmdbyte)); } /**************************************************************************** * Name: ads1115_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. The * ADS1115 can be put into a power-down state by setting the mode bit to * single-shot. This will stop the ADC from converting and reduce power * consumption. * ****************************************************************************/ static void ads1115_shutdown(FAR struct adc_dev_s *dev) { FAR struct ads1115_dev_s *priv = (FAR struct ads1115_dev_s *)dev->ad_priv; priv->cmdbyte |= ADS1115_MODE_MASK; ads1115_write_register(priv, ADS1115_CONFIG_REGISTER, htobe16(priv->cmdbyte)); } /**************************************************************************** * Name: ads1115_rxint * * Description: * Needed for ADC upper-half compatibility but conversion interrupts * are not supported by the ADS1115. * ****************************************************************************/ static void ads1115_rxint(FAR struct adc_dev_s *dev, bool enable) { } /**************************************************************************** * Name: ads1115_ioctl * * Description: * All ioctl calls will be routed through this method. * ****************************************************************************/ static int ads1115_ioctl(FAR struct adc_dev_s *dev, int cmd, unsigned long arg) { FAR struct ads1115_dev_s *priv = (FAR struct ads1115_dev_s *)dev->ad_priv; int ret = OK; switch (cmd) { case ANIOC_TRIGGER: { struct adc_msg_s msg; /* Read all channels in sequence */ for (uint8_t i = 0; i < ADS1115_NUM_CHANNELS; i++) { msg.am_channel = i; ret = ads1115_readchannel(priv, &msg); if (ret) { aerr("Error reading channel %d with error %d \n", i, ret); break; } priv->cb->au_receive(dev, i, msg.am_data); } } break; case ANIOC_ADS1115_SET_PGA: { DEBUGASSERT(arg >= ADS1115_PGA1 && arg <= ADS1115_PGA8); priv->cmdbyte &= ~ADS1115_PGA_MASK; priv->cmdbyte |= arg << ADS1115_PGA_SHIFT; ret = ads1115_write_register(priv, ADS1115_CONFIG_REGISTER, htobe16(priv->cmdbyte)); } break; case ANIOC_ADS1115_SET_MODE: { if (arg == ADS1115_MODE1) { priv->cmdbyte &= ~ADS1115_MODE_MASK; } else if (arg == ADS1115_MODE2) { priv->cmdbyte |= ADS1115_MODE_MASK; } else { ret = -EINVAL; break; } ret = ads1115_write_register(priv, ADS1115_CONFIG_REGISTER, htobe16(priv->cmdbyte)); } break; case ANIOC_ADS1115_SET_DR: { DEBUGASSERT(arg >= ADS1115_DR1 && arg <= ADS1115_DR8); priv->cmdbyte &= ~ADS1115_DR_MASK; priv->cmdbyte |= arg << ADS1115_DR_SHIFT; ret = ads1115_write_register(priv, ADS1115_CONFIG_REGISTER, htobe16(priv->cmdbyte)); } break; case ANIOC_ADS1115_SET_COMP_POL: { if (arg == ADS1115_COMP_POL1) { priv->cmdbyte &= ~ADS1115_COMP_POL_MASK; } else if (arg == ADS1115_COMP_POL2) { priv->cmdbyte |= ADS1115_COMP_POL_MASK; } else { ret = -EINVAL; break; } ret = ads1115_write_register(priv, ADS1115_CONFIG_REGISTER, htobe16(priv->cmdbyte)); } break; case ANIOC_ADS1115_SET_COMP_MODE: { if (arg == ADS1115_COMP_MODE1) { priv->cmdbyte &= ~ADS1115_COMP_MODE_MASK; } else if (arg == ADS1115_COMP_MODE2) { priv->cmdbyte |= ADS1115_COMP_MODE_MASK; } else { ret = -EINVAL; break; } ret = ads1115_write_register(priv, ADS1115_CONFIG_REGISTER, htobe16(priv->cmdbyte)); } break; case ANIOC_ADS1115_SET_COMP_LAT: { if (arg == ADS1115_COMP_LAT1) { priv->cmdbyte &= ~ADS1115_COMP_LAT_MASK; } else if (arg == ADS1115_COMP_LAT2) { priv->cmdbyte |= ADS1115_COMP_LAT_MASK; } else { ret = -EINVAL; break; } ret = ads1115_write_register(priv, ADS1115_CONFIG_REGISTER, htobe16(priv->cmdbyte)); } break; case ANIOC_ADS1115_SET_COMP_QUEUE: { DEBUGASSERT(arg >= ADS1115_COMP_QUEUE1 && arg <= ADS1115_COMP_QUEUE4); priv->cmdbyte &= ~ADS1115_COMP_QUE_MASK; priv->cmdbyte |= arg << ADS1115_COMP_QUE_SHIFT; ret = ads1115_write_register(priv, ADS1115_CONFIG_REGISTER, htobe16(priv->cmdbyte)); } break; case ANIOC_ADS1115_READ_CHANNEL: { ret = ads1115_readchannel(priv, (FAR struct adc_msg_s *)arg); } break; case ANIOC_ADS1115_TRIGGER_CONVERSION: { ret = ads1115_trigger_conversion(priv, (FAR struct adc_msg_s *)arg); } break; case ANIOC_ADS1115_READ_CHANNEL_NO_CONVERSION: { ret = ads1115_read_conversion(priv, (FAR struct adc_msg_s *)arg); } break; case ANIOC_ADS1115_SET_HI_THRESH: { DEBUGASSERT(arg >= 0 && arg <= UINT16_MAX); ret = ads1115_write_register(priv, ADS1115_HI_THRESH_REGISTER, htobe16(arg)); } break; case ANIOC_ADS1115_SET_LO_THRESH: { DEBUGASSERT(arg >= 0 && arg <= UINT16_MAX); ret = ads1115_write_register(priv, ADS1115_LO_THRESH_REGISTER, htobe16(arg)); } break; case ANIOC_GET_NCHANNELS: { ret = ADS1115_NUM_CHANNELS; } break; default: ret = -ENOTTY; break; } return ret; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: ads1115_initialize * * Description: * Initialize the selected ADC * * Input Parameters: * i2c - Pointer to a valid I2C master struct. * addr - I2C device address. * * Returned Value: * Valid ADC device structure reference on success; a NULL on failure * * **************************************************************************/ FAR struct adc_dev_s *ads1115_initialize(FAR struct i2c_master_s *i2c, uint8_t addr) { FAR struct ads1115_dev_s *priv; FAR struct adc_dev_s *adcdev; int ret; DEBUGASSERT(i2c != NULL); priv = kmm_malloc(sizeof(struct ads1115_dev_s)); if (priv == NULL) { aerr("ERROR: Failed to allocate ads1115_dev_s instance\n"); return NULL; } priv->cb = NULL; priv->i2c = i2c; priv->addr = addr; priv->cmdbyte = 0; ret = cmdbyte_init(priv); if (ret != OK) { aerr("Failed to initialize ADS1115\n"); free(priv); return NULL; } adcdev = kmm_malloc(sizeof(struct adc_dev_s)); if (adcdev == NULL) { aerr("ERROR: Failed to allocate adc_dev_s instance\n"); return NULL; } memset(adcdev, 0, sizeof(struct adc_dev_s)); adcdev->ad_ops = &g_ads1115ops; adcdev->ad_priv = priv; return adcdev; }