Offset in SAADC samples with Easy DMA.

Hello, 

I am experiencing a similar issue to the the following post  where the channels sampled from SAADC buffer are becoming out of order  (i.e {channels 0 , channel1, channel 6 ... }  are becoming miss-aligned  as {channel 6, channel 0, channel 1 .. } and so on .. ) after a a few hundred samples.

The differences between my code and the solution posted is that my code leverages DPPI channels (instead of PPI channels) so that the saadc is sampled at a rate of approx 6Khz for the 3 channels driven by the hardware timer. 

See code below: 


/*
	API for reading MFTM ADC Channels 
	- battery is measured 1/sec 
	- Force sensors are measured 6000Hz 
*/

#include <logging/log.h>
#include "mftm_adc_init.h"
#include "msg_nrf_sensors.h"
#include "msg_npb.h"
#include "msg_pkt.h"
#include "mftm_fsm.h"
#include "mftm_adc_init.h"
#include "mftm_gpio_init.h"
#include <zephyr.h>
#include <helpers/nrfx_gppi.h>
#include <nrfx_dppi.h>
#include <nrfx_saadc.h>
#include <nrfx_timer.h>
#include <logging/log.h>


#if defined(SEND_FORCE_DATA) || defined (SEND_BAT_DATA)

LOG_MODULE_REGISTER(mftm_adc, CONFIG_LOG_DEFAULT_LEVEL);

msg_buffer_t msg_bat; 
msg_buffer_t msg_force; 

static bool force_fsm_enabled =  true; //variable to syncronize temp with fsm 
// //at present we want to sample battery 1/sec. 
//At 165us samples x 150 sample buffers = 24.750 ms~ 25ms 
//therefore we will call the battery function 1 every 40 saadc interrupts
#define BATTERY_ADC_NUM_ITERATIONS (40) 

#define SAMPLE_RATE_DEFAULT	 165UL	//165uS sampling desired 
nrfx_timer_t timer1 = NRFX_TIMER_INSTANCE(1);		//timer instance

//global bool for timer syncronization between api calls
bool timer_running = false; 
uint8_t channel;


int16_t saadc_buf1[SAADC_BUF_SIZE];
int16_t saadc_buf2[SAADC_BUF_SIZE];
int channels_to_read = 0; // actual number of channles we detect
bool send_force_raw = false; //guard for sending force measurments once stop command has been sent to fsm
static bool saadc_prev_enabled = false; 
static bool timer_prev_enabled = false; 

static nrfx_saadc_channel_t channels[ADC_NUM_CHANNELS] = {NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN0, 0),
                                                             NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN1, 1),
                                                             NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN2, 2),
                                                             NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN3, 3),
															 NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN4, 4),
															 NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN5, 5),
															 NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN6, 6),
															 NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN7, 7)};	

#if defined(SEND_BAT_DATA)
// function to send bettery messages to TX buffer, since it will be different than the force sensor messages
// these will also be used in the BLE advertisment
static int send_battery_messages(int32_message_t raw_data){

  #if defined(TEST_DECODE_BATTERY)
   //send raw bytes to be serialized and then queued up  
    queue_data(MessageID_BAT, &raw_data.bytes, sizeof(int32_message_t),true, &msg_bat); 
  #else 
    queue_data(MessageID_BAT,  &raw_data.bytes, sizeof(int32_message_t), false, &msg_bat); 
  #endif //defined(TEST_DECODE_BATTERY)

  
} // defined(SEND_BAT_DATA)

#endif

#if defined(SEND_FORCE_DATA)
/*
	Description: PB Serializes Force messages and puts them on the bt_tx fifo 
	Note: for the mftm we are sending the 16 bit force values together, this is to conserve throughput 
*/
static int send_force_messages(adc_chan_t * forceData){

	#if defined(TEST_DECODE_FORCE)
	//send raw bytes to be serialized and then queued up  
		queue_data(MessageID_FORCE,  &forceData->bytes,sizeof(Force), true, &msg_force); 
	#else 
		queue_data(MessageID_FORCE,  &forceData->bytes,sizeof(Force), false, &msg_force); 

	#endif //defined(TEST_DECODE_FORCE)
	
}
#endif //defined(SEND_FORCE_DATA)




