前言
现在为止也开发了许多杰理TWS蓝牙耳机、音响项目SDK的案子,在调试案子时不断的向前辈们学习到了很多关于蓝牙音响、蓝牙TWS耳机专业的知识。想在这里做一个学习汇总,方便各位同行和对杰理芯片可视化SDK感兴趣的小伙伴们学习;
本章详细介绍杰理可视化SDK-蓝牙的可发现可连接和回连
在我们进行杰理蓝牙耳机、蓝牙音响等蓝牙产品软件开发时,蓝牙产品都有一个可发现可连接和回连的过程;本章详细对" 可发现可连接和回连 "这一模块详细讲解;方便大家对杰理可视化SDK有一个深度的了解;
什么是蓝牙的可发现可连接和回连呢?
可发现:是指蓝牙设备可被手机/电脑等智能产品发现
可连接:是指蓝牙设备可被手机/电脑等智能产品进行蓝牙连接
回连:是指蓝牙设备被手机/电脑等智能产品之前已经连接过了,蓝牙产品已经有了手机/电脑等智能产品连接的设备地址;然后蓝牙设备通过设备地址来进行回连;
蓝牙的可发现可连接和回连流程图

流程图讲解:
首先观察到开机完成后蓝牙初始化成功,会进入到C:\SDK\apps\earphone\mode\bt\dual_conn.c目录下的void dual_conn_page_devices_init()函数开始进行蓝牙的可发现可连接和回连操作;
利用int btstack_get_num_of_remote_device_recorded();函数获取当前蓝牙设备曾经连接过的设备数量;
根据是否有蓝牙连接记录这里就会出现分支:
分支1没有蓝牙连接记录,那么蓝牙设备就会开启可发现可连接功能
分支2有一个蓝牙连接记录,那么就会进入到void dual_conn_page_device()函数中处理;
处理有两种结果:一是连接成功,那么就关闭可发现可连接功能;二是连接不成功,这个适合蓝牙设备就会开启可发现可连接功能;
分支3有两个蓝牙连接记录,(这种一般是开启一拖二甚至一拖三功能才会出现),也会进入到void dual_conn_page_device()函数中处理;不管是否连接成功都会进入到static void page_next_device(void *p)中继续处理判断;

总结:
没有设备连接:开启可发现可连接;
有连接记录:短时间开启回连,超时没连接则开启可发现可连接;(其实在这里可发现可连接和回连都是交替执行的,这样设计是为了增加蓝牙设备的灵活性)
连接后如果还有连接记录:短时间内开启回连,超时没连接开启可连接(这种都是一拖二功能)
全部连接成功或已连接一台设备后等待连接超时"关闭可发现可连接;
可视化中蓝牙的可发现可连接及回连的设置

