ADC converter not reading correctly

I have a nRF52832 custom board derived from the 52832DK board. All is working perfectly with the exception of the ADC converter. I am only using AIN0 which is on pin 0.02. At P0.02 I can see the voltage changing from 0.0V to 1.67V based on a battery monitor pin value. I do not see the correct value being seen by the ADC. I have used the ADC converter in the nRF9160 many times for many projects and all worked fine. This is my first project using the nRF52832. as shown below I expect 1.67V when enabled and 0.0V when disabled. that is what I see on P 0.02 (AIN0). I am sure it is something I have set up incorrectly.

cli CMD: adc

0: ADC raw value: 88 0x0058 ret: 0
Measured voltage: 0.31 V
BAT_MON Enabled ADC: 0.31


0: ADC raw value: 79 0x004F ret: 0
Measured voltage: 0.28 V
BAT_MON Disabled ADC: 0.28

prj.conf

#
# Copyright (c) 2018 Nordic Semiconductor
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

# Enable the UART driver
CONFIG_UART_ASYNC_API=y
CONFIG_NRFX_UARTE=y

# iUART0 Enabled
CONFIG_SERIAL=y
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y
CONFIG_LOG=y

# UART0 Disabled
#CONFIG_SERIAL=n
#CONFIG_CONSOLE=n
#CONFIG_UART_CONSOLE=n
#CONFIG_LOG=n

CONFIG_PM_DEVICE=y

CONFIG_GPIO=y

# Make sure printk is printing to the UART console

CONFIG_HEAP_MEM_POOL_SIZE=2048

CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
#CONFIG_BT_DEVICE_NAME="Nordic_UART_Service"
#CONFIG_BT_DEVICE_NAME="Data_Logger_BLE_"
CONFIG_BT_DEVICE_NAME="DL_"
CONFIG_BT_DEVICE_APPEARANCE=833
CONFIG_BT_MAX_CONN=1
CONFIG_BT_MAX_PAIRED=1

# Watchdog
CONFIG_WDT_LOG_LEVEL_DBG=y
CONFIG_WDT_LOG_LEVEL_DBG=y
CONFIG_WATCHDOG=y
CONFIG_WDT_DISABLE_AT_BOOT=n

# Enable the NUS service
CONFIG_BT_NUS=y

# Enable bonding
CONFIG_BT_SETTINGS=y
CONFIG_FLASH=y
CONFIG_FLASH_PAGE_LAYOUT=y
CONFIG_FLASH_MAP=y
CONFIG_NVS=y
CONFIG_SETTINGS=y

# Enable DK LED and Buttons library
CONFIG_DK_LIBRARY=y

# This example requires more stack
CONFIG_MAIN_STACK_SIZE=1152
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048

# Config logger
CONFIG_USE_SEGGER_RTT=y
CONFIG_LOG_BACKEND_RTT=y
CONFIG_LOG_BACKEND_UART=n
CONFIG_LOG_PRINTK=n
CONFIG_NEWLIB_LIBC=y
CONFIG_NEWLIB_LIBC_FLOAT_PRINTF=y

CONFIG_ASSERT=y

#TJM Added
CONFIG_I2C=y
CONFIG_ADC=y
CONFIG_ADC_ASYNC=y
CONFIG_ADC_LOG_LEVEL_INF=y
#CONFIG_ADC_LOG_LEVEL_DBG=y
CONFIG_ADC_NRFX_SAADC=y
CONFIG_NRFX_SAADC=y
CONFIG_SPI=y
CONFIG_SPI_ASYNC=y
CONFIG_SPI_LOG_LEVEL_INF=y
CONFIG_WATCHDOG_LOG_LEVEL_DBG=y
CONFIG_LOG_DEFAULT_LEVEL=4

CONFIG_REBOOT=y

main.c


/* start ADC test */
#include <math.h>
#include <nrfx_saadc.h>
#include <zephyr/drivers/adc.h>
#define ADC_BUFFER_SIZE 1 //!< number of ADC channels to read
// #define ADC_BUFFER_SIZE 4 //!< number of ADC channels to read

