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.

Parents
  • Does the interrupt handler execute at all? Place a breakpoint in the interrupt handler to verify. 



    There are a few things that you can change to greatly reduce the complexity of your application. 

    The first thing you should do is to set RESULT.MAXCNT to 32, then you wont have to service the interrupt until you've got all 32 samples. You will also not have to keep track of the sample number with a global variable. 



    The second thing is to only use TIMER3, there's no need to use another timer as each TIMER has six compare match registers. You can set TIMER3's CC0 register to (128*4), trigger the SAADC SAMPLE task from EVENTS_COMPARE[0] and enable the SHORTS to clear TIMER3. 
    What I believe you're trying to do is to implement a prescaler in order to get the correct frequency for your SAADC sampling. The TIMERs already have this functionality with the PRESCALER register. From TIMER — Timer/counter:

     fTIMER = 16 MHz / (2PRESCALER)


    The third thing is using memset to erase a buffer instead of a for loop, f.ex:

    //Empty adc_buffer
    
    memset(&adc_buffer,0, sizeof(adc_buffer)); 
    

    See https://www.tutorialspoint.com/c_standard_library/c_function_memset.htm 

    Cheers!

  • Hi

    Thanks for the tips. 

    The Interrupt handler is not executed at all. That's what worries me. The reason why I am using two timers, instead of one, is because TIMER3 is used to generate two phase shifted PWM signals using PPI, based on the example here. The timer of the PWM signals resets faster than the ADC sampling rate => requirement for TIMER4.

    The ADC sampling is done at a lower frequency than the PWM. However, it is required that the ADC sampling should be synchronized with the PWM signals, which make it difficult to use RESULT.MAXCNT = 32. If I understood correctly, to set RESULT.MAXCNT=32, I should also specify the sampling frequency using NRF_SAADC->SAMPLERATE. This will make the synchronization between the two times difficult.The two timers are synchronized using PPI. So, my question is how should the SAADC IRQ handler be written so that it counts the number of ADC samples and appends that to the buffer. The main function will take care of the rest when the buffer is full.

  • You need to check the NVIC registers to see if the SAADC's END event interrupt is actually enabled.



    "TIMER3 is used to generate two phase shifted PWM signals using PPI, based on the example here. The timer of the PWM signals resets faster than the ADC sampling rate => requirement for TIMER4."

    - Alright your TIMERs looks good then :) 

    "If I understood correctly, to set RESULT.MAXCNT=32, I should also specify the sampling frequency using NRF_SAADC->SAMPLERATE."

    - Not correct. You should not use the SAMPLERATE register unless you're also using the SAADC's internal timer (poorly documented). If SAMPLERATE.MODE is set to 0x0 the SAMPLE task will trigger individual samples.
    In this mode one sample will be taken for each enabled SAADC channel every time a SAMPLE task is triggered until the number of samples taken across all channels equals RUSULT.MAXCNT.
    In your case you've only got one channel enabled and therefore you will reach MAXCNT after MAXCNT amount of SAMPLE tasks has been triggered. If you've had enabled two channels then you would reach MAXCNT after MAXCNT/2 SAMPLE tasks have been triggered, etc. 

    If you want continuous sampling with a buffer size of 32 samples, then you should update RESULT.PTR after you've triggered the START task and received the STARTED event. The RESULT register is double buffered, this means that when you trigger the START task the content of this register is copied into an internal SAADC register. The reason why this is done is that you can then immediately prepare the next sample buffer and write its address and size into the RESULT register, and then use PPI to connect the END event to the START task, thereby creating an endless loop of TIMER synchronized adc sampling. 

    nrf_drv_saadc_buffer_convert() does this with a set of two buffers that is swapped for each cycle of (32) samples. You can use any amount of buffers you like. 
     


  • Through debugging I can see that the IRQ handler is called once, and the adc_counter gets incremented only once. 

    "You need to check the NVIC registers to see if the SAADC's END event interrupt is actually enabled."

    Can I check those in kiel, debug mode?

    Can I use nrf_drv_saadc_buffer_convert() with PPI?

  • "Through debugging I can see that the IRQ handler is called once, and the adc_counter gets incremented only once. " 
    - You probably need to clear the interrupt flag in NVIC. You can do this by calling 

    nrf_saadc_event_clear(NRF_SAADC_EVENT_END);
    This is usually done in the saadc driver's interrupt handler. 


    "Can I check those in kiel, debug mode?"
    - Yes, they should appear as the INTEN, INTSET, and INTCLR registers along the other SAADC registers, this is true for all the other peripherals as well. 
     
    "Can I use nrf_drv_saadc_buffer_convert() with PPI?"
    -Yes, they are intended to be used together. 
Reply
  • "Through debugging I can see that the IRQ handler is called once, and the adc_counter gets incremented only once. " 
    - You probably need to clear the interrupt flag in NVIC. You can do this by calling 

    nrf_saadc_event_clear(NRF_SAADC_EVENT_END);
    This is usually done in the saadc driver's interrupt handler. 


    "Can I check those in kiel, debug mode?"
    - Yes, they should appear as the INTEN, INTSET, and INTCLR registers along the other SAADC registers, this is true for all the other peripherals as well. 
     
    "Can I use nrf_drv_saadc_buffer_convert() with PPI?"
    -Yes, they are intended to be used together. 
Children
  • 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