单次回连时间:因为可发现可连接和回连这两个模块功能是交替执行的;举列子:项目中配置一拖二功能,那么手机A回连时间为0~8秒,可发现可连接8~10秒,手机B回连时间为10~18秒,一直交替执行直到回连超时,就关闭回连功能就一直执行可发现可连接功能,直到无蓝牙连接自动关机时间到达,蓝牙设备自动关机;
开机回连时间:设置一个时间周期,超过这个时间周期就关闭回连功能;
超距断开回连时间:蓝牙设备因为距离因素导致断开蓝牙连接后,回连时间设置
开机可被发现时间:蓝牙设备开机后,可被发现时间设置;(TWS已连接,连接上一台设备且无其它配对记录的情况下,打开可发现时间)
等待第二台连接时间:连上一台设备且有其它设备配对记录的情况下,可被已配对设备连接的时间,0表示可以一直被连接;
无连接自动关机时间:没有蓝牙连接自动关机的时间设置;
经典蓝牙可发现可连接和回连常见打印:" I "、" P "、" C "的打印
在开启串口打印时,可以观察到有时候抓取log时会有字母" I "、" P "、" C "。
/********edr蓝牙相关状态debug打印说明***********************
*** 等待被发现inquiry_scan 打印 'I'(大写)
*** 回连手机page: 打印 'C'(大写)
*** 等待手机连接page_scan: 打印 'P'(大写)
*********************************************************/
本博主理解这个就是可发现、可连接和回连状态的显示;
蓝牙可发现可连接和回连的函数调用说明
设置蓝牙可发现可连接
cpp
/**
* @brief 设置inquiry scan和page scan (底层硬件操作函数)
* @details 这个函数通常是直接操作蓝牙控制器(Controller)的寄存器或HCI命令,
* 用于设置设备的扫描状态。它是一个比较底层的实现。
*
* @param scan_enable 是否开启Inquiry Scan (可发现). true: 开启, false: 关闭.
* 开启后,其他设备可以通过 inquiry 过程发现本设备。
* @param conn_enable 是否开启Page Scan (可连接). true: 开启, false: 关闭.
* 开启后,其他设备可以向本设备发起连接请求 (Page)。
*/
void write_scan_conn_enable(bool scan_enable, bool conn_enable);
这是一个非常底层的函数,很可能直接对应HCI(Host Controller Interface)的Write_Scan_Enable的命令
一个设备可以只可发现、只可连接、两者都可、或两者都不可。例如,一个设备在配对时通常需要同时开启两者,而在已连接并作为从机时,通常会关闭可发现和可连接以节省功耗和提高安全性。
cpp
/**
* @brief 蓝牙库API设置inquiry scan和page scan (库封装函数)
* @details 这是蓝牙协议栈(LMP/HCI层)提供的标准API,是对底层硬件操作的封装。
* 应用层通常调用此函数来控制设备的可发现和可连接状态。
*
* @param enable 一个8位无符号整数(u8),通过位掩码(bitmask)来控制状态:
* - BIT(0) (即值的第0位): 设置可发现 (Inquiry Scan). 1为开启, 0为关闭.
* - BIT(1) (即值的第1位): 设置可连接 (Page Scan). 1为开启, 0为关闭.
* 例如:
* - enable = 0x01: 只可发现,不可连接.
* - enable = 0x02: 只可连接,不可发现.
* - enable = 0x03: 既可发现也可连接.
* - enable = 0x00: 既不可发现也不可连接.
*
* @return int 返回执行结果,通常0表示成功,非0表示失败。
*/
int lmp_hci_write_scan_enable(u8 enable);
蓝牙库API设置inquiry scan和page scan (库封装函数)
这是蓝牙协议栈(LMP/HCI层)提供的标准API,是对底层硬件操作的封装。
应用层通常调用此函数来控制设备的可发现和可连接状态。
一个8位无符号整数(u8),通过位掩码(bitmask)来控制状态:
-
BIT(0) (即值的第0位): 设置可发现 (Inquiry Scan). 1为开启, 0为关闭.
-
BIT(1) (即值的第1位): 设置可连接 (Page Scan). 1为开启, 0为关闭.
例如:
-
enable = 0x01: 只可发现,不可连接.
-
enable = 0x02: 只可连接,不可发现.
-
enable = 0x03: 既可发现也可连接.
-
enable = 0x00: 既不可发现也不可连接.
返回执行结果,通常0表示成功,非0表示失败。
获取当前设备曾经连接过的设备数量
cpp
/**
* @brief 蓝牙库API获取当前设备曾经连接过的设备数量
* @details 此函数用于查询蓝牙协议栈中存储的已配对或已连接过的设备记录总数。
* 这些记录通常保存在非易失性存储器(如Flash)中,以便断电重启后仍能恢复。
*
* @return int 返回曾经连接过的设备数量。如果为0,表示没有任何历史记录。
*/
int btstack_get_num_of_remote_device_recorded();
蓝牙库API获取当前设备曾经连接过的设备数量
此函数用于查询蓝牙协议栈中存储的已配对或已连接过的设备记录总数。
这些记录通常保存在非易失性存储器(如Flash)中,以便断电重启后仍能恢复。
返回曾经连接过的设备数量。如果为0,表示没有任何历史记录。
获取指定index的蓝牙设备地址
cpp
/**
* @brief 蓝牙库API获取指定index的蓝牙设备地址
* @details 根据索引从已记录的设备列表中获取一个设备的蓝牙MAC地址。
*
* @param addr [out] 指向一个u8类型数组的指针,用于存放获取到的6字节蓝牙地址(BD_ADDR)。
* 调用者需确保该指针有效,且指向的内存空间至少为6字节。
* @param index [in] 设备记录的索引号,范围是 0 到 (btstack_get_num_of_remote_device_recorded() - 1)。
*
* @return bool 返回获取结果:
* - true: 获取成功,addr中已填入有效的蓝牙地址。
* - false: 获取失败,例如index超出范围。
*/
bool btstack_get_remote_addr(u8 *addr, u8 index);
蓝牙库API获取指定index的蓝牙设备地址
根据索引从已记录的设备列表中获取一个设备的蓝牙MAC地址。
addr [out] 指向一个u8类型数组的指针,用于存放获取到的6字节蓝牙地址(BD_ADDR)。
调用者需确保该指针有效,且指向的内存空间至少为6字节。
设备记录的索引号,范围是 0 到 (btstack_get_num_of_remote_device_recorded() - 1)。
return bool 返回获取结果:
-
true: 获取成功,addr中已填入有效的蓝牙地址。
-
false: 获取失败,例如index超出范围。
通过地址去连接,如果知道地址想去连接使用该接口
cpp
/**
* @brief 发起回连设备函数调用 (命令准备)
* @details 构造一个"通过地址发起连接"的控制命令,并将其发送到蓝牙协议栈的任务队列中执行。
* 这是一个异步操作,调用后不会立即建立连接,而是进入连接流程。
*
* @param USER_CTRL_START_CONNEC_VIA_ADDR [in] 命令类型,表示"通过地址开始连接"。
* @param 6 [in] MAC地址的长度,固定为6字节。
* @param mac_addr [in] 指向6字节蓝牙设备地址(BD_ADDR)的指针。
*/
bt_cmd_prepare(USER_CTRL_START_CONNEC_VIA_ADDR, 6, mac_addr);
构造一个"通过地址发起连接"的控制命令,并将其发送到蓝牙协议栈的任务队列中执行。
这是一个异步操作,调用后不会立即建立连接,而是进入连接流程。
USER_CTRL_START_CONNEC_VIA_ADDR [in] 命令类型,表示"通过地址开始连接"。
in\] MAC地址的长度,固定为6字节。 \[in\] 指向6字节蓝牙设备地址(BD_ADDR)的指针。 *** ** * ** *** #### 取消回连设备 ```cpp /** * @brief 取消回连设备函数调用 (命令准备) * @details 构造一个"取消页面/连接"的控制命令,用于停止正在进行的回连操作。 * 例如,当用户手动取消配对或连接超时,可以调用此函数。 * * @param USER_CTRL_PAGE_CANCEL [in] 命令类型,表示"取消页面/连接过程"。 */ bt_cmd_prepare(USER_CTRL_PAGE_CANCEL, 0, NULL); ``` 取消回连设备函数调用 (命令准备) 构造一个"取消页面/连接"的控制命令,用于停止正在进行的回连操作。 例如,当用户手动取消配对或连接超时,可以调用此函数。 USER_CTRL_PAGE_CANCEL \[in\] 命令类型,表示"取消页面/连接过程"。 *** ** * ** *** ### 蓝牙连接消息定义 C:\\SDK\\interface\\btstack\\avctp_user.h文件下 ``` BT_STATUS_INIT_OK, /*初始化完成*/ 蓝牙初始化成功 BT_STATUS_FIRST_CONNECTED, /*连接成功*/ 第一台设备连接成功 BT_STATUS_SECOND_CONNECTED, /*连接成功*/ 第二台设备连接成功 BT_STATUS_FIRST_DISCONNECT, /*断开连接*/ 第一台设备断开连接 BT_STATUS_SECOND_DISCONNECT, /*断开连接*/ 第二台设备断开连接 ``` ...... *** ** * ** *** ### 代码追踪观察 对应流程图做一个代码追踪, 芯片开机后,蓝牙初始化完成,此函数被调用dual_conn_page_devices_init(),主要负责 1. 初始化回连设备链表。 \* 2. 从协议栈加载历史连接设备,并加入回连队列。 3. 根据配置设置扫描和回连的定时器。 4. 根据唤醒源决定初始的蓝牙工作模式(回连模式 or 可连接模式)。 ```cpp void dual_conn_page_devices_init() { // 临时存储设备MAC地址(6字节) u8 mac_addr[6]; // 1. 初始化回连设备链表(双向链表,用于管理待回连的设备) INIT_LIST_HEAD(&g_dual_conn.page_head); g_dual_conn.page_head_inited = 1; // 标记链表已初始化完成 g_dual_conn.page_scan_auto_disable = 0; // 初始不自动禁用扫描 // 2. 获取协议栈中记录的历史远程设备数量 // 3. 遍历历史设备记录,将它们加入回连列表 // 注意:这里的循环逻辑 `i + 2 >= num` 意味着最多只取最后的2个设备进行回连 // 例如:如果 num=1, i=0, 0+2>=1 成立,循环1次。 // 如果 num=2, i=1, 1+2>=2 成立; i=0, 0+2>=2 成立,循环2次。 // 如果 num=3, i=2, 2+2>=3 成立; i=1, 1+2>=3 成立; i=0, 0+2>=3 不成立,循环2次。 // 这是一种巧妙的写法,确保最多处理2个设备,且是从索引大的开始(通常是最近连接的)。 int num = btstack_get_num_of_remote_device_recorded(); for (int i = num - 1; i >= 0 && i + 2 >= num ; i--) { btstack_get_remote_addr(mac_addr, i); // 获取指定索引的设备MAC地址 // 将该设备添加到回连列表,并设置回连超时时间 // TCFG_BT_POWERON_PAGE_TIME 是配置项,单位是秒,乘以1000转为毫秒 add_device_2_page_list(mac_addr, TCFG_BT_POWERON_PAGE_TIME * 1000); } // 记录历史设备总数 g_dual_conn.device_num_recorded = num; // 如果历史设备只有一个,额外做个备份(可能用于其他逻辑,此处仅作记录) if (num == 1) { memcpy(g_dual_conn.remote_addr[2], mac_addr, 6); } // 4. 配置"可发现(Inquiry Scan)"的自动关闭定时器 #if TCFG_DUAL_CONN_INQUIRY_SCAN_TIME // 如果定义了自动关闭时间 g_dual_conn.inquiry_scan_disable = 0; // 标记:可发现扫描未被禁用 // 创建一个定时器,在 TCFG_DUAL_CONN_INQUIRY_SCAN_TIME 秒后,调用 close_inquiry_scan 函数 // 这个函数的作用是关闭可发现扫描,只保留可连接扫描,以节省功耗 g_dual_conn.close_inquiry_scan_timer = sys_timeout_add(NULL, close_inquiry_scan, TCFG_DUAL_CONN_INQUIRY_SCAN_TIME * 1000); #else // 如果未定义,则默认直接禁用可发现扫描 g_dual_conn.inquiry_scan_disable = 1; #endif // 5. 根据唤醒源决定初始行为 #if (TCFG_LP_NFC_TAG_ENABLE && TCFG_LP_NFC_TAG_TYPE == JL_BT_TAG) static u8 nfc_wakeup_disable_page = 1; // 检查是否由NFC标签唤醒 if ((is_reset_source(MSYS_P2M_RST)) && (is_wakeup_source(PWR_WK_REASON_LPNFC)) && nfc_wakeup_disable_page) { nfc_wakeup_disable_page = 0; // 标记已处理,防止重复执行 // NFC唤醒后,直接开启可发现和可连接,不进行回连 // 这是因为NFC交互本身可能已经完成了信息交换,直接进入配对模式更合理 write_scan_conn_enable(1, 1); } else { //非nfc唤醒 dual_conn_page_device(); // 执行标准的回连流程 } #else dual_conn_page_device(); // 如果没有NFC功能,直接执行标准的回连流程 #endif } ``` dual_conn_page_device()此函数是回连流程的核心驱动。它会检查当前是否有空闲,然后从链表头取出一个设备进行连接。 它确保同一时间只有一个连接尝试在进行。 ```cpp void dual_conn_page_device() { struct page_device_info *info, *n; // n 用于安全地遍历链表 // 如果回连链表未初始化,直接返回 if (!g_dual_conn.page_head_inited) { return; } // 安全地遍历回连设备链表 list_for_each_entry_safe(info, n, &g_dual_conn.page_head, entry) { // 关键检查:如果 info->timer 不为0,说明这个设备正在被连接(已发起Page,等待超时或结果) // 此时不应再发起新的连接,直接返回,等待当前连接完成或超时 if (info->timer) { return; } // 找到了一个可以发起连接的空闲设备 printf("start_page_device: %lu, %d\n", jiffies, info->timeout); // 打印日志:当前系统节拍和超时时间 put_buf(info->mac_addr, 6); // 调试函数,打印目标MAC地址 // 为这个连接尝试设置一个超时定时器 // 超时后会调用 dual_conn_page_device_timeout 函数 // TCFG_BT_PAGE_TIMEOUT 是配置的Page超时时间(秒) info->timer = sys_timeout_add(info, dual_conn_page_device_timeout, TCFG_BT_PAGE_TIMEOUT * 1000); // 准备并发送"通过地址发起连接"的HCI命令到蓝牙内核 bt_cmd_prepare(USER_CTRL_START_CONNEC_VIA_ADDR, 6, info->mac_addr); // 设置全局标志位,表示当前正处于Page/回连模式 page_mode_active = 1; // 已经发起了一个连接,任务完成,退出函数 return; } // 如果循环结束都没有发起连接(说明链表为空或所有设备都在等待中) // 则调用状态处理器,进入下一个状态(例如,全部回连失败后,进入可连接模式) dual_conn_state_handler(); } ``` 回连超时处理函数dual_conn_page_device_timeout(void \*p) 当对某个设备的连接尝试超时后,此函数被调用。 \* 它负责清理超时的设备,并决定下一步操作:尝试下一个设备 or 结束回连流程。 ```cpp static void dual_conn_page_device_timeout(void *p) //回连超时 { struct page_device_info *info; if (!g_dual_conn.page_head_inited) { return; } /* 参数有效性检查:在链表中查找与传入指针p匹配的设备节点 */ list_for_each_entry(info, &g_dual_conn.page_head, entry) { if (info == p) { // 找到了超时的那个设备 printf("page_device_timeout: %lu, %d\n", jiffies, info->timeout); // 清除该设备的定时器句柄 info->timer = 0; // 将该设备从回连链表中移除 list_del(&info->entry); // 检查当前系统时间是否已经超过了预设的总回连超时时间 if (time_after(jiffies, info->timeout)) { // 如果总时间也超时了,则彻底删除这个设备记录 del_device_from_page_list(info->mac_addr); // 释放该设备节点占用的内存 free(info); } else { // 如果只是单次Page超时,但总回连时间未到,则将设备重新加回链表尾部 // 实现轮询重试的效果 list_add_tail(&info->entry, &g_dual_conn.page_head); } // 发送"取消Page"的HCI命令,通知蓝牙内核停止当前的连接尝试 bt_cmd_prepare(USER_CTRL_PAGE_CANCEL, 0, NULL); // 检查回连链表是否还有其他设备 if (!page_list_empty()) { // 如果还有设备待回连 // 如果已存在一个用于切换到下一个设备的定时器,先删掉它 if (g_dual_conn.timer) { sys_timeout_del(g_dual_conn.timer); g_dual_conn.timer = 0; } // 创建一个新的定时器,2秒后调用 page_next_device // 这个2秒的延迟是为了给蓝牙控制器一个喘息时间,并在此期间开启扫描 g_dual_conn.timer = sys_timeout_add(NULL, page_next_device, 2000); // 在等待下一次回连的间隙,开启"可发现"和"可连接"模式 // 这是一个关键步骤:回连间隙,设备变得可以被新设备发现 extern void bt_discovery_and_connectable_using_loca_mac_addr(u8 inquiry_scan_en, u8 page_scan_en); bt_discovery_and_connectable_using_loca_mac_addr(1, 1); // (可发现=1, 可连接=1) return; // 任务完成,等待 page_next_device 被调用 } // 如果链表空了,说明所有设备都回连失败或超时 page_mode_active = 0; // 关闭回连模式标志 // 调用状态处理器,进入最终状态(例如,保持可连接,或关闭扫描进入休眠) dual_conn_state_handler(); break; // 退出循环 } } } ``` 切换到下一个待回连设备的回调函数page_next_device(void \*p) ```cpp ** * @brief 切换到下一个待回连设备的回调函数 * @details 由 sys_timeout_add 触发,在上一个设备回连超时/失败后,延迟2秒执行。 * 它的作用是重新调用 dual_conn_page_device 来启动对下一个设备的连接。 */ static void page_next_device(void *p) { // 清除定时器句柄 g_dual_conn.timer = 0; // 再次尝试连接下一个设备 dual_conn_page_device(); } ``` 结合代码和注释,可以将整个运作方式总结为以下几个阶段: 阶段一:上电初始化 1、dual_conn_page_devices_init()被调用 2、程序从Flash中读取历史连接记录,比如有两个设备 3、设备A和B的MAC地址被创建为page_device_info节点,并添加到g_dual_conn.page_head链表中 4、每个节点都有一个timeout成员,记录了总的回连运行时间(例如30秒) 5、程序检查唤醒原因。假设时正常上电于是调用dual_conn_page_device() 阶段二:首次回连尝试 1、dual_conn_page_device()开始执行 2、它变量链表,发现设备A的timer为0空闲 3、它为设备设置一个超时定时器(例如10秒),回调函数是dual_conn_pahe_device_tineout() 4、它通过bt_cmd_prepare(USER_CTRL_START_CONNEC_VIA_ADDR, ...)命令蓝牙内核去连接设备A。 5、page_mode_active置为1.函数返回。此时设备进入page状态,不可被发现,只尝试连接设备A 阶段三:场景A-回连成功 1、连接A成功 2、连接成功的事件时,会调用dual_conn_state_handler()或类似函数 阶段四:场景B-回连超时/失败 1、在10秒内设备A没有响应,或者连接请求失败。设置一个2秒的定时器 2、关键点:在这两秒的等待期内,代码调用bt_discovery_and_connectable_using_loca_mac_addr(1, 1)函数,设设备进入可被发现和可被连接的状态。这给了其它新设备一个发现并连接它的机会 3、2秒后,page_next_device()被调用; 阶段五:所有回连尝试结束; ### 关键点讲解 可发现可连接和回连这两种状态:它们不是简单的一起执行,而是存在包含与先后的逻辑关系,且受不同定时器控制; 1、核心矛盾:蓝牙硬件不可以" 一心二用 " 蓝牙射频(RF)硬件在同一时刻通常只能处于一种主要状态: 状态A回连:盯着一个特定的MAC地址疯狂发送信号," 喂,是你吗?快理我!"。此时设备通常是不可以被发现(因为他在专心找老相好,没空理路人)。 状态B可发现可连接:扯着嗓子喊,"我在这儿!我在这儿!谁都可以来连接我 "此时设备不可进行特定的MAC地址回连; 矛盾点:如果一直处于状态A(回连),路人(新手机)就找不到搜不到你;如果一直处于状态B(可发现)就没法专心去连接老设备; 2、为什么需要那个" 2秒的等待" ? 看代码里的这个逻辑: ```cpp // 在 dual_conn_page_device_timeout 中 if (!page_list_empty()) { ... // 关键:开启可发现可连接 bt_discovery_and_connectable_using_loca_mac_addr(1, 1); // 关键:设置一个2秒后的定时器去试下一个设备 g_dual_conn.timer = sys_timeout_add(NULL, page_next_device, 2000); return; } ``` 这个2秒的作用有三个层面: 层面一:硬件" 喘息 "与状态重叠(必须的) 当上一次回连(Page)超时失败后,蓝牙控制器处于一种"激动"或"忙碌"的状态。你不能立即发起下一次Page,必须给硬件一点时间(比如几百毫秒)去复位状态机。这2秒里包含了硬件复位时间 + 软件逻辑处理时间。 层面二:给" 新设备 "一个插队的机会(策略性的) 这是最重要的原因! 假设你正在回连设备A,失败了 如果没有着2秒,代码会立刻(或很短延时后)发起对设备B的回连。 结果:在回连在回连设备B的这段时间里(比如8秒),设备又变成了"不可被发现"状态。如果此时用户拿出一个新手机想连接你,他搜不到你,因为你正在专心找设备B。 有了这2秒:在找A失败后,系统说:" A不在,那我先把门打开2秒 ",看看有没有新人来连我。如果没人来,我再去找B。 这就是为什么要在回连间隙强制开启:bt_discovery_and_connectable_using_loca_mac_addr(1, 1);可发现可连接 层面三:避免" 死循环 "阻塞 如果回连链表里有10个设备,每个都要试8秒,如果不加这个间隙,系统会联系回连80秒。在这80秒里,用户完全无法搜索到设备。加入2秒间隙,实际上把专用回连变成了回连为主,兼顾发现的混合模式。 如何一起执行任务的? 假设我的配置如下  开机可被发现时间:120秒(大定时器) 开机回连超时时间:60秒(大定时器) 单纯回连时间:8秒 回连间隙:2秒 时间轴如下: | 时间 | 蓝牙状态 | 后台定时器 | 解释 | |---------------|---------------------|----------------|---------------------------------------------| | **0s** | **可发现+可连接** | 120s倒计时开始 | 上电,初始化,开启扫描。 | | **0s\~8s** | **正在回连设备A** (不可发现) | 120s倒计时进行中 | 发起Page,专心找A。此时新人搜不到你。 | | **8s** | **回连A超时** | 120s倒计时进行中 | 没找到A。 | | **8s\~10s** | **可发现+可连接** (关键!) | 120s倒计时进行中 | **这就是那2秒!** 系统取消Page,强制开启扫描。此时如果有新人搜索,能搜到你。 | | **10s** | **正在回连设备B** (不可发现) | 120s倒计时进行中 | 2秒到了,没人连我,那我去找B。 | | **10s\~18s** | **正在回连设备B** | 120s倒计时进行中 | 专心找B,新人又搜不到了。 | | **...** | **循环...** | **120s倒计时进行中** | 找完B找C...每次失败都插播2秒"可发现时间"。 | | **60s** | **回连总超时** | 120s倒计时还剩60s | 假设配置了"开机回连超时60s"。此时停止回连所有设备。 | | **60s\~120s** | **可发现+可连接** (或只可连接) | 120s倒计时进行中 | 回连任务结束,但120s大定时器还没到。设备保持开放状态,等待新人。 | | **120s** | **关闭可发现** | 大定时器到期 | `close_inquiry_scan` 触发。设备变为"只可连接"或"休眠"。 | 总结: 你看到的"120S大定时"和"回连过程中的2S等待"并不冲突,它们的关系是: 1、120S是" 总时长 ":这是一个死线,到了这个时间,不管你在干什么,必须停止广播(Inquiry Scan),以省电。 2、回连是" 任务列表 ":在这60秒的总时长内,系统优先执行" 回连老设备 "的任务 所以,代码的逻辑是: 在120秒的大框架下,**"回连"和"可发现"是分时复用的**。在前60秒内大部分时间在回连(不可发现),每次回连失败后切换到可发现状态维持2秒,然后再切回回连。直到60秒耗尽或者所有老设备都试完就进入到60\~120秒专属可发现可连接状态; *** ** * ** *** 制作不易!喜欢的小伙伴给个小赞赞!喜欢我的小伙伴点个关注!有不懂的地方和需要的资源随时问我哟!