void saadc_evt_handler(nrfx_saadc_evt_t const * p_event)
{
	adc_chan_t * p_raw_adc; 
	static int32_t sample_adc_count = 0; 
	nrfx_err_t err = NRFX_SUCCESS;
	static uint8_t counter = 0;
	if(p_event->type == NRFX_SAADC_EVT_DONE)
	{		

		sample_adc_count++; 
		
		if(send_force_raw ){
				
			//at present, we only have 2 16 bit adc force samples that need to be transmitted at 6000Hz
			//minimum size of protobuffer is 32 bits, se we will currently send them both out as a combined adc value to reduce throughput by half
			//send force messages

			p_raw_adc = (adc_chan_t*)p_event->data.done.p_buffer; 
			send_force_messages(p_raw_adc);
		}	

	}
	
	else if(p_event->type == NRFX_SAADC_EVT_CALIBRATEDONE)
	{
		LOG_INF("SAADC calibrated.");
	}
	else if(p_event->type == NRFX_SAADC_EVT_BUF_REQ)
	{
		counter++;

		if(counter%2)
		{
			err = nrfx_saadc_buffer_set(saadc_buf1, SAADC_BUF_SIZE);
			if(err != NRFX_SUCCESS)
			{
				LOG_ERR("saadc_evt_handler(). Could not set buffer2: %d", err);
			}
		}
		else
		{
			err = nrfx_saadc_buffer_set(saadc_buf2, SAADC_BUF_SIZE);
			if(err != NRFX_SUCCESS)
			{
				LOG_ERR("saadc_evt_handler(). Could not set buffer1: %d", err);
			}
		}
		
	}
	else if(p_event->type == NRFX_SAADC_EVT_FINISHED)
	{
		LOG_INF("SAADC finished sampling");
	}
}

void timer1_evt_handler(nrf_timer_event_t event_type, void * p_context)	//called when timer is triggered
{
	// Nothing to do here, the timer's IRQ is not enabled

}

void timer_init(uint32_t sample_rate)
{
	nrfx_err_t err = NRFX_SUCCESS;
	uint32_t time_ticks;

	nrfx_timer_config_t timer_cfg = NRFX_TIMER_DEFAULT_CONFIG;
	
	timer_cfg.frequency          = NRF_TIMER_FREQ_16MHz;
    timer_cfg.mode               = NRF_TIMER_MODE_TIMER;
    timer_cfg.bit_width          = NRF_TIMER_BIT_WIDTH_32;		
    timer_cfg.interrupt_priority = NRFX_TIMER_DEFAULT_CONFIG_IRQ_PRIORITY;

	//if the timer has already been enabled, we want to disable the instance 
	if(timer_prev_enabled){ 
		nrfx_timer_uninit(&timer1); 

	}
	err = nrfx_timer_init(&timer1, &timer_cfg, timer1_evt_handler);
	if(err != NRFX_SUCCESS)
	{
		LOG_ERR("timer_init(). Could not initialize TIMER1: %d", err);
		return; //return from the function if something went wrong 
	}else{ 
		timer_prev_enabled = true;
	}
	time_ticks = nrfx_timer_us_to_ticks(&timer1, (unsigned long)sample_rate);


	nrfx_timer_extended_compare(&timer1, NRF_TIMER_CC_CHANNEL0, time_ticks, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);	

}

