nrf5340 | SAADC ISR blocking ble_data_recieve events.

Hello, 

I have a project that is based on the peripheral_uart example that receives start and stop commands from a periperal_central device via BLE to send ADC samples over BLE to the central device at a rate of 6Khz. 

The device is suppose to start sampling the adc at a rate of 6Khz. To accomplish this I created the following code  based off this example. Where the start/stop commands are used to start/stop the timer with a 160us sample rate (see lines 45/176). The following code snippet is from a sample where I am reading a single ADC channel at a sample rate of 160us, sending the packets over BLE to the central while  toggling a gpio pin to confirm the ISR is firing at the correct interval. 


#include <logging/log.h>
#include "mftm_adc_init.h"
#include "msg_nrf_sensors.h"
#include "msg_npb.h"
#include "msg_pkt.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>


// Generic message container. Can be used for messages to/from UART or BLE
// soh/len/checksum are only used for UART packets, since BLE provides its own frame.
// Important that struct is __PACKED so that all elements are aligned properly.
//  'fifo_reserved' is required for handling these messages with k_fifo_putt/k_fifo_get
typedef struct __PACKED  {
	void *fifo_reserved;
	uint8_t soh;
	// byte for device destination id only really applies to messages sent from Host PC. At present 
	// at present we will leave this apeart of the pkt structure so that we can send uart messages (test purposes only)
	uint8_t dest_id; 
	uint8_t len;
	uint8_t msg_id; // msg_id and data are recieved as the same thing on host-pc side 
	uint8_t data[MSG_MAX_DATA_BYTES + 1]; // add 1 for the checksum byte - only applicable on UART side 
	// uint8_t checksum; // send checksum before data so that when the buf->msg_len is modulated, the checksum will always be sent (debug only, move below Data field once layer 1 has been developed)

	// Checksum only applies on UART path
} msg_buffer_t;



// generic message type to modularize the pb decoding of int32 types 
typedef union int32_message { 
  int8_t bytes[4]; 
  int32_t value; 
}int32_message_t; 


#define SAMPLE_RATE 		160UL	//160uS sampling desired	

#ifdef MFTM_ADC_CHANNELS
	#define BUFFER_SIZE (1)
#else 
	#define BUFFER_SIZE (7)
#endif 

#define MESSAGE_ID_ADC (0x04)

//static bool for flow control between recieving commands 

static bool timer_running = false; 


int16_t saadc_buf1[BUFFER_SIZE];
int16_t saadc_buf2[BUFFER_SIZE];

nrfx_timer_t timer1 = NRFX_TIMER_INSTANCE(1);		//timer instance


LOG_MODULE_DECLARE(nrf_sensors);


uint8_t channel;

// static const struct device *adc_dev;

extern struct k_fifo fifo_msg_tx; 

								
// 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_adc_messages(uint8_t * raw_value){

  	//create new message for mftm_message_t
	msg_buffer_t * pmsg = get_msg_buffer_uart(); 

	if (!pmsg) {
      return 1;
    }
	pmsg->msg_id = MESSAGE_ID_ADC; 

	memcpy(pmsg->data, raw_value, sizeof(raw_value));


    // put message to be read by ble_nus_send thread here 
    k_fifo_put(&fifo_msg_tx, pmsg); 
	
	return 0;
  
}


