This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

SAADC IRQ Handler not working

Hi

I am working on a project that has two timers, TIMER3 and TIMER4 (TIMER3 as timer and theTIMER4 as a counter). TIMER3 resets after counting up to 4. I am using PPI to increment TIMER4 every time TIMER3 resets. TIMER4 also resets after counting up to 128. I have also a PPI event that compares the event when TIMER4 resets to trigger SAADC->TASKS_SAMPLE.  I have a global variable, adc_counter, that increments every time ADC sample i ready. 

I have configured my SAADC to take just one sample every time the task TASKS_SAMPLE is triggered from the PPI. I wanted to have a customized SAADC_IRQHandler that increments the global variable, the adc_counter, do the conversion, and put that in a buffer. The following is my code. I don't where I have the problem, but just noticed that the adc_counter never increments.

#include "nrf.h"
#include <stdbool.h>
#include <stdint.h>
//#include "bsp.h"
#include "nrf_gpio.h"
#include "nrf_delay.h"
#include "math.h"

//==================================
//PPI channels
#define TIMER4_PPI_CH_A						0 // TIMER3 reloads and TIMER4 increments
#define TIMER4_PPI_CH_B						1 // TIMER4 reloads, one ADC sample is taken
//===================================

//===================================
// TIMER CC registers
#define TIMER3_RELOAD_CC_NUM				5
#define TIMER4_RELOAD_CC_NUM 				5
//===================================

// TIMER3 reload value. The PWM frequency equals '16000000 / TIMER_RELOAD_VALUE'
#define TIMER3_RELOAD                   4 		
#define TIMER4_RELOAD					128		// TIMER4 counts upto 128. Count is incremented every timer TIMER3 RELOADs
#define SAMPLES_IN_BUFFER				32 	// Total number of ADC samples

static int adc_counter = 0; // counter for the ADC samples
volatile int16_t adc_value = 0;
volatile float adc_exact_value = 0;
static uint32_t adc_buffer[SAMPLES_IN_BUFFER];

//timer init
void timers_init(void)
{
    NRF_TIMER3->BITMODE                 		= TIMER_BITMODE_BITMODE_08Bit << TIMER_BITMODE_BITMODE_Pos;
    NRF_TIMER3->PRESCALER               		= 0;
    NRF_TIMER3->SHORTS                  		= TIMER_SHORTS_COMPARE0_CLEAR_Msk << TIMER3_RELOAD_CC_NUM;
    NRF_TIMER3->MODE                    		= TIMER_MODE_MODE_Timer << TIMER_MODE_MODE_Pos;
    NRF_TIMER3->CC[TIMER3_RELOAD_CC_NUM] 		= TIMER3_RELOAD;
	
		NRF_TIMER4->BITMODE                 	= TIMER_BITMODE_BITMODE_08Bit << TIMER_BITMODE_BITMODE_Pos;
    NRF_TIMER4->PRESCALER               		= 0;
    NRF_TIMER4->SHORTS                  		= TIMER_SHORTS_COMPARE0_CLEAR_Msk << TIMER4_RELOAD_CC_NUM;
    NRF_TIMER4->MODE                    		= TIMER_MODE_MODE_Counter << TIMER_MODE_MODE_Pos;
    NRF_TIMER4->CC[TIMER4_RELOAD_CC_NUM] 		= TIMER4_RELOAD;
}

void timers_start(void)
{
    NRF_TIMER3->TASKS_START = 1;		
	NRF_TIMER4->TASKS_START = 1;
}
void SAADC_IRQHandler(void)
{
  // Clear dataready event
	NRF_SAADC-> EVENTS_END = 0;
	adc_exact_value =(((float)adc_value/4096)*3.6f*1000)*5.545f;
	adc_buffer[adc_counter] = adc_exact_value;	
	adc_counter++;
}

