1. 蓝牙服务与特征的基础概念
在物联网和嵌入式开发领域,蓝牙低功耗(BLE)协议栈中的服务(Service)和特征(Characteristic)是最核心的两个数据结构。它们构成了BLE设备之间数据交互的基础框架,理解这两个概念对于开发蓝牙应用至关重要。
服务可以理解为蓝牙设备提供的功能模块容器。每个服务都有一个唯一的UUID(通用唯一标识符)来标识其类型。例如心率监测服务使用0x180D作为标准UUID,设备信息服务使用0x180A。一个蓝牙设备通常会提供多个服务,每个服务又包含若干个特征。
特征则是服务中的具体数据点,是实际进行数据读写操作的最小单元。每个特征同样有自己的UUID,并且包含一组属性(Properties)来描述其访问权限。例如,一个温度传感器服务可能包含"当前温度值"、"采样间隔"和"校准参数"三个特征。
提示:UUID有16位和128位两种格式。16位UUID是蓝牙技术联盟(SIG)定义的标准服务/特征,128位UUID则通常用于厂商自定义实现。
2. 服务与特征的层级关系解析
2.1 服务结构剖析
一个完整的蓝牙服务包含以下组成部分:
- 服务声明(Service Declaration):包含服务类型UUID和主要属性
- 特征列表(Characteristics):该服务包含的所有特征
- 包含的服务(Included Services):当前服务可能引用的其他服务
在协议栈中,服务采用树状结构组织。最顶层是GAP(通用访问规范)和GATT(通用属性规范)服务,下面是各种功能服务。例如一个健康监测设备可能包含:
- 设备信息服务(0x180A)
- 厂商名称特征(0x2A29)
- 固件版本特征(0x2A26)
- 电池服务(0x180F)
- 电量百分比特征(0x2A19)
- 心率服务(0x180D)
- 心率测量特征(0x2A37)
- 传感器位置特征(0x2A38)
2.2 特征属性详解
每个特征包含以下关键属性:
- UUID:标识特征类型
- 属性(Properties):定义特征支持的操作类型
- 读(Read)
- 写(Write)
- 通知(Notify)
- 指示(Indicate)
- 值(Value):特征存储的实际数据
- 描述符(Descriptors):提供特征的附加信息
例如,一个典型的温度特征可能配置为:
c复制// 伪代码示例
Characteristic tempChar = {
.uuid = 0x2A6E, // 温度测量UUID
.properties = READ | NOTIFY,
.value = 25.5, // 当前温度值
.descriptors = {
.unit = "℃",
.resolution = 0.1
}
};
3. 服务与特征的实际应用
3.1 数据交互模式
蓝牙设备通过特征实现多种数据交互方式:
-
读取(Read):客户端主动读取特征值
- 适用于不频繁变化的数据(如设备信息)
- 实现简单,但需要主动轮询
-
写入(Write):客户端修改特征值
- 用于配置参数(如采样间隔)
- 可分为带响应和不带响应两种方式
-
通知(Notify):服务端主动推送数据
- 客户端需先订阅通知
- 服务端变化时自动发送(如心率数据)
- 不保证送达,效率高
-
指示(Indicate):带确认的通知
- 客户端必须确认收到
- 保证可靠性,但延迟较高
3.2 典型开发流程
在嵌入式开发中操作服务/特征的典型流程:
- 初始化蓝牙协议栈
c复制ble_stack_init();
- 创建服务并添加特征
c复制// 创建电池服务
ble_service_t bat_svc = create_service(0x180F);
// 添加电量特征
ble_char_t bat_level = {
.uuid = 0x2A19,
.properties = READ | NOTIFY,
.init_value = 100
};
add_characteristic(bat_svc, &bat_level);
- 注册服务到协议栈
c复制register_service(bat_svc);
- 处理特征事件
c复制void on_char_write(ble_char_t *char, uint8_t *value) {
if(char->uuid == 0x2A19) {
// 处理电量特征写入
battery_set_level(*value);
}
}
4. 开发中的常见问题与解决方案
4.1 UUID冲突问题
问题现象:自定义UUID与其他服务冲突导致功能异常
解决方案:
- 优先使用SIG定义的标准UUID
- 自定义UUID应使用在线生成器确保唯一性
- 测试阶段可使用随机UUID,正式发布前固定
4.2 特征权限配置错误
典型错误:
- 配置了NOTIFY但未实现通知功能
- 允许写入但未验证数据有效性
正确实践:
c复制// 正确的特征权限配置示例
ble_char_t temp_char = {
.uuid = 0x2A6E,
.properties = READ | NOTIFY,
.access = {
.read = OPEN,
.write = ENCRYPTED, // 需要加密连接
.notify = AUTHENTICATED // 需要认证
}
};
4.3 数据格式不一致
常见问题:
- 特征值使用不同字节序
- 浮点数格式不统一
- 字符串编码差异
最佳实践:
- 明确定义特征值格式
- 使用标准数据类型:
- 整数:uint8_t, int16_t等
- 浮点:IEEE 754
- 字符串:UTF-8
- 提供格式说明文档
5. 性能优化技巧
5.1 服务设计优化
-
服务拆分原则:
- 高频变化数据独立为单独服务
- 低频配置参数集中管理
- 相关功能聚合到同一服务
-
特征大小优化:
- 单个特征值不超过MTU(通常20字节)
- 大数据拆分为多个特征
- 使用"长特征"规范(需要双方支持)
5.2 通信效率提升
- 通知间隔优化:
c复制// 动态调整通知间隔
void adjust_notify_interval(ble_char_t *char, int new_interval) {
if(new_interval < MIN_INTERVAL) {
char->notify_mode = BURST_MODE;
} else {
char->notify_mode = NORMAL_MODE;
}
update_ccc(char->handle, new_interval);
}
-
连接参数协商:
- 适当缩短连接间隔(Connection Interval)
- 根据数据量调整MTU大小
- 使用数据长度扩展(DLE)功能
-
功耗平衡技巧:
- 低频数据使用指示(Indicate)确保送达
- 高频数据使用通知(Notify)降低功耗
- 空闲时延长连接间隔
在实际项目中,我发现服务特征的设计往往需要多次迭代。初期建议先实现基本功能,再通过实际测试优化结构。一个常见的错误是过早优化导致设计过于复杂,反而影响可维护性。