adc_nrfx_saadc: set_resolution: ADC resolution value 0 is not valid.

Hi,

I took a look a your battery.c file. However, I found it had too much overhead code that does not apply to me (I'm using the nRF52 DK nrf52832 and the nRF Connect SDK).

So, I came up with the following code:

battery.c

/**
 * @file battery.c
 * @author Ernesto Gonzales
 * @brief Battery Measurement Module (using nRF52 ADC)
 * @version 0.1
 * @date 2023-02-16
 * 
 * @copyright Copyright (c) 2023
 * 
 */
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#include <zephyr/kernel.h>
#include <zephyr/init.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>

#include <zephyr/device.h>
#include <zephyr/devicetree.h>

#include "battery.h"
#define LOG_LEVEL CONFIG_BT_BAS_LOG_LEVEL
LOG_MODULE_REGISTER(BATTERY,CONFIG_ADC_LOG_LEVEL);

#define ZEPHYR_USER DT_PATH(zephyr_user)

//Since the ADC was set with a devicetree overlay, we can call the settings with:
//static const struct adc_channel_cfg ch4_cfg = ADC_CHANNEL_CFG_DT(DT_CHILD(DT_NODELABEL(adc),channel_4));

static const struct adc_dt_spec adc_ch4 = ADC_DT_SPEC_GET_BY_IDX(ZEPHYR_USER,0);
/* Initializes the adc_ch4 to:
 {
     .dev = DEVICE_DT_GET(DT_NODELABEL(adc)),
     .channel_id = 4,
     .channel_cfg_dt_node_exists = true,
     .channel_cfg = {
         .channel_id = 4,
         .gain = ADC_GAIN_1_5,
         .reference = ADC_REF_INTERNAL,
         .acquisition_time = ADC_ACQ_TIME_DEFAULT,
     },
     .vref_mv = 1200,
     .resolution = 12,
     .oversampling = 1,
 }
*/

bool battery_ok = false;
struct battery_measurement_periphs {

    const struct adc_dt_spec *adc_ch4;
    struct adc_sequence *adc_seq;
    uint16_t raw_data;

};

// Structure only for this file, not to be shared outside
static struct battery_measurement_periphs batt_meas = {
    .adc_ch4 = &adc_ch4,
    .adc_seq = &(struct adc_sequence) {

        .channels = BIT(4),
        .buffer = &(batt_meas.raw_data),
        .buffer_size = sizeof(batt_meas.raw_data),
        .calibrate = true,
    },
};

static int battery_meas_setup(const struct device *arg){

    const struct battery_measurement_periphs *batt_meas_setup = &batt_meas; 
    int success;

    /*
        Check wether device is ready, first.
    */
    if(!device_is_ready(batt_meas_setup->adc_ch4->dev)){
        LOG_INF("ADC device is NOT ready!");
        return -ENOENT;
    }

    // 0 = success
    success = adc_channel_setup_dt(batt_meas_setup->adc_ch4);
    if(success != 0){
        LOG_INF("Could NOT setup the ADC channel!");
        return -ENOENT;
    }

    battery_ok = true;
    LOG_INF("Battery measurement setup correctly!");
    return success;
}


//System drivers: Use SYS_INIT() when you need to run a device's function at boot.
SYS_INIT(battery_meas_setup,APPLICATION,CONFIG_APPLICATION_INIT_PRIORITY);

int battery_sample(void){
    int success = -ENOENT;
    int32_t val;
    if(battery_ok){
        const struct battery_measurement_periphs *batt_setup = &batt_meas;
        struct adc_sequence *adc_seq = batt_setup->adc_seq;

        success = adc_read(batt_setup->adc_ch4->dev, adc_seq);
        adc_seq->calibrate = false;
        if (success == 0){

            val = batt_setup->raw_data;
            adc_raw_to_millivolts_dt(batt_setup->adc_ch4, &val);
            LOG_INF("Battery read (mV) = %d",val);
            return (int)(val/(batt_setup->adc_ch4->vref_mv));

        } else {

            LOG_INF("Error getting ADC reading.");
            return success;
            
        }
        
    } else {
        LOG_INF("Battery not ok!");
        return success;
        
    }
}

and battery.h only shows the battery_sample() function:

/**
 * @file battery.h
 * @author Ernesto Gonzales
 * @brief Battery Measurement Module (using nRF52 ADC)
 * @version 0.1
 * @date 2023-02-16
 * 
 * @copyright Copyright (c) 2023
 * 
 */
#ifndef APPLICATION_BATTERY_H
#define APPLICATION_BATTERY_H

/**
 * @brief Takes sample measurement from the battery connected to the relevant GPIO  
 * 
 * @return an integer representing the battery capacity in percentage (0-100)
 */
int battery_sample(void);

#endif

I also implemented a simple bas and in the main.c I'm using the following function in an infinite loop to measure battery:

static void bas_notify(void)
{
	uint8_t batt_level = battery_sample();
	if (batt_level > 100) {
		LOG_INF("Battery level measured is higher than 100 percent");
	}

	bt_bas_set_battery_level(batt_level);
}

As you can see from battery.c, I'm obtaining the ADC initialization structure from a Devicetree overlay as follows:

/ {
    zephyr,user {
    io-channels = <&adc 4>;
    };
};

&adc {
    #address-cells = <1>;
    #size-cells = <0>;

    channel@4{
        reg = <4>;
        zephyr,gain = "ADC_GAIN_1_5";
        zephyr,reference = "ADC_REF_INTERNAL";
        zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 20)>;
        zephyr,input-positive = <NRF_SAADC_AIN4>;
        zephyr,resolution = <12>;
        zephyr,vref-mv=<1200>;
    };
};

