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

nRF52840 USB HID with simultaneous SPIM transfer

Hello,

I use SDK15.2, SES4.12 and custom board with nRF52840.

I have a strip of ws2812b LED's connected to my device. I've made a simple RGB animation, each frame of which is transferred via SPIM. It works fine.

Now I need to add some USB HID functionality. I have simple custom descriptor with one output report. And it works fine too, if there is no SPIM transfer.

When I try to run both SPIM and USB, my code breaks somewhere with no call stack available. If I manually break the execution, it breaks on line 72 in ses_startup_nrf52840.s file, here is a snippet from there:

  .thumb_func
  .weak   HardFault_Handler
HardFault_Handler:
  b     .

Here is my main

int main(void)
{ 
    nrf_gpio_cfg_output( LOGO );
    
	Logo_Initialize(LOGO);

	init_hid();
    
	RGB_Refresh();

    while (true)
    {
		while (app_usbd_event_queue_process())
        {
            /* Nothing to do */
        }
        /* Sleep CPU only if there was no interrupt since last loop processing */
        __WFE();
    }
}

SPIM part:

void spim_event_handler(nrfx_spim_evt_t const * p_event, void * p_context)
{
    NRF_LOG_INFO("Transfer completed.");
	
	nrf_delay_ms(10);
	RGB_Refresh();  
}

void Logo_Initialize(uint8_t logo_pin)
{
	uint8_t m_rx_buf[DATA_LENGTH];
	
	nrfx_spim_xfer_desc_t xfer_desc = NRFX_SPIM_XFER_TRX(m_tx_buf, DATA_LENGTH, m_rx_buf, DATA_LENGTH);

    nrfx_spim_config_t spi_config = NRFX_SPIM_DEFAULT_CONFIG;
    spi_config.frequency = NRF_SPIM_FREQ_4M;
    spi_config.mosi_pin = logo_pin;
    spi_config.use_hw_ss = true;
    spi_config.ss_active_high = false;
    APP_ERROR_CHECK(nrfx_spim_init(&spi, &spi_config, spim_event_handler, NULL));

	transfer_descriptor = &xfer_desc;

    NRF_LOG_INFO("NRFX SPIM example started.");

    bitmanio_init_array8(&bio_arr, m_tx_buf, CODED_BYTES_PER_RGB_BYTE);

//animation array initialization here
}

void RGB_Refresh()
{
//get next frame here

	APP_ERROR_CHECK(nrfx_spim_xfer_dcx(&spi, transfer_descriptor, 0, 15));
}

Basically now I can have either RGB_Refresh() call or app_usbd_event_queue_process() call in my main function, but not both.

  • I would suggest that you have an issue with putting stuff in the interrupt which does not belong in an interrupt, particularly when that stuff generates the same interrupt before the current interrupt completes. Perhaps change this:

    void spim_event_handler(nrfx_spim_evt_t const * p_event, void * p_context)
    {
        NRF_LOG_INFO("Transfer completed.");
    	
    	nrf_delay_ms(10);
    	RGB_Refresh();  
    }

    to something like this:

    // Flag to trigger next display refresh outside of any interrupt
    volatile uint16_t SPI_Signal = 0;
    
    void spim_event_handler(nrfx_spim_evt_t const * p_event, void * p_context)
    {
       SPI_Signal++;
    }
    
       // in main loop after wakeup due to SPI interrupt above
       if (SPI_Signal)
       {
        SPI_Signal = 0;
        NRF_LOG_INFO("Transfer completed.");
    	nrf_delay_ms(10); // <== what's this for? better to use a timer
    	RGB_Refresh();  
       }

    SPI_Signal should only be 0 or 1, but it doesn't hurt to allow more for debugging

  • Hello,

    I agree, calling RGB_Refresh in interrupt is not a good idea. I placed it there in attempt to find working solution.

    I have modified my main:

    int main(void)
    {  
        nrf_gpio_cfg_output( LOGO );
        
    	Logo_Initialize(LOGO);
    
    	HID_Initialize();
    
        while (true)
        {
    		while (app_usbd_event_queue_process())
            {
                /* Nothing to do */
            }
          
    		if (SPI_Status)
    		{
    			SPI_Status = 0;
    			NRF_LOG_INFO("Transfer completed.");
    			nrf_delay_ms(10); 
    			RGB_Refresh();  
    		}
            /* Sleep CPU only if there was no interrupt since last loop processing */
            __WFE();
        }
    }

    The behavior is the same as in my previous version, and the problem is still present. 

    I use nrf_delay_ms(10); just to slow down animation, perhaps it is not a good way to do it. I'll try to change it to timer api.

  • For this I would just count wakeups rather than complicating it with another interrupt for the timer. Something like this:

    // Trigger every 1,000 wakeups if 1mSec system tick, 100 if 10mSec system tick
    static uint32 LoopCount = 0;
    
    		if ((SPI_Status) && ((++LoopCount % 1000) == 0))
    		{
    			SPI_Status = 0;
    			NRF_LOG_INFO("Transfer completed.");
    			// Edit: remove this linenrf_delay_ms(10); 
    			RGB_Refresh();  
    		}
    

  • It works, but only without call to __WFE();, and without USB functionality.

    If I have app_usbd_event_queue_process() call in my main cycle, application breaks in the same way it was before any modifications.

  • How can I make USB and SPIM working together? These features work perfectly if I use them separately, but if I turn on both USB and SPIM my application crashes. Maybe there is some sort of interruption conflict?

Related