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

Recover from a sd_softdevice_disable()

When writing to flash it is MUCH easier to perform the write if SoftDevice is disabled. If it is not, one has to wait for an event to continue writing. ON the Nordic platforms, that is very complicated as one already has code for fragmented notifications handling asynchronous waits. The only 'wait' one can perform that is not a CPU busy loop is through sd_app_evt_wait().

Thus I simply disable the softdevice and the write is synchronous; it returns when done. However, now I need to recover so I can start advertising again.

My work flow is:

  • Advertise
  • Get connected
  • Get disconnected
  • disable soft device
  • write to flash
  • ? (what do I need to do here)
  • Advertise

My cop-out approach has been after the write to flash to simply call NVIC_SystemReset() and have the whole application start from scratch. That approach has worked fine on the DKs but as soon as I invoke that NVIC_SystemReset() on an nRF52840 dongle, it no longer works. The app simply dies and one needs to power cycle the dongle.

Since NVIC_SystemReset() does not work, how do I recover from an sd_softdevice_disable? What does that disable? The documentation suggests EVERYTHING. Have I lost my service tables? Do I have to re-initialize the BLE stack? How many of the startup 'init' methods do I need to call?

Parents
  • Hi

    When writing to flash it is MUCH easier to perform the write if SoftDevice is disabled. If it is not, one has to wait for an event to continue writing. ON the Nordic platforms, that is very complicated as one already has code for fragmented notifications handling asynchronous waits.

    Both the SoftDevice and the SDK is event based, and to handle this you could for instance set up some kind of state machine to handle larger operations that require multiple 'call some function -> wait for an event' type operations.

    Then this can run in parallel with whatever else the application is doing, and once the state machine gets to the end of its operation it can signal this to the rest of the system. 

    The only 'wait' one can perform that is not a CPU busy loop is through sd_app_evt_wait().

    If you mean a blocking wait then I guess this is true. Unless some RTOS is used on top of the SoftDevice there is no concept of multi-threading, and blocking wait calls are not recommended. 

    That approach has worked fine on the DKs but as soon as I invoke that NVIC_SystemReset() on an nRF52840 dongle, it no longer works.

    I assume the factory USB bootloader is present on the dongle?

    I have to check this with the DFU experts, but it is possible that the bootloader is causing some issue here. 

    Since NVIC_SystemReset() does not work, how do I recover from an sd_softdevice_disable? What does that disable? The documentation suggests EVERYTHING. Have I lost my service tables? Do I have to re-initialize the BLE stack? How many of the startup 'init' methods do I need to call?

    Disabling the SoftDevice disables everything, yes. All peripherals are released back to the application, and all the memory previously used by the SoftDevice is also available (except for the 8 first bytes of memory in the SoftDevice region). 

    In other words you have to call all the same functions, starting with the sd_softdevice_enable(..) call, and all the subsequent GAP/GATT initialization functions. 

    In other words I would strongly suggest trying to handle the flash writes through the SoftDevice instead. As long as the SoftDevice is not doing anything else you should be able to finish the writes almost as quick, unless you issue a lot of small writes. 

    Best regards
    Torbjørn

  • I have come a lot further on this. The reason that the NVIC_SystemReset() was not working had nothing to do with that method. The problem is the flash write itself. On the dongle, the flash write kills the application and the dongle dies. It needs a power cycle to restart. If I remove the flash write, everything works the same on the Dongle as the DK.

    The flash write is here (note that it works fine on the DK and I have tested it by writing data to the flash and reading it back right after. Well, at least on the DK it works fine - the code is below.

    void saveKeysToFlash(ble_gap_sec_keyset_t* keys,
        unsigned char **saveDataBuffer, unsigned short int *saveDataLength,
        bool* cccdSet, unsigned short* noOfCccds)
    {
        int i;
    //        char buffer[1024];
        unsigned char *keysDataBuffer = NULL;
        ret_code_t err_code;
    
        uint32_t pg_size = NRF_FICR->CODEPAGESIZE;
        uint32_t pg_num  = NRF_FICR->CODESIZE - 1;  // Use last page in flash
        uint32_t *addr;
        NRF_LOG_DEBUG("Saving data to flash.");
    
        int size = sizeof(nameKey) +
                   sizeof(ble_gap_enc_key_t) +      // should be a fixed length even if no pairing was done
                   sizeof(ble_gap_id_key_t) +       // should be a fixed length even if no pairing was done so no IRK
                   sizeof(ble_gap_enc_key_t) +
                   sizeof(ble_gap_id_key_t) +
                   sizeof(unsigned short) +         // Number of CCCDs
                   sizeof(bool) * (*noOfCccds) +    // CCCD set
                   sizeof(unsigned short) +         // Savedata length
                   ((*saveDataBuffer != NULL) ? *saveDataLength * sizeof(unsigned char) : 0);
        // The writes are done in 4-byte hunks so we have to even out the length
        size = 4 + ((size >> 2) << 2);  // Here we round up to the nearest 4-byte size so it is evenly divisible by 4
        NRF_LOG_DEBUG("Size of data to write %u.", size);
        keysDataBuffer = calloc(1, size);
        
        // Now load all the data we want to save into this buffer
        uint8_t *ptr = keysDataBuffer;
        memcpy(ptr, nameKey, sizeof(nameKey));          // Load identifier
        ptr = ptr + sizeof(nameKey);
        memcpy(ptr, keys->keys_own.p_enc_key, sizeof(ble_gap_enc_key_t)); // load our LTK
        ptr = ptr + sizeof(ble_gap_enc_key_t);
        memcpy(ptr, keys->keys_own.p_id_key, sizeof(ble_gap_id_key_t));   // load our LTK id
        ptr = ptr + sizeof(ble_gap_id_key_t);
        memcpy(ptr, keys->keys_peer.p_enc_key, sizeof(ble_gap_enc_key_t)); // load peer LTK
        ptr = ptr + sizeof(ble_gap_enc_key_t);
        memcpy(ptr, keys->keys_peer.p_id_key, sizeof(ble_gap_id_key_t));   // load peer LTK id
        ptr = ptr + sizeof(ble_gap_id_key_t);
        memcpy(ptr, noOfCccds, sizeof(unsigned short));             // Load number of CCCDs
        ptr = ptr + sizeof(unsigned short);
        memcpy(ptr, cccdSet, sizeof(unsigned char) * (*noOfCccds)); // Load the cccdSet[]
        ptr = ptr + sizeof(unsigned char) * (*noOfCccds);
        memcpy(ptr, saveDataLength, sizeof(unsigned short));        // Load saveDataLength
        ptr = ptr + sizeof(unsigned short);
        if (*saveDataBuffer != NULL)
        {
            memcpy(ptr, *saveDataBuffer, *saveDataLength * sizeof(unsigned char));   // Load the saveDataBuffer
            ptr = ptr + *saveDataLength * sizeof(unsigned char);
        }
    
    //    memset(buffer, 0 , 1024);
    //    NRF_LOG_DEBUG("Full set of data being written %s", (uint32_t)byteToHex(keysDataBuffer, buffer, " ", size));
    //    NRF_LOG_FLUSH();
    
        // Now we have to write the data in hunks into flash
        // Each page is 1024 bytes, and a write is in 4-byte hunks
        // So we will find the number of 1024 byte pages to write,
        // and then the number of 4-byte hunks left over.
    
        // Disable soft device so we don't have to deal with events which would make this write PAINFULLY complicated to implement
        //sd_softdevice_disable();
        nrf_sdh_disable_request();
        // Buffer to write to flash. Need to write it in four-byte hunks
        uint32_t *ptr32 = (uint32_t *)keysDataBuffer;
        while (true)
        {
            // Where to write
            addr = (uint32_t *)(pg_size * pg_num);
            // Erase page:
            while(true)
            {
                err_code = sd_flash_page_erase(pg_num);
                if (err_code == NRF_SUCCESS)
                {
                    break;
                }
                if (err_code != NRF_ERROR_BUSY)
                {
                    NRF_LOG_DEBUG("Erasing data returned error %u.", err_code);
                    APP_ERROR_CHECK(err_code);      // Program dies
                }
            }
    
            i = (size >= pg_size) ? (pg_size >> 2) : // Do as many write that will fill the page, eg if pg size = 1024, then 256 writes
                                    (size >> 2);     // All fits in one page, so divide size by four
            NRF_LOG_DEBUG("Number of 4-byte hunks to write %u.", i);
            while(true)
            {
                err_code = sd_flash_write(addr,     // Pointer to start of flash location to be written.
                                          ptr32,    // Pointer to buffer with data to be written.
                                          i);       // Number of 32-bit words to write. Maximum size is the number of words in one
                                                    // flash page. See the device's Product Specification for details (pg size I assume).
                if (err_code == NRF_SUCCESS)
                {
                    break;
                }
                if (err_code != NRF_ERROR_BUSY)
                {
                    NRF_LOG_DEBUG("Writing data returned error %u.", err_code);
                    APP_ERROR_CHECK(err_code);      // Program dies
                }
            }
            size = size - pg_size;  // Subtract a page size from the total size
            if (size <= 0)          // if zero or less, all data has been written
            {
                NRF_LOG_DEBUG("Flash written");
                break;
            }
            pg_num++;   // TODO we are on last page, so have to hope everything fits in one page. Still have not figured out how to get
                        // to the start of the first free page available.
            ptr32 = (uint32_t *)(keysDataBuffer + pg_size);
        }
        free(keysDataBuffer);
    }

    The flash write is to save pairing/bonding info. I happen to have a test app  (RGT Cycle) that connects and if it receives no measurements it disconnects only to reconnect if I advertise. One the DK, I go through endless cycles of this.

    On the dongle, the app dies in the flash write (which happens on the disconnect).

    I have gone to the point of returning in the above code just before the while loop that does the writes. Works okay on both platforms if I return before attempting any flash operations. 

    If I try just the first erase operation, the app dies on the dongle. So its pretty clear its the first attempt to do the erase that kills the app. That is as far as I have come.

    In the end if I have to do a flash write, I will call the NVIC_SystemReset(), and if I don't, I will skip the reset and just start advertising. Works great on the DK but if the flash write is needed the dongle dies.

    That is where I am at now. A lot of background is here: 

    devzone.nordicsemi.com/.../sd_softdevice_is_enabled-returns-false-but-then-nrf_sdh_enable_request-returns-error-8-softdevice-already-enabled

    Wondering if I should simply open up a new issue about the flash writes on the dongle vs the DK

  • Hi

    As you say there is quite a lot that happens during a reset, and doing this unnecessarily will increase the runtime and power consumption of your application. For instance various startup code is run, a lot of RAM variables will be initialized, and if you are using some kind of bootloader you will also run through that. 

    Personally I think that the way many of our examples are set up, with hundreds of lines of BLE related code in main.c, is not the best way to set up a project. By abstracting this into its own c file you make the main file much more clean, and if you ever need to change anything BLE related in your project you immediately know where to go. 

    Some of our more complex software deliveries, such as the mouse/keyboard reference examples, are set up this way. Below I added the main function for the desktop mouse as an example:

    /**@brief Application main function.
     */
    int main(void)
    {   
        APP_SCHED_INIT(SCHED_MAX_EVENT_DATA_SIZE, SCHED_QUEUE_SIZE);
        APP_TIMER_INIT(APP_TIMER_PRESCALER, APP_TIMER_MAX_TIMERS, APP_TIMER_OP_QUEUE_SIZE, false);
        modules_init();
        gzll_keep_alive_init();
        buffer_init();
        misc_io_init();
        
        // Enter main loop
        for (;;)
        {   
            app_sched_execute();
            m_pwr_mgmt_run();
        }
    }
    

    Best regards
    Torbjørn

  • IN my case most of the main is dedicated to initializing the Bluetooth, and its not that long because I have one service with two characteristics, so a reset didn't look like much was happening. I could easily make main a 10 line program or something of that size by calling a setup Bluetooth method.

  • Hi 

    If you want to check what is happening after reset before the main function is invoked you can have a look at the ses_startup files, and the system_nrf52840.c file. 

    Best regards
    Torbjørn

  • THanks - but in the in I took your advice. I crammed all those BTLE init methods and service table setup methods and stuffed them in a single function and call that function instead of the system reset. It works and I skip doing some free()s and calloc()s!

    Now I have a very short main().

  • Hi 

    Good to hear you got things working that way Slight smile

    Best regards
    Torbjørn

Reply Children
No Data
Related