BLE 开发¶
FreeRTOS 应用的 BLE 由两部分组成:Zephyr v4.4.0 的 BLE Host(源代码)与 IPRO BLE 控制器(预编译库)。应用使用 Zephyr 的 bt_* 回调 API 编写,.config 中的三个配置项决定从 16 个控制器归档文件中链接哪一个。本页示例取自 apps/bluetooth/ble/ipro_ble_remote,以 defconfig_reef 为 IPRO7AI Reef EVK 构建。
要点
- Host:Zephyr v4.4.0,含 GAP、GATT、L2CAP、SMP、ISO 与 LE Audio;Host 与控制器之间是芯片内的 HCI,没有 UART。
- 控制器:16 个 IPRO7 归档文件,
link1_periph/link1/link2/link4×bt52/bt54_iso2× 调试版/release 版,由CONFIG_IPRO_BLE_CTLR_LIB、CONFIG_IPRO_BLE_CTLR_FEATURE、CONFIG_IPRO_BLE_CTLR_CFG_DBG选择。 - 初始化顺序:
ipro_ble_ctlr_controller_init()→hci_driver_ipro_ble_ctlr_init()→bt_enable()。 - Reef 的 BLE 配置:
ipro_ble_remote、ipro_throughput_test、ipro_le_audio_headset加DEFCONFIG=defconfig_reef,以及ipro_agora_sentino(BLE 配网)。 - 未包含于 V2.2.1:控制器源代码、PAwR/ESL 应用、IPRO6S 的 BLE 控制器。
协议栈架构¶
ipro_bt_host/
├── upstream/zephyr/ Zephyr v4.4.0 Host 源代码(include/、subsys/bluetooth/)
├── port/ FreeRTOS 移植层:HCI 驱动、kernel 接口、设置存储、加密
└── tinycrypt/
ipro_ble_controller/
├── include/ 公开头文件 ipro_ble_controller.h
├── hcitl/ 芯片内 HCI 传输
├── plf/ipro7/ 控制器 API 头文件(初始化、低功耗、NVDS)
├── lib/ 16 个 IPRO7 预编译归档文件
└── cmake/ 归档文件选择规则(ipro7_build.cmake)
每个 BLE 应用的 .config 都需要:
缺少 CONFIG_BT_CTLR_IPRO 或 CONFIG_BT_HOST_IPRO_V44 时,构建在 CMake 配置阶段就会停止。Host 与控制器组件会导出自己的 include 路径,应用的 CMakeLists.txt 不需要另外添加 zephyr/bluetooth/*.h 的路径。
Zephyr RTOS 镜像使用 Zephyr 自身的 Host,配合同一组控制器归档文件;V2.2.1 的 Zephyr 只有 IPRO7 EVB 与 IPRO7AI EVK 的板级定义,见 Zephyr RTOS。
控制器选择¶
| 配置项 | 值 | 未设置时 | 含义 |
|---|---|---|---|
CONFIG_IPRO_BLE_CTLR_LIB |
link1_periph、link1、link2、link4 |
link1_periph |
link1_periph 只有 peripheral 与 broadcaster 角色;其余的数字是连接数 |
CONFIG_IPRO_BLE_CTLR_FEATURE |
bt52、bt54_iso2 |
bt52 |
bt54_iso2 加入 ISO 通道(LE Audio) |
CONFIG_IPRO_BLE_CTLR_CFG_DBG |
y 或不设置 |
不设置 | 不设置时链接 _release 归档文件;设为 y 时链接调试版 |
文件名规则为 libipro_ble_controller_ipro7_<LIB>[_bt54_iso2][_release].a,bt52 没有功能后缀。lib/ 下的 16 个归档文件:
| LIB | bt52 |
bt52 release |
bt54_iso2 |
bt54_iso2 release |
|---|---|---|---|---|
link1_periph |
…_link1_periph.a |
…_link1_periph_release.a |
…_link1_periph_bt54_iso2.a |
…_link1_periph_bt54_iso2_release.a |
link1 |
…_link1.a |
…_link1_release.a |
…_link1_bt54_iso2.a |
…_link1_bt54_iso2_release.a |
link2 |
…_link2.a |
…_link2_release.a |
…_link2_bt54_iso2.a |
…_link2_bt54_iso2_release.a |
link4 |
…_link4.a |
…_link4_release.a |
…_link4_bt54_iso2.a |
…_link4_bt54_iso2_release.a |
配置阶段会检查:
CONFIG_EM_SIZE(BLE Exchange Memory,单位 KB)必须设置。bt54_iso2要求CONFIG_EM_SIZE至少为 32。LIB或FEATURE的值不在上表中时,构建停止。
CONFIG_BT_CONN(Host 的连接数)不可超过归档文件的连接数。Exchange Memory 从 OCRAM 顶端保留,见内存布局。
V2.2.1 随附的 Reef 配置可作为起点:
| 应用与配置 | LIB | FEATURE | 调试版归档文件 | CONFIG_BT_CONN |
CONFIG_EM_SIZE |
|---|---|---|---|---|---|
ipro_ble_remote defconfig_reef |
link1_periph |
bt52 |
否 | 1 | 16 |
ipro_throughput_test defconfig_reef |
link2 |
bt52 |
否 | 2 | 32 |
ipro_le_audio_headset defconfig_reef |
link1_periph |
bt54_iso2 |
是 | 1 | 32 |
ipro_agora_sentino .config |
link1_periph |
bt52 |
否 | 1 | 16 |
ipro_ble_transparent、ipro_ble_hid_keyboard 与 ipro_pdm_mic_demo 是 IPRO7 EVB 与 IPRO7AI EVK 的 BLE 示例,不能为 Reef 构建。
CONFIG_BLUETOOTH_LE_ENABLE=y
CONFIG_BT_CTLR_IPRO=y
CONFIG_IPRO_BLE_CTLR_LIB="link1_periph"
CONFIG_IPRO_BLE_CTLR_FEATURE="bt52"
CONFIG_EM_SIZE=16
CONFIG_BT_CONN=1
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_OBSERVER=y
CONFIG_BT_BROADCASTER=y
CONFIG_PHY_UPDATE=y
CONFIG_BT_DATA_LEN_UPDATE=y
CONFIG_BT_HOST_IPRO_V44=y
CONFIG_BT_SMP=y
CONFIG_BT_GATT_CLIENT=y
CONFIG_ENABLE_NVDS=y
CONFIG_CFG_NVDS=y
构建不解析 Kconfig 的依赖关系,请从最接近的随附应用复制整份配置文件再修改,见构建系统与配置。
初始化顺序¶
flowchart LR
A["ipro_ble_ctlr_controller_init()"] --> B["hci_driver_ipro_ble_ctlr_init()"] --> C["bt_enable(bt_ready_cb)"] --> D["bt_ready_cb:<br/>host_ready、加载设置、<br/>注册回调、开始广播"]
ipro_ble_ctlr_controller_init(configMAX_PRIORITIES - 1);
vTaskDelay(pdMS_TO_TICKS(50));
hci_driver_ipro_ble_ctlr_init();
vTaskDelay(pdMS_TO_TICKS(50));
bt_enable(bt_ready_cb);
- 在调度器启动后的 FreeRTOS 任务中执行;中间的
vTaskDelay()需要调度器。 ipro_ble_ctlr_controller_init()创建控制器任务。控制器有无线电时序的硬性时限,优先级要高于应用任务;随附应用使用configMAX_PRIORITIES - 1。成功返回 0,控制器已启动过返回 -1,任务创建失败返回 -2。- 在
bt_ready_cb中先调用ipro_ble_ctlr_hcitl_host_ready(),告知 HCI 初始化完成,控制器才能在事件之间睡眠。 - 启用
CONFIG_BT_SETTINGS时,在bt_ready_cb中先用settings_load_subtree("bt")加载配对数据,再开始广播。 - Reef 没有 32.768 kHz 晶振。根据
ipro_throughput_test的 README,Reef 在复位后约 7 秒才打印 BLE 就绪的消息,因为控制器会先检验 32 kHz 时钟,再回退到 RC32K(日志中出现XTAL32K rejected)。
API 用法¶
以下三段按顺序放在同一个 .c 文件中即可编译,内容取自 ipro_ble_remote 的 src/services/remote_services.c 与 src/app/remote_app.c。应用使用 Zephyr 的回调式 API;控制器内部的消息机制不对应用开放。
GATT 服务¶
使用 BT_GATT_SERVICE_DEFINE 在编译期定义服务。下例是一个可读取并通知温度的服务(UUID 沿用 ipro_ble_remote 的环境服务 0xFF30/0xFF31;在 Reef 上报告芯片内置温度传感器的读数):
#include <FreeRTOS.h>
#include <task.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/settings/settings.h>
#include <hci_driver_ipro_ble_ctlr.h>
#include <ipro_ble_controller.h>
#define BT_UUID_ENV_SVC BT_UUID_DECLARE_16(0xFF30)
#define BT_UUID_ENV_TEMP BT_UUID_DECLARE_16(0xFF31)
static int16_t temperature = 2500; /* 0.01 °C */
static bool temp_notify_enabled;
static ssize_t temp_read(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
return bt_gatt_attr_read(conn, attr, buf, len, offset,
&temperature, sizeof(temperature));
}
static void temp_ccc_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
temp_notify_enabled = (value == BT_GATT_CCC_NOTIFY);
}
BT_GATT_SERVICE_DEFINE(env_svc,
BT_GATT_PRIMARY_SERVICE(BT_UUID_ENV_SVC),
BT_GATT_CHARACTERISTIC(BT_UUID_ENV_TEMP,
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_READ,
temp_read, NULL, NULL),
BT_GATT_CCC(temp_ccc_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
);
/* attr_env_svc[1] 是温度特征值的声明;
* bt_gatt_notify() 使用紧随其后的值属性 */
static int notify_temperature(struct bt_conn *conn)
{
if (!temp_notify_enabled) {
return 0;
}
return bt_gatt_notify(conn, &attr_env_svc[1], &temperature, sizeof(temperature));
}
广播与连接¶
#define DEVICE_NAME "IPRO-Remote"
#define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1)
static struct bt_conn *current_conn;
static const struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
};
static int start_advertising(void)
{
struct bt_le_adv_param adv_param = {
.id = BT_ID_DEFAULT,
.options = BT_LE_ADV_OPT_CONN, /* 可连接 */
.interval_min = 160, /* 100 ms,单位 0.625 ms */
.interval_max = 320, /* 200 ms */
};
return bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad), NULL, 0);
}
static void connected(struct bt_conn *conn, uint8_t err)
{
if (err) {
return;
}
current_conn = bt_conn_ref(conn);
(void)notify_temperature(current_conn);
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
(void)conn;
(void)reason;
if (current_conn) {
bt_conn_unref(current_conn);
current_conn = NULL;
}
/* 此回调在 Host 线程中执行,不可阻塞。ipro_ble_remote 不在这里
* 重新广播,而是用 FreeRTOS 定时器在 500 ms 后重新开始。 */
}
static struct bt_conn_cb conn_callbacks = {
.connected = connected,
.disconnected = disconnected,
};
本版本的 Host 没有 BT_LE_ADV_CONN_NAME 宏。请如上例把设备名称放进广播数据,或使用 Host 预定义的参数宏,例如 BT_LE_ADV_CONN_FAST_1。
启动¶
static void bt_ready_cb(int err)
{
if (err) {
return;
}
ipro_ble_ctlr_hcitl_host_ready();
#if defined(CONFIG_BT_SETTINGS)
(void)settings_load_subtree("bt");
#endif
bt_conn_cb_register(&conn_callbacks);
(void)start_advertising();
}
/* 在应用任务中调用 */
int ble_init(void)
{
int err;
err = ipro_ble_ctlr_controller_init(configMAX_PRIORITIES - 1);
if (err) {
return err;
}
vTaskDelay(pdMS_TO_TICKS(50));
err = hci_driver_ipro_ble_ctlr_init();
if (err) {
return err;
}
vTaskDelay(pdMS_TO_TICKS(50));
return bt_enable(bt_ready_cb);
}
- 要把配对数据存入 Flash,设置
CONFIG_BT_SETTINGS=y与CONFIG_EASYFLASH4=y(Host 的设置存储使用 EasyFlash,数据写在分区表的PSM分区中)。ipro_le_audio_headset的defconfig_reef就是这样配置的;ipro_ble_remote的defconfig_reef不保存配对数据。 - 连接后提高传输速率:
ipro_ble_remote在connected中用bt_gatt_exchange_mtu()交换 MTU,并在CONFIG_BT_USER_DATA_LEN_UPDATE时用bt_conn_le_data_len_update()请求 251 字节的 DLE。
在 Reef 上动手实践¶
把 ipro_ble_remote 构建给 Reef,从手机或电脑连接,通过 GATT 改变 Reef 上 RGB LED 的颜色。需要:U6 已接好控制台线的 Reef、IntelProCube 命令行工具(下文用 <ICUBE> 表示),以及装有通用 BLE App(nRF Connect、LightBlue)的手机,或装有 Python 并带蓝牙的 macOS/Linux 电脑。
-
确认配置
defconfig_reef随附即已完整。下面是其中与本例相关的配置:2 MB Flash 与 boot2 分区表的 Reef 开发板配置、应用本身的 OTA 开关,以及 BLE 部分。新的 Reef BLE 应用保留开发板与 BLE 的配置即可;CONFIG_IPRO_BLE_REMOTE_OTA只属于ipro_ble_remote。apps/bluetooth/ble/ipro_ble_remote/defconfig_reef(节选)CONFIG_IPRO7AI_REEF=y CONFIG_IPRO7_FLASH_2M=y CONFIG_PARTITION=y CONFIG_IPRO_BLE_REMOTE_OTA=y CONFIG_BLUETOOTH_LE_ENABLE=y CONFIG_BT_CTLR_IPRO=y CONFIG_IPRO_BLE_CTLR_LIB="link1_periph" CONFIG_IPRO_BLE_CTLR_FEATURE="bt52" CONFIG_EM_SIZE=16 CONFIG_BT_CONN=1 CONFIG_BT_PERIPHERAL=y CONFIG_BT_BROADCASTER=y CONFIG_BT_HOST_IPRO_V44=y CONFIG_BT_SMP=y -
看懂要写入的特征值
Reef 专用的 0xFF80 服务有一个 LED Colour 特征值 0xFF81:3 个字节 R、G、B。写入处理函数检查长度、记录颜色,再交给 LED 驱动:
apps/bluetooth/ble/ipro_ble_remote/src/services/reef_service.c(节选)static ssize_t color_write(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { const uint8_t *d = buf; (void)conn; (void)attr; (void)flags; if (offset != 0u) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); } if (len != sizeof(s_ctx.rgb)) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); } memcpy(s_ctx.rgb, d, sizeof(s_ctx.rgb)); IPRO_LOGI(LOG_TAG, "[LED] Colour %u,%u,%u", d[0], d[1], d[2]); if (s_ctx.h.led_color) { s_ctx.h.led_color(d[0], d[1], d[2]); } return len; } BT_GATT_SERVICE_DEFINE(reef_svc, BT_GATT_PRIMARY_SERVICE(BT_UUID_REEF_SVC), BT_GATT_CHARACTERISTIC(BT_UUID_REEF_LED_COLOR, BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE, color_read, color_write, NULL), /* ... 按键事件、音量、提示音与开发板信息特征值 ... */ );这个文件的
LOG_TAG是"REEF_SVC"。要加入自己的特征值,按同样方式多写一个BT_GATT_CHARACTERISTIC项与读写处理函数。 -
构建
输出:
apps/bluetooth/ble/ipro_ble_remote/build_reef/build_out/ipro_ble_remote_IPRO7.bin。 -
连同 boot2 与 A/B 分区表一起烧录
让 Reef 进入下载模式(按住 SW1 BOOT、按一下 SW2 RESET、松开 SW1;或在运行中的控制台输入
reboot_dl),再在 IntelProCube 目录下烧录:cd <ICUBE> ./ipro_iot_tool_macos --chipname ipro7 --chip-variant IPRO7AI \ --port /dev/cu.usbserial-XXXX --baudrate 921600 \ --boot2 chips/ipro7/builtin_imgs/boot2_v0.6.0/ipro_boot2_IPRO7_v0.6.0.bin \ --pt <SDK>/apps/bluetooth/ble/ipro_ble_remote/partition_cfg_2M_remote_ab.toml \ --firmware <SDK>/apps/bluetooth/ble/ipro_ble_remote/build_reef/build_out/ipro_ble_remote_IPRO7.bin烧录失败时工具也返回 0,请确认输出中有
[All Success],再按 SW2(RESET)。这份分区表就是 OTA 更新使用的布局;烧录细节见烧录与串口。 -
查看控制台并连接
以 115200 baud 打开控制台。在 boot2 的
[BOOT2] IPRO7 Boot2 v0.6.0之后,应用会打印下列消息(省略日志前缀,中间还有其他行):[BLE] Initialized [BLE] Services ready [OTA] Service ready [BLE] All services initialized [BLE] Advertising: IPRO-Remote连接到
IPRO-Remote,向特征值 0xFF81 写入FF 00 00。控制台打印Connected! Peer: …,接着是[LED] Colour 255,0,0。再向 0xFF11(LED State)写入01,LED 就以这个颜色点亮。没有手机时,可在电脑上运行随附的 bleak 测试,它会写入颜色并读回开发板信息:python3 -m venv .venv && .venv/bin/pip install bleak .venv/bin/python apps/bluetooth/ble/ipro_ble_remote/tools/reef_ble_test.py info led屏幕上打印
board info:与LED colour [255, 0, 0] …,LED 以红色闪烁。在控制台输入reef status可从开发板端看到同样的状态。
下一步:以 Reef 配置为起点创建自己的应用,见创建第一个应用;用控制台与主机端脚本检查应用,见测试应用。配合 Android App 的完整 GATT 操作见第一个 BLE 应用。
性能与吞吐量¶
ipro_throughput_test 的 README 实测:两块运行 defconfig_reef 的 Reef EVK(link2 控制器),ATT MTU 247,DLE 251 octets,每个数据包 244 字节,连接间隔 50 ms,启用 CONFIG_IPRO7_BLE_HOTCODE=y:
| PHY | 单向吞吐量 |
|---|---|
| 2M | 1.34 Mbps |
| 1M | 747 kbps |
CONFIG_IPRO7_BLE_HOTCODE=y把控制器、Host 与 FreeRTOS kernel 的每包处理路径放到 OCRAM 中执行,约占 88 KB OCRAM。相同条件下不启用时(代码在 Flash 中),2M PHY 约 0.6 Mbps。- 此选项不能与 AI+图像的 OCRAM 布局,或低功耗配置(
CONFIG_HAL_LP、CONFIG_IPRO_PM_*、CONFIG_OCRAM_NORET_SPLIT)同时使用,链接脚本生成器会拒绝。 - 大量数据建议使用 2M PHY、连接间隔 30–50 ms、244 字节的数据包。
测试在控制台中用 tp slave、tp master、tp conn、tp phy、tp start 操作,见 BLE 吞吐量测试。
LE Audio¶
LE Audio 需要 CONFIG_IPRO_BLE_CTLR_FEATURE="bt54_iso2" 与 CONFIG_EM_SIZE=32,再加上 CONFIG_BT_AUDIO=y 与 BAP、PACS、ASCS 等角色配置,LC3 通过 CONFIG_COMPONENTS_LC3_ENABLE=y 加入。Host 的 LE Audio 头文件在 ipro_bt_host/upstream/zephyr/include/zephyr/bluetooth/audio/。随附的 FreeRTOS 示例是单播耳机 ipro_le_audio_headset,以 DEFCONFIG=defconfig_reef 为 Reef 构建;配置、协议、音频路径与当前状态见音频与 LE Audio。
低功耗¶
ipro_ble_remote的defconfig_reef不使用 PDS:Reef 由 USB 供电,摄像头与音频也需要时钟。- 控制器的 PDS 睡眠只接受 16 KB EM 的布局;在
bt54_iso2(32 KB EM)配置下,控制器保留原生睡眠,但不进入 PDS。 CONFIG_IPRO_PM_BLE与CONFIG_IPRO_PM_PDS_CONNECTED会强制从源代码构建控制器,V2.2.1 无法构建,见本版本的限制。- BLE 配合 PDS31 睡眠与配对后重新连接的示例是
ipro_pdm_mic_demo,属于 IPRO7 EVB/IPRO7AI EVK。
睡眠模式、唤醒源与测量方法见低功耗。
本版本的限制¶
- 控制器只以预编译库提供,V2.2.1 不含其源代码。以下配置需要从源代码构建控制器,会让构建停止:
CONFIG_IPRO_BLE_CTLR_FORCE_SOURCE_BUILD、CONFIG_IPRO_BLE_CTLR_SUBRATING、CONFIG_IPRO_BLE_CTLR_CSSA、CONFIG_IPRO_PM_BLE、CONFIG_IPRO_PM_PDS_CONNECTED。 - PAwR 与 ESL:Host 头文件含周期性广播相关 API,但没有 PAwR/ESL 应用。未包含于 V2.2.1,请联系 INTELPRO。
ipro_ble_remote的 OTA 服务只在defconfig_reef中开启;默认.config构建的是不含 OTA 的 IPRO7 EVB 镜像。- V2.2.1 不含 IPRO6S 的 BLE 控制器;本套件中的 IPRO6S 应用只有 Wi-Fi。
参考应用¶
| 应用(Reef 构建) | 内容 |
|---|---|
ipro_ble_remote(DEFCONFIG=defconfig_reef) |
Reef 的 GATT 服务:WS2812 LED、按键、芯片温度、UART、OV5640 JPEG 拍照与移动侦测、图片上传、PDM 麦克风音量、提示音,以及 BLE OTA。附带 Android App ipro_remote_demo.apk 与主机端脚本 tools/reef_ble_test.py、tools/reef_ota.py |
ipro_throughput_test(DEFCONFIG=defconfig_reef) |
双板吞吐量测试:1M/2M/Coded PHY、DLE、MTU 247 |
ipro_le_audio_headset(DEFCONFIG=defconfig_reef) |
LE Audio 单播耳机 |
ipro_agora_sentino(.config) |
云端语音应用的 BLE 配网,Wi-Fi 经由 IPRO6S FIN 卡 |
ipro_ble_transparent(AT 接口的数据通道)、ipro_ble_hid_keyboard(HID over GATT)与 ipro_pdm_mic_demo 只能为 IPRO7 EVB 或 IPRO7AI EVK 构建。