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

nRF52810 app optimization ble_app_uart

Hello ...

Our BLE peripheral product (nRF52810, SDK 15.3.0, SD 112 6.1.1) is relatively simple. iOS device (central) connects to peripheral. Data is exchanged between the central and peripheral. Peripheral watches a sensor and responds to events (by sending data to the central). The size of data sent between central and peripheral does not exceed 16 bytes (future expansion might increase this by a few bytes). Encryption unnecessary. Peripheral device is powered by 2 AA batteries with adequate battery life. Power optimization is not needed.

I'm new to Nordic SDK and started with the ble_app_uart example. I would like to learn about important considerations in moving towards a final app. Here's, generally, how I've migrated ble_app_uart:

1) Removed all use and reference of/to UART as it's not needed. Simply sending data between central and peripheral.

2) Removed all use and reference of/to Board Support Package, and use nrf_gpio directly for IO.

3) Removed Board.c.

4) Replaced NUS UUIDs with our UUIDs.

App functionality is complete and is working well. I'm able to flash to our custom nRF52810 board using the debug out port of the nRF52 dev kit. All appears good.

Given basic requirements, is there more that can be trimmed to reduce app size and complexity?

Is the Queued Write module necessary?

Low and relatively consistent latency of delivery of messages between central and peripheral is important. Is there anything I can do to reduce message delivery latency? I understand there are many factors out of my control, such as the BLE stack on the iOS device and its sharing of RF resources, etc.

I'm using the Logger module (NRF_LOG_INIT, etc.) for debugging. To build and flash release product, how do I essentially turn off logging? Should I be using #define DEBUG and then surround all NRF_LOG statements with #ifdef DEBUG ... #endif, then remove #define DEBUG for building release app?

Anything else for building/flashing a release app?

Enjoying the learning ... thanks for any guidance.

Tim