#if DT_NODE_HAS_STATUS(DT_ALIAS(adc), okay) //!< check if adc alias is okay in devicetree and set ADC_DEV_NODE to adc alias
#define ADC_DEV_NODE DT_ALIAS(adc)          //!< if adc alias is okay, set ADC_DEV_NODE to adc alias
#else
#error "Please set the correct ADC device"
#endif

////#define ADC_DEVICE_NAME DT_ADC_0_NAME
#define ADC_RESOLUTION 10 //!< resolution for ADC readings in bits (e.g., 10 bits for values from 0 to 1023)
#define ADC_GAIN                                                                                                                           \
  ADC_GAIN_1_6 //!< gain setting for ADC readings, set to 1/6 to allow for a wider input voltage range (e.g., up to 3.6V with a 3.3V
               //!< reference)
#define ADC_REFERENCE                                                                                                                      \
  ADC_REF_INTERNAL //!< reference voltage for ADC readings, set to internal reference for stable and consistent measurements
#define ADC_ACQUISITION_TIME                                                                                                               \
  ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 10) //!< acquisition time for ADC readings, set to 10 microseconds to allow for accurate sampling
                                              //!< of the input signal while minimizing noise and power consumption
#define ADC_1ST_CHANNEL_ID 0 //!< channel ID for the first ADC channel to read, set to 0 for the first channel (e.g., AIN0 on nRF52832)
#define ADC_1ST_CHANNEL_INPUT                                                                                                              \
  NRF_SAADC_INPUT_AIN0 //!< input selection for the first ADC channel, set to AIN0 for the first analog input on nRF52832 (e.g., connected
                       //!< to a sensor output or battery voltage)
#define ADC_2ND_CHANNEL_ID 1 //!< channel ID for the second ADC channel to read, set to 1 for the second channel (e.g., AIN1 on nRF52832)
#define ADC_2ND_CHANNEL_INPUT                                                                                                              \
  NRF_SAADC_INPUT_AIN1 //!< input selection for the second ADC channel, set to AIN1 for the second analog input on nRF52832 (e.g., connected
                       //!< to a different sensor output or reference voltage)
#define ADC_3RD_CHANNEL_ID 2 //!< channel ID for the third ADC channel to read, set to 2 for the third channel (e.g., AIN2 on nRF52832)
#define ADC_3RD_CHANNEL_INPUT                                                                                                              \
  NRF_SAADC_INPUT_AIN2 //!< input selection for the third ADC channel, set to AIN2 for the third analog input on nRF52832 (e.g., connected
                       //!< to a different sensor output or reference voltage)
#define ADC_4TH_CHANNEL_ID 3 //!< channel ID for the fourth ADC channel to read, set to 3 for the fourth channel (e.g., AIN3 on nRF52832)
#define ADC_4TH_CHANNEL_INPUT                                                                                                              \
  NRF_SAADC_INPUT_AIN3 //!< input selection for the fourth ADC channel, set to AIN3 for the fourth analog input on nRF52832 (e.g., connected
                       //!< to a different sensor output or reference voltage)
#define ADC_5TH_CHANNEL_ID 4 //!< channel ID for the fifth ADC channel to read, set to 4 for the fifth channel (e.g., AIN4 on nRF52832)
#define ADC_5TH_CHANNEL_INPUT                                                                                                              \
  NRF_SAADC_INPUT_AIN4 //!< input selection for the fifth ADC channel, set to AIN4 for the fifth analog input on nRF52832 (e.g., connected
                       //!< to a different sensor output or reference voltage)
#define ADC_6ST_CHANNEL_ID 5 //!< channel ID for the sixth ADC channel to read, set to 5 for the sixth channel (e.g., AIN5 on nRF52832)
#define ADC_6ST_CHANNEL_INPUT                                                                                                              \
  NRF_SAADC_INPUT_AIN5 //!< input selection for the sixth ADC channel, set to AIN5 for the sixth analog input on nRF52832 (e.g., connected
                       //!< to a different sensor output or reference voltage)

