Zephyr adc sequence read returns not consistent values in periodic reads

Hi all,

I'm quite new in Zephyr. Just to let you know ;P

I'm developing a code based on the nRF52832 in a custom board and I'd like to measure the voltage of  two separate batteries.

These batteries are connected to a resistor voltage divider which is enabled or disabled by a digital switch. Here is the schema

* packEn: 1 --> enables the two voltage dividers, 0--> disables the connection to the batteries

* Vpack1 --> Connected to AIN4

* Vpack2 --> Connected to AIN5

I'm using the nRF Connect SDK v2.4.1 with VS code.

In the main loop I read periodically the Vpack1 and Vpack2, but the values I get are random in most of the cases. 

I'm sure that it's a configuration issue in the ADC, but I cannot find it, so I hope you can give a hand to find out what's the problem. Thank you.

This is my code:

#define HW_MAP_BAT_ENABLE_PIN				    NRF_GPIO_PIN_MAP(0, 30)
#define HW_MAP_BAT_R1_DIVIDER_VALUE		        150000.0f								//	Value in ohm
#define HW_MAP_BAT_R2_DIVIDER_VALUE	        	100000.0f								//	Value in ohm


#define	DRV_BAT_PACKS							2


typedef struct
{
	bool		valid;
	
	struct
	{
		bool	present;
		int16_t voltage;
	}pack[DRV_BAT_PACKS];
	
}drvBat_st;

static const struct device *adcDev =  DEVICE_DT_GET(DT_NODELABEL(adc));


#define	DRV_BAT_ADC_RESOLUTION						12
#define	DRV_BAT_ADC_GAIN							ADC_GAIN_1_3
#define	DRV_BAT_ADC_REFERENCE						ADC_REF_INTERNAL
#define	DRV_BAT_ADC_ADQUISITION_TIME				ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 40)

#define DRV_BAT_ADC_PRIMARY_CHANNEL_ID 				4
#define DRV_BAT_ADC_PRIMRAY_CHANNEL_INPUT 			NRF_SAADC_INPUT_AIN4
#define DRV_BAT_ADC_SECONDARY_CHANNEL_ID 			5
#define DRV_BAT_ADC_SECONDARY_CHANNEL_INPUT 		NRF_SAADC_INPUT_AIN5


static const struct adc_channel_cfg primary = 	{	
													.gain 				= DRV_BAT_ADC_GAIN,
													.reference 			= DRV_BAT_ADC_REFERENCE,
													.acquisition_time 	= DRV_BAT_ADC_ADQUISITION_TIME,
													.channel_id 		= DRV_BAT_ADC_PRIMARY_CHANNEL_ID,
													.differential		= 0,
													#if defined(CONFIG_ADC_CONFIGURABLE_INPUTS)
													.input_positive 	= DRV_BAT_ADC_PRIMRAY_CHANNEL_INPUT,
													#endif
												};

static const struct adc_channel_cfg secondary =	{
													.gain 				= DRV_BAT_ADC_GAIN,
													.reference 			= DRV_BAT_ADC_REFERENCE,
													.acquisition_time 	= DRV_BAT_ADC_ADQUISITION_TIME,
													.channel_id 		= DRV_BAT_ADC_SECONDARY_CHANNEL_ID,
													.differential		= 0,
													#if defined(CONFIG_ADC_CONFIGURABLE_INPUTS)
													.input_positive 	= DRV_BAT_ADC_SECONDARY_CHANNEL_INPUT,
													#endif
												};

static drvBat_st bat;


rc_t drvBat_Init (void)
{
	//
	//  Init the ADC channel configuration needed
	//

	static bool init = false;

	if ( true == init )
	{
		return RC_SUCCESS;
	}

	//	Configure adc channels used to measure battery voltage

	if ( 0 != adc_channel_setup(adcDev, &primary) )
	{
		return RC_ERROR;
	}

	if ( 0 != adc_channel_setup(adcDev, &secondary) )
	{
		return RC_ERROR;
	}

	//	Init battery power control
	
	nrfx_gpiote_out_config_t config = NRFX_GPIOTE_CONFIG_OUT_SIMPLE(false);
	nrfx_gpiote_out_init (HW_MAP_BAT_ENABLE_PIN, &config);											//	Configure battery control pin
	
	init = true;
		
	return RC_SUCCESS;
}