Parents
  • Hello,

    Step 1-3 sound like reasonable choices to optimize size and reduce complexity. There are a few more files you can remove if you don't need logging (nrf_printf, nrf_atomic.c, nrf_ringbuf.c, etc), but I would consider to keep them in so you can more easily re-enable logging if you ever need that.  There is not much to gain with regards to flash usage, maybe a couple of KB. Of course, that may be significant if you're starting to run out of memory.

    Is the Queued Write module necessary?

     It would probably be ok to remove the Queued Writes module as the ios device should not have to request any queued writes. However, the module is only around 200 bytes with the default setting ('NRF_BLE_QWR_MAX_ATTR' == 0 - reject queued write request), and it will ensure that request is handled if it gets received. 

    Low and relatively consistent latency of delivery of messages between central and peripheral is important. Is there anything I can do to reduce message delivery latency? I understand there are many factors out of my control, such as the BLE stack on the iOS device and its sharing of RF resources, etc.

    iOS usually honors the preferred connection interval range requested by the peripheral if it follows the Apple recommendations (link - see section 25.6). The lowest interval you typically can get is 15 ms, and you should get that if you request Interval Min == Interval Max == 15 ms.

    I'm using the Logger module (NRF_LOG_INIT, etc.) for debugging. To build and flash release product, how do I essentially turn off logging? Should I be using #define DEBUG and then surround all NRF_LOG statements with #ifdef DEBUG ... #endif, then remove #define DEBUG for building release app?

    It should be sufficient to disable the NRF_LOG_ENABLED config option in your sdk_config.h header and recompile.  This will exclude the nrf_log_*  source files from the build and remove processing of the NRF_LOG_* macros.

    Anything else for building/flashing a release app?

    I think the main point is to disable logging. Also make sure the app is compiled without DEBUG flag if you want the app to reset if an error occurs: Error module (note: does not include hard faults)

    Best regards,

    Vidar

  • Thank you Vidar. All makes sense.

    About disabling logging, in sdk_config.h I:

    #define NRF_LOG_ENABLED 0

    As expected, app does not log anything, but also does not behave properly. Normally the app reads some data from EEPROM via TWIM (sets some globals), then sets up BLE (ble_stack_init(), gap_params_init(), gatt_init(), services_init(), advertising_init(), conn_params_init()) and starts advertising (advertising_start()). It does not advertise. I've done a bit of tracing but can't make sense of it.

    Also, how do I "compile without DEBUG flag"?

    Many thanks,

    Tim

  • And still a bit more info. About the DEBUG flag, the ble_app_uart example app (on which my app is based) defines only a single configuration:

      <configuration Name="Release"
        c_preprocessor_definitions="NDEBUG"
        gcc_optimization_level="Optimize For Size" />

    Therefore DEBUG flag was not defined. I added the following configuration to the SES .emProject file (saw it in another thread):

      <configuration
        Name="Debug"
        c_preprocessor_definitions="DEBUG; DEBUG_NRF"
        gcc_optimization_level="None"/>

    When NRF_LOG_ENABLED is 1, sd_app_evt_wait() never hangs regardless of Release or Debug configuration and app functions normally. nrf_pwr_mgmt_run() (which calls sd_app_evt_wait()) is called in the main() loop.

    When NRF_LOG_ENABLED is 0, sd_app_evt_wait() hangs (third trip around main() loop when nrf_pwr_mgmt_run() called) regardless of Debug or Release configuration. If nrf_pwr_mgmt_run()/sd_app_evt_wait() is not called in main() loop, app runs properly when built for Debug configuration, but not Release. So I looked at the Debug and Release build configurations. When I change Release configuration to:

      <configuration
        Name="Release"
        c_preprocessor_definitions="NDEBUG"
        gcc_optimization_level="None" />

    app now runs fine (but still hangs in sd_app_evt_wait() if called from nrf_pwr_mgmt_run()). (I do a Build/Clean Solution before building whenever making changes to a build configuration.) So there seems to be something related to 1) NRF_LOG_ENABLED set to 0, and 2) gcc_optimization_level set to "Optimize For Size" in the build configuration.

    All strange to me. :-)

    Thx,

    Tim

  • Hi Tim,

    Tim said:
    I feel I need to learn more about power modes and clock source and frequency. Our device is powered by 2 AA batteries. Not concerned about power consumption. It's more important that the app does not go into low power mode so it is most responsive when sensor events occur.

    The low power mode may add a few extra microseconds to the IRQ latency but should be negligible for most applications. The current consumption will become close to the CPU run current if you don't go to sleep at all: CPU running.

    Tim said:
    What do I need to do to prevent entering low power mode? And how to I ensure greatest accuracy of TIMER1/2 and RTC1? Does app need to start the high frequency clock following BLE initialization?

     Calling sd_clock_hfclk_request() will ensure that the more accurate crystal oscillator (HFXO) is kept running. The system runs of HFINT by default. The LF clock source is selected when you enable the Softdevice. The crystal should be selected when available.  

    Tim said:
    A bit more info, Vidar. With NRF_LOG_ENABLED set to 0, and call to nrf_pwr_mgmt_run() in idle_state_handle() uncommented, when I trace and step into nrf_pwr_mgmt_run(), the call to sd_app_evt_wait() on line 351 of nrf_pwr_mgmt.c hangs (does not return). This happens on the third trip through the app's main() loop, which is after:

    It should return when an interrupt has been triggered. When you're debugging, try to do a " reset and run", then see if the device is advertising even if logging is turned off. Keep in mind that the Softdevice will assert if you resume execution after hitting a breakpoint or have paused the app. It's because it breaks the real-time requirements of the stack. 

  • Thanks Vidar for your patience with me. With what you say, entering low power mode should be acceptable for my app. I've added code to start hfclk after call to ble_stack_init().

    I remain puzzled about why app is hanging when I turn logging off. I've created a very simple app that is ble_app_uart stripped down (no uart), and with TWIM added for reading data from EEPROM. We're using the Microchip 24AA128 EEPROM. I've attached a zip file of the project (nothing proprietary) so you can see both main.c and sdk_config.h. I'm using SDK nRF5_SDK_15.3.0_59ac345. Project folder is inside examples/ble_peripheral along with other peripheral examples. Developing on macOS 10.13.6.

    I've made it easy to comment in or out the code that uses TWIM to read EEPROM. Here's the behaviour I see:

    With NRF_LOG_ENABLED set to 1, app using TWIM (or not) works properly. It starts advertising and I can connect/disconnect using LightBlue (iOS).

    With NRF_LOG_ENABLED set to 0, app hangs if using TWIM. App works properly if TWIM is not used.

    To me this suggests that something about my TWIM code is wrong and causes the hanging issue. Odd that the issue occurs only when logging is off.

    Would be grateful if you could take a look at my code.

    Many thanks,

    Tim

    ble_app_uart_learn3.zip

  • Thanks for sharing your code. I think everything is starting to make a bit more sense now.  The idle_state_handle() will not enter sleep (i.e., call nrf_power_mgmt_run()) until all log messages has been processed by the NRF_LOG_PROCESS macro. That means your program will get to run through the scheduler loop a few times more before going to sleep when you have logging enabled compared to when it's disabled. And I think this is what prevents it from getting in this "sleep forever" state.

    I would consider moving the initialization parts out of the main loop to avoid potential race conditions like this. Maybe something like this (note: is untested):

        do
        {
    
            if (onLoopQueuedTask != onLoopNoOp)
            {
                if (onLoopQueuedTask == onLoopReadEEPROMData)
                {
                    onLoopQueuedTask = onLoopNoOp;
                    read_EEPROM_data();
                }
                else if (onLoopQueuedTask == onLoopExamineEEPROMData)
                {
                    onLoopQueuedTask = onLoopNoOp;
                    examine_EEPROM_data();
                }
            }
        } while (onLoopQueuedTask != onLoopCompleteInitialization);
        onLoopQueuedTask = onLoopNoOp;
        complete_initialization();
    
    
        // Enter main loop.
        for (;;)
        {
            idle_state_handle();
        }

    Hope this helps.

    Vidar 

  • Thanks Vidar. Solved. You deserve a free one-year supply of ice cream. Where shall I send it? :-)

    Once initialized, my app generally responds to 1) GPIOTE interrupt calls, and 2) messages received via BLE from the connected central. It generally uses the approach shown where not much happens in the main loop. In the case of #1, the GPIOTE_IRQHandler() routine will capture some sensor data to globals, then set onLoopQueuedTask to an appropriate value to have the main loop call a routine to do something. For #2, nus_data_handler() is invoked when the connected BLE central sends data. nus_data_handler() saves the data to globals then sets onLoopQueuedTask to an appropriate value so, again, the main loop calls a routine to do something.

    I don't fully understand how a race condition occurs (and what it is) and what caused in my initial code caused it. I'd like to understand and also be sure that the same thing won't happen given the approach I've used for the app to respond to nus_data_handler() and GPIOTE_IRQHandler().

    Phew, I think I'm very close to finally closing this thread. Again, thank you for your help, Vidar.

    Tim

    P.S. Haven't heard back from Håkon regarding my other open thread.

