1. 项目概述与GAP层核心价值
在物联网和智能硬件开发领域,蓝牙低功耗(BLE)技术因其极低的功耗和广泛的设备兼容性,已成为短距离无线通信的绝对主流。然而,很多开发者初次接触BLE时,往往直接从GATT(通用属性配置文件)层开始,关注服务和特征值,却忽略了其底层基石——GAP(通用访问配置文件)层。这就像盖楼只关心房间里的家具(数据),却不关心如何开门(发现设备)、如何铺设稳固的地基(建立连接)以及如何管理访客(连接参数)。我曾在多个穿戴设备和传感器项目中,因为对GAP层理解不透彻,在设备频繁断连、功耗异常、配对失败等问题上耗费了大量调试时间。
GAP层是BLE通信的“外交官”和“交通警察”,它不负责具体的数据内容(那是GATT层的事),而是专注于设备如何被发现、如何建立连接、以什么角色通信以及如何管理连接的生命周期。本次我们将深入TI(德州仪器)BLE协议栈中的GAP层API,并以一个经典的“温度计传感器”应用为例,手把手带你从广播、发现、连接到参数调优,完整走通一个BLE外设的开发流程。无论你是正在开发智能手环、胎压监测还是资产追踪标签,理解并掌握GAP层,是确保设备稳定、可靠、低功耗运行的必经之路。
2. GAP层核心概念与设备角色解析
在深入API之前,我们必须先厘清几个核心概念,这是理解后续所有配置和代码的基础。
2.1 四大设备角色
GAP层定义了四种设备角色,一个设备可以同时承担多种角色,但通常我们说的“外设”和“主机”是最常见的组合。
- 广播者(Advertiser):周期性发送广播数据包的设备。它只发不收(针对广播信道),目的是宣告自己的存在。我们的温度计在等待连接时,就处于这个角色。
- 观察者(Observer):扫描并接收广播数据包的设备。它只收不发(针对广播信道),用于发现周围的广播者。手机上的BLE扫描工具就是这个角色。
- 外围设备(Peripheral):这是一种“从设备”角色。它通常是资源受限、功耗敏感的设备,如我们的温度计传感器。它作为广播者发起通信,并在连接建立后作为从设备(Slave)与中央设备通信。
- 中央设备(Central):这是一种“主设备”角色。它通常是资源相对丰富的设备,如手机、平板或网关。它作为观察者发现设备,并主动发起连接,在连接中作为主设备(Master)管理通信时序。
在我们的温度计示例中,温度计设备同时扮演了广播者和外围设备的角色;而与之配对的手机或采集器则扮演了观察者和中央设备的角色。
2.2 广播与扫描:设备发现的基石
设备发现是BLE通信的第一步。外设通过三个特定的广播信道(37, 38, 39)周期性发送广播报文。
- 广播间隔(Advertising Interval):这是两个连续广播事件之间的时间。TI的API中,该参数以
n × 0.625 ms为单位。例如,默认的160对应160 * 0.625 ms = 100 ms。更短的间隔能让设备被更快发现,但功耗显著增加;更长的间隔省电,但被发现的时间变长。在温度计项目中,我们通常会在“等待连接”阶段使用较短的间隔(如100ms)以便快速连接,在“已连接”或“空闲”阶段则停止广播以省电。 - 广播类型(Advertising Type):决定了广播的行为。
GAP_ADTYPE_ADV_IND(0x01):可连接的非定向广播。这是最常用的类型,允许任何中央设备连接。我们的温度计就使用此类型。GAP_ADTYPE_ADV_HDC_DIRECT_IND(0x02):可连接的高占空比定向广播。针对特定设备快速连接,功耗极高。GAP_ADTYPE_ADV_SCAN_IND(0x03):可扫描的非定向广播。允许扫描响应,但不允许连接。GAP_ADTYPE_ADV_NONCONN_IND(0x04):不可连接的非定向广播。仅用于广播数据,如信标(Beacon)。
中央设备通过扫描来发现广播者。扫描也有间隔和窗口参数:
- 扫描间隔(Scan Interval):两次扫描窗口起始点之间的时间。
- 扫描窗口(Scan Window):一次扫描持续的时间。 如果扫描窗口等于或大于扫描间隔,则称为连续扫描;否则为间歇扫描。中央设备需要在扫描窗口内监听广播信道才能发现设备。
2.3 连接参数:通信稳定与功耗的平衡术
一旦连接建立,主从设备将在37个数据信道上进行跳频通信。连接参数是影响功耗和吞吐量的最关键因素,由中央设备发起,但外围设备可以请求更新。
- 连接间隔(Connection Interval):两个连接事件之间的时间,单位为
1.25 ms。范围通常是7.5ms到4s。这是功耗的“主宰”。间隔越短,通信实时性越好,但功耗越高;间隔越长,功耗越低,但数据延迟变大。温度计这类低速传感器,可以使用较长的间隔(如1s)来极致省电。 - 从设备延迟(Slave Latency):允许从设备(外围设备)跳过若干个连接事件而不回复数据,单位是连接事件的个数。例如,间隔为100ms,延迟为9,则从设备最多可以1秒(10个事件)不与主设备通信,期间保持休眠,大幅降低功耗。但主设备发来的数据会延迟处理。
- 监督超时(Supervision Timeout):连接允许的最大无通信时间,单位为
10 ms。必须满足:监督超时 > (1 + 从设备延迟) * (连接间隔 * 2)。如果超过此时间没有成功通信,连接将被认为丢失而断开。通常设置为连接间隔的几倍到几十倍。
实操心得:很多连接不稳定(尤其是一跑就断)的问题,都源于连接参数设置不合理。例如,监督超时设置过小,当设备因环境干扰偶尔丢包时,很容易误判连接丢失。一个经验法则是:监督超时至少设置为
连接间隔 * (从设备延迟 + 1) * 6,为链路层重传和跳频避让留出足够余量。
3. TI BLE协议栈GAP API深度剖析
TI的BLE协议栈将GAP功能分层封装,提供了从底层直接操作(GAP API)到角色抽象(GAPRole API)的多种接口,方便不同需求的开发者使用。
3.1 底层GAP API:精细控制的利器
这部分API提供了最基础、最直接的控制能力,通常用于实现自定义的角色或高级功能。
1. 参数管理:GAP_GetParamValue/GAP_SetParamValue这是配置GAP层行为的核心。输入文档中列出了数十个TGAP_开头的参数ID。
// 示例:获取当前通用发现模式下的广播时间 uint16_t advTimeout = GAP_GetParamValue(TGAP_GEN_DISC_ADV_MIN); // 示例:设置连接建立时的广播超时为5秒(5000ms) bStatus_t status = GAP_SetParamValue(TGAP_CONN_EST_ADV_TIMEOUT, 5000); if (status != SUCCESS) { // 处理错误,例如参数ID无效或栈未就绪 }为什么需要手动设置这些参数?协议栈的默认值通常是兼顾通用性的保守值。例如,TGAP_CONN_EST_ADV_TIMEOUT默认是10.24秒,对于需要快速响应的设备来说太长了。我们可以将其设置为2-3秒,如果此时间内未连接,则停止广播进入深度睡眠,从而节省电量。
2. 设备地址配置:GAP_ConfigDeviceAddrBLE设备地址类型多样,配置错误会导致无法连接。
uint8_t staticAddr[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; bStatus_t status = GAP_ConfigDeviceAddr(ADDRMODE_STATIC, staticAddr);ADDRMODE_PUBLIC:使用芯片烧录的公共地址。这是最常用的,但地址固定。ADDRMODE_STATIC:使用开发者指定的静态随机地址。注意:地址最高两位有效位必须为‘1’(即0b11xxxxxx),否则部分手机系统会拒绝连接。这是我在早期开发中踩过的一个坑。ADDRMODE_PRIVATE_RESOLVE:使用可解析的私有地址(RPA)。地址会周期性变化,但中央设备如果拥有设备的IRK(身份解析密钥),可以解析出它的真实身份地址,保护隐私。常用于需要隐私保护的穿戴设备。
3. 事件注册:GAP_RegisterForMsgs协议栈通过ICall机制与应用程序任务通信。应用任务需要注册自己,才能接收到特定的GAP事件。
// 通常在任务初始化时调用,taskID是应用任务的任务ID GAP_RegisterForMsgs(myTaskId);注册后,应用任务就能在消息队列中收到如GAP_DEVICE_INIT_DONE_EVENT(设备初始化完成)、GAP_LINK_ESTABLISHED_EVENT(连接建立)等关键事件,从而驱动应用状态机。
3.2 外围设备角色API(GAPRole Peripheral):开箱即用的便捷层
对于大多数标准外设应用,直接使用GAPRole层是最高效的选择。它封装了状态机、广播管理、连接参数更新请求等复杂逻辑。
1. 设备启动与初始化:GAPRole_StartDevice这是启动外设角色的入口函数。
// 定义应用回调函数结构体 static gapRolesCBs_t thermometerAppCBs = { .pfnStateChange = Thermometer_StateChangeCB // 状态改变回调 }; // 在应用初始化函数中启动设备 bStatus_t status = GAPRole_StartDevice(&thermometerAppCBs); if (status != SUCCESS) { // 处理启动失败 }GAPRole_StartDevice会初始化协议栈,并自动处理从初始化、广播、连接到断开的完整状态流转。我们只需要在回调函数Thermometer_StateChangeCB中响应状态变化即可。
2. 参数配置:GAPRole_SetParameter与底层GAP API类似,但参数ID不同,更贴近角色行为。
// 设置设备名(会包含在广播数据或扫描响应数据中) uint8_t deviceName[] = "Thermometer-01"; GAPRole_SetParameter(GAPROLE_ADVERT_DATA, sizeof(deviceName), deviceName); // 设置连接参数更新请求的参数(连接后自动请求) uint16_t minInterval = 80; // 100ms uint16_t maxInterval = 800; // 1000ms uint16_t latency = 4; uint16_t timeout = 600; // 6秒 GAPRole_SetParameter(GAPROLE_MIN_CONN_INTERVAL, sizeof(minInterval), &minInterval); GAPRole_SetParameter(GAPROLE_MAX_CONN_INTERVAL, sizeof(maxInterval), &maxInterval); GAPRole_SetParameter(GAPROLE_SLAVE_LATENCY, sizeof(latency), &latency); GAPRole_SetParameter(GAPROLE_TIMEOUT_MULTIPLIER, sizeof(timeout), &timeout); GAPRole_SetParameter(GAPROLE_PARAM_UPDATE_ENABLE, sizeof(uint8_t), &enableUpdate);关键点:GAPROLE_PARAM_UPDATE_ENABLE设置为1后,设备在连接建立后会自动向中央设备发起连接参数更新请求。这是优化功耗的关键一步,因为连接刚建立时使用的往往是协议栈默认的、较快的参数。
3. 状态机回调:理解设备生命周期GAPRole通过回调函数pfnStateChange通知应用当前状态。理解这些状态是编写健壮应用的基础。
void Thermometer_StateChangeCB(gaprole_States_t newState) { switch(newState) { case GAPROLE_STARTED: // 设备已启动,可以开始配置广告参数 LOG("Device started.\n"); // 可以在这里调用GAPRole_SetParameter配置广告数据 break; case GAPROLE_ADVERTISING: // 正在广播,等待连接。此时可以点亮LED指示等待状态 LOG("Advertising started. Waiting for connection...\n"); Board_setLED(Board_LED1, Board_LED_ON); break; case GAPROLE_WAITING: // 广播超时或停止后进入等待状态(由GAPROLE_ADVERT_OFF_TIME控制) LOG("Advertising stopped, waiting before next cycle.\n"); Board_setLED(Board_LED1, Board_LED_OFF); break; case GAPROLE_CONNECTED: // 连接已建立!这是启动传感器采样、配置GATT服务通知/指示的时机 LOG("Connected! Start temperature measurement.\n"); Board_setLED(Board_LED2, Board_LED_ON); // 连接指示灯亮 // 启动一个定时器,周期性读取温度并发送 Util_startClock(&tempMeasurementClock); break; case GAPROLE_WAITING_AFTER_TIMEOUT: // 连接超时断开后进入等待状态 LOG("Connection timeout, waiting before re-advertising.\n"); Board_setLED(Board_LED2, Board_LED_OFF); Util_stopClock(&tempMeasurementClock); // 停止采样定时器 break; case GAPROLE_ERROR: // 发生错误,需要处理 LOG("GAPRole error occurred!\n"); // 可能的处理:重启广播或复位设备 break; } }这个状态机清晰地勾勒出了温度计的工作流:启动 -> 广播 -> 连接 -> 工作 -> 断开 -> 等待 -> 重新广播。
4. 温度计应用开发实战:从广播到数据传输
现在,我们将利用上述API,构建输入文档中描述的温度计传感器。这个温度计支持多种数据格式(摄氏度/华氏度、带时间戳、带类型),并具有完整的状态管理。
4.1 系统设计与状态映射
首先,我们需要将文档中描述的温度计状态,映射到GAPRole的状态机和我们的应用逻辑中。
| 文档中的温度计状态 | GAPRole 状态 | 应用层行为 |
|---|---|---|
| Idle | GAPROLE_STARTED | 设备上电初始化完成,等待用户按键(右键)触发广播。 |
| Idle Configured | 无直接对应,属于应用逻辑 | 在GAPROLE_ADVERTISING状态下,启动一个定时器,等待“测量间隔”到期。 |
| Idle Measurement Ready | GAPROLE_ADVERTISING | 定时器到期,测量数据已就绪,持续广播。此时广播数据中可包含标志位表示“有数据可用”。 |
| Connected Not Configured | GAPROLE_CONNECTED | 连接建立,但中央设备尚未启用温度服务的CCCD(客户端特征配置描述符)。不主动发送数据。启动一个20秒的连接超时定时器。 |
| Connected Configured | GAPROLE_CONNECTED | 中央设备写入了CCCD,启用了通知或指示。应用开始按设定间隔发送温度数据。 |
| Connected Bonded | GAPROLE_CONNECTED | 已绑定(配对)的设备重新连接。如果CCCD已在绑定信息中保存并恢复,则直接进入“Connected Configured”状态发送数据。 |
4.2 关键代码实现与解析
1. 初始化与广播启动
// 定义应用全局变量 static uint8_t advertData[] = { 0x02, // 长度 GAP_ADTYPE_FLAGS, // 类型:标志位 GAP_ADTYPE_FLAGS_BREDR_NOT_SUPPORTED | GAP_ADTYPE_FLAGS_GENERAL, // 仅BLE,普通发现模式 0x03, // 长度 GAP_ADTYPE_16BIT_MORE, // 类型:不完整16位服务UUID列表 LO_UINT16(TEMPERATURE_SERVICE_UUID), HI_UINT16(TEMPERATURE_SERVICE_UUID) // 温度服务UUID }; static uint8_t scanRspData[] = { 0x0D, // 长度 (设备名长度+2) GAP_ADTYPE_LOCAL_NAME_COMPLETE, // 类型:完整设备名 'T', 'h', 'e', 'r', 'm', 'o', 'm', 'e', 't', 'e', 'r', '-', '0', '1' }; void Thermometer_init() { // 1. 初始化硬件(按键、LED、温度传感器) Board_init(); Button_init(&rightButton, Board_BUTTON0, BUTTON_PRESSED); TempSensor_init(); // 2. 注册按键回调,按下右键启动广播 Button_registerCallback(&rightButton, BUTTON_PRESSED, onRightButtonPress); // 3. 配置GAPRole参数 uint8_t enable = TRUE; GAPRole_SetParameter(GAPROLE_ADVERT_ENABLE, sizeof(uint8_t), &enable); GAPRole_SetParameter(GAPROLE_ADVERT_DATA, sizeof(advertData), advertData); GAPRole_SetParameter(GAPROLE_SCAN_RSP_DATA, sizeof(scanRspData), scanRspData); // 4. 设置连接参数(为低功耗优化) uint16_t connInterval = 160; // 200ms uint16_t slaveLatency = 4; // 最多跳过4个连接事件 uint16_t connTimeout = 600; // 6秒 GAPRole_SetParameter(GAPROLE_MIN_CONN_INTERVAL, sizeof(connInterval), &connInterval); GAPRole_SetParameter(GAPROLE_MAX_CONN_INTERVAL, sizeof(connInterval), &connInterval); GAPRole_SetParameter(GAPROLE_SLAVE_LATENCY, sizeof(slaveLatency), &slaveLatency); GAPRole_SetParameter(GAPROLE_TIMEOUT_MULTIPLIER, sizeof(connTimeout), &connTimeout); // 5. 启动GAPRole外设 gapRolesCBs_t appCBs = { Thermometer_stateChangeCB }; GAPRole_StartDevice(&appCBs); // 6. 进入低功耗模式,等待事件 Power_setConstraint(PowerCC26XX_IDLE_PD_DISALLOW); // 允许进入IDLE模式 }代码解析:
- 广播数据(advertData):我们包含了“标志位”和“不完整的服务UUID列表”。这样中央设备在扫描时就能知道这是一个仅支持BLE的设备,并且它提供了温度服务,可以快速过滤无关设备。
- 扫描响应数据(scanRspData):包含了完整的设备名。扫描响应是在中央设备发送扫描请求后,外设回复的额外数据包,容量也是31字节。把设备名放在这里,可以节省初始广播数据包的空间,用于承载更重要的服务信息。
- 连接参数:我们设置了一个相对省电的参数(200ms间隔,4的从设备延迟)。这意味着在连接事件中,如果主设备没有数据下发,从设备最多可以保持休眠
200ms * (4+1) = 1秒。
2. 温度测量与数据格式切换文档中提到,通过“上键”可以循环切换数据格式。这需要在应用层维护一个状态变量。
typedef enum { DATA_FMT_CELSIUS_TIMESTAMP_TYPE = 0, DATA_FMT_CELSIUS_TIMESTAMP, DATA_FMT_CELSIUS, DATA_FMT_FAHRENHEIT, DATA_FMT_FAHRENHEIT_TIMESTAMP, DATA_FMT_FAHRENHEIT_TIMESTAMP_TYPE, DATA_FMT_COUNT } DataFormat_t; static DataFormat_t currentFormat = DATA_FMT_CELSIUS; static uint32_t measurementCounter = 0; void onUpButtonPress() { // 循环切换格式 currentFormat = (currentFormat + 1) % DATA_FMT_COUNT; Display_showFormat(currentFormat); // 更新显示(如果有屏幕) } static void readAndSendTemperature() { float tempCelsius; bStatus_t status = TempSensor_read(&tempCelsius); // 读取传感器 if (status != SUCCESS) return; uint8_t txBuffer[20]; // 足够容纳最大格式的数据 uint8_t dataLen = 0; // 根据当前格式组装数据 switch(currentFormat) { case DATA_FMT_CELSIUS: // 格式:1字节类型(0x01) + 2字节整数部分 + 1字节小数部分 txBuffer[0] = 0x01; // 摄氏度格式标识 int16_t tempInt = (int16_t)(tempCelsius * 100); // 放大100倍传输 txBuffer[1] = LO_UINT16(tempInt); txBuffer[2] = HI_UINT16(tempInt); dataLen = 3; break; case DATA_FMT_CELSIUS_TIMESTAMP: // 格式:类型 + 温度值 + 4字节时间戳 txBuffer[0] = 0x02; // ... 组装温度值 ... uint32_t timestamp = RTC_getSeconds(); // 获取当前时间戳 memcpy(&txBuffer[3], ×tamp, 4); dataLen = 7; break; case DATA_FMT_FAHRENHEIT: float tempFahr = tempCelsius * 9.0f / 5.0f + 32.0f; // ... 类似摄氏度组装 ... break; // 其他格式... } // 通过GATT层的特征值发送数据(假设已获取连接句柄和特征值句柄) if (connectionActive && cccdEnabled) { GATT_Notification(connHandle, &tempCharHandle, txBuffer, dataLen, FALSE); } }数据格式设计思考:为什么需要多种格式?这是为了兼容不同的客户端(Collector)。一个简单的手机App可能只需要摄氏度数值;而一个专业的数据记录仪可能需要带时间戳的数据用于分析;一个复杂的系统可能还需要知道数据包的类型以进行解析。在GATT层设计特征值时,可以设计一个可写的“数据格式”特征,让中央设备动态配置,这比用按键切换更灵活。
3. 连接与安全处理当中央设备发起配对时,温度计需要响应GAP_PASSKEY_NEEDED_EVENT事件。
// 在应用任务的事件处理循环中 case GAP_PASSKEY_NEEDED_EVENT: { gapPasskeyNeededEvent_t *pEvent = (gapPasskeyNeededEvent_t *)pMsg; // 根据文档,我们的固定密码是000000 uint32_t passkey = 0; // 六位数字0 // 调用SM(安全管理器)API回复密码 SM_PasskeyRsp(pEvent->connectionHandle, SUCCESS, passkey); break; }注意事项:在实际产品中,使用固定密码(如000000或123456)是极不安全的,仅适用于演示或无需安全性的场景。对于需要安全配对的设备,应使用“Just Works”(自动配对)或“Passkey Entry”(动态输入密码)等方式。TI的协议栈中,可以通过
GAPBondMgr_SetParameter来配置配对要求和IO能力。
4.3 功耗优化实战技巧
对于电池供电的温度计,功耗是生命线。以下是我在多个项目中总结的优化点:
- 动态广播间隔:不要在整个生命周期使用固定间隔。在刚上电或用户主动寻找设备时,使用短间隔(如20ms)快速广播;如果广播一段时间(如30秒)后仍未连接,则将间隔逐步拉长(如500ms甚至1s),进入“慢广播”省电模式。
- 连接后立即停止广播:一旦进入
GAPROLE_CONNECTED状态,应立即通过GAPRole_SetParameter(GAPROLE_ADVERT_ENABLE, sizeof(uint8_t), &FALSE)禁用广播。这是最直接的省电操作。 - 协商更长的连接间隔:在连接建立后的参数更新请求中,大胆地请求更长的连接间隔。对于温度计这种几分钟变化一次的数据,将间隔设为2秒、从设备延迟设为9(即最多20秒通信一次)是完全可以接受的。这能大幅降低射频活动时间。
- 利用从设备延迟(Slave Latency):这是BLE协议中为从设备设计的“免打扰金牌”。设置合理的延迟,允许设备在多个连接事件内深度睡眠。关键计算:确保
监督超时 > (1 + Slave Latency) * (连接间隔 * 2)。例如,间隔2秒,延迟9,监督超时必须大于(1+9)*2*2=40秒,我们可以设置为45秒。 - 在
GAPROLE_WAITING状态进入低功耗模式:GAPROLE_ADVERT_OFF_TIME参数定义了广播停止后到下次广播开始前的等待时间。在这个等待期内,应用应该调用Power_sleep()让芯片进入所能支持的最深睡眠模式。
5. 调试、问题排查与性能分析
开发BLE应用,一半时间在写代码,另一半时间在调试。以下是一些常见问题的排查思路和工具使用心得。
5.1 常见连接问题排查表
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 设备根本扫描不到 | 1. 广播未开启。 2. 广播参数(类型、信道)错误。 3. 物理层问题(天线、匹配)。 | 1. 确认GAPROLE_ADVERT_ENABLE已设为TRUE,且状态机进入GAPROLE_ADVERTISING。2. 使用BLE嗅探器(如TI的Packet Sniffer、nRF Sniffer)抓取空中包,检查广播报文格式是否正确。 3. 检查RF电路,用频谱仪查看是否有能量辐射。 |
| 扫描到但连接失败 | 1. 设备地址类型不匹配。 2. 连接参数请求被拒绝。 3. 协议栈资源不足。 | 1. 确认中央设备发起的连接请求中,地址类型与广播地址类型一致(公共/随机)。 2. 检查中央设备日志,看是否返回 LL_ERROR_UNACCEPTABLE_CONN_PARAMETERS(0x3B)。调整连接参数范围。3. 查看协议栈ICall内存配置是否足够,或已有过多连接。 |
| 连接后立即断开 | 1. 监督超时(Supervision Timeout)设置过小。 2. 射频干扰严重。 3. 从设备延迟导致通信超时。 | 1.这是最常见原因!确保监督超时满足公式。建议设置为计算值的1.5-2倍。 2. 更换环境测试,或调整跳频信道图(Channel Map)。 3. 如果从设备延迟很大,确保主设备在超时前至少有一次成功的通信。 |
| 数据发送不稳定,时断时续 | 1. 连接间隔太短,从设备处理不过来。 2. GATT层MTU或数据包长度设置不当。 3. 应用层数据处理太慢,堵塞协议栈。 | 1. 适当增加连接间隔,或减少从设备延迟。 2. 尝试协商更大的MTU(如通过 GATT_ExchangeMTU),或拆分大数据包发送。3. 优化应用代码,确保在连接事件回调中快速完成数据准备和发送,避免长时间占用任务。 |
| 配对失败 | 1. 密码输入错误或超时。 2. 安全需求不匹配(IO能力)。 3. 绑定信息存储失败。 | 1. 确认双方输入的密码一致,且在规定时间内(通常30秒)完成。 2. 检查 GAPBondMgr的配置,确保MITM(中间人保护)、bonding等标志位设置正确。3. 检查Flash(SNV)存储区是否已满或损坏。 |
5.2 使用TI工具进行深度调试
- SmartRF Packet Sniffer:这是TI的官方抓包工具,配合CC2540 USB Dongle等硬件,可以无损捕获空中的BLE报文。看连接建立过程:过滤
LL_CONNECTION_REQ和LL_CONNECTION_UPDATE_REQ包,能直观看到双方协商的连接参数是否和你代码设置的一致。 - EnergyTrace™:如果你的芯片支持(如CC26xx系列),这是分析功耗的神器。它可以实时显示芯片在不同模式(广播、连接、睡眠)下的电流消耗,并精确计算出电池寿命。我曾用它发现了一个Bug:连接后广播未关闭,导致功耗比预期高了10倍。
- SysConfig & TI Drivers:在新版本的SDK中,TI提供了图形化的SysConfig工具来配置引脚、射频参数、协议栈参数等。它能自动生成
ti_drivers_config.c文件,并进行参数合法性检查,能避免很多因手动配置错误导致的问题,强烈推荐使用。
5.3 连接参数优化实战:一个案例
在一个实际项目中,我们的温度计要求一颗CR2032电池工作一年以上。初始参数:广播间隔100ms,连接间隔50ms,从设备延迟0,监督超时1s。实测平均电流约200uA,电池寿命仅3个月。
优化过程:
- 广播阶段:改为快速广播(20ms)持续5秒,若未连接则切换到慢广播(1s)。平均电流从~80uA降至~15uA。
- 连接阶段:
- 连接后立即停止广播。
- 在
GAPROLE_CONNECTED状态回调中,主动调用GAPRole_SendUpdateParam请求更新参数。 - 请求参数:
minConnInterval = maxConnInterval = 1600(2秒),latency = 9,connTimeout = 600(6秒)。 - 计算验证:监督超时(6s) > (1+9)22 = 40s?不满足!这里我犯了个错,单位没统一。
connTimeout是n*10ms,600对应6秒。connInterval是n*1.25ms,1600对应2000ms即2秒。公式应为:6s > (1+9)2s2 = 40s?显然6<40。这是导致连接在几十秒后必然断开的根源! - 修正:将
connTimeout改为4000(40秒)。最终参数满足:40s > (1+9)2s2 = 40s,留有微小余量。
- 结果:优化后,连接态平均电流降至约20uA,结合更深的睡眠和传感器间歇工作,整体平均电流达到目标,电池寿命满足一年要求。
这个案例深刻说明:理解每一个参数的单位和含义,并亲手进行验算,是BLE开发中避免低级错误的关键。协议栈不会帮你检查这些逻辑错误,它只会按照你的指令执行,然后在参数非法时断开连接。