/*
	Decsription: Initalization function to parse the saadcCondig struct and create a bit mask to set the corresponding saadc channels
	Input: Pointer to saadc struct 
	Ouput: bit mask of enabled channels 
*/
uint32_t init_chan_cfgs(saadcConfig *saadcConfig){ 
	uint32_t chan_bit_mask = 0; 
	nrfx_err_t err; 
	int8_t i = 0;
	bool *pchan_cfg = &saadcConfig->enableChan0;  //set pointer to address of battery channel
	
	//initalize channels chronologically 
	for ( i = 0; i <= ADC_NUM_CHANNELS; i++) {
		//if paramter is set to true, initalize that channnel 
		if (*pchan_cfg){ 
			err = nrfx_saadc_channel_config(&channels[i]);
			if(err != NRFX_SUCCESS)
			{
				LOG_ERR("init_chan_cfgs(). Could not configure SAADC channel: %d on error: %d",i, err);
			}else{	
				chan_bit_mask |= (1 << i); 
				channels_to_read++; 
			}
		}
		pchan_cfg++;

	}
	return chan_bit_mask;
}


/*
	Description: saadc initalzation function 
	input: saadcConfig struct  
*/
void saadc_init(saadcConfig *saadc_cfg)
{
	nrfx_err_t err = NRFX_SUCCESS;
	uint32_t chan_bit_mask = 0; 

	nrfx_saadc_adv_config_t saadc_adv_cfg = NRFX_SAADC_DEFAULT_ADV_CONFIG;
	saadc_adv_cfg.start_on_end = true;
	
	//disable saadc if there already exists an instance 
	if(saadc_prev_enabled){ 

		nrfx_saadc_uninit(); 	
		k_sleep(K_MSEC(100));
	}
	
	err = nrfx_saadc_init(IRQ_PRIO_LOWEST);

	if(err != NRFX_SUCCESS)
	{
		LOG_ERR("saadc_init(). Could not initialize SAADC: %x", err);
		return; //return from the function if something went wrong 
	}else{ 
		saadc_prev_enabled = true;
	}

	err = nrfx_saadc_offset_calibrate(NULL);
	if(err != NRFX_SUCCESS)
	{
		LOG_ERR("saadc_init(). Could not calibrate offset: %d", err);
	}

	// set up ADC channels 
	chan_bit_mask = init_chan_cfgs(saadc_cfg); 

	err = nrfx_saadc_advanced_mode_set(chan_bit_mask, NRF_SAADC_RESOLUTION_12BIT, &saadc_adv_cfg, saadc_evt_handler);

	if(err != NRFX_SUCCESS)
	{
		LOG_ERR("saadc_init() Could not set advanced SAADC mode: %d", err);
	}

	err = nrfx_saadc_buffer_set(saadc_buf1, SAADC_BUF_SIZE);
	if(err != NRFX_SUCCESS)
	{
		LOG_ERR("saadc_init(). Could not set buffer1: %d", err);
	}

	err = nrfx_saadc_buffer_set(saadc_buf2, SAADC_BUF_SIZE);
	if(err != NRFX_SUCCESS)
	{
		LOG_ERR("saadc_init(). Could not set buffer2: %d", err);
	}

	err = nrfx_saadc_mode_trigger();
	if(err != NRFX_SUCCESS)
	{
		LOG_ERR("saadc_init(). Could trigger mode: %d", err);
	}else{
		LOG_INF("saadc_init(). Saadc initalizated"); 

	}	
}


