这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 【eDIY】蓝牙低功耗温湿度计03过程贴:BLE通信与微信小程序

共1条 1/1 1 跳转至

【eDIY】蓝牙低功耗温湿度计03过程贴:BLE通信与微信小程序

助工
2026-06-15 14:35:39     打赏

【eDIY】蓝牙低功耗温湿度计 03 过程贴:BLE通信与微信小程序任务目标

  • 实现基础任务第三项:蓝牙功能的实现,启用蓝牙相关配置选项,使用 NXP 蓝牙APP与开发板建立连接;

  • 实现进阶任务第二项:制作微信小程序,并实现实时环境监测。

任务实现

Kconfig 配置(prj.conf)修改

首先在开发板上打开 Zephyr 蓝牙相关的配置选项,修改 prj.conf,启动蓝牙协议栈基础支持,使能外设角色,设置蓝牙广播名,使能动态GATT服务注册,设置最大连接数,如下表:


image.png


设备树修改

需要在设备树中定义蓝牙 HCI 接口,如下所示:

&hci {
    status = "okay";
};

&nbu {
    status = "okay";
};

关键代码

蓝牙初始化

/* 初始化 BLE */
    err = bt_enable(NULL);
    if (err) {
        LOG_ERR("Failed to enable BLE: %d", err);
        return;
    }

    LOG_INF("BLE enabled");

    /* 初始化 EHS 服务 */
    err = ble_ehs_service_init();
    if (err) {
        LOG_ERR("Failed to init EHS service: %d", err);
        return;
    }

    /* 开始广播 */
    err = ble_ehs_start_advertising();
    if (err) {
        LOG_ERR("Failed to start advertising: %d", err);
        return;
    }

广播数据设置

image.png



GATT服务定义

image.png


蓝牙功能

蓝牙相关代码分布在以下文件中:


image.png


整体架构流程图

image.png



BLE 线程详解

所在文件 ble_thread.c ,通过宏定义 K_THREAD_DEFINE() 定义了蓝牙线程,线程名字 ble_thread,栈大小 2048 字节,优先级6,入口函数 bel_thread_entry()。

初始化流程


image.png



主循环逻辑


image.png


GATT 服务详解

EHS 服务结构

EHS(Environmental Health Sensor) 服务是一个自定义GATT服务,用于传输环境传感器数据。


image.png


数据包结构


image.png


GATT服务定义代码


image.png


广播配置

广播数据结构


image.png


广播流程


image.png


连接与通知机制

连接状态管理


image.png


数据通知流程


image.png


线程间数据共享

sensor_data 模块设计


image.png


数据同步机制

同步机制说明

  • 互斥锁保护:使用 k_mutex 保护共享数据结构,防止并发访问冲突

  • 有效性标志:valid 字段标识数据是否有效,消费者需检查此标志

  • 时间戳:timestamp 记录数据采集时间,便于消费者判断数据新鲜度

  • 非阻塞读取:sensor_data_get() 使用 k_mutex_lock(K_NO_WAIT) 避免阻塞

微信小程序

实际上在调试过程中使用了 nRF Connect 手机APP,扫描、连接 FRDM-MCXW71 广播,查看传感器数据。

之后再开发微信小程序,遇到一些问题,例如数据不缺、数据丢失的问题,好在最后解决了。

用于通过蓝牙BLE连接 NXP MCXW71 开发板上的 SEN66 空气质量传感器,实现实时数据监测和历史曲线展示。

核心功能


image.png


监测的传感器数据(9种)

  1. 温度 (Temperature) - 单位: °C

  2. 湿度 (Humidity) - 单位: %RH

  3. PM1.0 - 单位: μg/m³

  4. PM2.5 - 单位: μg/m³

  5. PM4.0 - 单位: μg/m³

  6. PM10 - 单位: μg/m³

  7. CO₂ - 单位: ppm

  8. VOC 指数 - 挥发性有机化合物

  9. NOx 指数 - 氮氧化物

项目结构

nxp_ble_ht_miniprogrm/
├── app.js                 # 全局应用逻辑
├── app.json               # 应用配置
├── app.wxss               # 全局样式
├── project.config.json    # 项目配置
├── .gitignore             # Git 忽略规则
└── pages/
    ├── realtime/          # 实时数据页面
    │   ├── realtime.js    # 页面逻辑
    │   ├── realtime.wxml  # 页面模板
    │   └── realtime.wxss  # 页面样式
    └── history/           # 历史曲线页面
        ├── history.js     # 页面逻辑
        ├── history.wxml   # 页面模板
        └── history.wxss   # 页面样式

系统流程图

BLE 连接流程图

