
1. 项目概述与核心价值如果你正在或打算涉足物联网设备的开发尤其是那些需要长时间待机、靠电池供电的传感器类产品那么低功耗蓝牙技术绝对是你绕不开的核心技能。我接触过不少项目从智能手环到医疗贴片大家遇到的第一个拦路虎往往不是功能实现而是如何让设备在有限的电量下“活”得更久。德州仪器的BLE SDK特别是其丰富的示例应用就像一位经验丰富的向导能帮你快速摸清门道避开很多新手容易栽进去的坑。这份SDK文档里列举的十多个示例远不止是几行演示代码那么简单。它系统性地展示了如何将一个具体的业务需求比如测量心率、血压通过BLE协议栈转化成一个稳定、可靠、低功耗的无线外设产品。从最基础的设备广播、连接建立到复杂的数据服务定义、安全配对乃至电源管理和连接参数优化每一个示例都是一个完整的、可编译运行的参考设计。对于开发者而言其价值在于提供了一个“最佳实践”的模板你可以直接基于它进行二次开发极大地缩短了从原理图到可演示原型的周期。今天我们就以其中最具代表性的血压传感器和心率传感器两个示例为切入点深入拆解其实现逻辑、关键配置以及在实际开发中那些文档里不会明说但至关重要的经验细节。无论你是刚接触BLE的新手还是想深入了解TI协议栈特点的资深工程师相信都能从中获得直接的启发和可复用的代码思路。2. TI BLE SDK示例应用整体架构解析在深入具体示例之前有必要先理解TI BLE SDK示例应用的整体设计哲学和代码组织方式。这能帮助你在修改和移植时清楚地知道该动哪里以及为什么这么动。2.1 基于角色的工程结构TI的示例应用严格遵循了蓝牙SIG定义的“角色”模型。例如血压/心率示例扮演的是“传感器”角色而像glucose_collector、simple_central则扮演“收集器”或“中心设备”角色。这种角色划分直接体现在工程的文件结构和初始化流程上。每个示例工程的核心通常包含以下几个关键文件main.c应用入口负责硬件初始化、ICall框架初始化和创建应用任务。app.c或类似命名的应用文件应用任务的主体实现了SimpleProfile的回调函数处理来自GATT层的事件如连接、断开、读/写/通知请求。peripheral.c或central.c实现了设备角色外设或中心设备的通用操作如广播控制、连接参数更新请求等。服务实现文件如heartrateservice.c、bloodpressureservice.c。这些文件定义了该示例专属的GATT服务、特征值及其属性读、写、通知、指示。这里是业务逻辑的核心数据如何生成、封装、发送都在这里完成。实操心得当你需要创建一个新的传感器类型时最快捷的方式不是从头开始而是复制一个最接近的示例比如心率传感器然后重点修改其对应的服务实现文件。你需要修改服务UUID、特征值定义以及数据模拟或真实采集的逻辑而广播、连接管理、电源管理这些底层框架几乎可以复用。2.2 协议栈与应用的分层ICall机制TI的BLE协议栈运行在一个独立的CPU内核或任务中对于CC26xx系列是ROM中的协议栈或Radio Core。应用层代码通过一个名为ICall的进程间通信机制与协议栈交互。简单理解ICall就是一套定义好的消息队列和API应用层通过发送消息来请求协议栈执行操作如启动广播、发送通知协议栈也通过回调消息来通知应用层事件如连接建立、特征值被写入。在示例代码中你会频繁看到ICall_registerApp、ICall_wait等函数。对于初学者可以暂时不用深究其内部机制但必须明白所有与蓝牙连接、数据收发相关的操作最终都需要通过ICall消息来驱动。例如当你在app.c中收到一个SBP_WRITE_EVT事件表示中心设备写入了一个特征值来使能通知你的应用代码需要构造一个GATT通知消息并通过GATT_Notification函数发送这个函数内部就是通过ICall将请求递交给协议栈的。2.3 示例的硬件抽象层与按键驱动几乎所有示例都依赖SmartRF06评估板上的按键和LCD进行交互。这背后是硬件抽象层在起作用。HAL层将具体的硬件操作如读取某个GPIO引脚的状态、控制LCD显示特定字符抽象成统一的API如HalKeyRead()、HalLcdWriteString()。为什么这很重要当你要将示例代码移植到自己的硬件板时最需要修改的就是HAL层。TI提供了HAL的源码你需要根据自己板子的原理图重新实现或修改hal_key.c、hal_lcd.c等文件中的函数。例如你的按键可能接在不同的GPIO上或者你根本不用LCD而改用串口打印日志。处理好HAL层是代码成功移植的第一步。以按键处理为例示例中通常采用中断或轮询方式检测按键。在heartrate.c的初始化函数里会调用HalKeyConfigure()来配置按键并注册一个回调函数。当用户按下“UP”键循环切换数据格式时实际上是HAL层检测到按键事件调用注册的回调回调函数再根据当前连接状态执行HeartRate_Notify来发送不同格式的心率数据。3. 血压传感器示例深度剖析与实操血压传感器示例是一个严格按照蓝牙SIG《血压剖面规范》实现的经典案例。它模拟了一个专业的医疗设备数据上报流程涉及多种数据格式和单位转换非常适合用来学习如何构建一个符合行业标准的BLE设备。3.1 服务与特征值定义解析在bloodpressureservice.c中你会找到血压服务的完整定义。它主要包含两个核心服务设备信息服务这是一个通用服务用于提供设备制造商、型号、序列号、固件版本等信息。任何标准的BLE设备扫描工具都能读取这些信息。血压服务这是核心其UUID为0x1810。它内部定义了多个特征值血压测量这是最重要的特征属性为Indicate。Indicate与Notify类似都能主动向中心设备发送数据但Indicate要求接收方回复一个确认因此更可靠适合血压这种重要的医疗数据。其值是一个结构体包含收缩压、舒张压、脉搏、时间戳、用户ID、状态标志等字段。血压测量上下文可选特征用于提供额外的测量环境信息如用户姿势、袖带尺寸等。血压特征用于描述血压测量特征值的各种描述符例如“客户端特征值配置描述符”中心设备通过向这个描述符写入0x0002来启用Indication。在代码中这些是通过一个静态常量数组bloodPressureServCBs来声明和初始化的。理解这个数据结构是自定义服务的基础。3.2 数据模拟与格式切换逻辑示例中的数据是模拟的但这恰恰是开发初期最高效的方式。在BloodPressure_MeasNotify函数中你会看到数据是如何被组装的。static uint8_t bpSimulation[BP_MEAS_LEN_MAX]; ... // 填充收缩压和舒张压 bpSimulation[0] LO_UINT16(systolic); bpSimulation[1] HI_UINT16(systolic); bpSimulation[2] LO_UINT16(diastolic); bpSimulation[3] HI_UINT16(diastolic); // 根据当前格式标志位选择性填充脉搏、时间戳等字段 if (formatFlags BP_FLAG_PULSE_RATE_PRESENT) { // 填充脉搏数据 } ... // 调用GATT_Notification发送 GATT_Notification( connHandle, bloodPressureMeas, ATT_BT_UUID_SIZE );格式切换是此示例的一个亮点。通过按下“UP”键可以在mmHg带时间戳、纯kPa、kPa带脉搏等多种格式间循环。这实际上是通过修改一个全局的formatFlags变量来实现的。这个标志位不仅控制着数据组装逻辑在中心设备读取“血压特征”描述符时也会返回这个标志告知中心设备当前数据包包含哪些字段。这种设计充分体现了GATT协议的灵活性。3.3 安全配对流程与实操细节文档中提到如果对端设备发起配对血压传感器会要求输入密码默认为000000。这个行为是在协议栈的GAP层配置的。在bloodpressure.c的初始化函数BloodPressure_init中通常会调用GAPBondMgr_SetParameter来设置配对参数比如是否要求绑定、使用哪种IO能力键盘显示、只输出等。对于血压计这种可能没有显示屏的设备IO能力通常设置为GAPBOND_IO_CAP_DISPLAY_ONLY或GAPBOND_IO_CAP_NO_INPUT_NO_OUTPUT并配合一个固定密码。关键配置点GAPBOND_DEFAULT_PASSCODE默认密码示例中可能硬编码为000000。GAPBOND_PAIRING_MODE设置为GAPBOND_PAIRING_MODE_WAIT_FOR_REQ等待中心设备发起配对。GAPBOND_MITM_PROTECTION是否要求中间人保护。对于医疗数据通常建议开启。注意事项在实际产品中绝对不要使用默认密码。你应该在应用初始化时动态生成一个随机密码或者通过某种用户交互方式如按特定组合键来设置。将固定密码编译进固件是严重的安全隐患。3.4 广播与连接状态机管理血压传感器的广播行为是典型的“按需广播”上电后设备处于休眠状态不广播。用户按下“RIGHT”键启动广播快速广播间隔如20ms。被连接后停止广播。连接断开后不会自动恢复广播必须再次按下“RIGHT”键。这个逻辑在app.c的事件处理函数中实现。当收到GAP_DEVICE_INIT_DONE_EVENT设备初始化完成后应用并不立即启动广播。只有当收到KEY_CHANGE事件且对应按键是“RIGHT”键时才调用GAP_DeviceInit设置广播参数并启动广播。连接断开事件GAP_LINK_TERMINATED_EVENT触发后应用也只是更新内部状态为“断开”等待下一次按键事件。为什么这样设计为了极致省电。在等待用户操作的待机状态下设备可以进入深度睡眠功耗可能低至1μA以下。这种设计非常适合不频繁使用的医疗设备。4. 心率传感器示例实现与优化策略心率传感器示例同样基于SIG规范但相比血压传感器它更侧重于连续、周期性的数据流传输并且在电源管理策略上有所不同。4.1 心率服务的数据结构与能量计算心率服务除了基本的心率测量值Heart Rate Measurement还定义了“身体传感器位置”、“能量消耗累计值”、“RR间隔”等可选特征。示例中通过“UP”键循环切换的7种数据格式正是这些特征值的不同组合。RR间隔是一个值得深入理解的参数。它代表连续心跳之间的时间间隔单位是毫秒。提供RR间隔数据对于计算心率变异性至关重要这在运动科学和健康监测中很有价值。在代码中模拟RR间隔数据需要生成一个时间间隔数组。能量消耗的计算是一个小难点。规范中定义能量消耗的单位是千焦耳。示例中通常采用一个简化的模拟算法可能基于心率值和模拟的体重、运动强度来估算。在实际产品中这需要集成加速度计等传感器数据通过更复杂的算法如ACSM代谢计算公式来估算。4.2 通知与指示的选用策略心率传感器使用的是Notify而血压传感器使用的是Indicate。这是有讲究的。通知服务器发送后不要求客户端确认。可能丢失但开销小、延迟低。指示服务器发送后要求客户端确认。可靠但每次发送都有额外的确认包开销功耗和延迟更高。心率数据是连续、高频每秒一次或多次且允许偶尔丢失的流式数据使用Notify更为合适。血压数据是单次、关键、不允许出错的测量结果使用Indicate保证可靠性更为重要。这个选择体现了BLE协议设计中对不同业务场景的权衡。4.3 连接参数与广播间隔的优化心率示例的广播行为比血压示例更智能按键启动或连接因链路丢失断开时先以快速间隔广播30秒以期快速重连然后切换到慢速间隔广播以节省电量。正常断开连接时直接以慢速间隔广播60秒然后休眠。连接参数对功耗和性能的影响巨大但示例中通常使用协议栈默认值。在实际开发中你必须根据应用场景调整它们。这些参数在连接建立后可以由外设通过GAP_UpdateLinkParamReq发起更新请求。连接间隔两个数据包之间的时间。间隔越短实时性越好但功耗越高。心率传输可能需要100ms以下的间隔而血压计可能用500ms甚至更长。从机延迟允许从设备跳过多少个连接事件而不监听。增大此值可显著降低平均功耗但会增大数据延迟。监督超时链路无通信多久后判定为断开。通常是连接间隔的10倍以上。在heartrate.c中你可以找到类似HEARTRATE_ADV_FAST_INT和HEARTRATE_ADV_SLOW_INT的宏定义这里就是调整广播行为的地方。4.4 低功耗模式下的传感器调度这是示例代码没有展示但真实产品必须考虑的。假设你的心率传感器集成了光学心率模块该模块每秒钟测量一次会消耗5mA电流。你不能让它一直工作。一个常见的策略是在未连接时心率传感器完全关闭设备处于深度睡眠。连接建立后收到中心设备“启用通知”的写入请求。应用层启动一个定时器例如1秒周期。定时器中断中唤醒心率传感器模块进行测量填充数据调用GATT_Notification发送然后立即关闭传感器。在连接间隔之间MCU和射频部分可以进入睡眠。你需要利用TI-RTOS的时钟模块或简单的硬件定时器来实现这个调度逻辑并确保测量、发送的时序不会错过连接事件。5. 从示例到产品关键问题排查与实战技巧把示例跑通只是第一步把它变成稳定可靠的产品中间还有很长的路要走。下面分享几个我踩过坑后总结的关键点和排查技巧。5.1 连接不稳定与断线重连问题现象设备经常无故断开或者手机App显示设备时连时断。排查步骤1检查电源。这是最常见的原因。使用示波器测量设备在射频发射时的电池电压。如果电压跌落严重例如低于芯片的最低工作电压会导致复位或掉线。解决方法优化电源电路增加大容量电容或选择更高放电能力的电池。排查步骤2分析空中包。使用TI的Packet Sniffer或商用蓝牙嗅探器抓取空中数据包。查看连接请求、参数更新、断开连接等指令是谁发起的以及原因码是什么。例如断开原因码0x08代表“连接超时”通常意味着监督超时时间内没有成功通信。排查步骤3调整连接参数。中心设备通常是手机可能拒绝了外设的参数更新请求。在peripheral.c的peripheralGapRoleCB回调函数中处理GAPROLE_PARAM_UPDATE_EVENT事件检查状态是否为SUCCESS。如果不成功可以尝试在应用层实现一个退避策略稍后再次发起请求。排查步骤4检查软件流控。如果使用串口与外部传感器通信确保串口缓冲区足够大且处理速度够快避免数据堵塞导致看门狗复位。5.2 数据发送失败或中心设备收不到通知现象设备日志显示调用了GATT_Notification但手机App收不到数据。排查步骤1确认通知已使能。这是最根本的原因。必须在中心设备写入“客户端特征值配置描述符”CCCD为0x0001后外设才能发送通知。在app.c的写事件处理中打印出写入的特征值句柄和值确认CCCD被正确写入。排查步骤2检查连接句柄。GATT_Notification函数需要传入正确的连接句柄。确保在连接建立事件GAP_LINK_ESTABLISHED_EVENT中保存了全局的连接句柄并且在断开事件中将其重置为无效值。排查步骤3检查缓冲区与长度。确保传递给GATT_Notification的数据指针有效且数据长度不超过该特征值定义的最大长度ATT_MTU - 3。超过长度会被协议栈静默丢弃。排查步骤4MTU交换。默认的ATT_MTU是23字节有效载荷只有20字节。如果数据包很大需要在连接后发起MTU交换请求以获取更大的传输单元。示例中可能没有启用你需要调用GATT_ExchangeMTU函数。5.3 功耗高于预期现象电池续航远低于理论计算值。优化点1最大化睡眠时间。使用TI-RTOS的电源管理框架确保在无事可做时任务调用Task_sleep()或进入POWER_SAVING模式。使用ICall_wait等待事件本身就是一种低功耗设计。优化点2优化广播参数。在非连接状态下功耗主要由广播决定。尽可能使用最慢的广播间隔如1秒甚至更长并缩短快速广播的持续时间。将广播数据包做得尽可能小。优化点3优化连接参数。如前所述在满足数据实时性要求的前提下尽可能增大连接间隔和从机延迟。一个从机延迟为5、连接间隔为100ms的连接其平均功耗可能比从机延迟为0时降低80%以上。优化点4关闭无用外设和调试接口。在最终产品固件中禁用所有未使用的GPIO、串口、LED驱动并将用于调试的LOG输出宏定义为空。一个闪烁的LED或持续的串口输出会消耗可观的电量。5.4 自定义服务的添加与调试当你需要基于示例创建自己的服务时使用GATT数据库工具TI提供了GATT DatabaseExcel表格或BTool来可视化地设计服务。定义好服务、特征值、属性、UUID后工具可以生成对应的.c和.h文件直接替换到工程中。这比手动编写数组要可靠得多。为每个特征值分配独立的事件在simpleGATTprofile.c中TI使用一个simpleProfileChangeEvt事件来处理所有特征值的通知使能。对于复杂服务最好为每个可通知/指示的特征值定义独立的事件标志逻辑更清晰。善用ATT_MTU如果你的特征值数据很长务必在连接后执行MTU交换。在app.c的连接建立事件中添加GATT_ExchangeMTU的调用。并在回调事件中检查交换后的MTU值用它来约束你后续发送的数据包大小。6. 开发环境搭建与调试实战指南理论最终要落到实操。基于TI BLE SDK开发通常有两种主流的路径基于IAR Embedded Workbench或德州仪器自家的Code Composer Studio。我个人更推荐CCS因为它对TI的芯片支持更原生且社区版免费。6.1 工程导入与基础编译获取SDK从TI官网下载最新的SimpleLink CC13xx/CC26xx SDK。确保其版本与你的芯片型号如CC2640R2F, CC2652R匹配。导入示例工程打开CCS选择“Import CCS Projects”导航到SDK安装目录下的examples\rtos\CC2640R2_LAUNCHXL\ble5stack选择你想用的示例工程如simple_peripheral。配置预编译符号这是关键一步。在项目属性 - Build - ARM Compiler - Predefined Symbols中你需要根据你的硬件板定义正确的符号。例如对于CC2650 LaunchPad需要定义CC2650_LAUNCHXL。如果使用自定义板你可能需要定义BOARD_DISPLAY_USE_UART用串口代替LCD等。解决头文件路径SDK工程通常已经配置好路径。如果编译报错找不到头文件检查“Include Options”中的路径是否指向了SDK的source和kernel\tirtos等目录。6.2 调试工具链从LOG到空中抓包LOG输出最基础的调试手段。TI的示例工程通常使用Display_printf或Log_info。你需要先在Board.h中启用Display_DISABLE_ALL或xdc.runtime.Log的支持并指定输出到UART。连接开发板的串口到电脑用串口助手工具如Putty、SecureCRT查看打印信息。TI-RTOS System AnalyzerCCS内置的强大工具。它可以图形化地显示任务切换、信号量、事件、队列等RTOS内核对象的状态对于分析多任务间的协作和死锁问题非常有效。EnergyTrace如果你使用的是TI的LaunchPad开发板并且连接了XDS110调试器可以使用EnergyTrace功能。它能实时测量芯片的电流消耗并分解到各个任务和模块是功耗优化的终极利器。蓝牙协议分析仪这是解决复杂无线问题的必备工具。除了TI的Packet Sniffer市面上还有Ellisys、Frontline等商业分析仪。它们不仅能抓取BLE空中包还能解码各层协议PHY, LL, L2CAP, ATT, GATT让你清晰地看到连接建立、数据交换、参数更新的全过程。当遇到手机兼容性问题时抓包对比分析往往是唯一有效的解决途径。6.3 向真实传感器演进替换模拟数据示例中使用的是模拟数据要连接真实传感器你需要选择通信接口根据传感器类型可能是I2C、SPI或ADC。TI的SDK提供了对应的驱动库DriverLib或TI-RTOS的GPIO,I2C,SPI驱动。编写传感器驱动创建一个独立的.c/.h文件封装传感器的初始化、配置、数据读取函数。参考SDK中SensorTag示例的传感器驱动写法。集成到应用任务在原有的应用任务如HeartRate_taskFxn中将原来生成模拟数据的代码替换为调用你的传感器驱动来读取真实数据。注意时序和阻塞问题传感器读取可能是毫秒级的操作要避免在任务中长时间阻塞。可以考虑使用TI-RTOS的时钟或硬件中断来触发周期性读取然后将数据通过消息队列发送给应用任务进行处理和发送。从阅读文档、运行示例到修改代码、调试问题最终打造出属于自己的低功耗蓝牙产品这个过程充满挑战但也极具成就感。TI的这套示例和协议栈为你铺好了最坚实的地基剩下的就是结合具体的业务需求在上面建造稳固而精巧的建筑。记住低功耗蓝牙开发一半是通信协议另一半是电源管理两者结合才能做出真正优秀的产品。