基于CH573的BLE温湿度传感器

在使用ESP32制作一些项目时,经常会想要加入温湿度检测的功能。但在实践中存在一个很严重的问题,即放在机壳内的温湿度传感器会被ESP32散发出的热量影响,导致度数出现温升。本文介绍基于CH573制作的低功耗的温湿度传感器,并使用蓝牙将数据传输到ESP32上。

为了解决将温湿度传感器放到机壳内的温升问题,最直接同时也是效果最好的方案就是将传感器放在机身外,同时传感器使用电池供电。但是这就会引入低功耗与通信的问题,即传感器如何在保证低功耗的前提下,将数据通过无线传输到ESP32上。

ESP32支持三种无线通信协议,它们分别是WiFiESP-NOWBLE。为了能够直接使用ESP32接受数据而不需要添加额外的无线收发芯片,传感器与ESP32通信的协议应该也从这三种协议里面选择。

其中,ESP-NOW协议只有乐鑫的芯片支持,但是乐鑫并没有低功耗芯片,因此排除。Wi-Fi连接、传输等的功耗也都非常大,因此BLE便成了最好的选择。

目前,市面上已经有基于BLE的温湿度传感器了,例如米家温湿度计2等。我也亲自购入了一个,研究了它的实现原理。

https://img.yuanze.wang/posts/ch573-temp-humid-beacon/xiaomi-temp-humid-sensor.jpg
米家温湿度计2

对于使用BLE发送传感器读数的场景,目前主要有两种做法:

  • 设备开启蓝牙广播,将自身标识为可连接,当有手机或网关连接时,建立GATT连接并传输数据。这种方式比较适合米家温湿度计2这种本身带有屏幕显示读数,蓝牙主要用于传输保存的历史数据的场景。
  • 设备将读数放入蓝牙广播中,进行周期性广播,并将自身标识为不可连接。网关通过不断进行BLE扫描来获取广播包内的传感器数据。这种方式比较适合本身不带屏幕,需要使用其他设备显示读数的场景。

对于一个没有屏幕显示的Beacon来说,第二种做法显然是更合适的。同时,由于不需要进行蓝牙连接,相比第一种做法也能节约下更多的电量。确定了方案后,接下来就是芯片选型了。

CH573是沁恒推出的一款BLE MCU,价格非常便宜,外围电路也非常简单,官方甚至贴心的提供了PCB天线的参考设计,配合内置的匹配电路,只需要复制粘贴天线即可完成简单的射频设计,对没有射频匹配条件的业余玩家来说非常友好。

https://img.yuanze.wang/posts/ch573-temp-humid-beacon/ant-design.png
沁恒提供的参考天线设计

对于一个BLE Beacon来说,除了传感器和MCU之外,剩下的只需要一个LED灯用于指示状态,一个按键用于切换状态,以及一个开关用于彻底断电。

下面是我制作的PCB实物与渲染图。板子采用2层板设计,板载SHT31温湿度传感器,使用CR2032电池供电。

https://img.yuanze.wang/posts/ch573-temp-humid-beacon/pcb-v2-front.jpg
PCB正面

https://img.yuanze.wang/posts/ch573-temp-humid-beacon/pcb-v2-back.jpg
PCB背面

https://img.yuanze.wang/posts/ch573-temp-humid-beacon/pcb-v2-render.png
PCB渲染图

PCB天线与匹配电路,我直接参考的沁恒官方提供的双层板天线。经测试信号尚可,0dbm的发射功率下,信号穿过一堵墙仍然可以被ESP32接收到。

完成了硬件的开发,接下来就是软件了。软件使用沁恒官方提供的开发包,在MounRiver下开发完成,可以到GitHub仓库ch573-sht3x-beacon中查看。

程序中将任务分为了3个任务,分别是task_broadcaster task_sensortask_bsp

  • task_broadcaster任务处理与蓝牙相关的操作,主要包括初始化蓝牙广播者模式、发送广播数据并处理传感器更新事件。
  • task_sensor任务负责初始化传感器,并定期触发传感器转换,在传感器转换完成时响应转换完成事件,并向task_broadcaster通知转换已完成。
  • task_bsp主要负责执行定期射频校准工作。

值得一提的时,蓝牙广播数据在蓝牙启动的时候是无法更新的。因此,在task_broadcaster接收到传感器更新事件时,需要先关闭广播,待广播数据更新完毕后产生更新完成事件后,在事件处理函数内重启开启广播。

BLE的广播数据是最长可以有31字节,在这里我使用了30字节。

static uint8_t advertising_data[30] = {
    2,                                                               //字段长度
    GAP_ADTYPE_FLAGS,                                                //蓝牙属性标志位
    GAP_ADTYPE_FLAGS_BREDR_NOT_SUPPORTED | GAP_ADTYPE_FLAGS_GENERAL, //不支持BR/EDR,General discoverable

    13,
    GAP_ADTYPE_LOCAL_NAME_COMPLETE, //设备名称
    'C', 'H', '5', '7', '3', '-', 'S', 'e', 'n', 's', 'o', 'r',

    12,
    GAP_ADTYPE_MANUFACTURER_SPECIFIC, //厂商自定义数据
    'Y', 'Z', //厂商ID
    0x00, 0x02, 0x00, 0x00, 0x12, 0x00, 0x00, 0x21, 0x00
};

