前言
(1)学习本文之前,需要先了解一下蓝牙的基本概念:BLE学习笔记(0.0) —— 基础概念(0)
(2) 学习一款芯片的蓝牙肯定需要先简单了解一下该芯片的体系结构,因此本文将会简单的介绍ESP32
的蓝牙结构。
(3)因为乐鑫目前主推的是BLE
低功耗蓝牙技术,因此我本人也主要侧重讲解BLE
部分。
ESP32蓝牙系统介绍
蓝牙堆栈
(1)
ESP-IDF
目前支持两个主机堆栈,Bluedroid
(默认) 和Apache NimBLE
。
- Bluedroid : 该堆栈支持传统蓝牙(
BR/EDR
)和低功耗蓝牙(BLE
)。如果是传统蓝牙(BR/EDR
)有需求,则必须使用该堆栈。- Apache NimBLE : 仅支持低功耗蓝牙。如果仅仅是对
BLE
有使用需求,建议选择该协议栈,因为该协议栈代码占用和运行时对内存的需求都会低一些。
蓝牙架构
(1)我们知道,蓝牙从整体架构上可以分为控制器 (
Controller
) 和主机 (Host
) 。
- 控制器 (
Controller
) : 通常是一个物理设备,它能够发送和接收无线电信号,并懂得如何将这些信号翻译成携带信息的数据包。主要用于硬件接口管理、链路管理等等。- 主机 (
Host
) : 它通常是一个软件协议栈,用于管理两台或多态设备间如何通讯以及如何实现无线电同时提供几种不同服务。它可以构建各种规范,向上层应用提供接口基础,方便应用层对蓝牙系统的访问编程。(2)首先,我们需要知道为什么蓝牙需要分为控制器 (
Controller
) 和主机 (Host
) 两层结构。这个时候我们就需要了解一下蓝牙技术设计初衷了,蓝牙致力于打造一种低成本的无线通讯方案,要实现低成本那么就需要有较大的销量。如今,手机作为当之无愧销量最大的消费电子设备,任何一项技术一旦进入了手机就非常容易取得成功。因此,低功耗蓝牙将会依附于蓝牙在手机上的高配售率快速拓宽市场。
(3)既然你要依附手机,那么很多东西都要从手机厂的角度进行思考问题。那么,如果你仔细阅读蓝牙核心规格,你会发现规格书更多地是站在手机角度来阐述的,然后“顺带”描述一下手机周边蓝牙设备的实现原理。
(4)大家都知道,手机厂一般不只是做手机,还会做一些手机相关的周边设备,例如华为不仅仅做华为手机,还有华为平板,华为耳机,华为电脑等等。华为手机和华为电脑只要通过无线网络连接在一起,就能够无感控制对方并且传输数据。但是,如果华为手机和苹果电脑组合在一起,却做不到无感控制对方。这是为什么呢?
(5)因为,手机厂都会在自家设备上跑一套协议栈用于适配自家的电子产品。协议栈设计的越好,手机厂自家的各种设备间信息传输更稳定,安全,用户体验也越好。因此手机厂不会把自家的协议栈分享给别人一起使用,这也导致的华为手机和苹果电脑之间部分功能无法实现的原因。(注意,虽然不同手机厂协议栈不一样,但还是符合SIG标准和规范的,因此大部分功能能够互通)
(6)既然手机厂的协议栈是不进行公开的,那么就存在一个问题,如果手机厂好不容易将自家的协议栈部署在一颗蓝牙芯片上,突然发现又有一颗性能更好、更便宜的蓝牙芯片了,需要更换芯片怎么办?这个时候需要更换部署,对于手机厂来说成本太高。因此SIG
将跑协议栈的Host
层与硬件管理的Control
层进行隔离,中间统一一个接口标准HCI
层。这样的话,手机厂只需要在AP
芯片上跑协议栈Host
层,而负责硬件的Control
层单独一个芯片。当需要更换蓝牙芯片的时候,直接换即可,因为都是统一的HCI
接口。
(7)现在我们有了上述基础,再来看看
ESP32
的蓝牙结构。ESP32
有一个Control
层,负责物理层相关的处理。Host
层就有三种应用场景:
- 单芯片跑蓝牙程序:使用
Control
层和Host
层都运行在ESP32
上,Host
层有三种选择,乐鑫官方提供的Bluedroid
(默认) 和NimBLE
协议栈,或者自己在ESP32
上编写一个自己的协议栈。- 双芯片跑蓝牙程序:在
ESP32
上运行Control
层,外接一个运行蓝牙协议栈的Host
层。- 认证测试:我们如果想要使用
ESP32
作为开发用的蓝牙芯片,那么就需要知道它是否符合一些认证标准。因此就可以使用一个UART
外接PC
机进行认证测试。
代码分析
(1)我们首先需要找到gatt_server_service_table例程根据上面所说的知识分析一下
app_main()
函数做了什么。
(2)该例程虽然是使用的Bluedroid
协议栈,但是只用了BLE
部分。
<1>esp_bt_controller_mem_release()
因为我们这里只需要BLE
部分,所以需要先将传统蓝牙的内存进行释放。
// 释放经典蓝牙控制器内存
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
<2>
esp_bt_controller_init()
是对Control
层进行初始化,BT_CONTROLLER_INIT_CONFIG_DEFAULT()
是一个宏作为蓝牙初始化的默认参数,这个宏默认仅初始化BLE
部分。
// 初始化蓝牙 Control 层
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
if (ret) {
ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
<3>我们对蓝牙
Control
初始化完成之后,需要调用esp_bt_controller_enable()
对Control
层进行使能。传入值是一个enum
参数。
ESP_BT_MODE_IDLE
: 失能蓝牙ESP_BT_MODE_BLE
: 仅运行低功耗蓝牙(BLE
)ESP_BT_MODE_CLASSIC_BT
: 仅运行传统蓝牙(BR/EDR
)ESP_BT_MODE_IDLE
: 即运行低功耗蓝牙(BLE
)又运行传统蓝牙(BR/EDR
)
// 使能蓝牙 Control 层
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
<4>如果我们的
Host
层不是运行在ESP32
上,那么只需要进行如上操作即可。但是该例程是采用的单芯片方案,因此还需要对Host
层进行初始化,这是使用的Bluedroid
协议栈。
// 初始化蓝牙 HOST 层
ret = esp_bluedroid_init();
if (ret) {
ESP_LOGE(GATTS_TABLE_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
<5>初始化
bluedroid
之后,再进行使能即可。
// 使能蓝牙 HOST 层
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(GATTS_TABLE_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
<6>如下部分在后面章节会进一步讲解,各位看一下代码注释简单了解即可。
// 注册 GATT 回调函数,处理所有的 GATT 事件
ret = esp_ble_gatts_register_callback(gatts_event_handler);
if (ret){
ESP_LOGE(GATTS_TABLE_TAG, "gatts register error, error code = %x", ret);
return;
}
// 注册 GAP 回调函数,
ret = esp_ble_gap_register_callback(gap_event_handler);
if (ret){
ESP_LOGE(GATTS_TABLE_TAG, "gap register error, error code = %x", ret);
return;
}
/* 注册一个app_id, 协议栈将会分配一个对应的 gatts_if,用于标识一个 GATT 服务。
* 调用这个函数就会触发 esp_ble_gatts_register_callback() 注册的回调函数中的 ESP_GATTS_REG_EVT 事件
*/
ret = esp_ble_gatts_app_register(heart_rate_profile_tab[PROFILE_APP_IDX].app_id);
if (ret){
ESP_LOGE(GATTS_TABLE_TAG, "gatts app register error, error code = %x", ret);
return;
}
// 设置本地 MTU 大小
esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500);
if (local_mtu_ret){
ESP_LOGE(GATTS_TABLE_TAG, "set local MTU failed, error code = %x", local_mtu_ret);
}
参考
(1)乐鑫官方文档:ESP32蓝牙架构
(2)《低功耗蓝牙开发权威指南》第三章 —— 低功耗蓝牙的体系结构。
(3)《低功耗蓝牙开发权威指南》2.9章节 —— 十亿只是小目标。
(4)博客园:三种蓝牙架构实现方案(蓝牙协议栈方案)