void adc_init(void)

	// Enable interrupt on ADC sample ready
	NRF_SAADC->INTENSET = SAADC_INTENSET_END_Msk;
	NVIC_EnableIRQ(SAADC_IRQn); 
	
	// configure ADC
	NRF_SAADC->CH[0].CONFIG = (SAADC_CH_CONFIG_GAIN_Gain1_6    << SAADC_CH_CONFIG_GAIN_Pos) |
                            (SAADC_CH_CONFIG_MODE_SE         << SAADC_CH_CONFIG_MODE_Pos) |
                            (SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos) |
                            (SAADC_CH_CONFIG_RESN_Bypass     << SAADC_CH_CONFIG_RESN_Pos) |
                            (SAADC_CH_CONFIG_RESP_Bypass     << SAADC_CH_CONFIG_RESP_Pos) |
                            (SAADC_CH_CONFIG_TACQ_3us        << SAADC_CH_CONFIG_TACQ_Pos);

  // Configure the SAADC channel with AIN2 as positive input, no negative input(single ended). // change it to AIN5 in the final version
  NRF_SAADC->CH[0].PSELP = SAADC_CH_PSELP_PSELP_AnalogInput2 << SAADC_CH_PSELP_PSELP_Pos;
  NRF_SAADC->CH[0].PSELN = SAADC_CH_PSELN_PSELN_NC << SAADC_CH_PSELN_PSELN_Pos; // Will be ignored if MODE is SE
	
  // Configure the SAADC resolution.
  NRF_SAADC->RESOLUTION = SAADC_RESOLUTION_VAL_12bit << SAADC_RESOLUTION_VAL_Pos;
	
  // Configure result to be put in RAM at the location of "adc_value" variable.
  // Number of sample counts
  NRF_SAADC->RESULT.MAXCNT = 1;
  NRF_SAADC->RESULT.PTR = (uint32_t)&adc_value;
	
  // Enable ADC
  NRF_SAADC->ENABLE = SAADC_ENABLE_ENABLE_Enabled << SAADC_ENABLE_ENABLE_Pos;
	
  //Start the ADC 
  NRF_SAADC->TASKS_START = 1;
  while(NRF_SAADC->EVENTS_STARTED ==0);
  NRF_SAADC->EVENTS_STARTED = 0;
  
}

void timer4_adc_ppi(void)
{
	// Create PPI event when TIMER3 reloads that triggers an increment counting task in TIMER4
	NRF_PPI->CH[TIMER4_PPI_CH_A].EEP 		= (uint32_t)&NRF_TIMER3->EVENTS_COMPARE[TIMER3_RELOAD_CC_NUM];
	NRF_PPI->CH[TIMER4_PPI_CH_A].TEP 		= (uint32_t)&NRF_TIMER4->TASKS_COUNT;
	
	
	// Create PPI event on TIMER4 when it reaches TIMER4_RELOAD, that triggers ADC sampling task
	NRF_PPI->CH[TIMER4_PPI_CH_B].EEP 		= (uint32_t)&NRF_TIMER4->EVENTS_COMPARE[TIMER4_RELOAD_CC_NUM];
	NRF_PPI->CH[TIMER4_PPI_CH_B].TEP 		= (uint32_t)&NRF_SAADC->TASKS_SAMPLE;	
	
	NRF_PPI->CHENSET 							= ( 1 << TIMER4_PPI_CH_A) | ( 1 << TIMER4_PPI_CH_B);
}

int main(void)
{
   //Empty adc_buffer
    for(int n = 0; n < sizeof(adc_buffer)/sizeof(adc_buffer[0]); n++)
	{
		adc_buffer[n] = 0;
	}
	
	// init adc
    adc_init();
    
    // init timers
    timers_init();
    
    // start ppi
    timer4_adc_ppi();
    
    // start timers
    timers_start();
    
    
    while(adc_counter < SAMPLES_IN_BUFFER)
    {
        //do nothing until all adc samples are collected
    }
    
    // calculate the average of the samples in the buffer
    float sample_sum =0;
	float sample_avg=0;
	
	for (int j = 0; j < sizeof(adc_buffer)/sizeof(adc_buffer[0]); j++)
	{
	    sample_sum += adc_buffer[j];
	}
	sample_avg = sample_sum/sizeof(adc_buffer)/sizeof(adc_buffer[0]);
	sample_avg = (uint16_t)sample_avg;
	// UART print sample_avg (to be implemented!)
	
	//Empty adc_buffer for next measurement
    for(int n = 0; n < sizeof(adc_buffer)/sizeof(adc_buffer[0]); n++)
	{
		adc_buffer[n] = 0;
	}
    
}

