Zephyr - BLE 广播

Zephyr - BLE 广播

此处介绍的广播是传统广播,不涉及扩展广播。

广播数据

概念介绍

在传统广播里,广播包里携带的数据只有 31 个字节,一个经典的图示如下:

这 31 个字节又分为有效部分无效部分

有效部分也就是我们想要发送的数据,里面每一个数据单元都由一个 AD Structure 组成,N 个 AD Structure 共同填充了有效部分。

每个 AD Structure 又是由 Length + AD Type + AD Data(LTD)组成,图示如下:

  • Length: 这个数据单元的长度。
  • AD Type:广播数据单元的类型,蓝牙 SIG 联盟定义了一些标准类型
  • AD Data:数据内容,需要根据 AD Type 来解析。

这样 BLE 传统广播的数据包内容基本介绍完了,接下来看看 Zephyr 里如何定义 AD Structure 的。

bt_data

struct bt_data {
    uint8_t type;
    uint8_t data_len;
    const uint8_t *data;
};

bt_data 就是 Zephyr 蓝牙协议栈定义的 AD Structure,其中 type 是该数据单元的类型(AD Type),data 指向数据内容(AD Data)缓冲区的首地址,而 data_len 是数据内容(AD Data)缓冲区的首地址,不是前面介绍的数据单元的长度(Length),数据单元的长度(Length)协议栈会自动设置。

协议栈还提供了一系列的宏来帮助我们快速构造 bt_data

BT_DATA

#define BT_DATA(_type, _data, _data_len) \
    { \
        .type = (_type), \
        .data_len = (_data_len), \
        .data = (const uint8_t *)(_data), \
    }

这个参数宏的参数和 bt_data 里的成员变量是一一对应的。

使用 BT_DATA(0x09, "name", sizeof("name")-1) 即可快速构造一个 bt_data,而且它的宏名称是相应的大写版本,方便记忆。

BT_DATA_BYTES

#define BT_DATA_BYTES(_type, _bytes...) \
    BT_DATA(_type, ((uint8_t []) { _bytes }), \
        sizeof((uint8_t []) { _bytes }))

有时候我们想要在定义 bt_data 时直接手动输入数据部分,让程序自己判断数据的长度,这个时候就可以用宏 BT_DATA_BYTES 了。这个参数宏将第 2 个参数及之后的参数全部放置到了 _bytes 里,然后用它构建了一个数组,并自动获取该数组的长度,从而达到了上述的目的。

例如:

static const struct bt_data ad[] = {
    BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_NO_BREDR),
    BT_DATA_BYTES(BT_DATA_UUID16_ALL, 0xaa, 0xfe),
    BT_DATA_BYTES(BT_DATA_SVC_DATA16,
              0xaa, 0xfe, /* Eddystone UUID */
              0x10, /* Eddystone-URL frame type */
              0x00, /* Calibrated Tx power at 0m */
              0x00, /* URL Scheme Prefix http://www. */
              'z', 'e', 'p', 'h', 'y', 'r',
              'p', 'r', 'o', 'j', 'e', 'c', 't',
              0x08) /* .org */
};

广播参数

概念介绍

BLE 设备是以一定间隔(Interval)来广播的,每一次广播就叫做广播事件(Advertising Event),如下图:

这里 advInterval 就是广播间隔,但实际上的 T_advEvent(广播事件)还加了一个 10ms 的伪随机数(advDelay)。

如果在这一次广播事件内,对端扫描到了这个广播,那么就可以进行一系列的交互了,否则只能等待下一次的广播事件。

bt_le_adv_param

struct bt_le_adv_param {
    uint8_t  id;
    uint8_t  sid;
    uint8_t  secondary_max_skip;
    /** Bit-field of advertising options */
    uint32_t options;
    /** Minimum Advertising Interval (N * 0.625) */
    uint32_t interval_min;
    /** Maximum Advertising Interval (N * 0.625) */
    uint32_t interval_max;
    /**
     * @brief Directed advertising to peer
     *
     * When this parameter is set the advertiser will send directed
     * advertising to the remote device.
     *
     * The advertising type will either be high duty cycle, or low duty
     * cycle if the BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY option is enabled.
     * When using @ref BT_LE_ADV_OPT_EXT_ADV then only low duty cycle is
     * allowed.
     *
     * In case of connectable high duty cycle if the connection could not
     * be established within the timeout the connected() callback will be
     * called with the status set to @ref BT_HCI_ERR_ADV_TIMEOUT.
     */
    const bt_addr_le_t *peer;
};

这里传统广播只需要关注 optionsinterval_mininterval_maxpeer 即可。

  • options: 广播包的类型。

这里的类型定义了很多,我这里列几个常用的,更全面地需要参考代码。

BT_LE_ADV_OPT_CONNECTABLE:可连接的广播包。

BT_LE_ADV_OPT_USE_NAME:广播使用 GAP 设备名称。