void saadc_evt_handler(nrfx_saadc_evt_t const * p_event)
{
	#ifdef MFTM_ADC_CHANNELS 
		static int32_message_t bat_raw; 
	#else
		int32_message_t raw_value; 
	#endif

	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 test point to get a sence of proper saadc sample rate 
        toggle_tp1(); 

        raw_value.value = p_event->data.done.p_buffer[0]; 
		//send adc messages
		send_adc_messages(raw_value);

	}
	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, BUFFER_SIZE);
			if(err != NRFX_SUCCESS)
			{
				LOG_ERR("Error! Could not set buffer2: %d", err);
			}
		}
		else
		{
			err = nrfx_saadc_buffer_set(saadc_buf2, BUFFER_SIZE);
			if(err != NRFX_SUCCESS)
			{
				LOG_ERR("Error! 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(void)
{
	nrfx_err_t err = NRFX_SUCCESS;

	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 = 4   ;//NRFX_TIMER_DEFAULT_CONFIG_IRQ_PRIORITY;

	err = nrfx_timer_init(&timer1, &timer_cfg, timer1_evt_handler);

	uint32_t time_ticks;
	time_ticks = nrfx_timer_us_to_ticks(&timer1, SAMPLE_RATE);

	if(err != NRFX_SUCCESS)
	{
		LOG_ERR("Error! Could not initialize TIMER1: %d", err);
	}
	nrfx_timer_extended_compare(&timer1, NRF_TIMER_CC_CHANNEL0, time_ticks, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);	

}



void saadc_init(void)
{
	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;
	// saadc_adv_cfg.low_power_mode  =true; 
	
	err = nrfx_saadc_init(IRQ_PRIO_LOWEST);
	// err = nrfx_saadc_init(4);

	if(err != NRFX_SUCCESS)
	{
		LOG_ERR("Error! Could not initialize SAADC: %d", err);
	}

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

	// set up ADC channels 

	nrfx_saadc_channel_t saadc_channel = NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN6, 6);
			err = nrfx_saadc_channel_config(&saadc_channel);
			if(err != NRFX_SUCCESS)
			{
				LOG_ERR("init_chan_cfgs(). Could not configure SAADC channels: %d", err);
			}	

	// err = nrfx_saadc_advanced_mode_set(chan_bit_mask, NRF_SAADC_RESOLUTION_12BIT, &saadc_adv_cfg, saadc_evt_handler);
	err = nrfx_saadc_advanced_mode_set((1<<6), NRF_SAADC_RESOLUTION_12BIT, &saadc_adv_cfg, saadc_evt_handler);

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

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

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

	err = nrfx_saadc_mode_trigger();
	if(err != NRFX_SUCCESS)
	{
		LOG_ERR("Error! Could trigger mode: %d", err);
	}	
}



int mftm_adc_channels_init(void)
{
	LOG_INF("nrfx_saadc sample on %s", CONFIG_BOARD);

	nrfx_err_t err;

	/* Connect SAADC IRQ to nrfx_saadc_irq_handler */
	IRQ_CONNECT(SAADC_IRQn, IRQ_PRIO_LOWEST, nrfx_isr, nrfx_saadc_irq_handler, 0);
		// IRQ_CONNECT(SAADC_IRQn, 4, nrfx_isr, nrfx_saadc_irq_handler, 0);

	
	timer_init();
	saadc_init();

	// Allocate a DPPI channel
	// uint8_t channel;
	err = nrfx_dppi_channel_alloc(&channel);

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

	/* 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));



	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; 
	// NRF_SAADC->TASKS_CALIBRATEOFFSET = 1;
	
	if(!timer_running){ 
		NRF_SAADC->ENABLE = 1;
		nrfx_timer_enable(&timer1);

		/* Enable DPPI channel. */
		err = nrfx_dppi_channel_enable(channel);

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

		timer_running = true; 
	}

}


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

}

Once the adc has collected the samples, the raw bytes are sent to a FIFO that is read on a separate thread which populates the packets and sends them to the central device by calling the ble_nus_send() api. 

I have confirmed that the packets are being received on the other side and that the sample rate is accurate by toggling a test point with my logic analyzer (see line 117). 

The problem is that I cannot receive any more BLE commands from the central device once the timer has started. 

I believe the problem is that the program is too consumed with the high SAADC ISR frequency so it it blocking any events from being triggered from the ble_data_recieved function which is where the start/stop commands are received from the central. The BLE link is stable because I am able to receive the data on the other side, only the incoming traffic is being blocked. 

I have confirmed that this api works by changing the sample rates greater than 2ms (i.e can receive inbound messages from central). For my application how ever, that is going to be too slow of an ADC sample rate. 

I am wondering if there are any suggestions as to how this data rate could be accomplished while not blocking incoming BLE messages . 

Thank you, 

Matt 

Parents
  • Hi,

    Do you need to have a buffer size of 1? Since the minimum connection interval of BLE is 7.5ms, you could set the buffer size larger to only get interrupt when the SAADC have sampled multiple samples, and then send the data over BLE as a larger chunk (you should be able to complete ~45 samples during 7.5ms). This will reduce the time that the CPU needs to spend in the SAADC interrupt handler. As the SAADC peripheral support EasyDMA for sampling directly to RAM, and you can set the buffer size up to 16383 samples.

    Best regards,
    Jørgen

  • Since BLE can only send data at the connection intervals, there should not be any changes in the data output rate. The only difference should be that you will be send more samples in a single transfer instead of sending many smaller packets containing a single sample.

    The priority of the timing critical BLE tasks should always be higher than the application interrupts, so I do not think that the SAADC handling itself is blocking any incoming packets. What might be blocking the transfers, however, is the many small packets that you are sending, creating much overhead in transfer time, preventing other BLE activity.

    It seems you already have set the SAADC interrupt priority to low (IRQ_PRIO_LOWEST), which should not make it preempt any other tasks than the ones with the same priority (or lower, like main thread).

Reply
  • Since BLE can only send data at the connection intervals, there should not be any changes in the data output rate. The only difference should be that you will be send more samples in a single transfer instead of sending many smaller packets containing a single sample.

    The priority of the timing critical BLE tasks should always be higher than the application interrupts, so I do not think that the SAADC handling itself is blocking any incoming packets. What might be blocking the transfers, however, is the many small packets that you are sending, creating much overhead in transfer time, preventing other BLE activity.

    It seems you already have set the SAADC interrupt priority to low (IRQ_PRIO_LOWEST), which should not make it preempt any other tasks than the ones with the same priority (or lower, like main thread).

Children
No Data
Related