一、背景
链路层(LL)控制设备的射频状态,有五个设备状态:待机、广播、扫描、初始化和连接。
广播 为广播数据包,而 扫描 则是监听广播。
GAP通信中角色,中心设备(Central - 主机)用来扫描和连接 外围设备(Peripheral - 从机)。
大部分情况下外围设备通过广播自己来让中心设备发现自己,并建立 GATT 连接,从而进行更多的数据交换。
也有些情况是不需要连接的,只要外设广播自己的数据即可,用这种方式主要目的是让外围设备,把自己的信息发送给多个中心设备。
在蓝牙 4.x 的协议中,广播包的大小为 31 个字节,如果主机有主动扫描,还有一个 31 字节大小的扫描响应包,也就是说如果是蓝牙 4.x 模式,最大可实现 62 个字节大小的广播内容。
在蓝牙 5.0 中,把广播信道抽象为两类,一种叫主广播信道(primary advertisement channels),另一种叫次广播信道,或者第二广播信道(secondary advertising packets)。
所谓的主广播类似于蓝牙 4.x 的广播,只工作在 37、38、39 三个信道,最大广播字节为 31 字节。而次广播允许蓝牙在除开 37、38、39 三个信道之外的其他 37 个信道上发送长度介于 0-255 字节的数据。次广播信道(0-36 channel)广播 255 字节数据。
二、广播内容参数
在 ble_advertising.h 文件中,提供了广播的初始化参数结构体,如果你需要自定义广播内容,那么就需要在广播数据包 advdata 或者扫描响应包 srdata 中添加内容。
/**@brief Initialization parameters for the Advertising Module.
* @details This structure is used to pass advertising options, advertising data,
* and an event handler to the Advertising Module during initialization.
*/
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;
- advdata:广播数据包
- srdata:扫描响应包
- config:配置广播参数(广播模式、广播间隔、广播时间)
- evt_handler:将在广播事件上调用的事件处理程序
- error_handler:错误处理程序,将把内部错误导入主应用程序
我们可以看到广播数据包 advdata 或者扫描响应包 srdata 都是结构体 ble_advdata_t 类型,该结构体内定义了广播数据包或扫描响应包可以定义的内容:
/**@brief Advertising data structure. This structure contains all options and data needed for encoding and
* setting the advertising data. */
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;
name_type:设备名称的类型
short_name_len:短设备名称的长度(如果指定了短类型)
include_appearance:确定是否包括展示图标
flags:广播数据标识字段
p_tx_power_level:TX 电平发送功率等级
uuids_more_available:部分服务UUID列表,只显示部分UUID列表在广播中,实际工程还有更多UUID
uuids_complete:全部服务UUID列表,广播中显示UUID列表就是实际工程中所有UUID
uuids_solicited:请求服务的UUID列表,一个从机设备可以发送服务请求数据类型广播去邀请主机进行连接,该主机设备包含一个或多个这个服务器请求数据广播所指定的服务
p_slave_conn_int:从机连接间隔范围
p_manuf_specific_data:制造商特定的数据,自定义广播数据
p_service_data_array:服务数据结构数组
service_data_count:服务数据结构的数量
include_ble_device_addr:确定是否包含LE蓝牙设备地址
le_role:LE角色区域。这个区域仅仅用于NFC。对应BLE广播,设置为NULL
p_tk_value:安全管理TK值的区域。这个区域仅仅用于NFC。对应BLE广播,设置为NULL
p_sec_mgr_oob_flags:安全管理器带外标志字段。这个区域仅仅用于NFC。对应BLE广播,设置为NULL
p_lesc_data:LE OOB数据的安全连接。这个区域仅仅用于NFC。对应BLE广播,设置为NULL
三、广播UUID的值
UUID的种类分为两种:
- 一种是 SIG 定义的公共服务 UUID,所有的公共服务共用一个 128bit 的基础 UUID,不同的服务采用一个 16bit UUID 进行定义。
- 另一种就是私有服务的 UUID,这是一个自定义的 128bit UUID。
注意:广播包里的 UUID 不影响服务特征值中 UUID 的值,仅仅是让广播把 UUID 的值广播给扫描设备,方便观察。
3.1 显示全部服务UUID列表
在广播参数里列出了三类 UUID 列表的情况:
typedef struct
{
···
ble_advdata_uuid_list_t uuids_more_available;
ble_advdata_uuid_list_t uuids_complete;
ble_advdata_uuid_list_t uuids_solicited;
···
} ble_advdata_t;
- uuids_more_available:部分服务UUID列表,只显示部分UUID列表在广播中,实际工程还有更多UUID
- uuids_complete:全部服务UUID列表,广播中显示UUID列表就是实际工程中所有UUID
- uuids_solicited:请求服务的UUID列表,一个从机设备可以发送服务请求数据类型广播去邀请主机进行连接,该主机设备包含一个或多个这个服务器请求数据广播所指定的服务
UUID专门有个一个结构体 ble_advdata_uuid_list_t 进行标识:
typedef struct
{
uint16_t uuid_cnt; // UUID的数目
ble_uuid_t * p_uuids; // 指向UUID列表的指针
} ble_advdata_uuid_list_t
如果需要在广播中广播 UUID,需要专门建立一个 UUID 的结构体,让指向UUID列表的指针指向这个结构体。
1. 首先,在主函数 main.c 中,声明如下 m_adv_uuids 结构体:
static ble_uuid_t m_adv_uuids[] =
{
{BLE_UUID_NUS_SERVICE, BLE_UUID_TYPE_VENDOR_BEGIN},
{BLE_UUID_BATTERY_SERVICE, BLE_UUID_TYPE_BLE},
{BLE_UUID_TX_POWER_SERVICE, BLE_UUID_TYPE_BLE}
};
这个结构体内包含了 3 个 UUID 的列表:一个蓝牙串口服务,一个电池服务,一个发射功率服务。同时标注服务 UUID 的类型,其中蓝牙串口服务为私有服务,电池服务和发射功率服务为 SIG 定义的公共服务。两种服务类型 UUID 长度是不同的,分别为 128bit 和 16bit,如下面定义:
/** @defgroup BLE_UUID_TYPES Types of UUID
* @{ */
#define BLE_UUID_TYPE_UNKNOWN 0x00 /**< Invalid UUID type. */
#define BLE_UUID_TYPE_BLE 0x01 /**< Bluetooth SIG UUID (16-bit). */
#define BLE_UUID_TYPE_VENDOR_BEGIN 0x02 /**< Vendor UUID types start at this index (128-bit). */
/** @} */
2.接着,在广播初始化函数中添加如下代码:
static void advertising_init(void)
{
ret_code_t err_code;
ble_advertising_init_t init;
memset(&init, 0, sizeof(init));
···
···
// 定义全部UUID列表(包含一个128bit的UUID和两个服务16bit的UUID)
init.srdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
init.srdata.uuids_complete.p_uuids = m_adv_uuids;
···
···
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);// 设置广播识别号
}
由于要显示 128bit 的 UUID 长度比较长,因此把 UUID 的参数放入扫描响应包中。通过手机APP nrf connect 扫描后显示,Complete list of 表示是完整 UUID 列表。
3.2 显示部分服务UUID列表
通过设置 uuid_cnt 的数目控制广播中显示的 UUID 数目,不管广播数据包还是扫描响应包,都只提供 31 个字节的空间,因此需要注意可使用的空间。
1.同上,首先在主函数 main.c 中,声明如下 m_adv_uuids 结构体:
static ble_uuid_t m_adv_uuids[] =
{
{BLE_UUID_NUS_SERVICE, BLE_UUID_TYPE_VENDOR_BEGIN},
{BLE_UUID_BATTERY_SERVICE, BLE_UUID_TYPE_BLE},
{BLE_UUID_TX_POWER_SERVICE, BLE_UUID_TYPE_BLE}
};
2.接着,在广播初始化函数中添加如下代码:
static void advertising_init(void)
{
ret_code_t err_code;
ble_advertising_init_t init;
memset(&init, 0, sizeof(init));
···
···
// 定义全部UUID列表(包含一个128bit的UUID和两个服务16bit的UUID)
init.srdata.uuids_more_available.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]) - 1;
init.srdata.uuids_more_available.p_uuids = m_adv_uuids;
···
···
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);// 设置广播识别号
}
通过手机APP nrf connect 扫描后显示,Incomplete list of 表示是部分 UUID 列表。这里只显示了一个 16bit UUID 和 一个 128bit UUID,而实际有三个 UUID。
3.3 显示请求服务UUID列表
一个典型的请求服务的 UUID 列表例子就是 ANCS 广播。这个例子需要一个 GATT 从机端和一个有这个 ANCS 的 GATT 主机端。通过在广播中广播 ANCS 请求服务的 UUID 去告诉扫描端(iOS设备)它正在“寻找”一个具有 ANCS 服务的主机设备。对于当前时间服务 CTS 的客户机也是如此。ble_app_cts_c 使用所请求的服务是因为它需要一个具有当前时间服务的 GATT 服务器的主机端,在 ble_app_cts_c 工程中,广播初始化的代码如下:
首先,声明 m_adv_uuids 结构体:
static ble_uuid_t m_adv_uuids[] =
{
{BLE_UUID_CURRENT_TIME_SERVICE, BLE_UUID_TYPE_BLE}
};
接着,在广播初始化函数中添加如下代码:
static void advertising_init(void)
{
ret_code_t err_code;
ble_advertising_init_t init;
memset(&init, 0, sizeof(init));
···
···
// 定义全部UUID列表(包含一个128bit的UUID和两个服务16bit的UUID)
init.srdata.uuids_solicited.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]) ;
init.srdata.uuids_solicited.p_uuids = m_adv_uuids;
···
···
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);// 设置广播识别号
}
通过手机APP nrf connect 扫描后显示:
四、广播从机的连接间隔参数
从机与主机之间的连接间隔,是由从机提出与主机进行协商,然后再由主机决定的参数。
1.在广播参数结构体 ble_advdata_t
中包含了一个结构体参数 ble_advdata_conn_int_t
typedef struct
{
···
ble_advdata_conn_int_t *p_slave_conn_int; /**< Slave Connection Interval Range. */
···
} ble_advdata_t;
2.指定了广播中可以广播的两个连接参数的值:
/**@brief Connection interval range structure. */
typedef struct
{
uint16_t min_conn_interval; /**< Minimum connection interval, in units of 1.25 ms, range 6 to 3200 (7.5 ms to 4 s). */
uint16_t max_conn_interval; /**< Maximum connection interval, in units of 1.25 ms, range 6 to 3200 (7.5 ms to 4 s). The value 0xFFFF indicates no specific maximum. */
} ble_advdata_conn_int_t;
3.使用这个结构体,可以在广播初始化中定义需要广播的连接间隔的参数:
static void advertising_init(void)
{
ret_code_t err_code;
ble_advertising_init_t init;
memset(&init, 0, sizeof(init));
···
···
ble_advdata_conn_int_t conn_range;
// 从机连接间隔范围最小值:10*1.25ms = 12.5ms
conn_range.min_conn_interval = 10;
// 从机连接间隔范围最大值:20*1.25ms = 25ms
conn_range.max_conn_interval = 20;
// 广播数据中包含从机连接间隔范围
init.advdata.p_slave_conn_int = &conn_range;
···
···
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);// 设置广播识别号
}
通过手机APP nrf connect 扫描后显示:
五、广播自定义数据
1.首先,在 ble_advdata.h 文件中,定义了结构体 ble_advdata_manuf_data_t
表示公司厂家的数据与ID代码:
typedef struct
{
uint16_t company_identifier; // 公司ID代码
uint8_arry_t data; // 制造者自定义的数据
} ble_advdata_manuf_data_t;
- company_identifier:公司ID号,每个公司都有独立申请的值,一般 company_identifier 都是厂家在 SIG 申请定义的唯一 ID 号。不同公司的 ID 号可以具体在 SIG 网站查询。例如 Nordic 的制造商 ID 号为:0x0059
- data:制造商自定义的数据,这个参数可以自由的设置,只要广播包的空间足够。假设自定义数据为 0x11,0x22,0x33,0x44,0x55。
- 接着,在广播初始化函数中添加如下代码:
static void advertising_init(void)
{
ret_code_t err_code;
ble_advertising_init_t init;
memset(&init, 0, sizeof(init));
···
···
uint8_t my_adv_manuf_data[5] = {0x11,0x22,0x33,0x44,0x55};
// 定义一个制造商自定义数据的结构体变量,配置广播数据时将该变量的地址赋值给广播数据包中
ble_advdata_manuf_data_t manuf_specific_data;
// 0x0059是Nordic的制造商ID
manuf_specific_data.company_identifier = 0x0059;
// 指向自定义数据
manuf_specific_data.data.p_data = my_adv_manuf_data;
// 自定义数据的大小
manuf_specific_data.data.size = sizeof(my_adv_manuf_data);
// 定义自定义数据到广播包中
init.advdata.p_manuf_specific_data = &manuf_specific_data;
···
···
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);// 设置广播识别号
}
通过手机APP nrf connect 扫描后显示:
六、动态更新广播内容
动态更新广播内容实际就是更新第四节中 manuf_specific_data.data.p_data 的值,然后停止广播,再更新广播内容,再开启广播,这里涉及到三个函数:
advertising_stop():停止广播
advertising_advdata_update():更新广播内容
advertising_start():开启广播
七、自定义广播内容及动态更新广播例子
下载 user_advertising.c
和 user_advertising.h
链接:https://pan.baidu.com/s/1zH3uwEwdla-s331a1XzvkQ 提取码:8gp5