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

Alternate connectable (ADV_IND) and non-connectable packets (ADV_NONCONN_IND)

To save energy, we want to sent connectable packets infrequently, say once every 10 secs. We also want to send some status info more frequently (once per sec) using non-connectable packets.

How to change an Advertisement from connectable to non-connectable and back, in the firmware code?

  • The Power Profiling Application implements both connectable and unconnectable advertising, and you could probably do it the same way. (This does not use the Advertising Module, as this only handles connectable advertising.)

  • A few words of note:

    Sending an advertising packet (connectable) once every 10 seconds means your scanning packet on the other end has to either check frequently enough not to miss it, or has to be held open enough to catch it (either can hurt you, power wise).

    Since the entire point of connectable advertising is to connect, it makes sense to do it quickly. Keep that in mind if you run into issues with your connectable advertising set to 10 seconds.

    To get around this, we spend the vast majority of our time advertising in non-connectable mode (at the standard minimum of 100ms, though we could probably get away with more), and switch to connectable advertising mode when needed (in our case; when we get a specific set of advertising data from another device and our device is in a certain mode).

    We do this in our application firmware. Since SDK 11 was the newest SDK at the time I wrote this code, this code compiles against SDK 11. Your mileage may vary otherwise.

    There are a few things to be aware of (that aren't altogether clear if you just go by the stock advertising example):

    1. Your advertising needs to work around any flash operations (in other words, you cannot access the softdevice's radio while a flash operation is taking place).
    2. Out of the box, Nordic's BLE Advertising library does not support non-connectable or scan-requests-only advertising.
    3. Nordic says you can use their advertising library and your own non-advertising library calls at the same time; but I ran into multiple issues (hardfaults) when trying to set advertising data. I gave up and instead took the library out of the equation. I'm sure it's my fault; but once I was deep into their SDK code it seemed a better idea to simply take that out of the equation than to open a support ticket -> create a minimal reproducible example -> wait ->etc. (Nothing against Nordic, I love Nordic).

    So here's how you do it.

    I created my own advertising.c file:

    ###advertising.h

    #ifndef ADVERTISING_H_
    #define ADVERTISING_H_
    #include <stdbool.h>
    
    typedef enum {
      ADV_MODE_OFF,
      ADV_MODE_CONNECTABLE,
      ADV_MODE_NONCONNECTABLE,
    } advertising_mode_t;
    
    advertising_mode_t get_advertising_mode(void);
    void init_advertising_module(void);
    void advertising_on_sys_evt(uint32_t sys_evt);
    void start_advertising(advertising_mode_t mode); 
    void stop_advertising(void);
    
    bool is_advertising(void);
    #endif
    

    ###advertising.c

    #include "advertising.h"
    #include "app_error.h"
    #include "ble.h"
    #include "ble_advdata.h"
    #include "ble_srv_common.h"
    #include "nordic_common.h"
    #include "nrf.h"
    #include "nrf_delay.h"
    #include "nrf_soc.h"
    #include "nrf_log.h"
    #include "sdk_errors.h"
    #include <stdint.h>
    
    
    #define MANUFACTURER_NAME "Acme"
    
    #define APP_COMPANY_IDENTIFIER 0xFFFF
    
    #define APP_ADV_INTERVAL 300
    #define APP_CFG_NON_CONN_ADV_TIMEOUT  0
    
    static bool m_advertising_start_pending = false;
    static bool m_advertising = false;
    static advertising_mode_t m_current_advertising_mode = ADV_MODE_OFF;
    
    static ble_gap_adv_params_t m_adv_params;
    static ble_advdata_t m_adv_data;
    
    static uint8_t custom_data_data[2];
    static ble_advdata_manuf_data_t manufacturer_specific_data;
    static uint8_t m_manuf_data_array[BLE_GAP_ADV_MAX_SIZE];
    
    static void get_manufact_specific_data(ble_advdata_manuf_data_t *manufacturer_specific_data) {
      custom_data_data[0] = 0xBB; 
      custom_data_data[1] = 0xAC;
       
      manufacturer_specific_data->company_identifier = APP_COMPANY_IDENTIFIER;
      manufacturer_specific_data->data.p_data = custom_data_data;
      manufacturer_specific_data->data.size = sizeof(custom_data_data);
    }
    
    void init_advertising_module() {
      memset(&manufacturer_specific_data, 0, sizeof(manufacturer_specific_data));
      memset(m_manuf_data_array, 0, sizeof(uint8_t[BLE_GAP_ADV_MAX_SIZE]));
      memset(custom_data_data, 0, sizeof(uint8_t[2]));
      memset(&m_adv_params, 0, sizeof(m_adv_params));
      memset(&m_adv_data, 0, sizeof(m_adv_data));
    
      m_adv_data.name_type = BLE_ADVDATA_FULL_NAME;
      m_adv_data.short_name_len = 6;
      m_adv_data.include_appearance = false;
      m_adv_data.include_ble_device_addr = true;
      m_adv_data.le_role = BLE_ADVDATA_ROLE_NOT_PRESENT;
      m_adv_data.p_tk_value = NULL;
      m_adv_data.p_sec_mgr_oob_flags = NULL;
      m_adv_data.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
      m_adv_data.uuids_more_available.uuid_cnt = 0;
      m_adv_data.uuids_complete.uuid_cnt = 0;      
      m_adv_data.uuids_solicited.uuid_cnt = 0;
      m_adv_data.p_slave_conn_int = NULL;
      m_adv_data.service_data_count = 0;
    
      get_manufact_specific_data(false, &manufacturer_specific_data);
      m_adv_data.p_manuf_specific_data = &manufacturer_specific_data;
      m_adv_data.p_manuf_specific_data->company_identifier =
          manufacturer_specific_data.company_identifier;
      m_adv_data.p_manuf_specific_data->data.size =
          manufacturer_specific_data.data.size;
    
      m_adv_params.fp = BLE_GAP_ADV_FP_ANY;
      m_adv_params.p_peer_addr = NULL;
      m_adv_params.p_whitelist = NULL;
      m_adv_params.timeout = BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED;
    }
    static void advertising_init() {
    
      m_adv_data.le_role = BLE_ADVDATA_ROLE_BOTH_CENTRAL_PREFERRED;
    
      get_manufact_specific_data(&manufacturer_specific_data);
      m_adv_data.p_manuf_specific_data = &manufacturer_specific_data;
      m_adv_data.p_manuf_specific_data->company_identifier =
          manufacturer_specific_data.company_identifier;
      m_adv_data.p_manuf_specific_data->data.size =
          manufacturer_specific_data.data.size;
    
      m_adv_params.type = BLE_GAP_ADV_TYPE_ADV_IND;
      m_adv_params.interval = BLE_GAP_ADV_INTERVAL_MIN; 
    }
    
    static void non_connectable_advertising_init() {
    
      m_adv_data.le_role = BLE_ADVDATA_ROLE_NOT_PRESENT;
    
      get_manufact_specific_data(false, &manufacturer_specific_data);
      m_adv_data.p_manuf_specific_data = &manufacturer_specific_data;
      m_adv_data.p_manuf_specific_data->company_identifier =
          manufacturer_specific_data.company_identifier;
      m_adv_data.p_manuf_specific_data->data.size =
          manufacturer_specific_data.data.size;
    
      m_adv_params.type = BLE_GAP_ADV_TYPE_ADV_NONCONN_IND; // or BLE_GAP_ADV_TYPE_ADV_SCAN_IND
      m_adv_params.interval = BLE_GAP_ADV_NONCON_INTERVAL_MIN; // >100 ms
    }
    
    static void advertising_stop() {
      if (m_advertising) {
        uint32_t err_code;
        err_code = sd_ble_gap_adv_stop();
        if (err_code == NRF_SUCCESS) {
          m_advertising = false;
          m_current_advertising_mode = ADV_MODE_OFF;
          NRF_LOG_PRINTF("M ADV STOPPED: %u\r\n", m_current_advertising_mode);
        }
      }
    }
    advertising_mode_t get_advertising_mode() { return m_current_advertising_mode; }
    
    static void non_connectable_advertising_start() {
      if (flash_access_in_progress()) {
        m_advertising_start_pending = true;
        NRF_LOG_PRINTF("NONCONN_ADV_START: FP %u \r\n",
                       m_advertising_start_pending);
        
        return;
      }
    
      advertising_stop();
    
      uint32_t err_code;
      non_connectable_advertising_init();
      err_code = ble_advdata_set(&m_adv_data, NULL);
      APP_ERROR_CHECK(err_code);
      err_code = sd_ble_gap_adv_start(&m_adv_params);
      if (err_code == NRF_SUCCESS  || err_code == NRF_ERROR_INVALID_STATE) {
        m_advertising = true;
      }
      if (err_code == NRF_SUCCESS) {
        m_current_advertising_mode = ADV_MODE_NONCONNECTABLE;
        m_advertising_start_pending = false;
        NRF_LOG_PRINTF("M ADV STARTED: %u\r\n", m_current_advertising_mode);
      } else {
        NRF_LOG_PRINTF("M ADV NONCONNECTABLE ERROR: %u\r\n", err_code);
      }
      APP_ERROR_CHECK(err_code);
    }
    
    static void advertising_start(bool friend_finding) {
      if (flash_access_in_progress()) {
        m_advertising_start_pending = true;
        NRF_LOG("CON_ADV_START: FP \r\n");
        return;
      }
      advertising_stop();
      advertising_init(friend_finding);
      uint32_t err_code;
      err_code = ble_advdata_set(&m_adv_data, NULL);
      APP_ERROR_CHECK(err_code);
      err_code = sd_ble_gap_adv_start(&m_adv_params);
      if (err_code == NRF_SUCCESS  || err_code == NRF_ERROR_INVALID_STATE) {
        m_advertising = true;
      }
      if (err_code == NRF_SUCCESS) {  
        m_current_advertising_mode = ADV_MODE_CONNECTABLE;
        m_advertising_start_pending = false;
        NRF_LOG_PRINTF("M ADV STARTED: %u\r\n", m_current_advertising_mode);
      } else {
        NRF_LOG_PRINTF("M ADV CONNECTABLE ERROR: %u\r\n", err_code);
      }
      APP_ERROR_CHECK(err_code);
    
    }
    
    void stop_advertising() {
      uint32_t err_code = sd_ble_gap_adv_stop();
      if (err_code == NRF_SUCCESS || NRF_ERROR_INVALID_STATE == err_code) {
        m_advertising = false;
        m_current_advertising_mode = ADV_MODE_OFF;
      }
      APP_ERROR_CHECK(err_code); // todo: remove app eror check here
    
    }
    void start_advertising(advertising_mode_t mode) {
      if (mode == ADV_MODE_CONNECTABLE) {
        advertising_start(friend_adding_mode());
      } else if (mode == ADV_MODE_NONCONNECTABLE) {
        non_connectable_advertising_start();
      }
    }
    
    void advertising_on_sys_evt(uint32_t sys_evt) {
      switch (sys_evt) {
      case NRF_EVT_FLASH_OPERATION_SUCCESS:
      case NRF_EVT_FLASH_OPERATION_ERROR:
        if (m_advertising_start_pending) {
          start_advertising(m_current_advertising_mode);
          NRF_LOG_PRINTF("FLASH OP ADV PENDING: %u\r\n", m_advertising_start_pending);
        }
        
        break;
    
      default:
        if (m_advertising_start_pending) {
          start_advertising(m_current_advertising_mode);
        }
        break;
      }
    }
    
    bool is_advertising() {
      return m_advertising;
    }
    

    ###main.c #include "advertising.h"

    int main(void) {
      ret_code_t err_code = 0;
      nrf_gpio_cfg_output(LED_RST);
      nrf_gpio_pin_clear(LED_RST);
      nrf_delay_us(10000);
      nrf_gpio_pin_set(LED_RST);
      nrf_delay_us(20000);
      err_code = NRF_LOG_INIT();
      APP_ERROR_CHECK(err_code);
      scheduler_init();
      timers_init();
      twi_init(); // 1
      leds_init(); // nrf devboard leds
      buttons_init();
    
      ble_stack_init();
      db_discovery_init();
      gap_params_init();
      conn_params_init();
    
      init_advertising_module();
      start_scanning();
      start_advertising(ADV_MODE_NONCONNECTABLE);
      flash_storage_init();
      nrf_delay_ms(1000); // wait because flash data storage is doing stuff.
                          // //TODO: make this a One Shot timer that when it expires
                          // does flash loading of data
      state_machine_init();
      for (;;) {
        state_machine_dispatch();
        app_sched_execute();
    #ifndef RTT
        ret_code_t err_code = sd_app_evt_wait();
        APP_ERROR_CHECK(err_code);
    #endif
      }
    }
    

    When I want to switch advertising modes, I simply do this:

    start_advertising(ADV_MODE_NONCONNECTABLE);
    

    or

    start_advertising(ADV_MODE_CONNECTABLE);
    

    Since each advertising mode takes care of stopping, seeing if Flash is doing anything, and starting again, I don't have to explicitly stop it each time.

    if I want to stop advertising altogether:

    stop_advertising();
    

    A few other sundries:

    in sys_evt_dispatch(), make sure to add:

    advertising_on_sys_evt(uint32_t sys_evt)
    

    Here's what my sys_evt_dispatch() looks like (it handles system events):

    static void sys_evt_dispatch(uint32_t sys_evt) {
      fs_sys_event_handler(sys_evt);
      advertising_on_sys_evt(sys_evt);
      scan_on_sys_evt(sys_evt);
      power_management_on_sys_evt(sys_evt);
    }
    

    I let the flash storage events go first so they're 'handled' before the advertising module does its thing.

    In the advertising_init and non_connectable_advertising_init, you can use that to set how the advertising data will change between connectable mode and non connectable mode; I include pieces of how we use it (we change advertising data depending on the mode) so you can see.

  • Thank you for including an example of this in SDK 12! I created my own version since this example wasn't present in SDK 11, so I'll have to check mine against yours to see where I could do better.

  • Hello, I read your comments that 'Nordic's BLE Advertising library does not support non-connectable or scan-requests-only advertising', I just want to realise the function that one peripheral advertises information, all the other mobiles nearby with BLE receive the information, I think the non-connectable mode is suitable for this, could you offer some guidances? Thank you very much!

Related