┌─────────────────────────────────────────────────────────────────────────────┐
│                          BLE 连接与数据接收流程                               │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────┐
│  用户点击   │
│ "扫描连接"  │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ resetBLEState│  ←── 清理旧连接状态
│ (清理状态)  │
└──────┬──────┘
       │ 延迟 500ms
       ▼
┌─────────────┐
│openBluetooth│
│  Adapter    │  ←── 打开蓝牙适配器
└──────┬──────┘
       │
       ▼
┌─────────────┐
│startDiscovery│  ←── 开始搜索 BLE 设备
│ (搜索设备)  │
└──────┬──────┘
       │
       ▼
┌─────────────┐     ┌─────────────┐
│onBluetooth  │────▶│ 匹配设备名  │
│ DeviceFound │     │ BLE/HT/Meter│
└──────┬──────┘     └──────┬──────┘
       │                   │
       │ 找到目标设备       │
       ▼                   ▼
┌─────────────┐     ┌─────────────┐
│stopDiscovery│────▶│connectToDevice│
│ (停止搜索)  │     │  (连接设备) │
└─────────────┘     └──────┬──────┘
                          │
                          ▼
                   ┌─────────────┐
                   │ setBLEMTU   │  ←── MTU 协商 (247字节)
                   │ (MTU协商)   │
                   └──────┬──────┘
                          │
                          ▼
                   ┌─────────────┐
                   │discoverServices│ ←── 发现 BLE 服务
                   │ (发现服务)  │
                   └──────┬──────┘
                          │
                          ▼
                   ┌─────────────┐
                   │discoverChar │ ←── 发现特征值
                   │ (发现特征)  │
                   └──────┬──────┘
                          │
                          ▼
                   ┌─────────────┐
                   │enableNotify │ ←── 启用数据通知
                   │ (启用通知)  │
                   └──────┬──────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                           数据接收循环                                        │
└─────────────────────────────────────────────────────────────────────────────┘
                          │
       ┌──────────────────┴──────────────────┐
       │                                     │
       ▼                                     │
┌─────────────┐                              │
│onBLECharValue│ ←── 接收 BLE 数据包          │
│   Change    │                              │
└──────┬──────┘                              │
       │                                     │
       ▼                                     │
┌─────────────┐                              │
│handleSensor │ ←── 解析 22 字节传感器数据    │
│   Data      │                              │
└──────┬──────┘                              │
       │                                     │
       ├──────────────┬──────────────┐       │
       │              │              │       │
       ▼              ▼              ▼       │
┌───────────┐  ┌───────────┐  ┌───────────┐  │
│更新全局   │  │添加历史   │  │更新UI显示 │  │
│ currentData│  │ Records  │  │ Display   │  │
└───────────┘  └─────┬─────┘  └───────────┘  │
                     │                      │
                     ▼                      │
              ┌─────────────┐               │
              │saveToStorage│               │
              │ (本地存储)  │               │
              └─────────────┘               │
                                              │
       ┌──────────────────────────────────────┘
       │ (持续监听)
       ▼
   [循环接收数据]

数据解析流程图

┌─────────────────────────────────────────────────────────────────────────────┐
│                         传感器数据包解析流程                                  │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│  BLE 数据包结构 (22 字节, 小端序)                                            │
├─────────────────────────────────────────────────────────────────────────────┤
│  偏移  │ 字段     │ 类型    │ 长度 │ 说明                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│  0    │ pm1_0    │ uint16  │ 2    │ PM1.0 浓度 (×10)                        │
│  2    │ pm2_5    │ uint16  │ 2    │ PM2.5 浓度 (×10)                        │
│  4    │ pm4_0    │ uint16  │ 2    │ PM4.0 浓度 (×10)                        │
│  6    │ pm10     │ uint16  │ 2    │ PM10 浓度 (×10)                         │
│  8    │ humidity │ int16   │ 2    │ 湿度 (×100)                             │
│  10   │ temp     │ int16   │ 2    │ 温度 (×200)                             │
│  12   │ voc      │ int16   │ 2    │ VOC 指数 (×10)                          │
│  14   │ nox      │ int16   │ 2    │ NOx 指数 (×10)                          │
│  16   │ co2      │ uint16  │ 2    │ CO₂ 浓度                                │
│  18   │ timestamp│ uint32  │ 4    │ 时间戳                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│  总计: 22 字节                                                               │
└─────────────────────────────────────────────────────────────────────────────┘

解析步骤:
┌─────────────┐
│ 接收 Buffer │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ DataView    │ ←── 创建 DataView 用于读取
│ 解析       │
└──────┬──────┘
       │
       ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                        数据转换公式                                          │
