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

Any relation between SPIM, PPI, TIMER and DFU bootloader?

Hi All,

Another question for those out there much smarter than myself...

We're developing an application using nrf52, sdk11 and softdevice s132 2.0.0 and have been using DFU single bank to successfully update the application OTA.

Recently, we've made some changes to enable the use of easyDMA with the SPI master, which broke the previously working OTA updates.

At a high level, we're using NRF_DRV_TIMER_INSTANCE(1) task to toggle an external CS pin and trigger a SPI transaction event every 1ms, to draw a line to a display without any mcu involvement.

All of this works fine and well, but when this application is loaded to the nrf52, and I try to OTA update another application, I get stuck in what seems to be an app_error_check() or hard fault inside of the bootloader unless I disable the ppi channel connecting the timer event with the SPIM task in bootloader_util.c

The single bank bootloader used is essentially the dual bank bootloader from the sdk examples, with the dual_bank_bootloader.c swapped with single_bank.c with two additions to main to toggle GPIO 0.15 HIGH and 0.26 LOW

I didn't find any possible explanations in the softdevice specification, which places a restriction on the upper 3 PPI channels but nothing related to SPIM I could see. Has anybody seen anything like this before, or can think of any possible explanations as to why this could be the case? Is this possibly a bug/PAN?

nrf_ppi_channel_t       m_ppi_channel;           // Used for the SPI transfer (originally the SAADC)

void dma_events_init(void){
        uint32_t ticks = nrf_drv_timer_ms_to_ticks(&m_timer, 1);
        
        nrf_drv_timer_extended_compare(&m_timer, NRF_TIMER_CC_CHANNEL0, ticks, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);
    nrf_drv_timer_enable(&m_timer);

    uint32_t timer_compare_event_addr = nrf_drv_timer_compare_event_address_get(&m_timer, NRF_TIMER_CC_CHANNEL0);
    
    // GPIOTE Stuff
    nrf_drv_gpiote_out_config_t cs_config = GPIOTE_CONFIG_OUT_TASK_HIGH;
    err_code = nrf_drv_gpiote_out_init(GPIO_DISP_CS0, &cs_config);    
    
    uint32_t gpiote_toggle_event_addr = nrf_drv_gpiote_out_task_addr_get(15);
    
    // SPI Stuff
    uint32_t spi_transfer_event_addr = nrf_drv_spi_start_task_get(&spi);
    
    // Setup next transfer sequence
    nrf_drv_spi_xfer_desc_t xfer = NRF_DRV_SPI_XFER_TX(dma_sharp_mlcd_display_buffer_line, 52);
    
    // Set flags for next transfer sequence
    uint32_t flags = NRF_DRV_SPI_FLAG_HOLD_XFER;
    
    err_code = nrf_drv_spi_xfer(&spi, &xfer, flags);
 
    /* setup ppi channel so that timer compare event is triggering SPI transfer */
    err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel);
   APP_ERROR_CHECK(err_code);

   err_code = nrf_drv_ppi_channel_assign(m_ppi_channel, timer_compare_event_addr, spi_transfer_event_addr);
   APP_ERROR_CHECK(err_code);

   err_code = nrf_drv_ppi_channel_enable(m_ppi_channel);
   APP_ERROR_CHECK(err_code);
}

changes to bootloader_util.c:

#include "nrf_drv_ppi.h"

void bootloader_util_app_start(uint32_t start_addr)
{
    nrf_drv_ppi_channel_free(m_ppi_channel);  // m_ppi_channel is the "short" between timer event and SPIM task
    bootloader_util_reset(start_addr);
}
  • @bvuong87: please correct me if I'm wrong. You want to continue doing the SPI task when you are in bootloader mode by using PPI and let the timer and SPI running without CPU involvement ? But you have hardfault/assert when you enter bootloader ?

    If you call nrf_drv_ppi_channel_free() you don't have any issue continue with the bootloader ?

    You would need to make sure all the interrupt handler is implemented or you disable the interrupt flag the peripherals you use but don't handle them in the bootloader code.

    The nrf_driver actually enable interrupt flag for the timer and the SPI. I would suggest to use the TIMER and SPI directly so you know for sure what you enable.

  • @hungbui: Apologies, my description may have been a little unclear.

    I'm not trying to continue running the SPI task while in bootloader mode. The SPI task is setup to run in the main application, connected to the timer event using:

        err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel);
       APP_ERROR_CHECK(err_code);
    
       err_code = nrf_drv_ppi_channel_assign(m_ppi_channel, timer_compare_event_addr, spi_transfer_event_addr);
       APP_ERROR_CHECK(err_code);
    
       err_code = nrf_drv_ppi_channel_enable(m_ppi_channel);
       APP_ERROR_CHECK(err_code);
    

    Inside of the main application, the SPI task works as expected. However, the problem I'm seeing is when entering DFU bootloader mode through the NRF Connect phone app, the log shows the application restarts and entering into bootloader mode, however NRF connect is unable to reconnect to the device to continue the DFU process and send the new application OTA unless I disable the ppi channel in the function which starts the bootloader from the main application.

    #include "nrf_drv_ppi.h"
    
    void bootloader_util_app_start(uint32_t start_addr)
    {
        nrf_drv_ppi_channel_free(m_ppi_channel);  // m_ppi_channel is the "short" between timer event and SPIM task
        bootloader_util_reset(start_addr);
    }
    

    I'm assuming it's some sort of hard fault, or app_error_check() (although haven't completely verified) after it enters the bootloader since when this occurs, the nrf52 doesn't advertise (as either DfuTarg, or the application name) and requires a power cycle. Note - that the DFU OTA application update also worked with the identical bootloader, before adding the PPI implementation

    Is this to be expected? I wouldn't think so since I have some another PPI channel allocated and enabled between the same timer event and a GPIOTE task which controls a CS pin which doesn't need to be disabled

  • If you don't plan to keep the SPI task when you are in bootloader mode. I don't see the point of keeping the ppi channel when you branch to the bootloader.

    I still keep my concern that if you keep the peripheral running when you branch to the bootloader, especially when you forward the vector table to the bootloader's one you will have trouble when the interrupt comes.

    It's recommended to stop all the peripherals you use in the application before you branch to the bootloader.

    If you want to dig deeper, I would suggest to run the bootloader project in debug mode and wait for the application to branch to the bootloader and debug from there (you would need to remove optimization in the bootloader to debug)

Related