Reply
  • Thanks Vidar. Solved. You deserve a free one-year supply of ice cream. Where shall I send it? :-)

    Once initialized, my app generally responds to 1) GPIOTE interrupt calls, and 2) messages received via BLE from the connected central. It generally uses the approach shown where not much happens in the main loop. In the case of #1, the GPIOTE_IRQHandler() routine will capture some sensor data to globals, then set onLoopQueuedTask to an appropriate value to have the main loop call a routine to do something. For #2, nus_data_handler() is invoked when the connected BLE central sends data. nus_data_handler() saves the data to globals then sets onLoopQueuedTask to an appropriate value so, again, the main loop calls a routine to do something.

    I don't fully understand how a race condition occurs (and what it is) and what caused in my initial code caused it. I'd like to understand and also be sure that the same thing won't happen given the approach I've used for the app to respond to nus_data_handler() and GPIOTE_IRQHandler().

    Phew, I think I'm very close to finally closing this thread. Again, thank you for your help, Vidar.

    Tim

    P.S. Haven't heard back from Håkon regarding my other open thread.

Children
  • Excellent, glad to hear that it's working now:) I'm actually not sure if it's a race condition or if it's more a logical error (the latter seems more likely now when I think about it) that causes the program to enter sleep without having a wake-up source to ever wake it up again. If I recall correctly, it was only the TWIM peripheral that could wake the chip during the startup phase. So it would be possible to get into this sleep forever state if the program did not initiate a TWIM transaction before going to sleep. If you want to get to the bottom of this, I would suggest starting by checking if "read_EEPROM_data()" and "examine_EEPROM_data()" get executed. We know it didn't reach the final init function. In any case, this is not going to cause any problems when you have completed the initialization. The app will always wake up to process Sensor and BLE events whenever it receives them. 

    I can check with Håkon, but could you try to add the busy-wait after task stop as I suggested first? The app should ensure a minimum delay of 46 us before attempting to update the Prescaler value, see TASK and EVENT jitter/delay for more details about this requirement. Also, from the same chapter: "The PRESCALER register is read-only once the RTC is STARTed. Writing to the PRESCALER register when the RTC is started has no effect."

    Vidar

Related