Transfer more than 255 bytes with SPI master on nrf52

Hi everybody,

I am trying to read a 1024 bytes FIFO buffer from an IMU (Bosh BMI160) using the SPI master driver on nrf52832 and sdk 12.2.

I know others had the same problem and I've read the other posts but since I just started with the nrf52 I couldn't figure out exactly how to implement this so I decided to open a new question.

For what I understood from other posts I should split the transfer into single transactions (4 in my case) and then using PPI count them with a TIMER and when the TIMER reaches 4, stop the SPI transfer, again with PPI. This should use the DMA and transfer the bytes directly into the ram.

Here is the code I am using:

// handler to disable the timer and set CS when the timer reaches 4
void timer_event_handler(nrf_timer_event_t event_type, void* p_context)
{
    switch (event_type)
    {
        case NRF_TIMER_EVENT_COMPARE0:
            burst_transfer_disable();
            break;

        default:
            //Do nothing.
            break;
    }
}

static void burst_setup()
{
    uint32_t spi_end_evt;
    uint32_t timer_count_task;
    uint32_t timer_cc_event;
    ret_code_t err_code;       

    //Configure timer
    nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
    timer_cfg.mode = NRF_TIMER_MODE_COUNTER;
    err_code = nrf_drv_timer_init(&timer, &timer_cfg, timer_event_handler);
    APP_ERROR_CHECK(err_code);

    // Compare event after 4 transmissions
    nrf_drv_timer_extended_compare(&timer, NRF_TIMER_CC_CHANNEL0, 4, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, true);

    timer_count_task = nrf_drv_timer_task_address_get(&timer, NRF_TIMER_TASK_COUNT);
	timer_cc_event = nrf_drv_timer_event_address_get(&timer,  NRF_TIMER_EVENT_COMPARE0);

    // allocate PPI channels
    err_code = nrf_drv_ppi_channel_alloc(&ppi_channel_spi);
    APP_ERROR_CHECK(err_code);
    err_code = nrf_drv_ppi_channel_alloc(&ppi_channel_timer);
    APP_ERROR_CHECK(err_code);

    spi_start_task = nrf_drv_spi_start_task_get(&spi);
    spi_end_evt = nrf_drv_spi_end_event_get(&spi);

    // Configure the PPI to toggle the CS (high to low) and to start the spi when there is an interrupt
    err_code = nrf_drv_ppi_channel_assign(ppi_channel_spi, spi_end_evt, timer_count_task);
    APP_ERROR_CHECK(err_code);

    // Configure another PPI to toggle again the CS (low to high) when the spi transfer is finished
    err_code = nrf_drv_ppi_channel_assign(ppi_channel_timer, timer_cc_event, NRF_SPIM_TASK_STOP);
    APP_ERROR_CHECK(err_code);
}

static void burst_transfer_enable()
{
    ret_code_t err_code;

    burst_completed = false;

    err_code = nrf_drv_ppi_channel_enable(ppi_channel_spi);
    APP_ERROR_CHECK(err_code);
    err_code = nrf_drv_ppi_channel_enable(ppi_channel_timer);
    APP_ERROR_CHECK(err_code);

    nrf_drv_timer_enable(&timer);

    // Configure short between spi end event and spi start task
    nrf_spim_shorts_enable(spi.p_registers, NRF_SPIM_SHORT_END_START_MASK);

    imu_select();
}

static void burst_transfer_disable()
{
    ret_code_t err_code;

    // Configure short between spi end event and spi start task
    nrf_spim_shorts_disable(spi.p_registers, NRF_SPIM_SHORT_END_START_MASK);

    err_code = nrf_drv_ppi_channel_disable(ppi_channel_spi);
    APP_ERROR_CHECK(err_code);
    err_code = nrf_drv_ppi_channel_disable(ppi_channel_timer);
    APP_ERROR_CHECK(err_code);
    
    nrf_drv_timer_disable(&timer);

    imu_deselect();

    burst_completed = true;
}