Is there  something I am missing in my code? My goal is to take 32 samples, calculate the average and print that over UART. I didn't want to use the nrf_drv_saadc_buffer_convert() API function, as the register interface is more readable and understandable to me. Any comments, or improved code is appreciated.

  • Sorry, I am still not able to move forward with this problem. I am not sure what goes wrong, but it seems the MCU gets jammed after the IRQ Handler is triggered once. I tried to clear the flags, tried to do it in different ways but didn't succeed. 

    I am planning to go with the second option, i.e using the nrf_saadc_buffer_convert() with the ppi. I just read in the documentation on SAADC (if I understand correctly), that I should use NRF_SAADC_TASK_SAMPLE to trigger one sample task. Is it so that I can replace line 106 of my code above with NRF_PPI->CH[TIMER4_PPI_CH_B].TEP = (uint32_t)&NRF_SAADC->NRF_SAADC_TASK_SAMPLE; ? If that is the case, how should I write my call back function? 

  • NRF_SAADC_TASK_SAMPLE is the address offset from the SAADC address base, you need to use NRF_SAADC->TASKS_SAMPLE when operating on absolute addresses. 

    NRF_SAADC_TASK_SAMPLE uses address offset because that's the API style of the low level drivers in the SDK. 

    I suggest you run the SAADC example as is, monitor the state of the registers, and compare those states with your own project. 

    I also suggest you take a look at the SAADC driver and see how it sets up the SAADC and how its ISR handles the SAADC events. 

    I also also suggest that you verify your PPI triggering separately from the SAADC operation, that way you can be sure that the PPI system gets the signal from TIMER 4 when it's supposed to. 

  • If I start from the last tip:

    "I also also suggest that you verify your PPI triggering separately from the SAADC operation, that way you can be sure that the PPI system gets the signal from TIMER 4 when it's supposed to. "

    Yes, I used a GPIOTE channel to toggle a PIN when TIMER 4 resets. Toggling of the PIN happens precisely as it is defined by the PPI. 

    I checked the SAADC registers while debugging. I just noticed that TASKs_SAMPLE in PPI enables (sets to 1) EVENTS_DONE, EVENTS_END and EVENTS_RESULTDONE  registers. I tried to disable them in my IRQ handler, but only EVENTS_END was able to clear to zero. 

  • You need to clear those events in your SAADC IRQ with nrf_saadc_event_clear(NRF_SAADC_EVENT_END) etc, and they should remain enabled. 

  • I used that, but had the same effect. Now I just modified the while loop in main function in the following way to start the ADC task for every sample. It seems to work with some bugs:

    #include "nrf.h"
    #include <stdbool.h>
    #include <stdint.h>
    //#include "bsp.h"
    #include "nrf_gpio.h"
    #include "nrf_delay.h"
    #include "math.h"
    
    //==================================
    //PPI channels
    #define TIMER4_PPI_CH_A						0 // TIMER3 reloads and TIMER4 increments
    #define TIMER4_PPI_CH_B						1 // TIMER4 reloads, one ADC sample is taken
    //===================================
    
    //===================================
    // TIMER CC registers
    #define TIMER3_RELOAD_CC_NUM				5
    #define TIMER4_RELOAD_CC_NUM 				5
    //===================================
    
    // TIMER3 reload value. The PWM frequency equals '16000000 / TIMER_RELOAD_VALUE'
    #define TIMER3_RELOAD                   4 		
    #define TIMER4_RELOAD					128		// TIMER4 counts upto 128. Count is incremented every timer TIMER3 RELOADs
    #define SAMPLES_IN_BUFFER				32 	// Total number of ADC samples
    
    static int adc_counter = 0; // counter for the ADC samples
    volatile int16_t adc_value = 0;
    volatile bool adc_flag = false;
    volatile float adc_exact_value = 0;
    static uint32_t adc_buffer[SAMPLES_IN_BUFFER];
    
    //timer init
    void timers_init(void)
    {
        NRF_TIMER3->BITMODE                 		= TIMER_BITMODE_BITMODE_08Bit << TIMER_BITMODE_BITMODE_Pos;
        NRF_TIMER3->PRESCALER               		= 0;
        NRF_TIMER3->SHORTS                  		= TIMER_SHORTS_COMPARE0_CLEAR_Msk << TIMER3_RELOAD_CC_NUM;
        NRF_TIMER3->MODE                    		= TIMER_MODE_MODE_Timer << TIMER_MODE_MODE_Pos;
        NRF_TIMER3->CC[TIMER3_RELOAD_CC_NUM] 		= TIMER3_RELOAD;
    	
    	NRF_TIMER4->BITMODE                     	= TIMER_BITMODE_BITMODE_08Bit << TIMER_BITMODE_BITMODE_Pos;
        NRF_TIMER4->PRESCALER               		= 0;
        NRF_TIMER4->SHORTS                  		= TIMER_SHORTS_COMPARE0_CLEAR_Msk << TIMER4_RELOAD_CC_NUM;
        NRF_TIMER4->MODE                    		= TIMER_MODE_MODE_Counter << TIMER_MODE_MODE_Pos;
        NRF_TIMER4->CC[TIMER4_RELOAD_CC_NUM] 		= TIMER4_RELOAD;
    }
    
    void timers_start(void)
    {
        NRF_TIMER3->TASKS_START = 1;		
    	NRF_TIMER4->TASKS_START = 1;
    }
    void SAADC_IRQHandler(void)
    {
          adc_flag = true;
          nrf_saadc_event_clear(NRF_SAADC_EVENT_END);
    }
    
    void adc_init(void)
    
    	// Enable interrupt on ADC sample ready
    	NRF_SAADC->INTENSET = SAADC_INTENSET_END_Msk;
    	NVIC_EnableIRQ(SAADC_IRQn); 
    	
    	// configure ADC
    	NRF_SAADC->CH[0].CONFIG = (SAADC_CH_CONFIG_GAIN_Gain1_6    << SAADC_CH_CONFIG_GAIN_Pos) |
                                (SAADC_CH_CONFIG_MODE_SE         << SAADC_CH_CONFIG_MODE_Pos) |
                                (SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos) |
                                (SAADC_CH_CONFIG_RESN_Bypass     << SAADC_CH_CONFIG_RESN_Pos) |
                                (SAADC_CH_CONFIG_RESP_Bypass     << SAADC_CH_CONFIG_RESP_Pos) |
                                (SAADC_CH_CONFIG_TACQ_3us        << SAADC_CH_CONFIG_TACQ_Pos);
    
      // Configure the SAADC channel with AIN2 as positive input, no negative input(single ended). // change it to AIN5 in the final version
      NRF_SAADC->CH[0].PSELP = SAADC_CH_PSELP_PSELP_AnalogInput2 << SAADC_CH_PSELP_PSELP_Pos;
      NRF_SAADC->CH[0].PSELN = SAADC_CH_PSELN_PSELN_NC << SAADC_CH_PSELN_PSELN_Pos; // Will be ignored if MODE is SE
    	
      // Configure the SAADC resolution.
      NRF_SAADC->RESOLUTION = SAADC_RESOLUTION_VAL_12bit << SAADC_RESOLUTION_VAL_Pos;
    	
      // Configure result to be put in RAM at the location of "adc_value" variable.
      // Number of sample counts
      NRF_SAADC->RESULT.MAXCNT = 1;
      NRF_SAADC->RESULT.PTR = (uint32_t)&adc_value;
    	
      // Enable ADC
      NRF_SAADC->ENABLE = SAADC_ENABLE_ENABLE_Enabled << SAADC_ENABLE_ENABLE_Pos;
    	
      //Start the ADC 
      NRF_SAADC->TASKS_START = 1;
      while(NRF_SAADC->EVENTS_STARTED ==0);
      NRF_SAADC->EVENTS_STARTED = 0;
      
    }
    
    void timer4_adc_ppi(void)
    {
    	// Create PPI event when TIMER3 reloads that triggers an increment counting task in TIMER4
    	NRF_PPI->CH[TIMER4_PPI_CH_A].EEP 		= (uint32_t)&NRF_TIMER3->EVENTS_COMPARE[TIMER3_RELOAD_CC_NUM];
    	NRF_PPI->CH[TIMER4_PPI_CH_A].TEP 		= (uint32_t)&NRF_TIMER4->TASKS_COUNT;
    	
    	
    	// Create PPI event on TIMER4 when it reaches TIMER4_RELOAD, that triggers ADC sampling task
    	NRF_PPI->CH[TIMER4_PPI_CH_B].EEP 		= (uint32_t)&NRF_TIMER4->EVENTS_COMPARE[TIMER4_RELOAD_CC_NUM];
    	NRF_PPI->CH[TIMER4_PPI_CH_B].TEP 		= (uint32_t)&NRF_SAADC->TASKS_SAMPLE;	
    	
    	NRF_PPI->CHENSET 							= ( 1 << TIMER4_PPI_CH_A) | ( 1 << TIMER4_PPI_CH_B);
    }
    
    int main(void)
    {
       //Empty adc_buffer
        memset(&adc_buffer,0, sizeof(adc_buffer));	
    	
    	// init adc
        adc_init();
        
        // init timers
        timers_init();
        
        // start ppi
        timer4_adc_ppi();
        
        // start timers
        timers_start();
        
        
        while(adc_counter < SAMPLES_IN_BUFFER)
        {
            if(adc_flag )
    			{
    				adc_flag = false;
    				adc_exact_value =(((float)adc_value/4096)*3.6f*1000)*5.545f;
    				adc_buffer[sample_count] = adc_exact_value;
    				NRF_SAADC->TASKS_START = 0x01UL;
    				while (!NRF_SAADC->EVENTS_STARTED); 
    				NRF_SAADC->EVENTS_STARTED = 0x00UL;
    				adc_counter++;
    			}	
    	
        }
        
        // calculate the average of the samples in the buffer
        float sample_sum =0;
    	float sample_avg=0;
    	
    	for (int j = 0; j < sizeof(adc_buffer)/sizeof(adc_buffer[0]); j++)
    	{
    	    sample_sum += adc_buffer[j];
    	}
    	sample_avg = sample_sum/sizeof(adc_buffer)/sizeof(adc_buffer[0]);
    	sample_avg = (uint16_t)sample_avg;
    	// UART print sample_avg (to be implemented!)
    	
    	nrf_drv_common_irq_disable(SAADC_IRQn);
        nrf_saadc_task_trigger(NRF_SAADC_TASK_STOP);
    				
    				// Wait for ADC being stopped.
        uint32_t timeout = 3; // tried different values
    
        while (nrf_saadc_event_check(NRF_SAADC_EVENT_STOPPED) == 0 && timeout > 0)
        {
            --timeout;
        }
        nrf_saadc_disable();
    				
    	//Empty adc_buffer for next measurement
         memset(&adc_buffer,0, sizeof(adc_buffer));
        
    }

    The bug I have right now is the IRQ handler function is called once again after it reaches the last line of code in the main function. I don't know what triggers it, but it just gets triggered ones. I tried to put different timeout values for the NRF_SAADC_TASK_STOP, but didn't fix the issue. Any idea on this?

Related