const struct device *const adc_dev = DEVICE_DT_GET(ADC_DEV_NODE); //!< get the device structure for the ADC device defined by ADC_DEV_NODE

#if 0
static const struct adc_channel_cfg m_4th_channel_cfg = //!< ADC config for ADC input
    {
        .gain = ADC_GAIN,
        .reference = ADC_REFERENCE,
        .acquisition_time = ADC_ACQUISITION_TIME,
        .channel_id = ADC_4TH_CHANNEL_ID,
#if defined(CONFIG_ADC_CONFIGURABLE_INPUTS)
        .input_positive = ADC_4TH_CHANNEL_INPUT,
#endif
};
#endif

#if 0
static const struct adc_channel_cfg m_3rd_channel_cfg = {
    .gain = ADC_GAIN,
    .reference = ADC_REFERENCE,
    .acquisition_time = ADC_ACQUISITION_TIME,
    .channel_id = ADC_3RD_CHANNEL_ID,
#if defined(CONFIG_ADC_CONFIGURABLE_INPUTS)
    .input_positive = ADC_3RD_CHANNEL_INPUT,
#endif
};
#endif

#if 0
static const struct adc_channel_cfg m_2nd_channel_cfg = //!< ADC config for Battery input
    {
        .gain = ADC_GAIN,
        .reference = ADC_REFERENCE,
        .acquisition_time = ADC_ACQUISITION_TIME,
        .channel_id = ADC_2ND_CHANNEL_ID,
#if defined(CONFIG_ADC_CONFIGURABLE_INPUTS)
        .input_positive = ADC_2ND_CHANNEL_INPUT,
#endif
};
#endif

#if 1
/*!
 * \brief ADC configuration for the first channel
 * This structure defines the configuration for the first ADC channel, including gain, reference voltage, acquisition time, channel ID, and
 * input selection. It is used to set up the ADC for reading from the specified channel with the desired settings.
 */
static const struct adc_channel_cfg m_1st_channel_cfg = {
    .gain = ADC_GAIN,
    .reference = ADC_REFERENCE,
    .acquisition_time = ADC_ACQUISITION_TIME,
    .channel_id = ADC_1ST_CHANNEL_ID,
#if defined(CONFIG_ADC_CONFIGURABLE_INPUTS)
    .input_positive = ADC_1ST_CHANNEL_INPUT,
#endif
};
#endif

static int16_t m_sample_buffer[ADC_BUFFER_SIZE]; //!< buffer to receive ADC data from configured channels

#define ADC_REF_MV 3600U
#define ADC_DIV_MV 1000U
#define ADC_MAX_VAL ((1U << ADC_RESOLUTION) - 1U)

static const float adc_volts_per_step = (float)ADC_REF_MV / ((float)ADC_MAX_VAL * (float)ADC_DIV_MV);

/** \fn static inline float adc_raw_to_voltage(uint16_t raw)
 *
 * \brief Convert raw ADC reading to voltage in volts.
 *
 * \param raw Raw ADC sample value (integer from 0..ADC_MAX_VAL)
 * \return Voltage corresponding to the raw ADC value in volts (float)
 *
 * Uses the precomputed adc_volts_per_step factor which incorporates the
 * reference voltage (ADC_REF_MV), ADC resolution and any divider (ADC_DIV_MV).
 */
static inline float adc_raw_to_voltage(uint16_t raw) { return (float)raw * adc_volts_per_step; }

/*!
 * \fn int adc_sample(void)
 *
 * \brief get a single reading from ADC channel
 *
 * will read ADC_1ST_CHANNEL_ID\n
 * resoultion set to ADC_RESOLUTION in millivolts\n
 * will printout floating point value based on 3.6V max\n
 *
 * \return ADC_RESOLUTION value on ADC_1ST_CHANNEL_ID if pass or -1 if failed\n
 */