├─────────────────────────────────────────────────────────────────────────────┤
│  PM 值:     raw / 10.0          → μg/m³                                    │
│  温度:      raw / 200.0         → °C (范围: -10 ~ 80)                      │
│  湿度:      raw / 100.0         → %RH (范围: 0 ~ 100)                      │
│  VOC/NOx:   raw / 10.0          → 指数值                                   │
│  CO₂:       raw                 → ppm (上限: 40000)                        │
└─────────────────────────────────────────────────────────────────────────────┘
       │
       ▼
┌─────────────┐
│ 更新 UI     │ ←── setData() 更新页面
└─────────────┘

历史曲线绘制流程图

┌─────────────────────────────────────────────────────────────────────────────┐
│                         历史曲线绘制流程                                      │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────┐
│ 页面加载    │
│ /onShow    │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ loadData    │ ←── 加载全局历史数据
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ filterData  │ ←── 筛选最近 24 小时数据
└──────┬──────┘
       │
       ▼
┌─────────────┐
│calculateStats│ ←── 计算最大/最小/平均值
└──────┬──────┘
       │
       ▼
┌─────────────┐
│  drawChart  │ ←── Canvas 绘制曲线
└──────┬──────┘
       │
       ├──────────────┬──────────────┬──────────────┐
       │              │              │              │
       ▼              ▼              ▼              ▼
┌───────────┐  ┌───────────┐  ┌───────────┐  ┌───────────┐
│ 绘制网格  │  │ 绘制Y轴  │  │ 绘制曲线  │  │ 绘制X轴  │
│ (10等分) │  │ 标签     │  │ 数据点   │  │ 时间标签 │
└───────────┘  └───────────┘  └───────────┘  └───────────┘
       │
       ▼
┌─────────────┐
│ ctx.draw()  │ ←── 渲染到 Canvas
└─────────────┘

自动更新机制:
┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│ setInterval │────▶│ 检查记录数 │────▶│ 有新数据?  │
│  (2秒)     │     │  变化      │     │            │
└─────────────┘     └──────┬──────┘     └──────┬──────┘
                           │                   │
                           │                   ▼
                           │            ┌─────────────┐
                           │            │ refreshChart│
                           │            │ (刷新曲线) │
                           │            └─────────────┘
                           │
                           ▼ (无变化)
                    [继续监听]

关键代码分析

BLE 设备搜索与连接

关键点:

  • 连接前必须清理旧状态,否则第二次连接会失败

  • Android 设备需要位置权限才能扫描 BLE

  • 小米/红米手机有特殊权限处理逻辑

// 关键代码: BLE 连接流程
connectBLE() {
  // 1. 先清理旧状态(避免第二次连接失败)
  this.resetBLEState();

  // 2. 延迟 500ms 让 BLE 栈完全清理
  setTimeout(() => {
    // 3. 打开蓝牙适配器
    wx.openBluetoothAdapter({
      success: () => {
        // 4. 开始搜索设备
        this.startDiscovery();
      }
    });
  }, 500);
}

// 设备匹配逻辑
wx.onBluetoothDeviceFound((res) => {
  res.devices.forEach(device => {
    const name = device.name || device.localName || '';
    // 匹配设备名称关键词
    if (name.includes('BLE') || name.includes('HT') || 
        name.includes('Meter') || name.includes('SEN66')) {
      // 停止搜索并连接
      wx.stopBluetoothDevicesDiscovery();
      this.connectToDevice(device.deviceId);
    }
  });
});

MTU 协商与数据接收

关键点:

  • MTU 协商到 247 字节,确保能接收完整的 22 字节传感器数据包

  • 启用 Notify 特征值才能接收设备主动推送的数据

// MTU 协商 - 关键! 确保能接收完整数据包
wx.setBLEMTU({
  deviceId,
  mtu: 247,  // 协商到 247 字节
  success: (res) => {
    console.log('MTU 协商成功:', res.mtu);
    // MTU 协商完成后发现服务
    setTimeout(() => {
      this.discoverServices();
    }, 500);
  }
});

// 启用数据通知
wx.notifyBLECharacteristicValueChange({
  deviceId: this.data.deviceId,
  serviceId: this.data.serviceId,
  characteristicId: characteristicId,
  state: true
});

// 数据监听
wx.onBLECharacteristicValueChange((res) => {
  this.handleSensorData(res.value);
});

传感器数据解析

关键点:

  • 使用 DataView 的 getUint16/getInt16 方法,第二个参数 true 表示小端序

  • 温度转换公式: raw / 200.0

  • 湿度转换公式: raw / 100.0

  • PM 值转换公式: raw / 10.0

