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

SPI manager queue limitations

Hi all,

Having a bit of an issue with the SPI manager scheduler. When sending small packets (<4 bytes), I seem to be able to queue as many requests as I want.

However, sending a larger packet - even without any other requests in the queue (in this case 15 bytes), the transfer completes successfully but hardfaults the nRF52840 before calling the .end_callback from the transaction. This behaviour occurs even when .end_callback is set to NULL.

Update: So keeping the code as-is with the same 15 byte arrays. Doing two transfers: one with the length at 8-9 bytes / 15 possible: no hard fault, 10 bytes: finish callback isn't run (hits breakpoint but the pins it manipulates don't change state) but no error, 11 bytes+ HardFault

image description

The code I am using is this:

void send_data_async(uint8_t * data, size_t len, uint8_t * handler) {

// Packet definition
nrf_spi_mngr_transfer_t packet = 
{
   .p_tx_data = (uint8_t const *) data,
   .tx_length = (uint8_t)        len, 
	.p_rx_data = (uint8_t *)      NULL,
   .rx_length = (uint8_t)        0, 
   };
	
	
// Transaction	
nrf_spi_mngr_transaction_t command_trans = {
    .begin_callback      = start_data_callback,
        .end_callback        = finish_data_callback,
        .p_user_data         = NULL,
        .p_transfers         = &packet,
        .number_of_transfers = 1,
        .p_required_spi_cfg  = NULL
};

// Queue
APP_ERROR_CHECK(nrf_spi_mngr_schedule(&m_nrf_spi_mngr, &command_trans));
}

From the call:

 NRF_SPI_MNGR_DEF(m_nrf_spi_mngr, 16, 0);

 static ret_code_t init_spi0_master(void)
 {
 // SPI0 (with transaction manager) initialization.
 nrf_drv_spi_config_t const m_master0_config =
 {
    .sck_pin        = SPI_CLK,
    .mosi_pin       = SPI_DATA,
    .miso_pin       = NRF_DRV_SPI_PIN_NOT_USED,
    .ss_pin         = NRF_DRV_SPI_PIN_NOT_USED,
    .irq_priority   = APP_IRQ_PRIORITY_LOWEST,
    .orc            = 0xFF,
    .frequency      = NRF_DRV_SPI_FREQ_8M,
    .mode           = NRF_DRV_SPI_MODE_0,
    .bit_order      = NRF_DRV_SPI_BIT_ORDER_MSB_FIRST
 };
 return nrf_spi_mngr_init(&m_nrf_spi_mngr, &m_master0_config);
 }

 uint8_t w_lut_29_22[15] = {0x84,0x01,0x02, 0x44,0x84,0x02, 0x44,0x84,0x02, 0x44,0x01,0x02, 0x00,0x00,0x00};
 send_data_async(w_lut_29_22, 15, NULL);

Interestingly, if I change the command to nrf_spi_mngr_perform instead, using the same nrf_spi_mngr_transfer_t object, it works perfectly.

This leads me to think that there may be some kind of maximum buffer size for individual requests being passed into the scheduler queue which I haven't configured and the defaults are too low. Alternatively, it may be something to do with it holding the system for too long and annoying the Softdevice?

If there aren't any special parameters which I'm missing, if there are any good guides for diagnosing a hard fault. I've looked at several from these forums (eg: using the HardFault_Handler function, taking the SP register, getting the memory address it points to, adding +18 etc. but the end address doesn't point to any address in my code. It always seems to be from either the SoftDevice or bootloader (hence my suspicion I may be annoying them))

  • WARNING HORRIBLE TEMPORARY SOLUTION WARNING

    Just to get this working in the meantime, since 8 byte transfers seem to work with no trouble, I've written a small routine to break larger packets into 8 byte transfers which are then given to the transaction as an array for queueing. It doesn't look as nice on the logic analyser, but as far as I understand SPI, breaking strings is allowed in this manner as long as the bits are still clocked out the same.

    void send_data_async(uint8_t * data, size_t len, uint8_t * handler) {
    // Number of packets
    uint8_t no_pkts = (len + 7) / 8;
    
    // Packet definition
    nrf_spi_mngr_transfer_t packet[no_pkts];
    
    uint8_t progress = 0;
    for (uint8_t i = 0; i < no_pkts; ++i) {
    	uint8_t send_len;
    	if ((len - progress) < 8 ) {
    		send_len = len - progress;
    	} else {
    		send_len = 8;
    	}
    	
    packet[i].p_tx_data = (uint8_t const *) data + progress;
    packet[i].tx_length = (uint8_t)        send_len;
    	packet[i].p_rx_data = (uint8_t *)      NULL;
    packet[i].rx_length = (uint8_t)        0;
    
    	progress = progress + 8;
    }
    
    // Transaction	
    nrf_spi_mngr_transaction_t data_trans = {
    					.begin_callback      = start_data_callback,
            .end_callback        = finish_data_callback,
            .p_user_data         = NULL,
            .p_transfers         = packet,
            .number_of_transfers = no_pkts,
            .p_required_spi_cfg  = NULL
    };
    
    // Queue
    APP_ERROR_CHECK(nrf_spi_mngr_schedule(&m_nrf_spi_mngr, &data_trans));
    
    }
    

    DISCLAIMER: I am not originally a C programmer. They are probably MUCH better ways to do this. If anyone wants to offer any suggestions, or if anyone from Nordic has a clue why this is happening in the first place, it would be very much appreciated!

  • Hi Daniel,

    Your first solution will not work as you expect. You are passing to function nrf_spi_mngr_schedule address of variable: command_trans. Unfortunately this variable is on stack and will be not valid as soon as you leave send_data_async function. Please try solution proposed by Petter and add static keyword to variables: command_trans and packet .

Related