去年夏天我在一个健康科技展会上,看到一款售价高达2000元的心率监测手环。拆解后发现它核心功能竟是用ESP32实现的——这让我意识到,掌握蓝牙协议栈开发能创造巨大商业价值。本文将带你用NimBLE从零构建专业级心率监测方案,代码可直接用于商业产品开发。
在开始前,请确保已安装ESP-IDF v4.4+。打开终端执行:
bash复制idf.py create-project ble_heart_rate
cd ble_heart_rate
配置NimBLE堆栈时,关键是要平衡功能与资源占用。推荐以下menuconfig设置路径:
code复制Component config → Bluetooth →
[*] Bluetooth controller → Bluetooth controller mode (NimBLE)
[*] NimBLE-only host stack
(3) NimBLE log level (INFO)
注意:生产环境建议日志等级设为WARN,调试时可临时调整为DEBUG
保存配置后,在main目录创建包含这些基础组件的CMakeLists.txt:
cmake复制set(srcs "main.c")
set(requires "nvs_flash nimble esp_hw_support")
register_component()
心率监测服务需要符合Bluetooth SIG规范。打开heart_rate.h定义服务结构:
c复制#define HEART_RATE_SERVICE_UUID 0x180D
#define HEART_RATE_MEASUREMENT_UUID 0x2A37
struct ble_hrm_svc {
uint16_t svc_handle;
uint16_t hr_measurement_handle;
uint16_t hr_measurement_ccc_handle; // Client Characteristic Configuration
};
服务特征属性配置表:
| 属性 | 值 | 说明 |
|---|---|---|
| 广播类型 | 0x01 | 可连接非定向广播 |
| 广播间隔 | 160ms | 平衡功耗与连接速度 |
| 服务UUID | 0x180D | 标准心率服务标识 |
| 特征属性 | 通知+读取 | 0x10 + 0x02组合 |
在ble_hrm_init()中注册服务:
c复制static const struct ble_gatt_svc_def hrm_svcs[] = {
{
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = &hr_svc_uuid.u,
.characteristics = (struct ble_gatt_chr_def[]) {
{
.uuid = &hr_measurement_uuid.u,
.access_cb = hrm_access,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
.val_handle = &hrm_svc->hr_measurement_handle,
.descriptors = (struct ble_gatt_dsc_def[]) {
{
.uuid = BLE_UUID16_DECLARE(BLE_GATT_DSC_CLT_CFG_UUID16),
.access_cb = hrm_ccc_access,
.att_flags = BLE_ATT_F_READ | BLE_ATT_F_WRITE,
.handle = &hrm_svc->hr_measurement_ccc_handle
},
{0}
}
},
{0}
}
},
{0}
};
心率数据需要遵循特定格式。创建数据打包函数:
c复制void hrm_package_data(uint8_t *buffer, uint8_t hr_value) {
uint8_t flags = 0x00; // 8-bit格式,无额外字段
buffer[0] = flags;
buffer[1] = hr_value;
}
定时上报使用FreeRTOS软件定时器:
c复制TimerHandle_t hr_timer;
void hr_timer_cb(TimerHandle_t xTimer) {
struct os_mbuf *om;
uint8_t hr_data[2];
static uint8_t simulated_hr = 60;
// 模拟心率波动
simulated_hr += (rand() % 3) - 1;
simulated_hr = MAX(50, MIN(140, simulated_hr));
hrm_package_data(hr_data, simulated_hr);
om = ble_hs_mbuf_from_flat(hr_data, sizeof(hr_data));
ble_gattc_notify_custom(conn_handle,
hrm_svc.hr_measurement_handle,
om);
}
void start_hr_monitoring() {
hr_timer = xTimerCreate("HR Timer",
pdMS_TO_TICKS(1000),
pdTRUE,
NULL,
hr_timer_cb);
xTimerStart(hr_timer, 0);
}
处理连接状态变化的回调:
c复制static void hrm_on_connect(struct ble_gap_event *event, void *arg) {
conn_handle = event->connect.conn_handle;
// 根据连接参数调整上报频率
uint16_t interval = OS_MBUF_PKTLEN(event->connect.conn_params.itvl);
uint32_t report_ms = MAX(1000, interval * 1.25);
xTimerChangePeriod(hr_timer, pdMS_TO_TICKS(report_ms), 0);
}
static void hrm_on_disconnect(struct ble_gap_event *event, void *arg) {
xTimerStop(hr_timer, 0);
conn_handle = BLE_HS_CONN_HANDLE_NONE;
}
功耗优化配置表:
| 参数 | 典型值 | 优化建议 |
|---|---|---|
| 广播间隔 | 160-200ms | 连接后立即停止广播 |
| 连接间隔 | 30-50ms | 根据数据量动态调整 |
| 从机延迟 | 3-6 | 允许跳过部分连接事件 |
| 监控超时 | 2000ms | 平衡响应速度与功耗 |
注册事件回调:
c复制static const struct ble_gap_event_listener hrm_event_listener = {
.connect = hrm_on_connect,
.disconnect = hrm_on_disconnect,
.mtu_update = hrm_on_mtu_update
};
ble_gap_event_listener_register(&hrm_event_listener, NULL);
在开发过程中,这些命令能极大提升效率:
bash复制# 查看内存使用
idf.py size-components
# 启用蓝牙嗅探
sudo btmon -w debug_log.snoop
# 测试GATT服务
gatttool -b <DEVICE_ADDR> --interactive
> connect
> char-read-uuid 0x2A37
常见问题排查指南:
服务注册失败
通知无法触发
内存泄漏
在最近一个可穿戴设备项目中,我们发现当心率超过120次/分钟时,采用压缩数据格式可以减少30%的功耗。这提醒我们,数据格式优化能带来显著的续航提升:
c复制void hrm_compress_data(uint8_t *buffer, uint16_t hr_value) {
if(hr_value <= 255) {
buffer[0] = 0x00; // 8-bit标志
buffer[1] = (uint8_t)hr_value;
} else {
buffer[0] = 0x01; // 16-bit标志
memcpy(&buffer[1], &hr_value, 2);
}
}