广播包主要分为3部分:

  • 第一部分是蓝牙属性标志位,总共置位了GAP_ADTYPE_FLAGS_BREDR_NOT_SUPPORTEDGAP_ADTYPE_FLAGS_GENERAL两个标志位,分别代表该设备不支持经典蓝牙与处于普通可发现模式。
  • 第二部分是设备名称,也就是手机App显示的设备名称,这里设置为CH573-Sensor
  • 第三部分是厂商自定义数据,其中包含了温湿度与电池电压信息,具体如下:
字节 1-2 3 4 5-6 7 8-9 10 11
数据 0x59, 0x5a 0x00 0x02 100倍温度数值 0x12 100倍湿度数值 0x21 100倍(电池电压-1V)
说明 厂商ID,‘Y’ ‘Z’ 高4位为协议类型,低4位为协议版本 高4位为数据类型,低4位为数据长度 小端int16_t,Temp*100 高4位为数据类型,低4位为数据长度 小端uint16_t 高4位为数据类型,低4位为数据长度 uint8_t

对于ESP32,只需要不断进行BLE扫描,并对扫描得到的每个广播数据通过设备名与厂商ID进行过滤,即可得到广播包内的传感器数据。下面是蓝牙的初始化部分:

nvs_flash_init();
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
esp_bt_controller_init(&bt_cfg);
esp_bt_controller_enable(ESP_BT_MODE_BLE); //配置为BLE模式
esp_bluedroid_init();
esp_bluedroid_enable(); //开启蓝牙
esp_ble_gap_register_callback(esp_gap_cb); //注册回调函数

由于Beacon的广播频率为2s,因此将扫描频率设置为3s,来保证能够搜到蓝牙广播包。初始化完毕后,只需要在主循环中不断循环扫描,并在回调函数中处理搜索结果即可:

while(1) {
    esp_ble_gap_start_scanning(3) //扫描2秒
    vTaskDelay(pdMS_TO_TICKS(5000));
}
const char *device_name_filter = "CH573-Sensor";
const char vendor_id[2] = {'Y', 'Z'};

/**
 * @brief 在蓝牙广播包中寻找特定字段
 * 
 * @param adv_data 广播数据
 * @param adv_data_len 广播数据长度
 * @param type 要查找的字段标识
 * @return uint8_t* 字段起始位置,包括长度与类型
 */
uint8_t *ble_adv_get_data(uint8_t *adv_data, uint8_t adv_data_len, uint8_t type)
{
    uint8_t *ptr = adv_data;
    uint8_t len = adv_data_len;

    while(len) {
        if(ptr[1] != type) { //第2个字节是字段类型
            len -= (ptr[0]+1); //第1个字节是字段长度
            ptr += ptr[0]+1; //不是要寻找的字段类型,移动到下一个字段
        } else { //是要寻找的字段
            return ptr;
        }
    }

    return NULL;
}

void ble_adv_scan_handler(struct ble_scan_result_evt_param *scan_rst)
{
    if(scan_rst->adv_data_len == 0) { //忽略没有广播内容的设备
        return;
    }

    uint8_t *adv_manufacturer_defined_data = 
        ble_adv_get_data(scan_rst->ble_adv, scan_rst->adv_data_len, 0xFF); //查找厂家自定义信息

    if(adv_manufacturer_defined_data == NULL) {
        return;
    }

    /* 检查厂商ID */
    if(adv_manufacturer_defined_data[2] == vendor_id[0] && adv_manufacturer_defined_data[3] == vendor_id[1]) {
        /* 是传感器设备 */
        int16_t temp = adv_manufacturer_defined_data[6] | adv_manufacturer_defined_data[7]<<8;
        int16_t humid = adv_manufacturer_defined_data[9] | adv_manufacturer_defined_data[10]<<8;
        uint8_t voltage = adv_manufacturer_defined_data[12];
        printf(MACSTR": rssi:%d, temp:%.1fC, humid:%.1f%%, batt:%.2fV\n", MAC2STR(scan_rst->bda),
            scan_rst->rssi, (float)temp/100, (float)humid/100, (float)voltage/100+2.0f); //打印设备信息与温湿度信息
    }
}

static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
    esp_err_t err;

    switch (event) {
    case ESP_GAP_BLE_SCAN_RESULT_EVT: //扫描结果事件,每一个扫描到的设备均会触发一次该函数
        if ((err = param->scan_start_cmpl.status) == ESP_BT_STATUS_SUCCESS) {
            // printf(MACSTR": rssi:%d, \n", MAC2STR(param->scan_rst.bda), param->scan_rst.rssi); //打印设备信息
            ble_adv_scan_handler(&param->scan_rst);
        }
        break;

    case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: //开始扫描完成事件
        if ((err = param->scan_start_cmpl.status) == ESP_BT_STATUS_SUCCESS) {
            ESP_LOGI(TAG, "start scanning...");
        } else {
            ESP_LOGE(TAG, "error while starting scanning, error code %s", esp_err_to_name(err));
        }
        break;

    default:
        ESP_LOGW(TAG, "unhandled bt event #%d", event);
        break;
    }
}

使用上述代码,即可扫描并获取传感器数据了。

https://img.yuanze.wang/posts/ch573-temp-humid-beacon/beacon-with-esp32.jpg
使用ESP32扫描

若有多个传感器,也可以根据MAC地址区分它们。从扫描结果中可以看出,SHT31传感器的精度与一致性非常不错。

https://img.yuanze.wang/posts/ch573-temp-humid-beacon/three-beacons.jpg
同时使用多个传感器

https://img.yuanze.wang/posts/ch573-temp-humid-beacon/esp32-scan-result.png
扫描结果

 PCB工程

 ESP32扫描例程