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

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!

Parents
  • Thanks for the sample code, it really helped to get going!

    I dont see how triggering the stop task from the timer event is going to do anything until the short is disabled by the cpu. Possibly that channel is not needed. With or without the channel, I see the same results - by the time the ISR gets to disable the short and also trigger a stop task, there is some extra bytes transfered as a result of this delay while the short is active (much less than 255). Setting the compare to 3 does not help either as you get to stop things before you get all that you want and/or the isr is sooner than when all the bytes needed has been transferred. I've just removed that channel, added a stop in the isr and padded the rx buffer (1024+1+20b, 20b for 8mhz spi). Also, you could do 5 x 205b transfers to deal with the extra spi rx byte and get all 1024 bytes. Note that with over reading the fifo, you possibly loose some new samples arriving while reading the fifo but if you sample the fifo at a period larger than it takes to fill it, then thats ok. You could avoid this if needed by waking up twice to service 2 isrs using two counters - one for the completion of 4x205b transfers to just disable the short, another for an app end event of the 5th transfer.

    It would be nice if there was a compare in the timer for a less than operation (to trigger the spi start task a predefined number of times), or a task to disable the short.

Reply
  • Thanks for the sample code, it really helped to get going!

    I dont see how triggering the stop task from the timer event is going to do anything until the short is disabled by the cpu. Possibly that channel is not needed. With or without the channel, I see the same results - by the time the ISR gets to disable the short and also trigger a stop task, there is some extra bytes transfered as a result of this delay while the short is active (much less than 255). Setting the compare to 3 does not help either as you get to stop things before you get all that you want and/or the isr is sooner than when all the bytes needed has been transferred. I've just removed that channel, added a stop in the isr and padded the rx buffer (1024+1+20b, 20b for 8mhz spi). Also, you could do 5 x 205b transfers to deal with the extra spi rx byte and get all 1024 bytes. Note that with over reading the fifo, you possibly loose some new samples arriving while reading the fifo but if you sample the fifo at a period larger than it takes to fill it, then thats ok. You could avoid this if needed by waking up twice to service 2 isrs using two counters - one for the completion of 4x205b transfers to just disable the short, another for an app end event of the 5th transfer.

    It would be nice if there was a compare in the timer for a less than operation (to trigger the spi start task a predefined number of times), or a task to disable the short.

Children
  • Just wanted to add a few things to this discussion as I found it very helpful and made  a few changes that make this work perfectly. First of all, as ShannonPahl mentioned, err_code = nrf_drv_ppi_channel_assign(ppi_channel_timer, timer_cc_event, NRF_SPIM_TASK_STOP); is not needed. this channel can be totally removed from configuration as well as start and stop routines. Second of all, I found that if you use the code as suggested, you get a few extra bytes via SPI, AFTER the burst stop is issued even if you use the NRF_SPIM_TASK_STOP inside the burst_stop() function. In other words, if you need to read or write an EXACT number of bytes, this is not going to work. I had to write drivers for a SPI NAND chip so the number of clock cycles needed to be exact. To fix this I did the following:

    1) In the nrf_drv_timer_extended_compare function, I use 1 compare cycle fewer than I need to get all the data - if I read 128 bytes at time and need to read 512, I would pass 3 in the cc_value of the function. This means that the compare interrupt will trigger at the end of 1 cycle before the very last one.

    2) When the compare interrupt is triggered, I disable the shorts via nrf_spim_shorts_disable(nand_spi.p_registers, NRF_SPIM_SHORT_END_START_MASK); and set another compare cycle, this time with cc_value of 1. I also set a flag indicating that the very last SPI cycle is to be expected. This means that when the interrupt is trggered again, I know that a) I had the exact number of full SPI cycles that I need and b) that no additional data will be sent / recieved becasue the shorts are disabled. 

    3) When the final compare interrupt is triggered I run all the remaining functions to disable the timer and the ppi_channel_spi, de-assert CS as well as set a flag indicating the end of the transmission. 

    ThIs process allows for large chunks of data to be sent received with EXACT number of bytes and clock cycles needed while using minimal interrupts (2).  

  • This works perfectly! I was having the same issue but with writing data where 1 extra byte would get written even if I used PPI to execute NRF_SPIM_TASK_SUSPEND from the timer counter to get around not having to disable shorts.

    Thanks!

Related