rc_t drvBat_Measure (void)
{
	//
	//
	//

	int16_t samples[2];

	const struct adc_sequence sequence = 	{
												.channels 		= BIT(DRV_BAT_ADC_PRIMARY_CHANNEL_ID) | BIT(DRV_BAT_ADC_SECONDARY_CHANNEL_ID) ,
												.buffer 		= samples,
												.buffer_size 	= sizeof(samples),
												.resolution 	= DRV_BAT_ADC_RESOLUTION,
												.oversampling 	= 0,					// don't oversample
												.calibrate 		= false					// calibrate												
											};

	nrfx_gpiote_out_set(HW_MAP_BAT_ENABLE_PIN);											//	Enable the voltage divider

	k_msleep(DRV_BAT_ENABLE_TIMEOUT);													//	Little wait to ensure that voltage is stable before performing an adc read

	if ( 0 != adc_read(adcDev, &sequence) )
	{
		return RC_ERROR;
	}

	nrfx_gpiote_out_clear(HW_MAP_BAT_ENABLE_PIN);										//	Disable the voltage divider

	for (uint8_t i=0; i<DRV_BAT_PACKS; i++)
	{
		int32_t raw = (int32_t)samples[i];

		if ( 0 != adc_raw_to_millivolts(DRV_BAT_ADC_REFERENCE, DRV_BAT_ADC_GAIN, DRV_BAT_ADC_RESOLUTION, &raw) )
		{
			return RC_ERROR;
		}

		bat.pack[i].present = false;
		bat.pack[i].voltage = (int16_t)(raw / DRV_BAT_DIVIDER_FACTOR);

		if ( 0 > bat.pack[i].voltage )
		{
			bat.pack[i].voltage = 0;
		}

		if ( DRV_BAT_MIN_VOLTAGE <= bat.pack[i].voltage )
		{
			bat.pack[i].present = true; 
		}
	}
		
	return RC_SUCCESS;
}

Thank you so much for your help

Ragards,

David

  • Hi,

    I can't find anything in specific, but just to rule out any hardware connection/electrical problem connect battery directly to one analog input, and only sample that specific input.

    I notice you have used:

    #define DRV_BAT_ADC_PRIMARY_CHANNEL_ID 4
    #define DRV_BAT_ADC_PRIMRAY_CHANNEL_INPUT NRF_SAADC_INPUT_AIN4
    #define DRV_BAT_ADC_SECONDARY_CHANNEL_ID 5
    #define DRV_BAT_ADC_SECONDARY_CHANNEL_INPUT NRF_SAADC_INPUT_AIN5

    But there is no direct dependency between ID and INPUT, so typically the following is more common to use:

    #define DRV_BAT_ADC_PRIMARY_CHANNEL_ID 0
    #define DRV_BAT_ADC_PRIMRAY_CHANNEL_INPUT NRF_SAADC_INPUT_AIN4
    #define DRV_BAT_ADC_SECONDARY_CHANNEL_ID 1
    #define DRV_BAT_ADC_SECONDARY_CHANNEL_INPUT NRF_SAADC_INPUT_AIN5

    I suggest to continue debugging for instance by comparing with the existing examples. What does you prj.conf look like btw.

    Best regards,
    Kenneth

  • Hi Kenneth,

    Thanks so much for your comments and suggestions.

    The hardware should look ok, because I tested the voltage with a code made with the nRF5 SDK v17.1.0 and It worked correctly, but I'll check it again anyway to be sure that all is ok.

    This is my prj.conf 

    CONFIG_MAIN_STACK_SIZE=2048
    CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
    CONFIG_PM_DEVICE=y
    CONFIG_FPU=y
    
    # Logger configuration
    CONFIG_LOG=y
    #CONFIG_LOG_BACKEND_UART=y
    
    CONFIG_NO_OPTIMIZATIONS=y
    CONFIG_NEWLIB_LIBC=y
    CONFIG_NEWLIB_LIBC_MIN_REQUIRED_HEAP_SIZE=2048
    CONFIG_HEAP_MEM_POOL_SIZE=16384
    
    #GPIO
    CONFIG_GPIO=y
    CONFIG_NRFX_GPIOTE=y
    
    #adc
    CONFIG_ADC=y
    CONFIG_ADC_NRFX_SAADC=y
    CONFIG_NRFX_SAADC=y
    

    I've made the changes you suggested me in my code and I debugged it step by step.
    I've seen that in debug mode, the measures are valid, but in runtime, the measures are mixed and not valid.
    Currently I have a battery connected to the Vpack1 meanwhile Vpack2 has no battery connected.
    The expected measure should be:
    Vpack1 = 3000mV
    Vpack2 = 0mV
    These results are the ones I get in debug mode, but without debugging it, the Vpack2 takes random values close to the Vpack1.
     
    I've checked with the osciloscope how much time takes to have a stable voltage after enabling the switch and it is less than 200us and I wait 2 ms, so more than enough to guarantee that the voltage is stable during the measurement.
     
    Do I have to wait or check any adc value before launching a measurement?
    This behaviour says something to you?
    Thank you
    Regards,
    David
  • Hi Kenneth,

    Finally I found the error.... It's not a firmware issue, just a hardwware behaviour of two zener diodes I have.

    I have two zener diodes to block the current from one battery pack to the other. These diodes have a forward leackage current that generates a voltage that is read from the ADC when no battery is connected.

    To solve this, I've changed the resistor values to prevent this leackage to affect the measure.

    Thank you so much for your help and comments.

    Regards,

    David

Related