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.

Related