s8 bmi160_bus_burst_read(u8 dev_addr, u8 reg_addr, u8 *reg_data, u32 cnt)
{
	m_tx_buf[0] = reg_addr | READ_MASK;
	m_tx_buf[1] = 0;

	nrf_drv_spi_xfer_desc_t xfer = NRF_DRV_SPI_XFER_TRX(m_tx_buf, 1, reg_data, 255);
    uint32_t flags = NRF_DRV_SPI_FLAG_HOLD_XFER |
                     NRF_DRV_SPI_FLAG_REPEATED_XFER |
                     NRF_DRV_SPI_FLAG_RX_POSTINC |
                     NRF_DRV_SPI_FLAG_NO_XFER_EVT_HANDLER;
    nrf_drv_spi_xfer(&spi, &xfer, flags);  

	burst_transfer_enable();

	spi_start_task = 0x1UL;

	while(!burst_completed){
		__WFE();
	}
	spi_xfer_done = false;
	burst_completed = false;

	return SUCCESS;
}

The device crashes after the SPI is started and I get a APP_ERROR:ERROR:Fatal.

Is this the correct way to do this? Do you have an idea of where the problem might be?

Thanks!

  • The SPIM peripheral can only transfer 255 bytes (0xFF) in one transaction, but if you use the LIST feature (NRF_DRV_SPI_FLAG_TX_POSTINC flag in the driver) the RAM address will be incremented with the length of the buffer after each transfer and you can automatically start the next transfer (using short between END and START). You should make sure that the buffer is large enough for the total number of bytes you will receive (bytes per transaction times the number of transactions), or else you may hardfault or get RAM corruption.

    If you disable the shorts on the forth END event, the SPIM will try to send out a fifth frame. You should stop it on the third END event to get four transfers.

    When you get APP_ERROR:ERROR:Fatal on the logging, you should enable DEBUG in your preprocessor symbols, go into debug mode and check which function returned an error code. See here for more info.

    1024 divided by 255 is larger than four so you have to do five transfers.

  • Hi Ole, thanks for your comment. I have updated the question by NRF_DRV_SPI_FLAG_TX_POSTINC with NRF_DRV_SPI_FLAG_RX_POSTINC since I want to do multiple reads. That however didn't fix the problem.

    Regarding when to disable the shorts, I disable them at the fourth END event because at that time I also issue a NRF_SPIM_TASK_STOP trough PPI so I thought that would be the right time to do it. My understanding is that when the fourth END arrives it will not start another transfer because it is stopped through PPI. Is that correct? Also, actually I only need to read 1020 bytes, so four transactions of 255 bytes each will be fine.

    For the debugging, I defined DEBUG and wrote the handler below. However, I only get the file number a nothing is printed for the file name and error code, not even the log string at the beginning ("APP:INFO:"). What am I doing wrong?

  • This is the fault handler:

    void app_error_fault_handler(uint32_t id, uint32_t pc, uint32_t info)
    {
        error_info_t* p_error_info = (error_info_t *)info;
    
        NRF_LOG_ERROR("\r\n*** ASSERTION FAILED ***\r\n");
    
        if(p_error_info->p_file_name){
            NRF_LOG_ERROR("Line Number: %u\r\n", (unsigned int) p_error_info->line_num);
            NRF_LOG_ERROR("File Name:   %s\r\n", (unsigned int) p_error_info->p_file_name);
            NRF_LOG_ERROR("Error Code: %s\r\n", (uint32_t) ERR_TO_STR(p_error_info->err_code));
        }
    
        NRF_LOG_FINAL_FLUSH();
    
        while(1);
    
        // NVIC_SystemReset();
    }
    
  • I wrote wrong in the comment, I meant NRF_DRV_SPI_FLAG_RX_POSTINC. NRF_DRV_SPI_FLAG_TX_POSTINC will enable the list feature on TX.

    The app_error_fault_handler should work, what is the default severity level in your logging? Check the NRF_LOG_DEFAULT_LEVEL define in sdk_config.h. I think you should use NRF_LOG_ERROR in the fault handler.

  • I got it to work by starting the SPI transfer with

    nrf_spim_task_trigger(spi.p_registers, NRF_SPIM_TASK_START);
    

    rather than with

    spi_start_task = 0x1UL;
    

    The four transactions happen correctly and I get some data. Now the problem is that it seems the data does not make much sense in terms of accelerometer and gyroscope. This also fixed the fault handler, now it prints the file name correctly.

    UPDATE: here is the basic code to implement the solution: gist.github.com/.../1708d549be50ee37e62dfc5f493b3fa9

Related