int adc_sample(void) {
#if 1
  int ret;
  const struct adc_sequence sequence = {
      .channels = BIT(ADC_1ST_CHANNEL_ID),
      .buffer = m_sample_buffer,
      .buffer_size = sizeof(m_sample_buffer),
      .resolution = ADC_RESOLUTION,
  };

  if (!adc_dev) {
    return -ENODEV;
  }

  ret = adc_read(adc_dev, &sequence);
  if (ret != 0) {
    myPrintkE("ADC read err: %d\n", ret);
    return ret;
  }

  for (int i = 0; i < ADC_BUFFER_SIZE; i++) {
    myPrintkI("%d: ADC raw value: %d 0x%04X ret: %d\n", i, m_sample_buffer[i], m_sample_buffer[i], ret);
    adc_voltage[i] = adc_raw_to_voltage(m_sample_buffer[i]);
    printf("Measured voltage: %3.2f V\n", (double)adc_voltage[i]);
  }

  return 0;
}
Parents
  • Hi Timothy,

    Thanks for sharing the detailed code snippets and app.overlay file. If I understand correctly the issue is that the ADC reads around 0.31V instead of the expected 1.67V on P0.2 when the battery monitor is enabled. 

    I feel that the most likely culprit is the missing ADC initialization file. 
    1. Can you please configure the ADC device initially calls adc_channel_setup() before adc_read(). Here is an devzone example you can follow [LINK]. This way you would be able to apply the hardware channel settings(pin routing, gain) to the SAADC peripheral.
    2. Also can you update your prj.conf with following: CONFIG_ADC_CONFIGURABLE_INPUTS=y.
    If this is not enabled, the struct.input_positive stays at default value 0. Thus the ADCS driver never connects to the Channel 0 to AIN0 (P0.2).The ADC reads the floating internal noise which you possible see a 0.31 V even when the monitoring battery show 1.67V.

    Please try applying these changes and let us know if your ADC readings now align with your measured 1.6 V.

    Kind regards

    Pallavi

Reply
  • Hi Timothy,

    Thanks for sharing the detailed code snippets and app.overlay file. If I understand correctly the issue is that the ADC reads around 0.31V instead of the expected 1.67V on P0.2 when the battery monitor is enabled. 

    I feel that the most likely culprit is the missing ADC initialization file. 
    1. Can you please configure the ADC device initially calls adc_channel_setup() before adc_read(). Here is an devzone example you can follow [LINK]. This way you would be able to apply the hardware channel settings(pin routing, gain) to the SAADC peripheral.
    2. Also can you update your prj.conf with following: CONFIG_ADC_CONFIGURABLE_INPUTS=y.
    If this is not enabled, the struct.input_positive stays at default value 0. Thus the ADCS driver never connects to the Channel 0 to AIN0 (P0.2).The ADC reads the floating internal noise which you possible see a 0.31 V even when the monitoring battery show 1.67V.

    Please try applying these changes and let us know if your ADC readings now align with your measured 1.6 V.

    Kind regards

    Pallavi

Children
  • Pollavi

    thank you for you fast response.

    1.  I have called setup before the read as show below. with no errors note console output below.

    *** ADC Init ***

    ADC device is ready
    ADC AIN0 is ready

      /* start ADC test */

      myPrintk("*** ADC Init ***\r\n");

      if (!device_is_ready(adc_dev)) /* check if ADC peripheral ready */
      {
        myPrintkE("ADC device is not ready\n");
        return FALSE;
      }
      myPrintkI("ADC device is ready\n");

      err = adc_channel_setup(adc_dev, &m_1st_channel_cfg);
      if (err) {
        myPrintkE("Error in adc AIN0 setup: %d\n", err);
      }
      myPrintkI("ADC AIN0 is ready\n");
    2. I get a build error when I try to set CONFIG_ADC_CONFIGURABLE_INPUTS=y in prj.conf. this is already set in autocon.h
    #define CONFIG_ADC_CONFIGURABLE_INPUTS 1
    note: I have copied this code from nrf9160 code that I have been using for many years with no problem. This is the first time I am using this on a nRF82532. It is also the first time I am using AIN0. I have never used that input before.
  • BLE_Prototype2_V0.15J.7z

    I have attached the complete project maybe this will help to see what I am missing.

Related