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
No Data
Related