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

nRF52 softdevice hangs at sd_softdevice_enable when using custom bootloader

I have a problem initializing the ble stack when I'm starting my application via a custom bootloader. I'm using the nrf52832 device on a custom board.

My application has been working fine for a while on its own. But now I want to add a bootloader to the mix...

The memory layout in flash is like this:

0x00000000   - softdevice (s132_nrf52_4.0.2)
0x0001F000   - Primary bootloader
0x00022000   - Main application

There is no overlap in RAM

The point where the execution stops is at sd_softdevice_enable in softdevice_handler.c. I have seen several other questions about this where the solution often seemed to be related to the clock source. However I don't think this is my problem since the application works fine on its own. But I've tried both external and internal clock sources, with the same results:

// external clock source
nrf_clock_lf_cfg_t clock_lf_cfg =
{
    .source        = NRF_CLOCK_LF_SRC_XTAL,
    .rc_ctiv       = 0,
    .rc_temp_ctiv  = 0,
    .xtal_accuracy = NRF_CLOCK_LF_XTAL_ACCURACY_30_PPM
};

// internal clock source
nrf_clock_lf_cfg_t clock_lf_cfg =
{
    .source = NRF_CLOCK_LF_SRC_RC,
    .rc_ctiv = 16, // Interval in 0.25 s, 16 * 0.25 = 4 sec
    .rc_temp_ctiv = 2, // Check temperature every .rc_ctiv, but calibrate every .rc_temp_ctiv
    .xtal_accuracy = 0,
};

I'm currently out of ideas as to what could be wrong. Can anyone here point me in the right direction?

  • Could be that you didn't tell the SD the location of your app. Normally the bootloader does this. You can't just move the PC since the SD will return the program to the normal start location per the SDK unless you tell it otherwise. See this question from someone running two applications: devzone.nordicsemi.com/.../

  • Thanks for the info! I wasn't aware of this at all. Do I need to do something before calling sd_softdevice_vector_table_base_set? I tried to call it before I jump to my application, but I get a hard fault.

  • Never had to do this before either. And I will look for a better reference but the way the legacy DFU handles this is:

    void bootloader_app_start(uint32_t app_addr)
    {
        // If the applications CRC has been checked and passed, the magic number will be written and we
        // can start the application safely.
        uint32_t err_code = sd_softdevice_disable();
        APP_ERROR_CHECK(err_code);
    
        interrupts_disable();
    
        err_code = sd_softdevice_vector_table_base_set(CODE_REGION_1_START);
        APP_ERROR_CHECK(err_code);
    
        bootloader_util_app_start(CODE_REGION_1_START);
    }
    

    Bootloader util is a bunch of assembly code:

    static inline void bootloader_util_reset(uint32_t start_addr)
    {
        asm("ldr   r5, [%0]\n"                    // Get App initial MSP for bootloader.
            "msr   msp, r5\n"                     // Set the main stack pointer to the applications MSP.
            "ldr   r0, [%0, #0x04]\n"             // Load Reset handler into R0.
    
            "movs  r4, #0x00\n"                   // Load zero into R4.
            "mvns  r4, r4\n"                      // Invert R4 to ensure it contain ones.
    
            "mrs   r5, IPSR\n"                    // Load IPSR to R5 to check for handler or thread mode 
            "cmp   r5, #0x00\n"                   // Compare, if 0 then we are in thread mode and can continue to reset handler of bootloader.
            "bne.n isr_abort\n"                   // If not zero we need to exit current ISR and jump to reset handler of bootloader.
    
            "mov   lr, r4\n"                      // Clear the link register and set to ones to ensure no return.
            "bx    r0\n"                          // Branch to reset handler of bootloader.
    
            "isr_abort: \n"
                                                  // R4 contains ones from line above. We be popped as R12 when exiting ISR (Cleaning up the registers).
            "mov   r5, r4\n"                      // Fill with ones before jumping to reset handling. Will be popped as LR when exiting ISR. Ensures no return to application.
            "mov   r6, r0\n"                      // Move address of reset handler to R6. Will be popped as PC when exiting ISR. Ensures the reset handler will be executed when exist ISR.
            "movs  r7, #0x21\n"                   // Move MSB reset value of xPSR to R7. Will be popped as xPSR when exiting ISR. xPSR is 0x21000000 thus MSB is 0x21.
            "rev   r7, r7\n"                      // Reverse byte order to put 0x21 as MSB.
            "push  {r4-r7}\n"                     // Push everything to new stack to allow interrupt handler to fetch it on exiting the ISR.
    
            "movs  r4, #0x00\n"                   // Fill with zeros before jumping to reset handling. We be popped as R0 when exiting ISR (Cleaning up of the registers).
            "movs  r5, #0x00\n"                   // Fill with zeros before jumping to reset handling. We be popped as R1 when exiting ISR (Cleaning up of the registers).
            "movs  r6, #0x00\n"                   // Fill with zeros before jumping to reset handling. We be popped as R2 when exiting ISR (Cleaning up of the registers).
            "movs  r7, #0x00\n"                   // Fill with zeros before jumping to reset handling. We be popped as R3 when exiting ISR (Cleaning up of the registers).
            "push  {r4-r7}\n"                     // Push zeros (R4-R7) to stack to prepare for exiting the interrupt routine.
    
            "movs  r0, #0x06\n"                   // Load 0x06 into R6 to prepare for exec return command.
            "mvns  r0, r0\n"                      // Invert 0x06 to obtain EXEC_RETURN, 0xFFFFFFF9.
            "bx    r0\n"                          // No return - Handler mode will be exited. Stack will be popped and execution will continue in reset handler initializing other application.
            :: "r" (start_addr)                   // Argument list for the IAR assembly. start_addr is %0.
            :  "r0", "r4", "r5", "r6", "r7");     // List of register maintained manually.
    }
    
  • Ok, I did a little more reading on the subject and the above is correct for SDK11 and prior. The bootloader_util.h is a library that you include and the assembly code twiddles with some vectors in the NVIC.

    When they switched to SDK12 and later and buttonless DFU the method changed slightly. The still is some twiddling of the NVIC manually but now you approach this as including #include "nrf_bootloader_app_start.h"

    and using nrf_bootloader_app_start(MAIN_APPLICATION_START_ADDR);

    That function takes care of the SD app start and does all the manual stuff too.

    There could be other dependencies. Just look at the #includes for the secure_dfu_ble_s132

    So, all you really need to do is give that function your actual app start address and I think that is it.

  • Also the problem came about due to the order of your memory layout. Normally Nordic puts the DFU/bootloader at the end of flash space leaving everything in between open for application code. The stock dfu code just looks at the size of the SD and assumes that the app code is next. I assume you based your bootloader on the stock stuff so when it does this calculation it just points back at itself.

    See this:

    #if defined(SOFTDEVICE_PRESENT)
    
    /** @brief  Main application start address (if the project uses a SoftDevice).
     *
     * @note   The start address is equal to the end address of the SoftDevice.
     */
    #define MAIN_APPLICATION_START_ADDR             (SD_SIZE_GET(MBR_SIZE))
    
    #else
    #define MAIN_APPLICATION_START_ADDR             MBR_SIZE
    #endif // #ifdef SOFTDEVICE_PRESENT
    #endif // #ifndef MAIN_APPLICATION_START_ADDR
    
Related