// 解析 22 字节传感器数据包
handleSensorData(buffer) {
  const dataView = new DataView(buffer);

  // 数据包结构 (小端序)
  const pm1_0      = dataView.getUint16(0, true);   // 偏移 0
  const pm2_5      = dataView.getUint16(2, true);   // 偏移 2
  const pm4_0      = dataView.getUint16(4, true);   // 偏移 4
  const pm10       = dataView.getUint16(6, true);   // 偏移 6
  const rawHumi    = dataView.getInt16(8, true);    // 偏移 8
  const rawTemp    = dataView.getInt16(10, true);   // 偏移 10
  const voc_index  = dataView.getInt16(12, true);   // 偏移 12
  const nox_index  = dataView.getInt16(14, true);   // 偏移 14
  const co2        = dataView.getUint16(16, true);  // 偏移 16
  const timestamp  = dataView.getUint32(18, true);  // 偏移 18

  // 数据转换
  const data = {
    pm1_0: (pm1_0 / 10.0).toFixed(1),
    pm2_5: (pm2_5 / 10.0).toFixed(1),
    humidity: Math.max(0, Math.min(100, rawHumi / 100.0)).toFixed(1),
    temperature: Math.max(-10, Math.min(80, rawTemp / 200.0)).toFixed(1),
    voc_index: (voc_index / 10.0).toFixed(1),
    nox_index: (nox_index / 10.0).toFixed(1),
    co2: Math.min(40000, co2)
  };

  // 更新全局数据
  app.globalData.currentData = data;

  // 添加到历史记录
  app.addHistoryRecord({...data, timestamp: Date.now()});
}

历史数据存储

关键点:

  • 使用 wx.setStorageSync 本地存储,最多保存 10000 条记录

  • 历史数据在 App 启动时自动加载

// 全局数据管理
globalData: {
  bleConnected: false,
  deviceId: null,
  serviceId: null,
  characteristicId: null,
  currentData: null,
  historyRecords: []
}

// 添加历史记录(限制最多 10000 条)
addHistoryRecord(record) {
  this.globalData.historyRecords.push(record);
  if (this.globalData.historyRecords.length > 10000) {
    this.globalData.historyRecords = 
      this.globalData.historyRecords.slice(-10000);
  }
  this.saveHistoryToStorage();
}

// 本地存储
saveHistoryToStorage() {
  wx.setStorageSync('sen66_history', 
    this.globalData.historyRecords.slice(-10000));
}

Canvas 曲线绘制

关键点:

  • 使用微信小程序旧版 Canvas API (wx.createCanvasContext)

  • Y 轴范围固定,避免数据波动导致曲线跳跃

  • 数据量大时自动采样(最多显示 200 个点)

// 绘制历史曲线
drawChart() {
  const ctx = wx.createCanvasContext('mainChart', this);

  // Y 轴固定范围配置
  const Y_AXIS_RANGES = {
    '温度':   { yMin: 0,   yMax: 41,  unit: '°C' },
    '湿度':   { yMin: 0,   yMax: 80,  unit: '%RH' },
    'PM2.5':  { yMin: 0,   yMax: 1200, unit: 'μg/m³' },
    'CO₂':    { yMin: 100, yMax: 2000, unit: 'ppm' }
  };

  // 绘制网格 (10 等分)
  for (let i = 0; i <= 10; i++) {
    const y = paddingTop + (chartHeight / 10) * i;
    ctx.beginPath();
    ctx.moveTo(paddingLeft, y);
    ctx.lineTo(width - paddingRight, y);
    ctx.stroke();
  }

  // 绘制曲线
  ctx.beginPath();
  ctx.setStrokeStyle(color);
  ctx.setLineWidth(2);
  data.forEach((d, i) => {
    const x = paddingLeft + (chartWidth / (data.length - 1)) * i;
    const y = paddingTop + chartHeight - 
              ((d[key] - yMin) / yRange) * chartHeight;
    i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
  });
  ctx.stroke();
  ctx.draw();
}

技术要点总结

BLE连接注意事项


image.png


数据协议

  • 数据包长度: 22 字节

  • 字节序: 小端序 (Little Endian)

  • 传输方式: BLE Notify (设备主动推送)

  • 更新频率: 约 5 秒/次

UI设计特点

  • TabBar: 两个页面(实时数据、历史曲线)

  • 实时页面: 9 种传感器数据卡片式展示

  • 历史页面: Canvas 曲线图 + 9 宫格快速查看

  • 颜色编码: 每种数据类型有专属颜色

功能演示

附上手机APP演示截图。


image.png

image.png


image.png


image.png


image.png


image.png


image.png












关键词: 蓝牙     湿度计     微信     程序    

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]