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

NRF52840 Missing Timer / Counter Interrupts when BLE is on

Hello,

I am experiencing a problem similar to this thread, when I am using the counter feature of timer 2 together with PPI to count the number of SPI DMA (SPI3 currently but I cnfirmed the problem with SPI0-3) transactions and stop data transfer when the required number has been reached. I was able to test this extensively in NRF52832 and confirm that it works with BLE. However the same implementation on NRF52840 seems to fail due to missed counter interrupts. The original thread did not post a solution to this so I am wondering if there is one. My interrupts are configured to priority 3 for both SPI and Timer/Counter. I am using 128-byte DMA transfers and transfer at least 2048 bytes at a time for read / write operations. The BLE code is currently leveraged from the Nordic NUS example and the device which writes the data to a SPI NAND chip is configured as the BLE Central. I am able to write thousands of 16kB files without a problem when BLE is off but as soon as I enable scanning, things go awry. Failure happens semi-randomly, within a couple of minutes or a couple of hours but if I trigger a 100-file write (approx 2.5s duration) it fails every time at some spot. Any help on this would be highly appreciated.

Thanks,

-Konstantin

Parents
  • So I'm a bit confused here, you say you're using PPI but then you talk about interrupt priorities. If you're using PPI then interrupts don't matter. So I think you're going to have to explain in a little more detail what exactly you're doing which is PPI and what relies on an actual interrupt to be handled. It would also help if you explained what the failure is, too much data written, not enough data written, wrong data, buffers being written twice and why you think it's due to 'missed counter interrupts'. 

    You've given a description of a problem and said you think it's like another problem but you haven't quite gotten to the meat of the issue in a way someone could start suggesting ideas. 

  • Sure, I'm happy to explain further. SPI DMA can send 255 bytes max, I need to send much more than that (4k+) while using minimal power. To achieve that, I do the following:

    1. Configure timer 2 to work in counter mode

    2.  Link NRF_TIMER_TASK_COUNT task with NRF_SPIM_EVENT_END event using the following code:

        //get timer counter address
        timer_count_task = nrf_drv_timer_task_address_get(&timer2, NRF_TIMER_TASK_COUNT);
    	
        //Allocate PPI channels
        err = nrf_drv_ppi_channel_alloc(&ppi_channel_spi);
    	if(err != NRF_SUCCESS)
    	{
    		return err;
    	}
    	
        //get SPI event end address 
        spi_end_evt = nrf_spim_event_address_get(m_spi_master.p_reg, NRF_SPIM_EVENT_END);
    	
    	// Configure the PPI to count the trasnsactions on the TIMER
    	err = nrf_drv_ppi_channel_assign(ppi_channel_spi, spi_end_evt, timer_count_task);
    	if(err != NRF_SUCCESS)
    	{
    		return err;
    	}

    3. When data needs to be read / written, I calculate the number of SPI transactions I need (spi_cycle_num), configure the counter register with that value. spi_cycle_num holds one transaction less then the total needed because I want to make sure that I do not read/write too much data and set up the last transaction manually. The code for this step is as follows:

        spi_cycle_num = (len / MAX_DMA_XFER_SIZE - 1);
    
    	//set up SPI for consecutive reads / writes, each one will be MAX_DMA_XFER_SIZE bytes
    	nrf_drv_timer_extended_compare(&timer2, NRF_TIMER_CC_CHANNEL0, spi_cycle_num, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, true);
    	
        nrfx_spim_xfer_desc_t xfer = NRFX_SPIM_XFER_TRX(data_tx, MAX_DMA_XFER_SIZE, NULL, 0);
       
        uint32_t flags = NRFX_SPIM_FLAG_HOLD_XFER |
                        NRFX_SPIM_FLAG_REPEATED_XFER |
                        NRFX_SPIM_FLAG_TX_POSTINC  |
                        NRFX_SPIM_FLAG_NO_XFER_EVT_HANDLER;
    	
    	//set up proper SPI flags
        err = nrfx_spim_xfer(&m_spi_master, &xfer, flags);
      
    	if(err != NRF_SUCCESS)
    	{
    		return err; 
    	}
    	
    	//configure burst transfer
    	err = nand_burst_start();
    	if(err != NRF_SUCCESS)
    	{
    		return err; 
    	}
    	
    	//kick off SPI transfer
    	nrf_spim_task_trigger(m_spi_master.p_reg, NRF_SPIM_TASK_START);
    	
    	//wait for a flag or semaphore release here to signal a complete transfer...

    The function nand_burst_start that configures PPI

    	err = nrf_drv_ppi_channel_enable(ppi_channel_spi);
    	if(err != NRF_SUCCESS)
    	{
    		return err;
    	}
    	
    	//This may not need to be enabled
    	nrf_drv_timer_enable(&timer2);
    	
    	//configure shortcut 
    	nrf_spim_shorts_enable(m_spi_master.p_reg, NRF_SPIM_SHORT_END_START_MASK);
    	
    	nrf_spim_enable(m_spi_master.p_reg);
    
    	NAND_CS_ASSERT();

    4. Finally when the spi_cycle_num cycle is complete, the timer peripheral triggers a compare interrupt (this is the one that occasionally misses). This interrupt signals the end of data with the exception of one last bit to be configured manually, not sent using PPI. The timer interrupt looks like this:

        switch (event_type)
    	{
    		case NRF_TIMER_EVENT_COMPARE0:
    			nand_burst_stop();
    			break;
    
    		default:
    			//Do nothing.
    			break;

    5. nand_burst_stop function stops PPI and sets up one last SPI transaction. When the final transaction is complete this same function sets the "cycle complete" flag or releases the semaphore to indicate that all of the data has been read / written

        if(nand_burst_last_trx == true)
    	{
    		nrf_drv_timer_disable(&timer2);
    		nand_burst_last_trx = false;
    		err = nrf_drv_ppi_channel_disable(ppi_channel_spi);
    		if(err != NRF_SUCCESS)
    		{
    			return err;
    		}
    		
    		if(!keep_cs_active)
    		{
    			NAND_CS_DEASSERT();
    		}
    		
    
            //data transfer is complete set flag or release semaphore here
    	}
    	else
    	{
    		//disable the shortcut between SPI END and Counter increment
    		nrf_spim_shorts_disable(m_spi_master.p_reg, NRF_SPIM_SHORT_END_START_MASK);
    		
    		// set up final transaction
    		nrf_drv_timer_extended_compare(&timer2, NRF_TIMER_CC_CHANNEL0, 1, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, true);
    		nand_burst_last_trx = true;
    	}
    	
    	return err;

    If the counter does not trigger an interrupt, the code never enters the nand_burst_stop function, which means that the function writing the data never gets the "transfer completed" notification. This only fails when BLE is enabled, not necessarily even connected to another device, just on and in scanning mode. It is worth noting that I am using SDK 15.0 and previously when the code was running fine on NRF52832, the SoC was in peripheral mode. I have not tried changing that here but I'm not sure how that could affect this issue. Using a logic analyzer I can tell that the final transaction before the interrupt failure is always complete, as in the SPI sends the correct amount of data, just never triggers the counter interrupt. 

    Thanks,

    -Konstantin

Reply
  • Sure, I'm happy to explain further. SPI DMA can send 255 bytes max, I need to send much more than that (4k+) while using minimal power. To achieve that, I do the following:

    1. Configure timer 2 to work in counter mode

    2.  Link NRF_TIMER_TASK_COUNT task with NRF_SPIM_EVENT_END event using the following code:

        //get timer counter address
        timer_count_task = nrf_drv_timer_task_address_get(&timer2, NRF_TIMER_TASK_COUNT);
    	
        //Allocate PPI channels
        err = nrf_drv_ppi_channel_alloc(&ppi_channel_spi);
    	if(err != NRF_SUCCESS)
    	{
    		return err;
    	}
    	
        //get SPI event end address 
        spi_end_evt = nrf_spim_event_address_get(m_spi_master.p_reg, NRF_SPIM_EVENT_END);
    	
    	// Configure the PPI to count the trasnsactions on the TIMER
    	err = nrf_drv_ppi_channel_assign(ppi_channel_spi, spi_end_evt, timer_count_task);
    	if(err != NRF_SUCCESS)
    	{
    		return err;
    	}

    3. When data needs to be read / written, I calculate the number of SPI transactions I need (spi_cycle_num), configure the counter register with that value. spi_cycle_num holds one transaction less then the total needed because I want to make sure that I do not read/write too much data and set up the last transaction manually. The code for this step is as follows:

        spi_cycle_num = (len / MAX_DMA_XFER_SIZE - 1);
    
    	//set up SPI for consecutive reads / writes, each one will be MAX_DMA_XFER_SIZE bytes
    	nrf_drv_timer_extended_compare(&timer2, NRF_TIMER_CC_CHANNEL0, spi_cycle_num, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, true);
    	
        nrfx_spim_xfer_desc_t xfer = NRFX_SPIM_XFER_TRX(data_tx, MAX_DMA_XFER_SIZE, NULL, 0);
       
        uint32_t flags = NRFX_SPIM_FLAG_HOLD_XFER |
                        NRFX_SPIM_FLAG_REPEATED_XFER |
                        NRFX_SPIM_FLAG_TX_POSTINC  |
                        NRFX_SPIM_FLAG_NO_XFER_EVT_HANDLER;
    	
    	//set up proper SPI flags
        err = nrfx_spim_xfer(&m_spi_master, &xfer, flags);
      
    	if(err != NRF_SUCCESS)
    	{
    		return err; 
    	}
    	
    	//configure burst transfer
    	err = nand_burst_start();
    	if(err != NRF_SUCCESS)
    	{
    		return err; 
    	}
    	
    	//kick off SPI transfer
    	nrf_spim_task_trigger(m_spi_master.p_reg, NRF_SPIM_TASK_START);
    	
    	//wait for a flag or semaphore release here to signal a complete transfer...

    The function nand_burst_start that configures PPI

    	err = nrf_drv_ppi_channel_enable(ppi_channel_spi);
    	if(err != NRF_SUCCESS)
    	{
    		return err;
    	}
    	
    	//This may not need to be enabled
    	nrf_drv_timer_enable(&timer2);
    	
    	//configure shortcut 
    	nrf_spim_shorts_enable(m_spi_master.p_reg, NRF_SPIM_SHORT_END_START_MASK);
    	
    	nrf_spim_enable(m_spi_master.p_reg);
    
    	NAND_CS_ASSERT();

    4. Finally when the spi_cycle_num cycle is complete, the timer peripheral triggers a compare interrupt (this is the one that occasionally misses). This interrupt signals the end of data with the exception of one last bit to be configured manually, not sent using PPI. The timer interrupt looks like this:

        switch (event_type)
    	{
    		case NRF_TIMER_EVENT_COMPARE0:
    			nand_burst_stop();
    			break;
    
    		default:
    			//Do nothing.
    			break;

    5. nand_burst_stop function stops PPI and sets up one last SPI transaction. When the final transaction is complete this same function sets the "cycle complete" flag or releases the semaphore to indicate that all of the data has been read / written

        if(nand_burst_last_trx == true)
    	{
    		nrf_drv_timer_disable(&timer2);
    		nand_burst_last_trx = false;
    		err = nrf_drv_ppi_channel_disable(ppi_channel_spi);
    		if(err != NRF_SUCCESS)
    		{
    			return err;
    		}
    		
    		if(!keep_cs_active)
    		{
    			NAND_CS_DEASSERT();
    		}
    		
    
            //data transfer is complete set flag or release semaphore here
    	}
    	else
    	{
    		//disable the shortcut between SPI END and Counter increment
    		nrf_spim_shorts_disable(m_spi_master.p_reg, NRF_SPIM_SHORT_END_START_MASK);
    		
    		// set up final transaction
    		nrf_drv_timer_extended_compare(&timer2, NRF_TIMER_CC_CHANNEL0, 1, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, true);
    		nand_burst_last_trx = true;
    	}
    	
    	return err;

    If the counter does not trigger an interrupt, the code never enters the nand_burst_stop function, which means that the function writing the data never gets the "transfer completed" notification. This only fails when BLE is enabled, not necessarily even connected to another device, just on and in scanning mode. It is worth noting that I am using SDK 15.0 and previously when the code was running fine on NRF52832, the SoC was in peripheral mode. I have not tried changing that here but I'm not sure how that could affect this issue. Using a logic analyzer I can tell that the final transaction before the interrupt failure is always complete, as in the SPI sends the correct amount of data, just never triggers the counter interrupt. 

    Thanks,

    -Konstantin

Children
  • After a little more debugging, I can now see that the interrupt that it is missing the final one that I set up nand_burst_stop function with 

    nrf_drv_timer_extended_compare(&timer2, NRF_TIMER_CC_CHANNEL0, 1, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, true);

    Perhaps the reason why it isn't triggering is becasue the next SPI transaction finishes before I set up this final interrupt due to a higher priority interrupt running a BLE routine? I'm investigating...

  • haven't had a chance to read it all yet, did have one comment "SPI DMA can send 255 bytes max" .. doesn't the 52840 have 16bit DMA lengths instead of 8 bit? I remember this was a regular complaint about the nRF52836, that DMA was limited to 255, so I thought it was changed. I had a quick look at the SPIM/SPIS peripherals in the datasheet and it looks to me like the relevant ones are 16 bit now. 

  • Ah, well I guess that solves it. I was used to NRF52832 and made the assumption that DMA size was the same. From the NRF52840 datasheet: "Any data stored in memory type(s) not accessible by the DMA engine must be copied to SRAM before it can be processed by the CRYPTOCELL subsystem. Maximum DMA transaction size is limited to (2^16)-1 bytes." So DMA should be 65535... which is more than I will need. Thanks!

Related