/*
	Description: Initalization function for saadc channels. This function initalizes the dppi channels to allow for non-cpu bound hardware timer interrupts for the saadc channels
	Input: void pointer to saadc struct  
	Output: number of channels initalized
	Note: in order to meet the 7.5ms BLE connection interval time, the cpu could not be bound for less than 7.5ms in the ISR so
	the sample buffer size had to be hard coded as 150 (corresponding to ~25ms interupt latency) to provide us with some margin 
*/
int saadc_driver_config(saadcConfig *saadc_cfg)
{
	if(saadc_cfg == NULL){ 
		return 0 ;
	}

	nrfx_err_t err;

	force_fsm_enabled = saadc_cfg->enabled;
	if(!force_fsm_enabled){ 
		stop_mftm_adc(); // stop adc sample collection 
		LOG_INF("ssaadc_driver_config(). driver disabled");
		return 0; // return 0 force sensors have been enabled 
	}
	/* Connect SAADC IRQ to nrfx_saadc_irq_handler */
	IRQ_CONNECT(SAADC_IRQn, IRQ_PRIO_LOWEST, nrfx_isr, nrfx_saadc_irq_handler, 0);
	//initalize hardware timer
	timer_init(saadc_cfg->odr);
	//initalize saadc driver 
	saadc_init(saadc_cfg);


	//allocate dppi channels
	err = nrfx_dppi_channel_alloc(&channel);

	if (err != NRFX_SUCCESS) {
		LOG_ERR("mftm_adc_channels_init(). DPPI channel allocation error: %08x", err);
		return 0 ;
	}

	/* Configure endpoints of the channel so that the TIMER1 CAPTURE0
		event is connected with the SAADC SAMPLE task. This means that each time
		TIMER1 reaches it's set compare value, the SAADC will sample all 
		enabled channel once.
	 */
	nrfx_gppi_channel_endpoints_setup(channel,
		nrfx_timer_event_address_get(&timer1, NRF_TIMER_EVENT_COMPARE0),
			nrf_saadc_task_address_get(NRF_SAADC, NRF_SAADC_TASK_SAMPLE));

	nrfx_dppi_channel_disable(channel);

	nrfx_timer_disable(&timer1);

	LOG_INF("mftm_adc_channels_init(). nrfx_saadc initalized on %s", CONFIG_BOARD);
	
	return channels_to_read;

}


void start_mftm_adc(bool send_force){ 

	/*  Trigger offset calibration
	 	As this generates a _DONE and _RESULT event
	    the first result will be incorrect.
	 */

	send_force_raw = send_force; 
	int err; 

	if(!timer_running){ 
		err = nrfx_dppi_channel_enable(channel);

		if (err != NRFX_SUCCESS) {
			LOG_ERR("Failed to enable (D)PPI channel, error: %08x", err);
			return;
		}

		nrfx_timer_enable(&timer1);

		timer_running = true; 
	}

}


void stop_mftm_adc(){ 
	//kill the timer to stop sample measurments 	
	if(timer_running){ 
		nrfx_dppi_channel_disable(channel);
		nrfx_timer_disable(&timer1);
		timer_running = false; 
	}

}


#endif //defined(SEND_FORCE_DATA)

According to the solution that the above post, I could resolve this issue by "Using PPI to trigger START task on an END event". I am wondering how that could be implemented using the DPPI driver considering the triggering is already issued by the hardware timer through the DPPI endpoints. 

Any help would be appreciated. 

Thanks, 

Matt 

