While developing a set of C++ tools to simplify configuring and managing BLE on NRF52 chips, I've encountered an error code returned from the advertising module's ble_advertising_start function that is not listed as one of the possible return values for that function, 0x7 (NRF_ERROR_INVALID_PARAM).
Digging into advertising_start's implementation, I found the code to be thrown by sd_ble_gap_adv_set_configure.
My code for starting up the BLE stack is as follows:
void BluetoothLE::begin(){ // Perform intialization procedure NRF_LOG_INFO("Beginning BLE initiliazation procedure..."); bleStackInit(); NRF_LOG_INFO("\tStack Initilized"); gapParamsInit(); NRF_LOG_INFO("\tGAP Parameters Established"); gattInit(); NRF_LOG_INFO("\tGATT Initilized"); servicesInit(); NRF_LOG_INFO("\tServices Initilized"); advertisingInit(); NRF_LOG_INFO("\tAdvertising Initilized"); connParamsInit(); NRF_LOG_INFO("\tConnection Parameters Established"); // Begin advertising advertisingStart(); NRF_LOG_INFO("--BLE Stack Operable--"); } /** * Initializes the softdevice and sets up the BLE event interrupt. * * Derived from Nordic sample code. */ void BluetoothLE::bleStackInit(){ ret_code_t errCode = nrf_sdh_enable_request(); APP_ERROR_CHECK(errCode); // Get the start address of app RAM uint32_t ramStart = 0; errCode = nrf_sdh_ble_default_cfg_set(bleConf.bleConnCfgTag, &ramStart); APP_ERROR_CHECK(errCode); // Enable BLE stack. errCode = nrf_sdh_ble_enable(&ramStart); APP_ERROR_CHECK(errCode); // Register handler for BLE events NRF_SDH_BLE_OBSERVER(m_ble_observer, 3, bleEventHandler, NULL); } /** * Setup generic access profile ("GAP") for the device. * * This includes device name and other connection parameters */ void BluetoothLE::gapParamsInit(){ ble_gap_conn_params_t gapConnParams; ble_gap_conn_sec_mode_t secMode; ret_code_t errCode; BLE_GAP_CONN_SEC_MODE_SET_OPEN(&secMode); errCode = sd_ble_gap_device_name_set(&secMode, (const uint8_t *)(bleConf.deviceName.data()), bleConf.deviceName.size()); APP_ERROR_CHECK(errCode); memset(&gapConnParams, 0, sizeof(gapConnParams)); gapConnParams.min_conn_interval = bleConf.minConnInt; gapConnParams.max_conn_interval = bleConf.maxConnInt; gapConnParams.slave_latency = bleConf.slaveLat; gapConnParams.conn_sup_timeout = bleConf.connSupTimeout; errCode = sd_ble_gap_ppcp_set(&gapConnParams); APP_ERROR_CHECK(errCode); } void BluetoothLE::gattInit(){ ret_code_t errCode = nrf_ble_gatt_init(&mGatt, NULL); APP_ERROR_CHECK(errCode); } void BluetoothLE::servicesInit(){ uint32_t errCode; for(BleService s : services){ // BleService is a custom class representing a service and the necessary code to register one with the stack errCode = s.initService(); // See service initilization procedure below APP_ERROR_CHECK(errCode); serviceNrfUUIDs.push_back(s.getNrfUUID()); // Add uuid to list of implemented services } } BLE_ADVERTISING_DEF(mAdvertising); // Create a static C-styled object representing the advertising module void BluetoothLE::advertisingInit(){ ret_code_t errCode = NRF_SUCCESS; ble_advertising_init_t advInit; memset(&advInit, 0, sizeof(advInit)); advInit.advdata.name_type = BLE_ADVDATA_FULL_NAME; advInit.advdata.include_appearance = false; advInit.advdata.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE; advInit.srdata.uuids_complete.uuid_cnt = serviceNrfUUIDs.size(); // Service UUIDs stored in a C++ vector, constructed at compile time advInit.srdata.uuids_complete.p_uuids = serviceNrfUUIDs.data(); advInit.config.ble_adv_fast_enabled = true; advInit.config.ble_adv_fast_interval = bleConf.appAdvInterval; advInit.config.ble_adv_fast_timeout = bleConf.appAdvDuration; //advInit.evt_handler = onAdvEvt; errCode = ble_advertising_init(&mAdvertising, &advInit); APP_ERROR_CHECK(errCode); ble_advertising_conn_cfg_tag_set(&mAdvertising, bleConf.bleConnCfgTag); } void BluetoothLE::connParamsInit(){ ble_conn_params_init_t cpInit; memset(&cpInit, 0, sizeof(cpInit)); cpInit.p_conn_params = NULL; cpInit.first_conn_params_update_delay = bleConf.firstConnParamsUpdateDelay; cpInit.next_conn_params_update_delay = bleConf.nextConnParamsUpdateDelay; cpInit.max_conn_params_update_count = bleConf.maxConnParamsUpdateCnt; cpInit.start_on_notify_cccd_handle = BLE_GATT_HANDLE_INVALID; cpInit.disconnect_on_fail = false; // TODO - I don't think I need these handlers //cpInit.evt_handler = on_conn_params_evt; //cpInit.error_handler = conn_params_error_handler; ret_code_t errCode = ble_conn_params_init(&cpInit); APP_ERROR_CHECK(errCode); } void BluetoothLE:: advertisingStart(){ ret_code_t errCode = ble_advertising_start(&mAdvertising, BLE_ADV_MODE_FAST); // XXX - Error code 7 tossed here APP_ERROR_CHECK(errCode); }
Where "bleConf" is a reference to a custom structure containing configuration parameters that would normally be defined in macros. Defined as follows:
/** * Struct defining the various configuration parameters used in the initilization of the radio. * The default values are those defined by Nordic and will work in the vast majority of cases */ // Parameter Default Value Definition typedef struct{ int bleConnCfgTag = 1; // A tag identifying the SoftDevice BLE configuration int minConnInt = MSEC_TO_UNITS(100, UNIT_1_25_MS); // Minimum acceptable connection interval (0.5 seconds) int maxConnInt = MSEC_TO_UNITS(200, UNIT_1_25_MS); // Maximum acceptable connection interval (1 second) int slaveLat = 0; // Slave latency int connSupTimeout = MSEC_TO_UNITS(4000, UNIT_10_MS); // Connection supervisory time-out (4 seconds) int firstConnParamsUpdateDelay = APP_TIMER_TICKS(20000); // Time from initiating event (connect or start of notification) to first time sd_ble_gap_conn_param_update is called (15 seconds) int nextConnParamsUpdateDelay = APP_TIMER_TICKS(5000); // Time between each call to sd_ble_gap_conn_param_update after the first call (5 seconds) int maxConnParamsUpdateCnt = 3; // Number of attempts before giving up the connection parameter negotiation int appAdvInterval = 64; // The advertising interval (in units of 0.625 ms; this value corresponds to 40 ms). int appAdvDuration = BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED; uint8_t advHandle = BLE_GAP_ADV_SET_HANDLE_NOT_SET; std::string deviceName = "BleDevice"; // The advertised name of this device } bleConf_T;
And services are initialized via the following functions:
uint32_t BleService::initService(){ uint32_t errCode; // Set connection to open. No security implemented for now. BLE_GAP_CONN_SEC_MODE_SET_OPEN(&securityAttr.read_perm); BLE_GAP_CONN_SEC_MODE_SET_OPEN(&securityAttr.write_perm); BLE_GAP_CONN_SEC_MODE_SET_OPEN(&securityAttr.cccd_write_perm); // Register UUID errCode = sd_ble_uuid_vs_add(&baseUUID, &(this->uuidType)); VERIFY_SUCCESS(errCode); ble_uuid_t nrfUUID = getNrfUUID(); errCode = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &nrfUUID, &serviceHandle); if(errCode != NRF_SUCCESS) // No point in continuing if the above line biffs it return errCode; // Register characteristics for(BleChar c : *chars){ errCode = registerChar(c); if(errCode != NRF_SUCCESS) return errCode; } return NRF_SUCCESS; } uint32_t BleService::registerChar(BleChar c){ ble_gatts_char_md_t charMd; ble_gatts_attr_md_t cccdMd; ble_gatts_attr_t attrCharValue; ble_uuid_t nrfCharUUID; ble_gatts_attr_md_t attrMd; memset(&cccdMd, 0, sizeof(cccdMd)); // Read operation on Cccd should be possible without authentication. BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccdMd.read_perm); BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccdMd.write_perm); cccdMd.vloc = BLE_GATTS_VLOC_STACK; memset(&charMd, 0, sizeof(charMd)); if(c.isReadable) charMd.char_props.read = 1; if(c.isWritable) charMd.char_props.write = 1; if(c.isSubscribable) charMd.char_props.notify = 1; charMd.p_cccd_md = &cccdMd; charMd.p_char_user_desc = NULL; charMd.p_char_pf = NULL; charMd.p_user_desc_md = NULL; charMd.p_sccd_md = NULL; nrfCharUUID.type = uuidType; nrfCharUUID.uuid = c.UUID; memset(&attrMd, 0, sizeof(attrMd)); attrMd.read_perm = securityAttr.read_perm; attrMd.write_perm = securityAttr.write_perm; attrMd.vloc = BLE_GATTS_VLOC_STACK; attrMd.rd_auth = 0; attrMd.wr_auth = 0; attrMd.vlen = 0; memset(&attrCharValue, 0, sizeof(attrCharValue)); attrCharValue.p_uuid = &nrfCharUUID; attrCharValue.p_attr_md = &attrMd; attrCharValue.init_len = sizeof(uint8_t); attrCharValue.init_offs = 0; attrCharValue.max_len = 50; // TODO return sd_ble_gatts_characteristic_add(serviceHandle, &charMd, &attrCharValue, &c.handle); }
Are there any obvious issues that poke out that would cause the advertising module to provide malformed advertising data/params to sd_ble_gap_adv_set_configure? Is there some step or nuance I am missing?
As you can see, I have omitted several event handlers that I believe I do not need. I have tried implementing these to see if they fixed the problem, but their inclusion had no effect.