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 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?

  • From earlier:
    "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 only need EVENTS_END to be cleared, it's the only interrupt that we care about. Does you SAADS IRQ not fire at the end of the 32 samples? 

  • " Does you SAADS IRQ not fire at the end of the 32 samples? "

    MY SAADC_IRQ does not fire at the end of the 32 samples. After the the 32th sample, the mcu exits the  while(adc_counter < SAMPLES_IN_BUFFER) loop in the main function, continue executing the remaining lines of the main function. At the end of the main function, the IRQ_SAADC fires again. And when it leaves the IRQ, the MCU jumps back to line 164 in my code above (nrf_saadc_disable()) and continues executing the remaining lines of the main function before it finally halts. 

  • You need to encase your state machine in a while(1), otherwise the CPU will halt after the first execution:

    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(1)
        {
            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;
        	}
        }    
    }

Reply
  • You need to encase your state machine in a while(1), otherwise the CPU will halt after the first execution:

    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(1)
        {
            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;
        	}
        }    
    }

Children
Related