基于TI CC2564x的BLE健康服务开发实战:心率与体温监测
1. 项目概述与核心价值如果你正在为可穿戴设备、医疗监护仪或者任何需要无线传输生理数据的嵌入式项目选型那么蓝牙低功耗BLE技术几乎是一个绕不开的选项。它功耗低、协议栈成熟但真正让它在健康监测领域大放异彩的是其背后一套标准化的数据交换架构——通用属性配置文件GATT。GATT通过定义服务、特征和描述符为心率、体温、血压等健康数据的传输提供了“通用语言”。然而从理解协议到在具体的硬件平台上跑通第一个数据点中间往往隔着一条鸿沟。官方文档可能过于简略示例代码又常常语焉不详让不少开发者尤其是刚接触BLE嵌入式开发的朋友感到无从下手。本文将以德州仪器TI的CC2564x系列蓝牙模块为例手把手带你实现两个最经典的健康服务心率服务HRS和健康温度计服务HTP。选择CC2564x是因为它在工业界和学术界都有广泛的应用基础其配套的软件栈和示例相对完整非常适合作为学习BLE服务开发的切入点。我不会只停留在理论层面而是会深入到具体的API调用、命令行操作和参数配置中把我在实际项目中调试CC2564x模块时遇到的坑和总结的技巧一并分享给你。无论你是想快速验证一个健康监测产品的原型还是希望深入理解BLE服务在嵌入式端的实现细节这篇文章都能为你提供一条清晰的路径。2. 核心概念与开发环境搭建在动手写代码之前我们必须先统一“语言”。BLE通信的核心是客户端-服务器Client-Server模型在GATT语境下我们通常称数据提供方为“服务器”Server或“外设”Peripheral数据接收方为“客户端”Client或“中央设备”Central。一个“服务”Service就像是一个功能容器例如“心率服务”。服务内部包含若干个“特征”Characteristic这是实际承载数据的基本单元比如“心率测量特征”。每个特征都有属性例如可读、可写、可通知Notify、可指示Indicate。其中Notify和Indicate是服务器主动向客户端推送数据的两种机制区别在于Indicate要求客户端回复确认因此更可靠但功耗稍高而Notify则是一种“发了就不管”的轻量级推送。我们的实战平台是TI的CC2564x模块它通常与MSP430、Tiva C系列或STM32F4等微控制器搭配构成一个完整的嵌入式蓝牙解决方案。TI提供了完整的蓝牙协议栈Bluetooth Stack及上层应用API我们的开发工作主要就是调用这些API。2.1 硬件与软件准备首先你需要准备两块支持CC2564x模块的开发板例如MSP-EXP430F5438、EK-TM4C123GXL等一块作为服务器一块作为客户端。同时确保你的PC上已安装好对应的IDE如Code Composer Studio, IAR Embedded Workbench和串口调试工具如PuTTY、Tera Term。最关键的一步是获取并编译TI的蓝牙协议栈及示例工程。通常你需要从TI官网下载BLE-STACKSDK。这个SDK里包含了协议栈库文件、API头文件以及丰富的示例项目。找到针对你所用开发板和CC2564x模块的HRS和HTP示例工程并成功编译、下载到你的两块开发板中。注意不同版本的SDK和不同型号的开发板其工程配置和引脚定义可能有细微差别。务必仔细阅读SDK包内的Getting Started Guide确认工程中的蓝牙模块型号、UART引脚、电源配置与你手中的硬件完全匹配。我曾因为忽略了电源管理配置导致模块无法正常启动排查了大半天。2.2 理解示例应用框架TI的示例工程通常提供了一个基于命令行的交互式演示Demo。通过串口终端连接到开发板你会看到一个命令列表。这种设计非常适合我们分步学习和调试。其背后的软件框架大致如下初始化层主函数初始化硬件、蓝牙协议栈。任务调度层通常基于一个简单的事件循环或RTOS任务处理来自协议栈和用户输入的事件。命令解析层解析你在串口终端输入的字符串命令如RegisterHRS并调用对应的处理函数。GATT应用层即我们关注的核心实现了HRS或HTP服务的具体回调函数和API调用。我们的实战将围绕这些串口命令展开但我会同时解释其背后对应的C语言API调用让你知其然更知其所以然。3. 心率服务HRS实战开发心率服务Heart Rate Service, HRS是BLE健康设备配置文件Health Device Profile, HDP中最常用的服务之一用于传输心率及相关数据。3.1 服务器端注册服务与广播首先我们将一块开发板配置为HRS服务器模拟心率传感器。步骤一注册HRS服务在服务器的串口终端中输入命令RegisterHRS 1。命令作用在本地蓝牙协议栈中创建并注册一个心率服务实例。底层API这个命令最终调用了HRS_Initialize_Service()函数。// 伪代码示意 int BTPSAPI HRS_Initialize_Service( unsigned int BluetoothStackID, // 协议栈ID通常为0 unsigned long Supported_Commands, // 支持的控制命令位掩码 HRS_Event_Callback_t EventCallback, // 事件回调函数指针 unsigned long CallbackParameter, // 传递给回调函数的用户参数 unsigned int *ServiceID // 输出参数返回服务实例ID );Supported_Commands参数是一个位掩码例如HRS_HEART_RATE_CONTROL_POINT_RESET_ENERGY_EXPENDED_SUPPORTED表示服务器支持“重置能量消耗”这个控制点命令。这决定了客户端能执行哪些高级操作。EventCallback至关重要。当客户端尝试读取、写入或订阅Notify/Indicate某个特征时服务器会通过这个回调函数收到事件你需要在回调函数中编写处理逻辑比如返回传感器位置信息。实操心得ServiceID是一个重要的输出句柄后续所有针对这个服务的操作如发送通知都需要用到它。虽然示例命令里没体现但在你自己的代码中一定要保存好这个ServiceID。步骤二开启低功耗广播输入命令AdvertiseLE 1。命令作用使设备开始发送BLE广播包宣告自己的存在。广播包中包含了设备名称、服务UUID0x180D即HRS服务等信息。关键参数实际的GAP_Advertise()API调用会涉及广播间隔、广播类型可连接、可扫描等参数。较短的广播间隔能被更快发现但功耗更高。对于心率带这类设备通常使用较慢的间隔以节省电量。注意事项确保广播数据Advertising Data中正确包含了HRS服务的128位UUID或16位短UUID0x180D。客户端正是通过扫描这些信息来识别你的设备是一个心率传感器。3.2 客户端扫描、连接与服务发现在另一块作为客户端的开发板上进行操作。步骤一扫描设备输入命令StartScanning。 客户端会开始扫描周围的BLE广播设备。你会在终端看到扫描结果其中应包含之前服务器的设备地址BD_ADDR一个类似00:1A:7D:DA:71:13的MAC地址和设备名。步骤二发起连接获得服务器地址后输入命令ConnectLE BD_ADDR例如ConnectLE 00:1A:7D:DA:71:13。 如果连接成功服务器和客户端的终端都会打印LE_Connection_Complete信息并显示连接句柄Connection ID。这个ConnectionID是后续所有基于此连接进行通信的关键参数。步骤三服务与特征发现连接建立后GATT通信尚未就绪。客户端需要查询服务器端提供了哪些服务及其特征。发现服务输入DiscoverHRS 1。这个命令会触发客户端向服务器发起“服务发现”请求。底层调用GDIS_Service_Discovery_Start()函数传入HRS服务的UUID。成功后客户端会打印出服务器支持的HRS服务特征列表例如Heart Rate Measurement(UUID: 0x2A37) - 属性NotifyBody Sensor Location(UUID: 0x2A38) - 属性ReadHeart Rate Control Point(UUID: 0x2A39) - 属性Write配置通知要接收服务器主动推送的心率数据客户端必须“订阅”Heart Rate Measurement特征的通知功能。输入命令ConfigureRemoteHRS 1。这个“1”就代表启用通知。底层会向服务器的该特征写入一个特定的“客户端特征配置描述符CCCD”值通常是0x0001以启用通知。核心原理DiscoverHRS和ConfigureRemoteHRS这两个命令本质上是在执行GATT协议规定的“服务发现”和“描述符写入”流程。这是任何BLE客户端与一个陌生服务器交互的标准步骤不仅仅是HRS所有服务都遵循此流程。3.3 数据收发与服务交互配置完成后数据通道就打通了。服务器发送心率数据在服务器终端输入NotifyHeartRate 80 1 2 1 0这个命令的参数需要仔细理解80: 心率值80 BPM。1: 心率格式。0 8位1字节1 16位2字节。这里选1表示心率值80是以16位格式发送的虽然80这个值用8位也足够。2: 传感器接触状态。0不支持1支持但未检测到2支持且已检测到。佩戴好的心率带应设为2。1: 能量消耗标志。0不发送能量消耗字段1发送。0: RR间隔数。0不发送RR间隔数据。RR间隔是两次心跳之间的时间差用于分析心率变异性HRV是高级功能。这条命令调用了HRS_Notify_Heart_Rate_Measurement()API将封装好的心率测量数据包通过之前建立的连接发送给客户端。客户端终端会立即显示收到的心率数据。其他交互示例查询传感器位置客户端可以发送GetBodySensorLocation命令。这会触发一个GATT读请求服务器端的HRS_EventCallback会收到读事件并返回之前通过SetBodySensorLocation命令设置的位置值如手腕、胸部。重置能量消耗如果服务器支持控制点客户端可以发送ResetEnergyExpended命令。这是一个GATT写操作服务器收到后应在回调函数中处理将内部累计的能量消耗值清零。4. 健康温度计服务HTP实战开发健康温度计服务Health Thermometer Service, HTP用于传输体温数据其逻辑与HRS类似但特征更复杂包含了温度类型、时间戳、测量间隔等更多信息。4.1 服务器端配置与广播步骤一注册HTP服务在服务器终端输入RegisterHTS。 与HRS类似此命令调用HTS_Initialize_Service()初始化服务。HTP服务包含的特征更多例如Temperature Measurement指示、Temperature Type读写、Measurement Interval读写等。步骤二开启广播同样使用AdvertiseLE 1命令开始广播。广播数据中应包含HTP服务的UUID0x1809。4.2 客户端连接与深度配置客户端扫描StartScanning、连接ConnectLE的步骤与HRS完全一致此处不再赘述。连接建立后HTP的服务发现与配置更为细致。步骤一发现服务与特性输入DiscoverHTS命令。客户端会列出服务器HTP服务支持的所有特征及其属性。你会发现Temperature Measurement特征的属性是Indicate指示这意味着服务器发送温度数据时需要客户端的确认比HRS的Notify更可靠。步骤二配置远程HTP服务输入命令ConfigureRemoteHTS 1 1 1。 这个命令的三个参数分别对应Temperature Measurement Indicate: 是否启用温度测量指示。1启用。Measurement Interval Indicate: 是否启用测量间隔指示。1启用当间隔改变时通知客户端。Intermediate Temperature Notify: 是否启用中间温度通知。1启用用于连续测量中的实时数据流。启用这些配置同样是向服务器对应的CCCD描述符写入特定值。4.3 温度数据上报与参数管理发送温度指示在服务器端使用IndicateTemperature命令上报一次温度测量值。命令格式更复杂IndicateTemperature 0 100 1 20120724095000 20: 指数Exponent。用于表示温度值的小数点位置。0表示尾数Mantissa即实际值。100: 尾数Mantissa。温度值。结合指数这里表示100.0。1: 单位。0摄氏度1华氏度。这里表示100°F。20120724095000: 时间戳。格式为YYYYMMDDHHMMSS。这里表示2012年7月24日9点50分00秒。2: 温度类型。2体表温度Body。其他类型如1腋下3耳温6口温等。这条命令调用了HTS_Indicate_Temperature_Measurement()API。由于是Indicate服务器发送后会等待客户端的确认Confirmation收到确认后才会认为发送成功否则可能会重发。管理测量参数HTP服务提供了丰富的参数管理功能体现了BLE服务在医疗设备中的严谨性设置/获取温度类型服务器可用SetTemperatureType 3设置传感器测量位置为耳温。客户端可用GetTemperatureType读取。设置/获取测量间隔SetMeasurementInterval 2000将测量间隔设置为2000毫秒2秒。客户端可以读取或写入此值如果服务器允许以控制数据上报频率。设置有效范围SetValidRange 320 420可以设置温度的有效范围例如32.0°C 到 42.0°C。这对于数据校验和客户端UI显示很有用。5. 关键API解析与开发要点通过命令行演示我们看到了功能的实现。但在实际产品开发中你需要将这些命令转换为嵌入式C代码。下面深入解析几个最核心的API。5.1 服务初始化与事件处理无论是HRS还是HTP服务初始化的核心都是注册一个事件回调函数。这是服务器应用的“大脑”。// HRS 事件回调函数示例框架 void HRS_EventCallback(unsigned int BluetoothStackID, HRS_Event_Data_t *HRS_Event_Data, unsigned long CallbackParameter) { switch(HRS_Event_Data-EventType) { case HRS_EVENT_CHARACTERISTIC_VALUE_READ_REQUEST: // 客户端请求读取特征值例如读取Body Sensor Location if(HRS_Event_Data-EventData.CharacteristicValueReadRequest.CharacteristicUUID UUID_BODY_SENSOR_LOCATION) { uint8_t location HRS_BODY_SENSOR_LOCATION_WRIST; // 假设是手腕 // 调用 API 回复读取值 HRS_Send_Characteristic_Value(BluetoothStackID, InstanceID, ConnectionID, HRS_Event_Data-EventData.CharacteristicValueReadRequest, location, sizeof(location)); } break; case HRS_EVENT_CLIENT_CONFIGURATION_CHANGED: // 客户端修改了CCCD启用/禁用了Notify // 这里可以记录客户端的订阅状态决定是否开始定时发送心率数据 if(HRS_Event_Data-EventData.ClientConfigurationChanged.Configuration GATT_CLIENT_CONFIGURATION_NOTIFICATION) { g_is_notification_enabled TRUE; // 启动一个定时器周期性调用 HRS_Notify_Heart_Rate_Measurement } else { g_is_notification_enabled FALSE; // 停止定时器 } break; case HRS_EVENT_HEART_RATE_CONTROL_POINT_WRITTEN: // 客户端写入了控制点例如重置能量消耗 if(HRS_Event_Data-EventData.HeartRateControlPointWritten.ControlPointCommand HRS_HEART_RATE_CONTROL_POINT_RESET_ENERGY_EXPENDED) { g_energy_expended 0; // 重置内部能量消耗累计值 } break; // ... 处理其他事件 } }开发要点回调函数必须快速执行完毕不能进行长时间阻塞操作如软件延时。如果需要执行耗时任务如读取传感器应设置一个标志在主循环或另一个任务中处理。5.2 数据发送Notify与Indicate的差异HRS使用NotifyHTP使用Indicate选择依据是数据可靠性要求。HRS_Notify_Heart_Rate_Measurement()发送后不等待确认直接返回。适合心率这种连续、高频、允许偶尔丢失的数据。HTS_Indicate_Temperature_Measurement()发送后协议栈会等待客户端的GATT确认。只有在收到确认或超时后本次“指示”操作才算完成。适合体温这种关键性较高、发送频率较低的数据。参数结构体填充发送数据前需要正确填充对应的数据结构体。以HRS为例HRS_Heart_Rate_Measurement_Data_t hrData; memset(hrData, 0, sizeof(hrData)); hrData.Flags 0; hrData.Flags | HRS_HEART_RATE_VALUE_FORMAT_UINT16; // 16位格式 hrData.Flags | HRS_SENSOR_CONTACT_STATUS_DETECTED; // 接触状态 hrData.Flags | HRS_ENERGY_EXPENDED_PRESENT; // 包含能量消耗 // hrData.Flags | HRS_RR_INTERVAL_PRESENT; // 如果包含RR间隔 hrData.HeartRateMeasurementValue 80; // 心率值 hrData.EnergyExpended g_energy_expended; // 能量消耗值 // 如果包含RR间隔需要设置 hrData.NumberOfRRIntervals 和 hrData.RRInterval // 调用发送函数 int ret HRS_Notify_Heart_Rate_Measurement(BluetoothStackID, g_hrs_instance_id, g_connection_id, hrData); if(ret ! 0) { // 错误处理可能是连接断开、资源不足等 }避坑技巧Flags字段的位设置必须与后续实际提供的数据字段严格对应。如果设置了HRS_ENERGY_EXPENDED_PRESENT就必须给EnergyExpended赋值否则接收端解析会错乱。5.3 连接管理与资源释放一个健壮的应用必须处理连接断开的情况。连接事件在GAP层的事件回调非HRS/HTP回调中你会收到GAP_EVENT_CONNECTION_ESTABLISHED和GAP_EVENT_DISCONNECTION_COMPLETE事件。连接建立时记录下ConnectionID连接断开时清除所有与该连接相关的状态如订阅标志g_is_notification_enabled。服务清理在应用退出或需要重启服务时务必调用HRS_Cleanup_Service()或HTS_Cleanup_Service()来释放资源。否则再次初始化时可能会失败。6. 调试技巧与常见问题排查基于CC2564x开发BLE服务时以下几个工具和技巧能极大提升效率。6.1 调试工具链串口日志这是最基础的调试手段。确保你的应用代码在各个关键节点初始化成功/失败、连接建立/断开、收到事件、发送数据前/后都打印了清晰的日志。TI的示例工程通常已包含大量日志。逻辑分析仪用于抓取CC2564x与主MCU之间UARTHCI接口上的原始数据包。当遇到底层连接问题时这是终极武器。你可以看到完整的连接请求、数据包交换过程。手机BLE调试APP如nRF Connect、LightBlue。这是功能强大的可视化客户端。你可以用手机直接连接你的CC2564x服务器查看它广播的所有服务和特征手动读写特征值验证你的服务器实现是否正确。这能快速排除是服务器问题还是你自己的客户端代码问题。6.2 常见问题速查表问题现象可能原因排查步骤客户端扫描不到设备1. 服务器未开始广播。2. 广播间隔太长。3. 广播数据过长或被过滤。1. 确认服务器已执行AdvertiseLE且无错误返回。2. 检查代码中GAP_Advertise()的广播间隔参数Advertising_Interval_Min和Max尝试设置为100ms0x00A0和150ms0x00F0看看。3. 用手机APP扫描看是否能发现其他BLE设备。如果能说明问题在你的服务器广播数据。连接建立后立即断开1. 配对/加密问题如果启用了安全功能。2. 协议栈资源连接数、内存不足。3. 硬件干扰或电源不稳定。1. 检查协议栈安全配置。对于演示可以先禁用所有安全功能配对、绑定、加密。2. 确认协议栈配置的头文件如ble_user_config.h中允许的最大连接数MAX_NUM_BLE_CONNS是否≥1。3. 检查电源电压是否在CC2564x要求范围内通常3.3V并确保有足够的去耦电容。客户端发现服务失败1. 服务未正确注册。2. 连接句柄ConnectionID错误。3. MTU最大传输单元太小导致服务发现响应包被截断。1. 在服务器端确认RegisterHRS/HTS命令返回成功且服务实例ID有效。2. 打印并核对用于服务发现API的ConnectionID是否正确。3. 尝试在连接建立后执行一次MTU交换客户端发起。TI协议栈可能有相关API或命令。能发现服务但无法启用Notify/Indicate1. CCCD描述符写入失败。2. 客户端特征配置属性不支持。3. 服务器端事件回调未正确处理配置更改事件。1. 用手机APP连接手动点击“启用通知”看是否成功。如果成功问题在你的客户端配置代码如果失败问题在服务器。2. 检查服务定义确认该特征属性包含Notify或Indicate。3. 在服务器的CLIENT_CONFIGURATION_CHANGED事件回调中加日志看是否收到请求。服务器发送数据客户端收不到1. 客户端未正确启用通知/指示。2. 服务器使用的ConnectionID已失效连接已断开。3. 数据发送API返回错误未处理。4. 数据格式错误被客户端协议栈丢弃。1. 再次确认客户端配置流程已成功完成。2. 在发送数据前检查连接状态。3.务必检查HRS_Notify_Heart_Rate_Measurement或HTS_Indicate_Temperature_Measurement的返回值。非0即错误根据错误码排查。4. 用手机APP订阅通知看是否能收到数据。如果能对比你的客户端解析代码。发送Indicate后服务器卡死未正确处理Indicate确认。Indicate需要等待客户端确认。确认是通过协议栈底层自动完成的但你的应用需要确保在收到HTS_EVENT_INDICATION_CONFIRMATION事件前不要重复发送新的Indicate到同一个特征否则协议栈可能会阻塞。检查是否在收到确认事件后才进行下一次发送。6.3 功耗优化要点对于电池供电的设备功耗是生命线。广播优化产品运行时使用较慢的广播间隔如1秒以上。甚至可以采用“慢速广播快速广播”结合的策略平时慢速广播当有特定事件如按键时切换到快速广播一段时间以加速连接。连接参数协商连接建立后主从设备会协商连接间隔、从机延迟等参数。更长的连接间隔和合理的从机延迟可以显著降低从机通常是我们的传感器设备的功耗。你可以在服务器的GAP事件回调中响应GAP_EVENT_CONNECTION_PARAMETER_UPDATE_REQUEST事件向主机请求更电的连接参数。数据发送频率根据实际需求调整NotifyHeartRate或IndicateTemperature的发送频率。不需要每秒发送10次的心率数据可以降低到每秒1次甚至更低。睡眠模式充分利用MCU和CC2564x的低功耗睡眠模式。在广播间隔或连接间隔内如果没有任务应让系统进入低功耗状态。TI的协议栈通常与TI-RTOS或FreeRTOS集成提供了相应的电源管理框架。从理解GATT模型到在CC2564x上跑通HRS和HTP服务再到深入API和调试排错这个过程实际上是对BLE嵌入式开发从入门到熟悉的一次完整穿越。最关键的是动手实践拿两块开发板严格按照步骤操作一遍观察每一个命令的输入输出再尝试修改示例代码中的参数观察现象的变化。当你能够根据自己的需求定制特征值、修改广播参数、稳定地收发数据时你就已经掌握了基于CC2564x进行BLE健康服务开发的核心技能。这套方法论同样适用于其他GATT服务比如血压监测、血糖监测等你需要做的就是去理解对应服务的规范文档然后按照“初始化-广播-连接-发现-配置-交互”这个流程将规范映射到具体的API调用上。