However, I'm getting a failure using the ADC read function, as it's not returning 0.

On top of that I get the following message: "adc_nrfx_saadc: set_resolution: ADC resolution value 0 is not valid"

How can I solve this? I thought the devicetree should've taken care of initializing with the right resolution.

Parents
  • Can you share the value of adc_seq->resolution before calling adc_read(batt_setup->adc_ch4->dev, adc_seq) ?

  • I added the following code around adc_read():

    //...
    if(battery_ok){
            const struct battery_measurement_periphs *batt_setup = &batt_meas;
            struct adc_sequence *adc_seq = batt_setup->adc_seq;
    
            LOG_INF("ADC Resolution: %d", batt_setup->adc_ch4->resolution);
            
            success = adc_read(batt_setup->adc_ch4->dev, adc_seq);
            adc_seq->calibrate = false;
            //..

    The following is printed:

    So, it seems that the resolution is correctly obtained.
    Do you find, perhaps, something wrong in my battery.c code?

  • 1) Ok, I realized my problem was a bit involved than I thought. I found that, if I disconnect the voltage divider out of the ADC input, the readings I get in software oscillate between 19V and 2mV.

    In other words, I get garbage readings.

    This would mimic a situation where there's a connection problem in my board between the voltage divider that measures the battery voltage and the ADC input.

    Sounds to me that, the easiest way to handle this, is to collect a few samples of these garbage readings and decide that I cannot sense the battery and provide an error message. What do you think?

    2) Got it, thanks.

    3) This article: Measuring Lithium battery voltage with nRF52 says explicity, and I quote:

    Choosing the sampling frequency
    
    Over time the SAADC input can be seen as a resistor with the value 1/(f*C). The sampling frequency should be chosen such that the saadc input impedance is much larger than the resistor values in the voltage divider

    I know the acquisition time can be chosen, and I already did. I also guess acquisition time is correlated with sampling frequency, but from the article I quote it's implied that you can choose the sampling frequency independently, but they don't say how, nor do I find anything related in the documentation. Since I want to measure a DC quantity, lowering the sampling rate would be very beneficial.

    4) Got it, thanks.

    5) So, I did the following and it worked:

    static struct battery_measurement_periphs batt_meas = {
        .adc_ch4_ptr = &adc_ch4,
        .adc_seq = &(struct adc_sequence) {
    
            .channels = BIT(4),
            .buffer = &(batt_meas.raw_data),
            .buffer_size = sizeof(batt_meas.raw_data),
            .calibrate = true,
            //.resolution = adc_ch4.resolution,
        },
    };
    
    static int battery_meas_setup(const struct device *dev){
    
        ARG_UNUSED(dev);
        //Defining resolution here
        batt_meas.adc_seq->resolution = adc_ch4.resolution;
        
        const struct battery_measurement_periphs *batt_meas_setup = &batt_meas; 
        int success;
        //...

    My question was: how can I, instead of doing what I just did, have the resolution designated initializer within the "batt_meas" designated initializers?
    I tried:
    .resolution = adc_ch4.resolution, but I got an error about trying to initialize .resolution with a non-const struct member.

  • 1)

    builder01 said:
    Ok, I realized my problem was a bit involved than I thought. I found that, if I disconnect the voltage divider out of the ADC input, the readings I get in software oscillate between 19V and 2mV.

    This must be an artefact of the battery measurement module/driver. Can you verify whether this is a physical transient or not?

    At 19V there's likely damage to the ESD diodes of the analog input pin, and the measurements should not be trusted. If this is expected operating conditions of your design you need to add some extra input protection.

    3)
    That statement is a tiny bit misleading as the increased acquisition time has already limited the sample frequency such that the SAADC can sample an 800kOhm source without adding additional sampling errors.

    If you add an external capacitor and have a source impedance greater than 800kOhm you will need to calculate and set your external capacitance and sample frequency accordingly.

    5)
    //.resolution = adc_ch4.resolution. If I'm not mistaken it seems your are assigning a 32-bit address to a uint8_t variable that is not a pointer.


    Does this work?

    static struct battery_measurement_periphs batt_meas = {
        .adc_ch4_ptr = &adc_ch4,
        .adc_seq = &(struct adc_sequence) {
    
            .channels = BIT(4),
            .buffer = &(batt_meas.raw_data),
            .buffer_size = sizeof(batt_meas.raw_data),
            .calibrate = true,
            .resolution = adc_ch4_ptr->resolution,
        },
    };

  • Hi , sorry for the late reply. Life, this time, really got in the way.

    I just tried your suggestion about setting adc_ch4_ptr->resolution. However, I get the following error:

    error: 'adc_ch4_ptr' undeclared here (not in a function); did you mean 'adc_ch4'?
       71 |         .resolution = adc_ch4_ptr->resolution,
          |                       ^~~~~~~~~~~
          |                       adc_ch4
    ninja: build stopped: subcommand failed.

    My code:

    // Structure only for this file, not to be shared outside
    static struct battery_measurement_periphs batt_meas = {
        .adc_ch4_ptr = &adc_ch4,
        .adc_seq = &(struct adc_sequence) {
    
            .channels = BIT(4),
            .buffer = &(batt_meas.raw_data),
            .buffer_size = sizeof(batt_meas.raw_data),
            .calibrate = true,
            .resolution = adc_ch4_ptr->resolution,
        },
    };

  • builder01 said:
    sorry for the late reply. Life, this time, really got in the way.

    No worries, we're not in a rush.

    You need to declare the struct before you can define it. I assume that the battery_measurement_periphs struct is declared with the old member name 'adc_ch4' instead of the new name 'adc_ch4_ptr'. Try the following: 

    struct battery_measurement_periphs {
    
        const struct adc_dt_spec *adc_ch4_ptr;
        struct adc_sequence *adc_seq;
    };
    
    static struct battery_measurement_periphs batt_meas = {
        .adc_ch4_ptr = &adc_ch4,
        .adc_seq = &(struct adc_sequence) {
    
            .channels = BIT(4),
            .buffer = &(batt_meas.raw_data),
            .buffer_size = sizeof(batt_meas.raw_data),
            .calibrate = true,
            .resolution = adc_ch4_ptr->resolution,
        },
    };

  • Hi ,

    I think this is what I've been doing, this is the code with which I got the error about "'adc_ch4_ptr' undeclared here...".

    This is my code (which is exactly the same as yours, as far as I see):

    struct battery_measurement_periphs {
    
        const struct adc_dt_spec *adc_ch4_ptr;
        struct adc_sequence *adc_seq;
        uint16_t raw_data;
        //uint8_t resolution;
    
    };
    
    // Structure only for this file, not to be shared outside
    static struct battery_measurement_periphs batt_meas = {
        .adc_ch4_ptr = &adc_ch4,
        .adc_seq = &(struct adc_sequence) {
    
            .channels = BIT(4),
            .buffer = &(batt_meas.raw_data),
            .buffer_size = sizeof(batt_meas.raw_data),
            .calibrate = true,
            .resolution = adc_ch4_ptr->resolution,
        },
    };

    I get this error even though I have declared the battery_measurement_periphs struct before.

Reply
  • Hi ,

    I think this is what I've been doing, this is the code with which I got the error about "'adc_ch4_ptr' undeclared here...".

    This is my code (which is exactly the same as yours, as far as I see):

    struct battery_measurement_periphs {
    
        const struct adc_dt_spec *adc_ch4_ptr;
        struct adc_sequence *adc_seq;
        uint16_t raw_data;
        //uint8_t resolution;
    
    };
    
    // Structure only for this file, not to be shared outside
    static struct battery_measurement_periphs batt_meas = {
        .adc_ch4_ptr = &adc_ch4,
        .adc_seq = &(struct adc_sequence) {
    
            .channels = BIT(4),
            .buffer = &(batt_meas.raw_data),
            .buffer_size = sizeof(batt_meas.raw_data),
            .calibrate = true,
            .resolution = adc_ch4_ptr->resolution,
        },
    };

    I get this error even though I have declared the battery_measurement_periphs struct before.

Children
Related