BLE 广播/中心与定位示例
本文档介绍 AC63_BT_SDK 中 spp_and_le 工程内的 Find My(苹果查找网络)示例应用:设备作为 BLE 外设(广播者/Peripheral)实现 Apple FMNA 配件协议,可被 iOS 设备(中心/Central)发现、配对并执行播放声音、SN 查询、固件升级等定位与查找操作。
Purpose and Scope
本页面向 apps/spp_and_le/examples/findmy/ 目录下的完整示例应用,覆盖:
- 应用的注册方式与启动流程(
app_main.c→app_fmy.c的状态机与 intent 分发); - BLE 协议栈的初始化、广播(Advertising/Broadcaster)与连接(Central/Peripheral)角色配置;
- Find My 配件所必需的 GATT 服务与 FMNA 协议实现(
ble_fmy.c、ble_fmy_fmna.c); - 配对安全(SM)、低功耗与软关机、VM 配置存储、调试与日志开关。
本文档不展开的内容(请参见各自文档页):经典蓝牙(EDR/AVCTP/RCSP)相关能力、通用 LE 透传(le_trans)示例、OTA 升级的底层协议细节、以及 third_party/fmna 库的内部算法。这些属于兄弟主题,此处仅在涉及调用边界时提及。
Overview
什么是"定位示例"
findmy 示例在 SDK 中对应 CONFIG_APP_FINDMY 配置,注册的应用名为 findmy、动作值为 ACTION_FINDMY。它把一个杰理 AC63 系列蓝牙 SoC 模拟为苹果 Find My 网络中的"配件(Accessory)":
- 设备开机后进入 BLE 广播(Broadcast),广播内容符合 FMNA 规范,可被 iPhone/iPad 的"查找"App 发现;
- iOS 设备作为 BLE 中心(Central)发起连接,通过 FMNA 控制点特征完成配对、绑定(Owner)与各类操作;
- 设备侧通过 ATT 读写回调响应控制点命令:播放声音(
FMY_CAPABILITY_SUPPORTS_PLAY_SOUND)、SN 查询(FMY_CAPABILITY_SUPPORTS_SN_LOOKUP_BY_BLE)、固件升级(FMY_OTA_SUPPORT_CONFIG时使能FMY_CAPABILITY_SUPPORTS_FW_UPDATE_SERVICE)、运动检测(TCFG_GSENSOR_ENABLE时使能FMY_CAPABILITY_SUPPORTS_MOTION_DETECTOR_UT)。
广播/中心两种角色在本示例中的体现
| 角色 | 本示例中的实现 | 说明 |
|---|---|---|
| 广播者 Broadcaster(Peripheral) | fmy_set_adv_enable() / fmy_set_adv_mode(),广播周期 ADV_INTERVAL_MIN | 设备默认行为:持续广播 Find My 可发现包 |
| 中心 Central(Scanner) | 协议栈 btstack 的 LE 扫描/连接能力,config_btctler_le_rx_nums 按 CONFIG_APP_FINDMY 放大 RX 缓冲 | 本示例以被动响应为主;中心侧能力由协议栈通用接口提供 |
关键源文件
| 文件 | 职责 |
|---|---|
apps/spp_and_le/examples/findmy/app_fmy.c | 应用入口:启动、状态机、HCI 事件转发、软关机、电源事件 |
apps/spp_and_le/examples/findmy/ble_fmy.c | BLE 主实现:ATT 回调、FMNA 属性表、广播/连接控制、GATT 控制块 |
apps/spp_and_le/examples/findmy/ble_fmy.h | 全局结构 fmy_glb_t、VM 结构 fmy_vm_t、Profile 模式枚举与导出 API |
apps/spp_and_le/examples/findmy/ble_fmy_fmna.c/.h | FMNA 配件框架集成(第三方 fmna_api.h 的桥接) |
apps/spp_and_le/examples/findmy/ble_fmy_ota.c/.h | 固件升级服务(94110001-6D9B-4225-A4F1-6A4A7F01B0DE) |
apps/spp_and_le/examples/findmy/ble_fmy_sensor_uart.c/.h | 调试用 SENSOR UART 输出 |
apps/spp_and_le/examples/findmy/ble_fmy_cfg.h | 示例级配置项汇总 |
apps/spp_and_le/examples/findmy/ble_fmy_profile.h | GATT Profile 特征句柄定义 |
apps/spp_and_le/app_main.c | 应用任务表注册(task_info_table、ACTION_FINDMY) |
Architecture
flowchart TD
subgraph sg_App["应用层 (app_fmy.c)"]
AppStart["fmy_app_start()"]
StateMachine["fmy_state_machine()"]
HciHandler["fmy_bt_hci_event_handler()"]
PowerOff["fmy_set_soft_poweroff()"]
end
subgraph sg_Ble["BLE 协议层 (ble_fmy.c)"]
GattCb["fmy_att_read_callback / fmy_att_write_callback"]
EventHandler["fmy_event_packet_handler()"]
Adv["fmy_set_adv_enable() / fmy_set_adv_mode()"]
SmCfg["fmy_sm_init_config (SM 安全)"]
end
subgraph sg_Fmna["FMNA 配件框架"]
FmnaApi["third_party/fmna/fmna_api.h"]
FmnaAtt["fmna_att_handle_table (4F8600xx UUID)"]
Ota["ble_fmy_ota.c (固件升级)"]
end
subgraph sg_Ext["外设 / 存储 / 调试"]
Gsensor["gSensor/fmy/gsensor_api.h"]
Uart["ble_fmy_sensor_uart.c"]
Vm["VM 存储 (fmy_vm_info)"]
end
AppStart --> StateMachine
AppStart --> HciHandler
StateMachine --> AppStart
HciHandler --> PowerOff
GattCb --> FmnaAtt
FmnaAtt --> FmnaApi
EventHandler --> Adv
EventHandler --> GattCb
SmCfg --> GattCb
GattCb --> Ota
GattCb --> Gsensor
GattCb --> Uart
GattCb --> Vm
架构说明:
- 应用层:
fmy_app_start()是唯一入口,负责系统时钟、看门狗、BT 协议栈与外围(UART、按键)初始化;fmy_state_machine()是应用生命周期状态机,在APP_STA_START收到ACTION_FINDMY后启动;HCI 事件统一由fmy_bt_hci_event_handler()接收并转发给公共处理函数。 - BLE 协议层:
ble_fmy.c是核心,向协议栈注册三个回调(ATT 读、ATT 写、事件包),并持有 GATT 控制块(MTU、发送缓冲、服务配置)与 SM 安全配置。广播使能/模式、静态 MAC 读写、主动断开等操作均在此层。 - FMNA 框架:
fmna_att_handle_table把 FMNA 的控制点特征句柄(4F860001~4F860005与 OTA 的94110001)映射到第三方 FMNA 库,实现配对控制、Owner/Non-Owner 控制、调试控制与固件升级。 - 外围/存储:G-Sensor 提供运动检测能力位,SENSOR UART 提供调试输出,VM 保存配对/重置标志等掉电不丢失数据。
应用注册与启动流程
应用任务注册
示例应用通过 task_info_table 与 REGISTER_APPLICATION 宏挂接到系统。app_main.c 中在 CONFIG_APP_FINDMY 下注册 app_core 任务:
#if CONFIG_APP_FINDMY
{"app_core", 1, 0, 640 * 2, 128 },
Source: app_main.c
intent 携带 ACTION_FINDMY,应用名为 findmy:
#elif CONFIG_APP_FINDMY
it.name = "findmy";
it.action = ACTION_FINDMY;
Source: app_main.c
app_fmy.c 末尾通过 REGISTER_APPLICATION(app_fmy_action) 将名称与动作绑定:
REGISTER_APPLICATION(app_fmy_action) = {
.name = "findmy",
.action = ACTION_FINDMY,
Source: app_fmy.c
应用启动函数 fmy_app_start()
启动函数承担三件事:硬件加速(看门狗与主频)、BT 协议栈初始化(EDR/BLE 双模)、外围使能(按键、调试 UART)。设计意图:Find My 的 Apple 算法(AL)对 CPU 频率敏感,因此把系统主频固定提到 96 MHz,并把看门狗放宽到 32 秒以容纳较长的配对/加密流程。
static void fmy_app_start()
{
log_info("=======================================");
log_info("-----------findmy demo-------------");
log_info("=======================================");
log_info("app_file: %s", __FILE__);
wdt_init(WDT_32S);//need set long time
clk_set("sys", 96 * 1000000L);//need run fast for AL
log_info("vm start address: 0x%8x", CONFIG_VM_ADDR);
if (__fydata->enter_btstack_num == 0) {
__fydata->enter_btstack_num = 1;
//有蓝牙
#if (TCFG_USER_EDR_ENABLE || TCFG_USER_BLE_ENABLE)
uint32_t sys_clk = clk_get("sys");
bt_pll_para(TCFG_CLOCK_OSC_HZ, sys_clk, 0, 0);
#if TCFG_USER_EDR_ENABLE
btstack_edr_start_before_init(NULL, 0);
#if DOUBLE_BT_SAME_MAC
//手机自带搜索界面,默认搜索到EDR
__change_hci_class_type(BD_CLASS_TRANSFER_HEALTH);//
#endif
#endif
#if TCFG_USER_BLE_ENABLE
btstack_ble_start_before_init(&fmy_data_ble_config, 0);
#endif
btstack_init();
#else
//no bt,to for test
sys_timer_add(NULL, fmy_timer_handle_test, 1000);
#endif
}
/* 按键消息使能 */
sys_key_event_enable();
/* SENSOR UART使能 */
#if FMY_DEBUG_SENSOR_TO_UART_ENBALE
sensor_uart_init();
#endif
fmy_state_idle_set_active(false);
}
Source: app_fmy.c
要点解读:
enter_btstack_num防止重复进入应用时重复初始化协议栈(幂等保护)。fmy_data_ble_config配置same_address = 0、appearance = 0,即 BLE 使用独立地址、不声明外观类型,避免与 EDR 的发现行为冲突:static const ble_init_cfg_t fmy_data_ble_config = { .same_address = 0, .appearance = 0, };Source: app_fmy.c
DOUBLE_BT_SAME_MAC时修改 EDR 的 class of device 为BD_CLASS_TRANSFER_HEALTH,让手机搜索界面默认命中 EDR,避免与 Find My 的 BLE 广播互相干扰。
应用状态机 fmy_state_machine()
应用生命周期由系统按 APP_STA_* 状态驱动,示例只在 APP_STA_START 分支按 intent 动作分发到 fmy_app_start(),其余状态保持空实现(可扩展):
static int fmy_state_machine(struct application *app, enum app_state state, struct intent *it)
{
switch (state) {
case APP_STA_CREATE:
break;
case APP_STA_START:
if (!it) {
break;
}
switch (it->action) {
case ACTION_FINDMY:
fmy_app_start();
break;
}
break;
case APP_STA_PAUSE:
break;
case APP_STA_RESUME:
break;
case APP_STA_STOP:
break;
case APP_STA_DESTROY:
log_info("APP_STA_DESTROY\n");
break;
}
return 0;
}
Source: app_fmy.c
HCI 事件转发
所有蓝牙连接/断开事件统一进入 fmy_bt_hci_event_handler(),再按 EDR/BLE 使能情况转发给 bt_comm_* 公共处理,保证上层状态一致:
static int fmy_bt_hci_event_handler(struct bt_event *bt)
{
//对应原来的蓝牙连接上断开处理函数 ,bt->value=reason
log_info("----%s reason %x %x", __FUNCTION__, bt->event, bt->value);
#if TCFG_USER_EDR_ENABLE
bt_comm_edr_hci_event_handler(bt);
#endif
#if TCFG_USER_BLE_ENABLE
bt_comm_ble_hci_event_handler(bt);
#endif
return 0;
}
Source: app_fmy.c
BLE 协议层与 FMNA 实现(ble_fmy.c)
ATT/GATT 运行参数
ble_fmy.c 顶部用宏固定 ATT 参数,这些参数直接影响吞吐与内存占用:
| 宏 | 值 | 设计意图 |
|---|---|---|
PASSKEY_ENABLE | 0 | 默认免输入;置 1 后 SM 改为 IO_CAPABILITY_DISPLAY_ONLY 并启用 passkey 加密 |
ATT_MTU_REQUEST_ENALBE | 0 | 设备端不主动发起 MTU 交换;置 1 可主动发起 |
ATT_LOCAL_MTU_SIZE | 247 | 本端 ATT 发送包长,23 ≤ 值 ≥ MTU,支持大包 |
ATT_PACKET_NUMS_MAX | 4 | ATT 缓存支持的包个数(约需 1 KB) |
ATT_SEND_CBUF_SIZE | 4 * (ATT_PACKET_HEAD_SIZE + 247) | 发送环形缓冲总大小 |
ADV_INTERVAL_MIN | 160 * 5(单位 0.625 ms,约 500 ms) | 广播周期,兼顾低功耗与可发现性 |
FMY_MAX_CONNECTIONS | 2 | Find My 允许的最大连接数,不可修改 |
//广播周期 (unit:0.625ms)
#define ADV_INTERVAL_MIN (160 * 5)//
// FMY最大连接数,不可修改
#define FMY_MAX_CONNECTIONS 2
Source: ble_fmy.c
配件设备信息与能力位
设备名称、制造商、型号、配件类别、发射功率与电池类型都是 FMNA 规范要求暴露的静态信息。发射功率被固定声明为 +4 dBm(规范要求配件 ≥ +4 dBm):
static const char the_original_Name[] = "Find My Accessory";//"Accessory- Find My.";
static const char the_suffix_Name[] = "Acce- Find My.";
static const char FMY_ManufacturerName[64] = "Zhuhai Jieli Technology Co.,Ltd.";
static const char FMY_ModelName[64] = "JLtag";
static const uint8_t FMY_AccessoryCategory[8] = {FMY_CATEGORY_Finder, 0, 0, 0, 0, 0, 0, 0};
static const uint8_t read_tx_power_level = 4;//The Bluetooth LE transmit power level of the accessory shall be fixed at ≥ +4dBm
static const uint8_t fmy_battery_type = FMNA_BAT_NON_RECHARGEABLE;
Source: ble_fmy.c
能力位(Capabilities)通过位或组合,条件编译决定是否暴露某项能力,FMY_CHECK_CAPABILITIES 宏供上层查询:
static const uint8_t FMY_AccessoryCapabilities[4] = {
(FMY_CAPABILITY_SUPPORTS_PLAY_SOUND
#if FMY_OTA_SUPPORT_CONFIG
| FMY_CAPABILITY_SUPPORTS_FW_UPDATE_SERVICE
#endif
#if TCFG_GSENSOR_ENABLE
| FMY_CAPABILITY_SUPPORTS_MOTION_DETECTOR_UT
#endif
| FMY_CAPABILITY_SUPPORTS_SN_LOOKUP_BY_BLE),
0x00, 0x00, 0x00
};
#define FMY_CHECK_CAPABILITIES(bit) ((FMY_AccessoryCapabilities[0] & bit) != 0)
Source: ble_fmy.c
FMNA 属性句柄映射
FMNA 控制点特征使用厂商专用 UUID 4F860001-943B-49EF-BED4-2F730304427A(配对控制点、Owner 配置控制点、Non-Owner 控制点、Owner 信息点、调试控制点),固件升级使用 94110001-6D9B-4225-A4F1-6A4A7F01B0DE。句柄来自 ble_fmy_profile.h 生成的 Profile 表:
static const fmna_att_handle_t fmna_att_handle_table = {
.pairing_control_point_handle = ATT_CHARACTERISTIC_4F860001_943B_49EF_BED4_2F730304427A_01_VALUE_HANDLE,
.owner_cfg_control_point_handle = ATT_CHARACTERISTIC_4F860002_943B_49EF_BED4_2F730304427A_01_VALUE_HANDLE,
.owner_info_porint_handle = ATT_CHARACTERISTIC_4F860004_943B_49EF_BED4_2F730304427A_01_VALUE_HANDLE,
.non_owner_control_point_handle = ATT_CHARACTERISTIC_4F860003_943B_49EF_BED4_2F730304427A_01_VALUE_HANDLE,
.debug_control_point_handle = ATT_CHARACTERISTIC_4F860005_943B_49EF_BED4_2F730304427A_01_VALUE_HANDLE,
.firmware_update_handle = ATT_CHARACTERISTIC_94110001_6D9B_4225_A4F1_6A4A7F01B0DE_01_VALUE_HANDLE,
};
Source: ble_fmy.c
配对安全(SM)配置
默认免输入免输出、要求绑定(BONDING),密钥长度 7~16 字节;启用 PASSKEY_ENABLE 后改为 Display Only(设备显示 passkey):
static const sm_cfg_t fmy_sm_init_config = {
.slave_security_auto_req = 0,
.slave_set_wait_security = 0,
#if PASSKEY_ENABLE
.io_capabilities = IO_CAPABILITY_DISPLAY_ONLY,
#else
.io_capabilities = IO_CAPABILITY_NO_INPUT_NO_OUTPUT,
#endif
.authentication_req_flags = SM_AUTHREQ_BONDING,// | SM_AUTHREQ_MITM_PROTECTION,
.min_key_size = 7,
.max_key_size = 16,
.sm_cb_packet_handler = NULL,
};
Source: ble_fmy.c
GATT 服务控制块
把 ATT 读/写回调与事件包回调绑定到协议栈,并指定 MTU 与发送缓冲:
static const gatt_server_cfg_t fmy_server_init_cfg = {
.att_read_cb = &fmy_att_read_callback,
.att_write_cb = &fmy_att_write_callback,
.event_packet_handler = &fmy_event_packet_handler,
};
static gatt_ctrl_t fmy_gatt_control_block = {
//public
.mtu_size = ATT_LOCAL_MTU_SIZE,
.cbuffer_size = ATT_SEND_CBUF_SIZE,
.multi_dev_flag = 0,
#if CONFIG_BT_GATT_SERVER_NUM
.server_config = &fmy_server_init_cfg,
#else
.server_config = NULL,
#endif
Source: ble_fmy.c
全局状态与 Profile 模式
ble_fmy.h 定义全局状态结构 fmy_glb_t 与 Profile 模式枚举。Profile 模式描述配件与 iOS 设备的关系状态,是 Find My 行为(广播内容、可执行命令)的决策依据:
enum {
PROFILE_MODE_UNPAIR = 0,
PROFILE_MODE_OWNER,
PROFILE_MODE_NON_OWNER,
PROFILE_MODE_NEARBY,
PROFILE_MODE_SEPARATED,
};
typedef struct {
uint16_t cur_con_handle;//记录最新的连接handle
uint16_t sound_ctrl_timer_id;//蜂鸣器定时器
uint16_t pairing_mode_timer_id;//配对模式定时器
uint8_t is_app_fmy_active;//控制是否进入低功耗,0-yes,1-no
uint8_t enter_btstack_num;//是否重复进入应用
uint8_t battery_level;// init BAT_STATE_FULL
uint8_t adv_fmna_state;//fmna adv 状态
uint8_t profile_mode; //profile 配置模式
uint8_t sound_onoff; //蜂鸣器状态
uint8_t pairing_mode_enable; //配对模式使能
uint8_t testbox_mode_enable; //测试频偏校准模式
} fmy_glb_t;
Source: ble_fmy.h
核心流程
启动与连接时序
sequenceDiagram
participant AM as "app_main (task_info_table)"
participant SM as "fmy_state_machine()"
participant ST as "fmy_app_start()"
participant BT as "btstack 协议栈"
participant GA as "GATT 服务 (ble_fmy.c)"
participant FM as "FMNA 框架 (fmna_api)"
AM->>SM: intent { action = ACTION_FINDMY }
SM->>ST: fmy_app_start()
ST->>BT: wdt_init + clk_set(sys, 96MHz)
ST->>BT: bt_pll_para / btstack_ble_start_before_init
ST->>BT: btstack_init()
BT-->>GA: 注册 att_read/write/event 回调
Note over GA,BT: 进入广播 (ADV_INTERVAL_MIN ≈ 500ms)
BT->>GA: 连接建立事件 (HCI_EVENT)
GA->>FM: 读取控制点 / 写入控制点 (4F8600xx)
FM-->>GA: 命令处理结果 (声音/SN/OTA)
GA-->>BT: ATT 响应
启动路径从 app_main.c 的任务表出发:系统创建 app_core 任务后按 intent 分发到 ACTION_FINDMY,fmy_state_machine() 在 APP_STA_START 调用 fmy_app_start();后者完成主频/看门狗/协议栈初始化,随后 GATT 服务注册回调并开始广播。iOS 中心设备连接后,所有 FMNA 操作都经由 ATT 读写回调进入 fmna_api 处理,结果再以 ATT 响应返回。
Profile 模式状态机
Profile 模式决定配件处于"未配对 / 已绑定 Owner / 其他设备 / 附近 / 分离"中的哪种状态,各模式下的广播内容与允许命令不同:
stateDiagram-v2
[*] --> UNPAIR
UNPAIR --> OWNER: 配对绑定 (owner 控制点)
UNPAIR --> NON_OWNER: 其他 iOS 设备连接
OWNER --> SEPARATED: 断开/超范围
NON_OWNER --> NEARBY: 靠近
NEARBY --> SEPARATED: 远离
SEPARATED --> OWNER: Owner 重新连接
OWNER --> UNPAIR: 解绑/恢复出厂
NON_OWNER --> OWNER: 成为新 Owner
低功耗与软关机
fmy_state_idle_set_active() 控制 is_app_fmy_active 标志,决定系统是否允许进入低功耗(0 = 允许);fmy_set_soft_poweroff() 是软关机路径——必须先主动断开蓝牙链路,否则要等链路超时断开,会显著拖慢关机:
static void fmy_set_soft_poweroff(void)
{
log_info("set_soft_poweroff\n");
fmy_state_idle_set_active(true);
//必须先主动断开蓝牙链路,否则要等链路超时断开
#if TCFG_USER_BLE_ENABLE
btstack_ble_exit(0);
#endif
#if TCFG_USER_EDR_ENABLE
btstack_edr_exit(0);
#endif
#if (TCFG_USER_EDR_ENABLE || TCFG_USER_BLE_ENABLE)
//延时300ms,确保BT退出链路断开
sys_timeout_add(NULL, power_set_soft_poweroff, WAIT_DISCONN_TIME_MS);
#else
power_set_soft_poweroff();
#endif
}
Source: app_fmy.c
电源事件通过系统事件机制上报(fmy_power_event_to_user),把内部电源事件包装为 SYS_DEVICE_EVENT / DEVICE_EVENT_FROM_POWER,供上层 UI/策略统一处理:
void fmy_power_event_to_user(uint8_t event)
{
struct sys_event e;
e.type = SYS_DEVICE_EVENT;
e.arg = (void *)DEVICE_EVENT_FROM_POWER;
e.u.dev.event = event;
e.u.dev.value = 0;
sys_event_notify(&e);
}
Source: app_fmy.c
配置持久化(VM)
配对/重置标志存放在 fmy_vm_t,通过 fmy_vm_deal() 读写 VM:
typedef struct {
uint8_t head_tag;
uint8_t reset_config;//flag
} fmy_vm_t;
extern fmy_vm_t fmy_vm_info;
#define __fy_vm (&fmy_vm_info)
Source: ble_fmy.h
head_tag 用作校验头:读取时先校验标记合法性,非法则按默认值重置并回写,避免 VM 区被破坏时误用脏数据。
使用示例
示例 1:广播周期与连接数约束
Find My 规范对最大连接数有硬性约束,示例在 ble_fmy.c 中直接以宏固化,防止上层误改:
// 广播周期 (unit:0.625ms)
#define ADV_INTERVAL_MIN (160 * 5)//
// FMY最大连接数,不可修改
#define FMY_MAX_CONNECTIONS 2
Source: ble_fmy.c
示例 2:能力位条件编译
能力位通过编译开关组合,产品定制时只需调整 FMY_OTA_SUPPORT_CONFIG / TCFG_GSENSOR_ENABLE 即可决定是否对外暴露"固件升级/运动检测"能力:
static const uint8_t FMY_AccessoryCapabilities[4] = {
(FMY_CAPABILITY_SUPPORTS_PLAY_SOUND
#if FMY_OTA_SUPPORT_CONFIG
| FMY_CAPABILITY_SUPPORTS_FW_UPDATE_SERVICE
#endif
#if TCFG_GSENSOR_ENABLE
| FMY_CAPABILITY_SUPPORTS_MOTION_DETECTOR_UT
#endif
| FMY_CAPABILITY_SUPPORTS_SN_LOOKUP_BY_BLE),
0x00, 0x00, 0x00
};
Source: ble_fmy.c
示例 3:全局状态访问宏
所有模块通过 __fydata 宏访问 fmy_glb_t,fmy_state_idle_set_active() 就是其典型用法(低功耗开关):
void fmy_state_idle_set_active(uint8_t active)
{
__fydata->is_app_fmy_active = active;
}
Source: app_fmy.c
示例 4:协议栈 RX 缓冲放大
lib_btctler_config.c 在 CONFIG_APP_FINDMY 下放大 LE RX 通道数(按连接数与扩展广播配置 × 3),保证多连接 + 扩展广播场景下收包不丢:
#if CONFIG_APP_FINDMY
const int config_btctler_le_rx_nums = ((CONFIG_BT_GATT_CONNECTION_NUM + EXT_ADV_CFG_HW) * 3);
Source: lib_btctler_config.c
示例 5:日志分级开关
log_config.c 中 FINDMY 标签按 v/i/d/w/e 五级单独开关,便于量产时裁剪日志:
const char log_tag_const_v_FINDMY AT(.LOG_TAG_CONST) = 0;
const char log_tag_const_i_FINDMY AT(.LOG_TAG_CONST) = 1;
const char log_tag_const_d_FINDMY AT(.LOG_TAG_CONST) = 1;
const char log_tag_const_w_FINDMY AT(.LOG_TAG_CONST) = 1;
const char log_tag_const_e_FINDMY AT(.LOG_TAG_CONST) = 1;
Source: log_config.c
配置选项
应用级开关
| 选项 | 类型 | 默认/典型值 | 说明 |
|---|---|---|---|
CONFIG_APP_FINDMY | 编译宏 | 0/1 | 是否编译 Find My 示例应用(同时影响 task_info_table、RX 通道数与日志标签) |
TCFG_USER_BLE_ENABLE | 编译宏 | 1 | 使能 BLE 协议栈(广播/连接的前提) |
TCFG_USER_EDR_ENABLE | 编译宏 | 0/1 | 使能经典蓝牙(HCI 事件会转发给 bt_comm_edr_hci_event_handler) |
DOUBLE_BT_SAME_MAC | 编译宏 | 0/1 | EDR/BLE 同 MAC 时,把 EDR class 改为 BD_CLASS_TRANSFER_HEALTH 避免与 BLE 广播冲突 |
CONFIG_BT_GATT_CONNECTION_NUM | 编译宏 | 按工程 | GATT 连接数,参与 RX 通道数计算 |
EXT_ADV_CFG_HW | 编译宏 | 按工程 | 扩展广播硬件配置数 |
示例内部配置(ble_fmy.c / ble_fmy_cfg.h)
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
PASSKEY_ENABLE | 宏 | 0 | 1 时启用 passkey 加密,SM IO 能力改为 Display Only |
ATT_MTU_REQUEST_ENALBE | 宏 | 0 | 1 时设备端主动发起 MTU 交换 |
ATT_LOCAL_MTU_SIZE | 宏 | 247 | 本端 ATT 发送包长 |
ATT_PACKET_NUMS_MAX | 宏 | 4 | ATT 缓存包个数(约 1 KB 内存) |
ATT_SEND_CBUF_SIZE | 宏 | 4*(ATT_PACKET_HEAD_SIZE+247) | 发送环形缓冲大小 |
ADV_INTERVAL_MIN | 宏 | 160*5(≈500 ms) | 广播周期,单位 0.625 ms |
FMY_MAX_CONNECTIONS | 宏 | 2 | Find My 最大连接数(不可修改) |
FMY_OTA_SUPPORT_CONFIG | 宏 | 0/1 | 使能固件升级能力位与 OTA 服务 |
TCFG_GSENSOR_ENABLE | 宏 | 0/1 | 使能运动检测能力位(G-Sensor) |
FMY_DEBUG_SENSOR_TO_UART_ENBALE | 宏 | 0/1 | 使能 SENSOR UART 调试输出 |
WAIT_DISCONN_TIME_MS | 宏 | 300 | 软关机前等待蓝牙断开的时间 |
运行时行为参数(ble_fmy.c 常量)
| 常量 | 值 | 说明 |
|---|---|---|
read_tx_power_level | 4 | 对外声明的发射功率(规范要求 ≥ +4 dBm) |
fmy_battery_type | FMNA_BAT_NON_RECHARGEABLE | 电池类型(不可充电) |
FMY_AccessoryCategory | FMY_CATEGORY_Finder | 配件类别(查找器) |
API 参考
void fmy_state_idle_set_active(uint8_t active)
设置应用活跃标志 is_app_fmy_active,控制系统是否可进入低功耗。active=1 表示应用处于关键流程(如软关机),禁止低功耗。
参数:
active(uint8_t):1 禁止低功耗,0 允许低功耗。
Source: app_fmy.c
void fmy_power_event_to_user(uint8_t event)
把内部电源事件封装为 SYS_DEVICE_EVENT + DEVICE_EVENT_FROM_POWER 系统事件并通知上层。
参数:
event(uint8_t):电源事件码。
Source: app_fmy.c
bool fmy_vm_deal(fmy_vm_t *info, uint8_t rw_flag)
读写 Find My 的 VM 配置(配对/重置标志)。读取时校验 head_tag,非法则重置为默认值。
参数:
info(fmy_vm_t *):VM 数据结构指针。rw_flag(uint8_t):读/写方向标志。
返回: 操作成功返回 true。
Source: ble_fmy.h
bool fmy_check_capabilities_is_enalbe(uint8_t cap)
查询能力位(FMY_CAPABILITY_*)是否使能。
参数:
cap(uint8_t):能力位。
返回: 使能返回 true。
Source: ble_fmy.h
bool att_set_handle_enable(hci_con_handle_t con_handle, uint16_t start_handle, uint16_t end_handle, uint8_t enable)
按句柄区间使能/禁止某连接上的 ATT 特征可访问性,用于按 Profile 模式(Owner/Non-Owner)动态开放或收紧特征权限。
参数:
con_handle(hci_con_handle_t):连接句柄。start_handle/end_handle(uint16_t):ATT 句柄区间。enable(uint8_t):1 使能,0 禁止。
Source: ble_fmy.h
内部回调(ble_fmy.c,注册到 GATT 服务)
| 回调 | 签名 | 职责 |
|---|---|---|
fmy_att_read_callback | uint16_t (hci_con_handle_t, uint16_t att_handle, uint16_t offset, uint8_t *buffer, uint16_t buffer_size) | 响应特征读取(设备信息、SN 等) |
fmy_att_write_callback | int (hci_con_handle_t, uint16_t att_handle, uint16_t mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size) | 处理控制点写入(配对/声音/OTA 等) |
fmy_event_packet_handler | int (int event, uint8_t *packet, uint16_t size, uint8_t *ext_param) | 处理协议栈事件(连接、MTU、加密等) |
fmy_set_adv_enable | int (uint8_t enable) | 广播使能/停止 |
fmy_set_adv_mode | int (uint8_t mode) | 切换广播模式(可发现性) |
fmy_get_static_mac / fmy_set_static_mac | int (uint8_t *mac) | 读写静态 MAC(FMNA 要求固定地址) |
fmy_disconnect | int (uint16_t conn_handle, uint8_t reason) | 主动断开指定连接 |
失败模式、边界情况与并发
重复进入应用(幂等保护)
fmy_app_start() 以 __fydata->enter_btstack_num 标志保证协议栈只初始化一次;若应用被反复 START 而标志未复位,btstack_init() 不会被二次调用,避免协议栈状态错乱。
软关机前必须断链
fmy_set_soft_poweroff() 先调用 btstack_ble_exit() / btstack_edr_exit() 主动断开链路,再延时 WAIT_DISCONN_TIME_MS(300 ms)执行 power_set_soft_poweroff()。若省略断链步骤,关机将被链路超时(可达数秒)阻塞;is_app_fmy_active=true 同时阻止低功耗进入,确保关机流程不被中断。
连接数上限与多连接
FMY_MAX_CONNECTIONS = 2 是 FMNA 规范的硬约束。多连接时通过 att_set_handle_enable() 按连接句柄管理特征权限,防止非 Owner 设备访问 Owner 专属控制点。config_btctler_le_rx_nums 按 (CONN_NUM + EXT_ADV_CFG_HW) * 3 放大 RX 通道,正是为多连接 + 扩展广播并发收包预留余量;若裁剪该值,高负载下可能丢包导致 ATT 超时。
未使能蓝牙的回退路径
当 TCFG_USER_EDR_ENABLE 与 TCFG_USER_BLE_ENABLE 均关闭时,fmy_app_start() 不再初始化协议栈,而是挂一个 1 秒周期的 fmy_timer_handle_test 定时器(仅打印 not_bt),用于无蓝牙环境下的内存/功耗测试。
广播周期与可发现性的权衡
ADV_INTERVAL_MIN = 160 * 5(约 500 ms)是功耗与可发现延迟的折中:周期过长则 iOS 端发现慢,过短则耗电。产品若需要更快的"查找"响应,可缩短该值,但需评估平均电流。
VM 数据损坏
fmy_vm_t.head_tag 作为校验头:读取时校验失败即视为脏数据并按默认值重置回写,避免配对/重置标志错乱导致配件行为异常(例如误判已绑定)。
并发访问
ATT 回调(读/写/事件)运行在协议栈上下文,与应用层任务并发;示例通过单一全局结构 fmy_glb_t(__fydata)集中共享状态,且关键流程(如软关机)用 is_app_fmy_active 标志与电源管理互斥,避免低功耗打断 ATT 事务。定制时新增共享字段需注意原子性。
性能与运维考虑
- 主频:
fmy_app_start()将sys时钟提到 96 MHz 并调用bt_pll_para()同步 PLL,保证 FMNA 的 Apple 算法(AL)在时限内完成;降低主频可能导致配对/加密超时。 - 看门狗:
wdt_init(WDT_32S)使用 32 秒长窗口,因为配对绑定与 OTA 存在长阻塞路径;量产固件应评估是否回调。 - 内存:
ATT_SEND_CBUF_SIZE = 4 * (ATT_PACKET_HEAD_SIZE + 247)约 1 KB 专用于 ATT 发送缓冲;ATT_PACKET_NUMS_MAX=4决定突发写命令的排队深度,增大可提升吞吐但增加内存。 - MTU:
ATT_LOCAL_MTU_SIZE = 247支持大包传输(OTA 数据特征),前提是对端也协商到相同 MTU;ATT_MTU_REQUEST_ENALBE=1可让设备端主动发起交换。 - 日志裁剪:
log_config.c中 FINDMY 标签的 v/i/d/w/e 五级开关可独立关闭,量产时关闭v级可显著减少串口输出与内存占用。
扩展点
- 能力位:修改
FMY_AccessoryCapabilities[4](配合FMY_OTA_SUPPORT_CONFIG、TCFG_GSENSOR_ENABLE)即可对外增删能力,无需改动协议栈。 - 新增特征/服务:在
ble_fmy_profile.h增加特征句柄定义,并在fmy_att_read_callback/fmy_att_write_callback中按att_handle分发即可扩展私有服务。 - FMNA 桥接:
ble_fmy_fmna.c是third_party/fmna/fmna_api.h的桥接层;替换或升级 FMNA 库只需更新该文件与fmna_att_handle_table的句柄映射。 - 调试通道:
FMY_DEBUG_SENSOR_TO_UART_ENBALE打开sensor_uart_init(),可把 FMNA 状态、Profile 模式等通过 UART 导出,方便产测。 - G-Sensor 运动检测:
TCFG_GSENSOR_ENABLE使能FMY_CAPABILITY_SUPPORTS_MOTION_DETECTOR_UT,接入gSensor/fmy/gsensor_api.h可实现防丢/移动告警类功能。
测试与验证
示例工程未在 examples/findmy 内提供独立单元测试;验证依赖实机与产测工具:
- 配对/绑定链路:使用 iPhone"查找"App 验证广播可见、配对、Owner/Non-Owner 控制点行为;
- 产测工具:
testbox_mode_enable字段支持测试盒模式(频偏校准),配合 SENSOR UART 输出验证 RF 指标; - OTA 验证:
FMY_OTA_SUPPORT_CONFIG下通过94110001-6D9B-4225-A4F1-6A4A7F01B0DE特征验证固件升级流程; - 功耗验证:确认
is_app_fmy_active=0时系统可进入低功耗,广播/连接状态下的电流符合产品规格。
Related Links
- app_fmy.c(应用入口)
- ble_fmy.h(全局状态/API)
- ble_fmy.c(BLE/FMNA 主实现)
- ble_fmy_fmna.c、ble_fmy_ota.c、ble_fmy_sensor_uart.c
- ble_fmy_cfg.h(示例配置)
- app_main.c(任务注册)
- 相关目录:
third_party/fmna/(FMNA 库)、gSensor/fmy/(运动检测)、gatt_common/(GATT 公共层) - 兄弟主题:BLE 通用透传示例(LE 中心/外设数据通道)、OTA 升级示例(固件升级完整流程)