Parents Reply Children
  • Hi Jørgen,

    Thanks for you reply.

    I tried setting the registers directly through the bate metal implementation you linked and it didnt seem to fix the issue. Could you take a look at my code and see what Im missing? see lines 359-363. 

    
    /*
    	API for reading MFTM ADC Channels 
    	- battery is measured 1/sec 
    	- Force sensors are measured 6000Hz 
    */
    
    #include <logging/log.h>
    #include "mftm_adc_init.h"
    #include "msg_nrf_sensors.h"
    #include "msg_npb.h"
    #include "msg_pkt.h"
    #include "mftm_fsm.h"
    #include "mftm_adc_init.h"
    #include "mftm_gpio_init.h"
    #include <zephyr.h>
    #include <helpers/nrfx_gppi.h>
    #include <nrfx_dppi.h>
    #include <nrfx_saadc.h>
    #include <nrfx_timer.h>
    #include <logging/log.h>
    
    
    #if defined(SEND_FORCE_DATA) || defined (SEND_BAT_DATA)
    
    LOG_MODULE_REGISTER(mftm_adc, CONFIG_LOG_DEFAULT_LEVEL);
    
    msg_buffer_t msg_bat; 
    msg_buffer_t msg_force; 
    
    static bool force_fsm_enabled =  true; //variable to syncronize temp with fsm 
    // //at present we want to sample battery 1/sec. 
    //At 165us samples x 150 sample buffers = 24.750 ms~ 25ms 
    //therefore we will call the battery function 1 every 40 saadc interrupts
    #define BATTERY_ADC_NUM_ITERATIONS (40) 
    
    #define SAMPLE_RATE_DEFAULT	 165UL	//165uS sampling desired 
    nrfx_timer_t timer1 = NRFX_TIMER_INSTANCE(1);		//timer instance
    
    //global bool for timer syncronization between api calls
    bool timer_running = false; 
    uint8_t channel;
    
    
    int16_t saadc_buf1[SAADC_BUF_SIZE];
    int16_t saadc_buf2[SAADC_BUF_SIZE];
    int channels_to_read = 0; // actual number of channles we detect
    bool send_force_raw = false; //guard for sending force measurments once stop command has been sent to fsm
    static bool saadc_prev_enabled = false; 
    static bool timer_prev_enabled = false; 
    
    static nrfx_saadc_channel_t channels[ADC_NUM_CHANNELS] = {NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN0, 0),
                                                                 NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN1, 1),
                                                                 NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN2, 2),
                                                                 NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN3, 3),
    															 NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN4, 4),
    															 NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN5, 5),
    															 NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN6, 6),
    															 NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN7, 7)};	
    
    #if defined(SEND_BAT_DATA)
    // function to send bettery messages to TX buffer, since it will be different than the force sensor messages
    // these will also be used in the BLE advertisment
    static int send_battery_messages(int32_message_t raw_data){
    
      #if defined(TEST_DECODE_BATTERY)
       //send raw bytes to be serialized and then queued up  
        queue_data(MessageID_BAT, &raw_data.bytes, sizeof(int32_message_t),true, &msg_bat); 
      #else 
        queue_data(MessageID_BAT,  &raw_data.bytes, sizeof(int32_message_t), false, &msg_bat); 
      #endif //defined(TEST_DECODE_BATTERY)
    
      
    } // defined(SEND_BAT_DATA)
    
    #endif
    
    #if defined(SEND_FORCE_DATA)
    /*
    	Description: PB Serializes Force messages and puts them on the bt_tx fifo 
    	Note: for the mftm we are sending the 16 bit force values together, this is to conserve throughput 
    */
    static int send_force_messages(adc_chan_t * forceData){
    
    	#if defined(TEST_DECODE_FORCE)
    	//send raw bytes to be serialized and then queued up  
    		queue_data(MessageID_FORCE,  &forceData->bytes,sizeof(Force), true, &msg_force); 
    	#else 
    		queue_data(MessageID_FORCE,  &forceData->bytes,sizeof(Force), false, &msg_force); 
    
    	#endif //defined(TEST_DECODE_FORCE)
    	
    }
    #endif //defined(SEND_FORCE_DATA)
    
    
    
    
    void saadc_evt_handler(nrfx_saadc_evt_t const * p_event)
    {
    	adc_chan_t * p_raw_adc; 
    	static int32_t sample_adc_count = 0; 
    	nrfx_err_t err = NRFX_SUCCESS;
    	static uint8_t counter = 0;
    	if(p_event->type == NRFX_SAADC_EVT_DONE)
    	{		
    
    		// toggle_tp1(); //REMOVE once satisfied with force samples
    
    		sample_adc_count++; 
    	#if defined(SEND_FORCE_DATA)
    		
    		if(send_force_raw ){
    				
    			//at present, we only have 2 16 bit adc force samples that need to be transmitted at 6000Hz
    			//minimum size of protobuffer is 32 bits, se we will currently send them both out as a combined adc value to reduce throughput by half
    			//send force messages
    
    			p_raw_adc = (adc_chan_t*)p_event->data.done.p_buffer; 
    			send_force_messages(p_raw_adc);
    		}	
        #endif 
    		//initalize bat channel  
        #if defined(SEND_BAT_DATA) && !defined(SEND_FORCE_DATA) 
    		int32_message_t bat_raw; 
    
    		
    		if(sample_adc_count == BATTERY_ADC_NUM_ITERATIONS) {
    
    			//reset adc access count
    			sample_adc_count = 0; 
    			//allocate raw bat_value
    			//we will disguard other 49 samples for now 
    			bat_raw.value = p_event->data.done.p_buffer[2]; 
    			//send battery messages
    			send_battery_messages(bat_raw);
    		
    		}
        #endif 
    	}
    	
    	else if(p_event->type == NRFX_SAADC_EVT_CALIBRATEDONE)
    	{
    		LOG_INF("SAADC calibrated.");
    	}
    	else if(p_event->type == NRFX_SAADC_EVT_BUF_REQ)
    	{
    		counter++;
    
    		if(counter%2)
    		{
    			err = nrfx_saadc_buffer_set(saadc_buf1, SAADC_BUF_SIZE);
    			if(err != NRFX_SUCCESS)
    			{
    				LOG_ERR("saadc_evt_handler(). Could not set buffer2: %d", err);
    			}
    		}
    		else
    		{
    			err = nrfx_saadc_buffer_set(saadc_buf2, SAADC_BUF_SIZE);
    			if(err != NRFX_SUCCESS)
    			{
    				LOG_ERR("saadc_evt_handler(). Could not set buffer1: %d", err);
    			}
    		}
    		
    	}
    	else if(p_event->type == NRFX_SAADC_EVT_FINISHED)
    	{
    		LOG_INF("SAADC finished sampling");
    	}
    }
    
    void timer1_evt_handler(nrf_timer_event_t event_type, void * p_context)	//called when timer is triggered
    {
    	// Nothing to do here, the timer's IRQ is not enabled
    
    }
    
    void timer_init(uint32_t sample_rate)
    {
    	nrfx_err_t err = NRFX_SUCCESS;
    	uint32_t time_ticks;
    
    	nrfx_timer_config_t timer_cfg = NRFX_TIMER_DEFAULT_CONFIG;
    	
    	timer_cfg.frequency          = NRF_TIMER_FREQ_16MHz;
        timer_cfg.mode               = NRF_TIMER_MODE_TIMER;
        timer_cfg.bit_width          = NRF_TIMER_BIT_WIDTH_32;		
        timer_cfg.interrupt_priority = NRFX_TIMER_DEFAULT_CONFIG_IRQ_PRIORITY;
    
    	//if the timer has already been enabled, we want to disable the instance 
    	if(timer_prev_enabled){ 
    		nrfx_timer_uninit(&timer1); 
    
    	}
    	err = nrfx_timer_init(&timer1, &timer_cfg, timer1_evt_handler);
    	if(err != NRFX_SUCCESS)
    	{
    		LOG_ERR("timer_init(). Could not initialize TIMER1: %d", err);
    		return; //return from the function if something went wrong 
    	}else{ 
    		timer_prev_enabled = true;
    	}
    	time_ticks = nrfx_timer_us_to_ticks(&timer1, (unsigned long)sample_rate);
    
    
    	nrfx_timer_extended_compare(&timer1, NRF_TIMER_CC_CHANNEL0, time_ticks, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);	
    
    }
    
    /*
    	Decsription: Initalization function to parse the saadcCondig struct and create a bit mask to set the corresponding saadc channels
    	Input: Pointer to saadc struct 
    	Ouput: bit mask of enabled channels 
    */
    uint32_t init_chan_cfgs(saadcConfig *saadcConfig){ 
    	uint32_t chan_bit_mask = 0; 
    	nrfx_err_t err; 
    	int8_t i = 0;
    	bool *pchan_cfg = &saadcConfig->enableChan0;  //set pointer to address of battery channel
    	
    	//initalize channels chronologically 
    	for ( i = 0; i <= ADC_NUM_CHANNELS; i++) {
    		//if paramter is set to true, initalize that channnel 
    		if (*pchan_cfg){ 
    			err = nrfx_saadc_channel_config(&channels[i]);
    			if(err != NRFX_SUCCESS)
    			{
    				LOG_ERR("init_chan_cfgs(). Could not configure SAADC channel: %d on error: %d",i, err);
    			}else{	
    				chan_bit_mask |= (1 << i); 
    				channels_to_read++; 
    			}
    		}
    		pchan_cfg++;
    
    	}
    	return chan_bit_mask;
    }
    
    
    /*
    	Description: saadc initalzation function 
    	input: saadcConfig struct  
    */
    void saadc_init(saadcConfig *saadc_cfg)
    {
    	nrfx_err_t err = NRFX_SUCCESS;
    	uint32_t chan_bit_mask = 0; 
    
    	nrfx_saadc_adv_config_t saadc_adv_cfg = NRFX_SAADC_DEFAULT_ADV_CONFIG;
    	saadc_adv_cfg.start_on_end = false; //set this to false to avoid double triggering 
    	
    	//disable saadc if there already exists an instance 
    	if(saadc_prev_enabled){ 
    
    		nrfx_saadc_uninit(); 	
    		k_sleep(K_MSEC(100));
    	}
    	
    	err = nrfx_saadc_init(IRQ_PRIO_LOWEST);
    
    	if(err != NRFX_SUCCESS)
    	{
    		LOG_ERR("saadc_init(). Could not initialize SAADC: %x", err);
    		return; //return from the function if something went wrong 
    	}else{ 
    		saadc_prev_enabled = true;
    	}
    
    	err = nrfx_saadc_offset_calibrate(NULL);
    	if(err != NRFX_SUCCESS)
    	{
    		LOG_ERR("saadc_init(). Could not calibrate offset: %d", err);
    	}
    
    	// set up ADC channels 
    	chan_bit_mask = init_chan_cfgs(saadc_cfg); 
    
    	err = nrfx_saadc_advanced_mode_set(chan_bit_mask, NRF_SAADC_RESOLUTION_12BIT, &saadc_adv_cfg, saadc_evt_handler);
    
    	if(err != NRFX_SUCCESS)
    	{
    		LOG_ERR("saadc_init() Could not set advanced SAADC mode: %d", err);
    	}
    
    	err = nrfx_saadc_buffer_set(saadc_buf1, SAADC_BUF_SIZE);
    	if(err != NRFX_SUCCESS)
    	{
    		LOG_ERR("saadc_init(). Could not set buffer1: %d", err);
    	}
    
    	err = nrfx_saadc_buffer_set(saadc_buf2, SAADC_BUF_SIZE);
    	if(err != NRFX_SUCCESS)
    	{
    		LOG_ERR("saadc_init(). Could not set buffer2: %d", err);
    	}
    
    	err = nrfx_saadc_mode_trigger();
    	if(err != NRFX_SUCCESS)
    	{
    		LOG_ERR("saadc_init(). Could trigger mode: %d", err);
    	}else{
    		LOG_INF("saadc_init(). Saadc initalizated"); 
    
    	}	
    }
    
    
    /*
    	Description: Initalization function for saadc channels. This function initalizes the dppi channels to allow for non-cpu bound hardware timer interrupts for the saadc channels
    	Input: void pointer to saadc struct  
    	Output: number of channels initalized
    	Note: in order to meet the 7.5ms BLE connection interval time, the cpu could not be bound for less than 7.5ms in the ISR so
    	the sample buffer size had to be hard coded as 150 (corresponding to ~25ms interupt latency) to provide us with some margin 
    */
    int saadc_driver_config(saadcConfig *saadc_cfg)
    {
    	if(saadc_cfg == NULL){ 
    		return 0 ;
    	}
    
    	nrfx_err_t err;
    
    	force_fsm_enabled = saadc_cfg->enabled;
    	if(!force_fsm_enabled){ 
    		stop_mftm_adc(); // stop adc sample collection 
    		LOG_INF("ssaadc_driver_config(). driver disabled");
    		return 0; // return 0 force sensors have been enabled 
    	}
    	/* Connect SAADC IRQ to nrfx_saadc_irq_handler */
    	IRQ_CONNECT(SAADC_IRQn, IRQ_PRIO_LOWEST, nrfx_isr, nrfx_saadc_irq_handler, 0);
    	//initalize hardware timer
    	timer_init(saadc_cfg->odr);
    	//initalize saadc driver 
    	saadc_init(saadc_cfg);
    
    
    	//allocate dppi channels
    	err = nrfx_dppi_channel_alloc(&channel);
    
    	if (err != NRFX_SUCCESS) {
    		LOG_ERR("mftm_adc_channels_init(). DPPI channel allocation error: %08x", err);
    		return 0 ;
    	}
    
    	/* Configure endpoints of the channel so that the TIMER1 CAPTURE0
    		event is connected with the SAADC SAMPLE task. This means that each time
    		TIMER1 reaches it's set compare value, the SAADC will sample all 
    		enabled channel once.
    	 */
    	nrfx_gppi_channel_endpoints_setup(channel,
    		nrfx_timer_event_address_get(&timer1, NRF_TIMER_EVENT_COMPARE0),
    			nrf_saadc_task_address_get(NRF_SAADC, NRF_SAADC_TASK_SAMPLE));
    
    	
    	NRF_SAADC->PUBLISH_END  =  channel | 0x01;
    
    	NRF_SAADC->SUBSCRIBE_START   = channel | 0x01;
    	
    	NRF_DPPIC->CHENSET = (1 << channel);
    
    
    	nrfx_dppi_channel_disable(channel);
    
    	nrfx_timer_disable(&timer1);
    
    	LOG_INF("mftm_adc_channels_init(). nrfx_saadc initalized on %s", CONFIG_BOARD);
    	
    	return channels_to_read;
    
    }
    
    
    void start_mftm_adc(bool send_force){ 
    
    	/*  Trigger offset calibration
    	 	As this generates a _DONE and _RESULT event
    	    the first result will be incorrect.
    	 */
    
    	send_force_raw = send_force; 
    	int err; 
    
    	if(!timer_running){ 
    		err = nrfx_dppi_channel_enable(channel);
    
    		if (err != NRFX_SUCCESS) {
    			LOG_ERR("Failed to enable (D)PPI channel, error: %08x", err);
    			return;
    		}
    
    		nrfx_timer_enable(&timer1);
    
    		timer_running = true; 
    	}
    
    }
    
    
    void stop_mftm_adc(){ 
    	//kill the timer to stop sample measurments 	
    	if(timer_running){ 
    		nrfx_dppi_channel_disable(channel);
    		nrfx_timer_disable(&timer1);
    		timer_running = false; 
    	}
    
    }
    
    
    #endif //defined(SEND_FORCE_DATA)

    Thanks for your help. 

    Matt 

  • Hi,

    You can't use the same channel for the END->START connection as you use for TIMER_COMPARE->SAADC_SAMPLE, this will create a many-to-many connection and will cause START task to be triggered by TIMER and SAMPLE task to be triggered by the END event. This will cause more problems than what you are trying to solve. Since you already use the dppi/gppi driver API for setting up the sampling, I recommend that you continue using this and allocate a new (D)PPI channel for this new task. This way you avoid choose a channel that is used by another library/driver.

    Best regards,
    Jørgen

  • Hi Jørgen,

    This solution appeared to fix my issue, Im going to do some further testing but I had a little trouble associating the correct macros to the PUBLISH_END, SUBSCRIBE START registers for the GPPI driver api.  Is this the correct implementation? 

    	/* Configure  one-to-one connection connection between NRF_SAADC->PUBLISH_END and NRF_SAADC->SUBSCRIBE_START
    	 */
    	nrfx_gppi_channel_endpoints_setup(channel2,
    		nrf_saadc_event_address_get(NRF_SAADC, NRF_SAADC_EVENT_END   ),
    			nrf_saadc_task_address_get(NRF_SAADC, NRF_SAADC_TASK_START));
    

    Thank you very much for your help. 

    Matt 

Related