蓝牙自定义广播的实现方式
1.基于SDK应用函数的编写
static void advertising_init(void)
{
uint32_t err_code;
ble_advertising_init_t init;
memset(&init, 0, sizeof(init));
init.advdata.name_type = BLE_ADVDATA_FULL_NAME;
init.advdata.include_appearance = false;
init.advdata.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE;
init.srdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
init.srdata.uuids_complete.p_uuids = m_adv_uuids;
init.config.ble_adv_fast_enabled = true;
init.config.ble_adv_fast_interval = APP_ADV_INTERVAL;
init.config.ble_adv_fast_timeout = APP_ADV_DURATION;
init.evt_handler = on_adv_evt;
err_code = ble_advertising_init(&m_advertising, &init);
APP_ERROR_CHECK(err_code);
ble_advertising_conn_cfg_tag_set(&m_advertising, APP_BLE_CONN_CFG_TAG);
}
static void advertising_start(void)
{
uint32_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);
APP_ERROR_CHECK(err_code);
}
这是标准SDK的广播初始化程序,可以很清晰的看到初始化函数,只调用了两个ble函数:
1 ble_advertising_init(&m_advertising, &init);
2 ble_advertising_conn_cfg_tag_set(&m_advertising,
APP_BLE_CONN_CFG_TAG);
这里要看一下传的参数 &m_advertising ,&init。
&m_advertising在这里定义的
BLE_ADVERTISING_DEF(m_advertising); 主函数定义的观察者,不理解可以先不用管。
&int是一个结构体类型变量 ble_advertising_init_t init;可以看到初始化全篇都是在配置init,最后把init丢进ble_advertising_init里面。
ble_advertising_conn_cfg_tag_set()里面其实是配置了conn_cfg_tag这个标记,
p_advertising->conn_cfg_tag = ble_cfg_tag;
那看一下 init里面到底有哪些参数:
typedef struct
{
ble_advdata_t advdata; /**< Advertising data: name, appearance, discovery flags, and more. */
ble_advdata_t srdata; /**< Scan response data: Supplement to advertising data. */
ble_adv_modes_config_t config; /**< Select which advertising modes and intervals will be utilized.*/
ble_adv_evt_handler_t evt_handler; /**< Event handler that will be called upon advertising events. */
ble_adv_error_handler_t error_handler; /**< Error handler that will propogate internal errors to the main applications. */
} ble_advertising_init_t;
typedef struct
{
ble_advdata_name_type_t name_type; /**< Type of device name. */
uint8_t short_name_len; /**< Length of short device name (if short type is specified). */
bool include_appearance; /**< Determines if Appearance shall be included. */
uint8_t flags; /**< Advertising data Flags field. */
int8_t * p_tx_power_level; /**< TX Power Level field. */
ble_advdata_uuid_list_t uuids_more_available; /**< List of UUIDs in the 'More Available' list. */
ble_advdata_uuid_list_t uuids_complete; /**< List of UUIDs in the 'Complete' list. */
ble_advdata_uuid_list_t uuids_solicited; /**< List of solicited UUIDs. */
ble_advdata_conn_int_t * p_slave_conn_int; /**< Slave Connection Interval Range. */
ble_advdata_manuf_data_t * p_manuf_specific_data; /**< Manufacturer specific data. */
ble_advdata_service_data_t * p_service_data_array; /**< Array of Service data structures. */
uint8_t service_data_count; /**< Number of Service data structures. */
bool include_ble_device_addr; /**< Determines if LE Bluetooth Device Address shall be included. */
ble_advdata_le_role_t le_role; /**< LE Role field. Included when different from @ref BLE_ADVDATA_ROLE_NOT_PRESENT. @warning This field can be used only for NFC. For BLE advertising, set it to NULL. */
ble_advdata_tk_value_t * p_tk_value; /**< Security Manager TK value field. Included when different from NULL. @warning This field can be used only for NFC. For BLE advertising, set it to NULL.*/
uint8_t * p_sec_mgr_oob_flags; /**< Security Manager Out Of Band Flags field. Included when different from NULL. @warning This field can be used only for NFC. For BLE advertising, set it to NULL.*/
ble_gap_lesc_oob_data_t * p_lesc_data; /**< LE Secure Connections OOB data. Included when different from NULL. @warning This field can be used only for NFC. For BLE advertising, set it to NULL.*/
} ble_advdata_t;
typedef struct
{
bool ble_adv_on_disconnect_disabled; /**< Enable or disable automatic return to advertising upon disconnecting.*/
bool ble_adv_whitelist_enabled; /**< Enable or disable use of the whitelist. */
bool ble_adv_directed_high_duty_enabled; /**< Enable or disable high duty direct advertising mode. Can not be used together with extended advertising. */
bool ble_adv_directed_enabled; /**< Enable or disable direct advertising mode. */
bool ble_adv_fast_enabled; /**< Enable or disable fast advertising mode. */
bool ble_adv_slow_enabled; /**< Enable or disable slow advertising mode. */
uint32_t ble_adv_directed_interval; /**< Advertising interval for directed advertising. */
uint32_t ble_adv_directed_timeout; /**< Time-out (number of tries) for direct advertising. */
uint32_t ble_adv_fast_interval; /**< Advertising interval for fast advertising. */
uint32_t ble_adv_fast_timeout; /**< Time-out (in units of 10ms) for fast advertising. */
uint32_t ble_adv_slow_interval; /**< Advertising interval for slow advertising. */
uint32_t ble_adv_slow_timeout; /**< Time-out (in units of 10ms) for slow advertising. */
bool ble_adv_extended_enabled; /**< Enable or disable extended advertising. */
uint32_t ble_adv_secondary_phy; /**< PHY for the secondary (extended) advertising @ref BLE_GAP_PHYS (BLE_GAP_PHY_1MBPS, BLE_GAP_PHY_2MBPS or BLE_GAP_PHY_CODED). */
uint32_t ble_adv_primary_phy; /**< PHY for the primary advertising. @ref BLE_GAP_PHYS (BLE_GAP_PHY_1MBPS, BLE_GAP_PHY_2MBPS or BLE_GAP_PHY_CODED). */
} ble_adv_modes_config_t;
主要是ble_advdata_t; 和 ble_adv_modes_config_t结构体。
其中 advdata 主要包括广播的相关内容,包括,名称,蓝牙工作模式,发射功率,UUID,制造商特定数据,服务数据,等,config主要是广播的模式配置,包含快速广播,慢速广播,高速定向广播,以及对应的广播间隔和超时时间等;需要哪个就配置对应的内容,最后调用 ble_advertising_init();把配置好的结构体,装载进去。最后调用advertising_start 发射出去。初级小白可以先在上面的基础上修改参数试试。
比如:修改名字长度
init.advdata.name_type = BLE_ADVDATA_SHORT_NAME;
init.advdata.short_name_len = 3; //要跟上长度
1.1增加制造商信息
一般用这个方便自定义广播信息
typedef struct
{
uint16_t company_identifier; /**< Company identifier code. */
uint8_array_t data; /**< Additional manufacturer specific data. */
} ble_advdata_manuf_data_t;
typedef struct
{
uint16_t size; /**< Number of array entries. */
uint8_t * p_data; /**< Pointer to array entries. */
} uint8_array_t;
可以在初始化增加这些
//主函数的全局数组,一个广播包,一个扫描响应包
uint8_t test_array_adv[] = {0x55,0x55,0x55,0x55};
uint8_t test_array_scan[] = {0xAA,0xAA,0xAA,0xAA,};
//主函数自定义的全局结构体,包括Init结构体,定义为全局是方便后面改动
ble_advertising_init_t init;
ble_advdata_manuf_data_t adv_data;
ble_advdata_manuf_data_t sr_data;
adv_data.company_identifier = 0x8888;
adv_data.data.p_data = test_array_adv;
adv_data.data.size = sizeof(test_array_adv)/sizeof(test_array_adv[0]);
init.advdata.p_manuf_specific_data = &adv_data;
sr_data.company_identifier = 0x8888;
sr_data.data.p_data = test_array_scan;
sr_data.data.size = sizeof(test_array_scan) /sizeof(test_array_scan[0]);
init.srdata.p_manuf_specific_data = &sr_data;
增加上面这些,用nRE Connect 查看广播包:
会出现两个TYPE为FF,的一串数据,一个是广播包,一个是扫描响应包,数据内容就是你 test_array_adv[] 和 test_array_scan[] 当然数组会有长度限制,所有广播内容一起不超过31个字节(扫描响应包可选,是额外的扩充)。
1.2广播的结构
补充一下广播基本结构:
AD struct = AD len + AD type +AD data
其中AD len 是AD type AD data的长度和。AD type 蓝牙官方定义广播类型。AD data是 AD type后对应的数据
大概是这样的一个表,Bluetooth官网有定义的。
1.3广播名称和MAC地址修改
名称在gap_params_init函数里面,
err_code = sd_ble_gap_device_name_set(&sec_mode,
(const uint8_t *) DEVICE_NAME,
strlen(DEVICE_NAME));
#define DEVICE_NAME "Nordic_UART"
修改DEVICE_NAME 这个宏即可。
mac地址的话设置方法如下:
typedef struct
{
uint8_t addr_id_peer : 1; /**< Only valid for peer addresses.
This bit is set by the SoftDevice to indicate whether the address has been resolved from
a Resolvable Private Address (when the peer is using privacy).
If set to 1, @ref addr and @ref addr_type refer to the identity address of the resolved address.
This bit is ignored when a variable of type @ref ble_gap_addr_t is used as input to API functions. */
uint8_t addr_type : 7; /**< See @ref BLE_GAP_ADDR_TYPES. */
uint8_t addr[BLE_GAP_ADDR_LEN]; /**< 48-bit address, LSB format.
@ref addr is not used if @ref addr_type is @ref BLE_GAP_ADDR_TYPE_ANONYMOUS. */
} ble_gap_addr_t;
ble_gap_addr_t mac_address; //地址相关的结构体变量
mac_address.addr[5] = 0x20;
mac_address.addr[4] = 0x21;
mac_address.addr[3] = 0x06;
mac_address.addr[2] = 0x01;
mac_address.addr[1] = 0X66;
mac_address.addr[0] = 0x66;
mac_address.addr_type = BLE_GAP_ADDR_TYPE_PUBLIC; //默认是静态随机地址,改为了公共地址
NRF_LOG_INFO("mac address is");
NRF_LOG_HEXDUMP_INFO(mac_address.addr, 6);
err_code = sd_ble_gap_addr_set(&mac_address);
可以把上面代码直接放在广播初始化里面。
2基于SDK底层函数的编写
应用函数其实是基于底层sd函数的再封装;我们把ble_advertising_init()打开看一下
uint32_t ble_advertising_init(ble_advertising_t * const p_advertising,
ble_advertising_init_t const * const p_init)
{
uint32_t ret;
if ((p_init == NULL) || (p_advertising == NULL))
{
return NRF_ERROR_NULL;
}
if (!config_is_valid(&p_init->config))
{
return NRF_ERROR_INVALID_PARAM;
}
p_advertising->adv_mode_current = BLE_ADV_MODE_IDLE;
p_advertising->adv_modes_config = p_init->config;
p_advertising->conn_cfg_tag = BLE_CONN_CFG_TAG_DEFAULT;
p_advertising->evt_handler = p_init->evt_handler;
p_advertising->error_handler = p_init->error_handler;
p_advertising->current_slave_link_conn_handle = BLE_CONN_HANDLE_INVALID;
p_advertising->p_adv_data = &p_advertising->adv_data;
memset(&p_advertising->peer_address, 0, sizeof(p_advertising->peer_address));
// Copy advertising data.
if (!p_advertising->initialized)
{
p_advertising->adv_handle = BLE_GAP_ADV_SET_HANDLE_NOT_SET;
}
p_advertising->adv_data.adv_data.p_data = p_advertising->enc_advdata;
if (p_advertising->adv_modes_config.ble_adv_extended_enabled == true)
{
#ifdef BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_CONNECTABLE_MAX_SUPPORTED
p_advertising->adv_data.adv_data.len = BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_CONNECTABLE_MAX_SUPPORTED;
#else
p_advertising->adv_data.adv_data.len = BLE_GAP_ADV_SET_DATA_SIZE_MAX;
#endif // BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_CONNECTABLE_MAX_SUPPORTED
}
else
{
p_advertising->adv_data.adv_data.len = BLE_GAP_ADV_SET_DATA_SIZE_MAX;
}
ret = ble_advdata_encode(&p_init->advdata, p_advertising->enc_advdata, &p_advertising->adv_data.adv_data.len);
VERIFY_SUCCESS(ret);
p_advertising->adv_data.scan_rsp_data.p_data = p_advertising->enc_scan_rsp_data;
if (p_advertising->adv_modes_config.ble_adv_extended_enabled == true)
{
#ifdef BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_CONNECTABLE_MAX_SUPPORTED
p_advertising->adv_data.scan_rsp_data.len = BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_CONNECTABLE_MAX_SUPPORTED;
#else
p_advertising->adv_data.scan_rsp_data.len = BLE_GAP_ADV_SET_DATA_SIZE_MAX;
#endif // BLE_GAP_ADV_SET_DATA_SIZE_EXTENDED_CONNECTABLE_MAX_SUPPORTED
}
else
{
p_advertising->adv_data.scan_rsp_data.len = BLE_GAP_ADV_SET_DATA_SIZE_MAX;
}
ret = ble_advdata_encode(&p_init->srdata,
p_advertising->adv_data.scan_rsp_data.p_data,
&p_advertising->adv_data.scan_rsp_data.len);
VERIFY_SUCCESS(ret);
// Configure a initial advertising configuration. The advertising data and and advertising
// parameters will be changed later when we call @ref ble_advertising_start, but must be set
// to legal values here to define an advertising handle.
p_advertising->adv_params.primary_phy = BLE_GAP_PHY_1MBPS;
p_advertising->adv_params.duration = p_advertising->adv_modes_config.ble_adv_fast_timeout;
p_advertising->adv_params.properties.type = BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED;
p_advertising->adv_params.p_peer_addr = NULL;
p_advertising->adv_params.filter_policy = BLE_GAP_ADV_FP_ANY;
p_advertising->adv_params.interval = p_advertising->adv_modes_config.ble_adv_fast_interval;
ret = sd_ble_gap_adv_set_configure(&p_advertising->adv_handle, NULL, &p_advertising->adv_params);
VERIFY_SUCCESS(ret);
p_advertising->initialized = true;
return ret;
}
可以看到大部分工作实在配置 p_advertising这个结构体变量,此外还有两个底层的函数:ble_advdata_encode 和sd_ble_gap_adv_set_configure
原型是
ble_advdata_encode(ble_advdata_t const * const p_advdata,
uint8_t * const p_encoded_data,
uint16_t * const p_len)
和 sd_ble_gap_adv_set_configure(uint8_t *p_adv_handle, ble_gap_adv_data_t const *p_adv_data, ble_gap_adv_params_t const *p_adv_params));
第一个函数的作用,相当于编码的作用,是把advdata 里面的结构体的配置,转换为一个encoded_data数组,这个数组有p_len指向的长度。
第二个函数是设置advertising data 和params然后直接发射出去,ble_gap_adv_data_t 结构体下是最原始的广播内容,里面存放的数组是按 AD_len, AD_type, AD_data,的结构存放,可以自己动手编辑好这种格式的数组,然后调用sd_ble_gap_adv_set_configure发射出去。
编写一个使用 sd_ble_gap_adv_set_configure 的广播实例。先观察sd_ble_gap_adv_set_configure的参数 。
static uint8_t m_adv_handle = BLE_GAP_ADV_SET_HANDLE_NOT_SET; //可以作为全局变量
uint8_t test_array_adv[31] = {0x02,0x01,0x06,0x02,0x0A,0x00};
uint8_t test_array_scan[31] = {0x00};
//data
ble_gap_adv_data_t set_data;
set_data.adv_data.p_data = test_array_adv;
set_data.adv_data.len = 31;
set_data.scan_rsp_data.p_data = test_array_scan;
set_data.scan_rsp_data.len = 31;
//config params
ble_gap_adv_params_t const adv_params = {
.properties =
{
.type = BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED,
},
.p_peer_addr = NULL,
.filter_policy = BLE_GAP_ADV_FP_ANY,
.interval = 300,
.duration = 0,
.primary_phy = BLE_GAP_PHY_1MBPS, // Must be changed to connect in long
// range. (BLE_GAP_PHY_CODED)
.secondary_phy = BLE_GAP_PHY_1MBPS,
};
APP_ERROR_CHECK(
sd_ble_gap_adv_set_configure(&m_adv_handle, &set_data, &adv_params));
APP_ERROR_CHECK(
sd_ble_gap_adv_start(m_adv_handle, APP_BLE_CONN_CFG_TAG));
这里把原始广播数据指向了自定义的数组,这个数组大小是31.数组内按照上面广播结构的格式去放数据,否则会报错。
解析一下 test_array_adv[31] = {0x02,0x01,0x06,0x02,0x0A,0x00};
02表示AD_len 长度为2 ,
01表示 AD_type ,-----flags.
06-表示AD_data,对应了普通发现模式,不支持BR.
等价于执行了以下配置:
init.advdata.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
而 0x02,0x0A,0x00 表示发射功率0dB
APP上可以看到
接下来,在以上基础上我们加上ble_advdata_encode(),感受一下它的用法,依然先观察形参,
static __attribute__((unused)) void adv_init(void) {
int8_t tx_power_leval = TX_POWER_LEVAL;
ble_gap_adv_data_t set_data;
ble_advdata_t adv_data1;
ble_advdata_t adv_data2;
memset(&adv_data1, 0, sizeof(adv_data1));
memset(&adv_data2, 0, sizeof(adv_data2)); //省略会没有广播,奇怪!
adv_data.company_identifier = 0x8888;
adv_data.data.p_data = test_array;
adv_data.data.size = 8;
adv_data1.flags = 0x06; //广播包和扫描响应包中只能初始化一个
adv_data1.p_tx_power_level = &tx_power_leval;
adv_data1.p_manuf_specific_data = &adv_data;
adv_data1.name_type = BLE_ADVDATA_NO_NAME;
adv_data2.name_type = BLE_ADVDATA_FULL_NAME;
adv_data2.p_manuf_specific_data = &adv_data;
ble_gap_adv_params_t const adv_params = {
.properties =
{
.type = BLE_GAP_ADV_TYPE_CONNECTABLE_SCANNABLE_UNDIRECTED,
},
.p_peer_addr = NULL,
.filter_policy = BLE_GAP_ADV_FP_ANY,
.interval = 300,
.duration = 0,
.primary_phy = BLE_GAP_PHY_1MBPS, // Must be changed to connect in long
// range. (BLE_GAP_PHY_CODED)
.secondary_phy = BLE_GAP_PHY_1MBPS,
};
set_data.adv_data.p_data = test_array_adv;
set_data.adv_data.len = 31;
set_data.scan_rsp_data.p_data = test_array_scan;
set_data.scan_rsp_data.len = 31;
APP_ERROR_CHECK(ble_advdata_encode(&adv_data1, set_data.adv_data.p_data,
&set_data.adv_data.len));
APP_ERROR_CHECK(ble_advdata_encode(&adv_data2, set_data.scan_rsp_data.p_data,
&set_data.scan_rsp_data.len));
APP_ERROR_CHECK(
sd_ble_gap_adv_set_configure(&m_adv_handle, &set_data, &adv_params));
APP_ERROR_CHECK(sd_ble_gap_adv_start(m_adv_handle, APP_BLE_CONN_CFG_TAG));
}
定义了ble_advdata_t 类型的adv_data1,adv_data2作为广播包和扫描响应包,接着配置这两个结构体,与初始化类似。
接下来就是ble_advdata_encode函数的作用,看他干了什么,可以打开进入函数内部,能更加清晰看到处理过程,简单说明一下
ble_advdata_encode(&adv_data1, set_data.adv_data.p_data,
&set_data.adv_data.len)
把刚才配置好的结构体 adv_data1,编码进adv_data这个数组,len是这个数组的长度。同样的
ble_advdata_encode(&adv_data2, set_data.scan_rsp_data.p_data,
&set_data.scan_rsp_data.len)
把adv_data2,编码进了scan_rsp_data。
因为set_data的adv_data和scan_rsp_data都已经编码进了数据 ,最后
sd_ble_gap_adv_set_configure(&m_adv_handle, &set_data, &adv_params));
把set_data设置进去,调用sd_ble_gap_adv_start()打开广播发射