ADC On Nrf5340DK, Modifying the Battery Voltage Measurement

Hi, I have been trying to do a simple analogRead using the nrf5340 using the adc, the closest working example I found was Battery Voltage Measurement working with SDK 2.4 and Zephyr 3.2.

The example is working perfectly fine, but I am trying to modify it so I can read the voltage of an outside battery, a Classic AnalogRead on a pin. 

I am sure it's a simple modification, but I couldn't figure it out Sweat smile, I would appreciate if you could help me figure out which configuration I need to modify to not read the power supply voltage but the one coming into the Pin from an outside source  Thank you very much.

.overlay: 

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


main.c
/*
 */

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <zephyr/init.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/kernel.h>


#define ZEPHYR_USER DT_PATH(zephyr_user)
#define BATTERY_ADC_GAIN ADC_GAIN_1_6

struct io_channel_config {
	uint8_t channel;
};


struct divider_config {
	struct io_channel_config io_channel;
	struct gpio_dt_spec power_gpios;
	/* output_ohm is used as a flag value: if it is nonzero then
	 * the battery is measured through a voltage divider;
	 * otherwise it is assumed to be directly connected to Vdd.
	 */
	uint32_t output_ohm;
	uint32_t full_ohm;
};
static const struct divider_config divider_config = {.io_channel = {
		DT_IO_CHANNELS_INPUT(ZEPHYR_USER),
	},
};


struct divider_data {
	const struct device *adc;
	struct adc_channel_cfg adc_cfg;
	struct adc_sequence adc_seq;
	int16_t raw;
};


static struct divider_data divider_data = {
.adc = DEVICE_DT_GET(DT_IO_CHANNELS_CTLR(ZEPHYR_USER)),
};


static bool battery_ok;


static int divider_setup(void)
{
	const struct divider_config *cfg = &divider_config;
	const struct io_channel_config *iocp = &cfg->io_channel;
	const struct gpio_dt_spec *gcp = &cfg->power_gpios;
	struct divider_data *ddp = &divider_data;
	struct adc_sequence *asp = &ddp->adc_seq;
	struct adc_channel_cfg *accp = &ddp->adc_cfg;
	int rc;

	if (!device_is_ready(ddp->adc)) {
		printk("ADC device is not ready %s", ddp->adc->name);
		return -ENOENT;
	}

	if (gcp->port) {
		if (!device_is_ready(gcp->port)) {
			printk("%s: device not ready", gcp->port->name);
			return -ENOENT;
		}
		rc = gpio_pin_configure_dt(gcp, GPIO_OUTPUT_INACTIVE);
		if (rc != 0) {
			printk("Failed to control feed %s.%u: %d",
				gcp->port->name, gcp->pin, rc);
			return rc;
		}
	}

	*asp = (struct adc_sequence){
		.channels = BIT(0),
		.buffer = &ddp->raw,
		.buffer_size = sizeof(ddp->raw),
		.oversampling = 4,
		.calibrate = true,
	};

#ifdef CONFIG_ADC_NRFX_SAADC
	*accp = (struct adc_channel_cfg){
		.gain = BATTERY_ADC_GAIN,
		.reference = ADC_REF_INTERNAL,
		.acquisition_time = ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 40),
	};

	if (cfg->output_ohm != 0) {
		accp->input_positive = SAADC_CH_PSELP_PSELP_AnalogInput0
			+ iocp->channel;
			printk("OUTPUT != 0\n");
			//this should not be activated in my situation
	} else {
		accp->input_positive = SAADC_CH_PSELP_PSELP_VDD;
		printk("OUTPUT == 0, else\n");
	}
	asp->resolution = 14;
#else /* CONFIG_ADC_var */
#error Unsupported ADC
#endif /* CONFIG_ADC_var */

	rc = adc_channel_setup(ddp->adc, accp);
	printk("Setup AIN%u got %d \n", iocp->channel, rc);

	return rc;
}


static int battery_setup(void)
{
	int rc = divider_setup();

	battery_ok = (rc == 0);
	printk("Battery setup: %d %d \n", rc, battery_ok);
	return rc;
}

SYS_INIT(battery_setup, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); //when system start this initialise

int main(void)
{
		const struct gpio_dt_spec *gcp = &divider_config.power_gpios;
		if (gcp->port) {
		gpio_pin_set_dt(gcp, true);
		}

	while (true) {
		struct divider_data *ddp = &divider_data;
		const struct divider_config *dcp = &divider_config;
		struct adc_sequence *sp = &ddp->adc_seq;

		int batt_mV = adc_read(ddp->adc, sp);
			//printk("%d adc read;\n",batt_mV);

		sp->calibrate = false;

		if (batt_mV == 0) {
			int32_t val = ddp->raw;
			adc_raw_to_millivolts(adc_ref_internal(ddp->adc),ddp->adc_cfg.gain,sp->resolution,&val);
				batt_mV = val;
		}
		printk("%d mV;\n",batt_mV);
		k_busy_wait( ((1000U) * (1000U)));
	}
	return 0;
}

  • Hello,

    I suggest to take a look again on the existing adc example in zephyr and the adc driver test: 
    \zephyr\samples\drivers\adc
    \zephyr\tests\drivers\adc

    If you are not able to make any of them work as you want, you can always use the low level nrfx adc driver directly (instead of the zephyr api):
    https://github.com/zephyrproject-rtos/hal_nordic/tree/master/nrfx/samples/src/nrfx_saadc

    I also recommend to read up on the SAADC peripheral hardware to understand how it can be used:
    https://infocenter.nordicsemi.com/topic/ps_nrf5340/saadc.html 

    Kenneth

  • It's been a while but for anybody wondering the things to change was the input positive : 

    #ifdef CONFIG_ADC_NRFX_SAADC
    	*accp = (struct adc_channel_cfg){
    		.gain = ADC_GAIN_1_6,
    		.reference = ADC_REF_INTERNAL, //+-6V ,so gains should be 1/6, so that 3600mv/6 = 600mV -> 500mV = 3V
    		//https://infocenter.nordicsemi.com/index.jsp?topic=%2Fps_nrf5340%2Fsaadc.html&anchor=concept_qh3_spp_qr
    		.acquisition_time = ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 40),
    	};
    
    		//accp->input_positive = SAADC_CH_PSELP_PSELP_VDD; //this thing connect ot the mcu power supply
    		//for our use we just want to read a pin
    		accp->input_positive = SAADC_CH_PSELP_PSELP_AnalogInput1;
    		printk("Setup as analog input\n");
    
    	//asp->resolution = 14; //for 14 its oversampling, we dont need
    	asp->resolution = 12;
    #else /* CONFIG_ADC_var */
    #error Unsupported ADC
    #endif /* CONFIG_ADC_var */
    
    	rc = adc_channel_setup(ddp->adc, accp);
    	printk("Setup AIN%u got %d", iocp->channel, rc);
    
    	return rc;
    }

Related