BT_LE_ADV_OPT_SCANNABLE:可扫描的广播包。

BT_LE_ADV_OPT_DISABLE_CHAN_37:不允许在通道 37 打广播。

  • interval_min: 最小的广播间隔。
  • interval_max: 最大的广播间隔。
  • peer: 定向广播的地址。若该地址被设置,则广播包自动变为定向的,否则为不定向的。

协议栈也提供了辅助宏来帮助我们初始化广播参数。

BT_LE_ADV_PARAM_INIT

#define BT_LE_ADV_PARAM_INIT(_options, _int_min, _int_max, _peer) \
{ \
    .id = BT_ID_DEFAULT, \
    .sid = 0, \
    .secondary_max_skip = 0, \
    .options = (_options), \
    .interval_min = (_int_min), \
    .interval_max = (_int_max), \
    .peer = (_peer), \
}

BT_LE_ADV_PARAM_INIT 宏传入选项,广播间隔以及对端地址来初始化一个传统广播的参数。

当前,我们一般不直接使用这个来初始化广播参数,协议栈提供了一种更便利的方式。

BT_LE_ADV_PARAM

#define BT_LE_ADV_PARAM(_options, _int_min, _int_max, _peer) \
    ((struct bt_le_adv_param[]) { \
        BT_LE_ADV_PARAM_INIT(_options, _int_min, _int_max, _peer) \
     })

协议栈利用 BT_LE_ADV_PARAM 来构造了一个 struct bt_le_adv_param 类型的数组,里面只有一个元素,并且该参数宏展开后的含义是该数组的数组名,也就是广播参数的首地址。

借助该参数宏,协议栈定义了一系列更便捷的宏供我们使用。

/* 可连接的,定向的广播包 */
#define BT_LE_ADV_CONN_DIR(_peer) BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONNECTABLE |  \
                          BT_LE_ADV_OPT_ONE_TIME, 0, 0,\
                          _peer)

/* 可连接的,不定向的广播包 */
#define BT_LE_ADV_CONN BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONNECTABLE, \
                       BT_GAP_ADV_FAST_INT_MIN_2, \
                       BT_GAP_ADV_FAST_INT_MAX_2, NULL)
/* 可连接的,不定向的广播包,并且带有 GAP 设备名 */
#define BT_LE_ADV_CONN_NAME BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONNECTABLE | \
                        BT_LE_ADV_OPT_USE_NAME, \
                        BT_GAP_ADV_FAST_INT_MIN_2, \
                        BT_GAP_ADV_FAST_INT_MAX_2, NULL)

这里只列举出了这三个宏,全部的辅助宏感兴趣的可以去参考代码。如果没有我们需要的,我们自己也可以按照这个格式实现。

可以看出本质上就是借用 BT_LE_ADV_PARAM 来构建了一个 bt_le_adv_param 参数,并获取了其地址,只不过中间利用了一些宏的技巧而已。

广播控制

协议栈提供了接口 bt_le_adv_start 来启动广播,bt_le_adv_update_data 来更新数据,bt_le_adv_stop 来停止广播。

bt_le_adv_start

int bt_le_adv_start(const struct bt_le_adv_param *param,
            const struct bt_data *ad, size_t ad_len,
            const struct bt_data *sd, size_t sd_len)

启动广播,并同时设置广播数据和扫描回复数据。

  • 返回值

    • 0 表示成功,其他值表示失败。
  • 参数

    • param:广播参数,bt_le_adv_param 结构体类型的指针,可有上述辅助宏创建。
    • ad, ad_len:广播数据数组的首地址,以及数组的长度。
    • sd, sd_len : 扫描回复数据数组的首地址,以及数组的长度。扫描回复数据与广播数据一模一样。

例如:

static const struct bt_data ad[] = {
    BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_NO_BREDR),
    BT_DATA_BYTES(BT_DATA_UUID16_ALL, 0xaa, 0xfe),
};
/* Set Scan Response data */
static const struct bt_data sd[] = {
    BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
};
{
    ...
    bt_le_adv_start(BT_LE_ADV_CONN, ad, ARRAY_SIZE(ad),
                  sd, ARRAY_SIZE(sd));
    ...
}

bt_le_adv_update_data

int bt_le_adv_update_data(const struct bt_data *ad, size_t ad_len,
              const struct bt_data *sd, size_t sd_len);

更新广播数据。

  • 返回值

    • 0 表示成功,其他值表示失败。
  • 参数

    • ad, ad_len:广播数据数组的首地址,以及数组的长度。
    • sd, sd_len : 扫描回复数据数组的首地址,以及数组的长度。扫描回复数据与广播数据一模一样。

bt_le_adv_stop

int bt_le_adv_stop(void);

停止广播。

  • 返回值
    • 0 表示成功,其他值表示失败。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356

推荐阅读更多精彩内容