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.