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)

    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.

  • Have you included the necessary headers for the adc_dt_spec type? 

    What is ADC_DT_SPEC_GET_BY_IDX(ZEPHYR_USER,0); evaluated to?

    Try removing the comma on line 20.

Reply Children
No Data
Related