这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 【e起DIY】低功耗蓝牙温湿度计-5,过程贴-获取温湿度数据

共1条 1/1 1 跳转至

【e起DIY】低功耗蓝牙温湿度计-5,过程贴-获取温湿度数据

助工
2026-05-15 18:20:29     打赏

目前已经能实现蓝牙连接和蓝牙传输,但是有个bug,不能在断开之后重新广播。值得注意的是我发现在蓝牙调试助手断开,并不能触发 断开回调 disconnected,后来发现是手机蓝牙还连接着。。。像乐鑫的ESP32-C2、ESP32-C5与ESP32-S3等或者HC-05开启的蓝牙,使用调试助手连接,断开就实打实的断开了。

         1,那么先来实现蓝牙断开重新广播功能,新建文件ble.c,放在main.c同目录下就行,因为我的 CMakeLists.txt 是这样写的,头文件也一样,系统已经设置好该目录下的头文件路径,不需要再设置,除非文件不在该目录下。

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(Ble_Thermohygrometer)

file(GLOB app_sources src/*.c)#使用 GLOB 自动收集文件
target_sources(app PRIVATE ${app_sources})

具体代码实现

#include <zephyr/types.h>  
#include <stddef.h>  
#include <string.h>  
#include <errno.h>  
#include <zephyr/sys/printk.h>  
#include <zephyr/kernel.h>  
 
#include <zephyr/settings/settings.h>  
 
#include <zephyr/bluetooth/bluetooth.h>  
#include <zephyr/bluetooth/hci.h>  
#include <zephyr/bluetooth/conn.h>  
#include <zephyr/bluetooth/uuid.h>  
#include <zephyr/bluetooth/gatt.h>  


// 自定义服务UUID (128-bit)  
#define BT_UUID_CUSTOM_SERVICE_VAL BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef0)  
 
// 自定义特征UUID (128-bit)  
#define BT_UUID_CUSTOM_CHAR_VAL BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef1)  
 
// 定义UUID结构体  
static const struct bt_uuid_128 vnd_svc_uuid = BT_UUID_INIT_128(BT_UUID_CUSTOM_SERVICE_VAL);  
static const struct bt_uuid_128 vnd_char_uuid = BT_UUID_INIT_128(BT_UUID_CUSTOM_CHAR_VAL);  
 
// 特征值最大长度  
#define VND_MAX_LEN 20  
 
// 特征值存储  
static uint8_t vnd_value[VND_MAX_LEN + 1] = {0};  
 
// 连接监控定时器
static struct k_timer conn_monitor_timer;
// 当前连接
static struct bt_conn *current_conn = NULL;
// 定义一个延迟工作项
static struct k_work_delayable restart_adv_work;
// 读取特征值回调  
static ssize_t read_vnd(struct bt_conn *conn, const struct bt_gatt_attr *attr,  
            void *buf, uint16_t len, uint16_t offset)  
{  
    const char *value = attr->user_data;  
     
    printk("Read request received\n");  
    return bt_gatt_attr_read(conn, attr, buf, len, offset, value, strlen(value));  
}  
 
// 写入特征值回调  
static ssize_t write_vnd(struct bt_conn *conn, const struct bt_gatt_attr *attr,  
             const void *buf, uint16_t len, uint16_t offset,  
             uint8_t flags)  
{  
    uint8_t *value = attr->user_data;  
 
    if (offset + len > VND_MAX_LEN)  
    {  
        return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);  
    }  
    memcpy(value + offset, buf, len);  
    value[offset + len] = 0;  
    printk("Write request received, data: %s\n", value);  
    return len;  
}  
 
// 定义GATT服务(只包含一个读写特征)  
BT_GATT_SERVICE_DEFINE(vnd_svc,  
    BT_GATT_PRIMARY_SERVICE(&vnd_svc_uuid.uuid),  
    BT_GATT_CHARACTERISTIC(&vnd_char_uuid.uuid,  
                   BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,  
                   BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,  
                   read_vnd, write_vnd, vnd_value),  
);  
 
// 广播数据  
static const struct bt_data ad[] = {  
    BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),  
    BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_CUSTOM_SERVICE_VAL),  
};  
 
// 扫描响应数据  
static const struct bt_data sd[] = {  
    BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1),  
};  

// 连接回调  
static void connected(struct bt_conn *conn, uint8_t err)
{
    if (err)
    {
        printk("Connection failed, err 0x%02x %s\n", err, bt_hci_err_to_str(err));
       
        // 连接失败时也尝试重新广播
        int adv_err = bt_le_adv_start(BT_LE_ADV_CONN_FAST_1, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
        if (adv_err)
        {
            printk("Restart advertising after connection failure failed (err %d)\n", adv_err);
        }
    }
    else 
    {
        printk("Connected\n");
       
        // 停止广播
        bt_le_adv_stop();
       
        // 保存连接对象
        if (current_conn)
        {
            bt_conn_unref(current_conn);
        }
        current_conn = bt_conn_ref(conn);
        // 启动定时器, 每秒打印连接状态
        k_timer_start(&conn_monitor_timer, K_SECONDS(1), K_SECONDS(1));
    }
}
 
// 延迟重启广播的工作函数
static void restart_adv_work_fn(struct k_work *work)
{
    int err;
   
    // 先确保广播完全停止
    bt_le_adv_stop();
    // 给协议栈一点时间完成清理
    k_sleep(K_MSEC(100));// 很重要
   
    // 使用原有的 BT_LE_ADV_CONN_FAST_1 参数
    err = bt_le_adv_start(BT_LE_ADV_CONN_FAST_1, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
    if (err)
    {
        printk("Restart advertising failed (err %d)\n", err);
    }
    else 
    {
        printk("Restart advertising successfully\n");
    }
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
    printk("Disconnected, reason 0x%02x %s\n", reason, bt_hci_err_to_str(reason));
   
    k_timer_stop(&conn_monitor_timer);
   
    if (current_conn)
    {
        bt_conn_unref(current_conn);
        current_conn = NULL;
    }
   
    // 调度执行:500ms 后自动调用 restart_adv_work_fn
    k_work_schedule(&restart_adv_work, K_MSEC(500));
}
// 注册连接回调  
BT_CONN_CB_DEFINE(conn_callbacks) = {  
    .connected = connected,  
    .disconnected = disconnected,  
};  
 
// 蓝牙初始化完成回调  
static void bt_ready(void)  
{  
    int err;  
    printk("Bluetooth initialized\n");  
    if (IS_ENABLED(CONFIG_SETTINGS))  
    {  
        settings_load();  
    }  
    // 启动广播  
    err = bt_le_adv_start(BT_LE_ADV_CONN_FAST_1, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));  
    if (err)  
    {  
        printk("Advertising failed to start (err %d)\n", err);  
        return;  
    }  
    printk("Advertising successfully started\n");  
}  

// 连接监控定时器超时回调
static void conn_monitor_timeout(struct k_timer *timer)
{
    if (!current_conn)
    {
        printk("[Monitor] No active connection\n");
        return;
    }
   
    struct bt_conn_info info;
    int err = bt_conn_get_info(current_conn, &info);
   
    if (err)
    {
        printk("[Monitor] Failed to get connection info (err %d)\n", err);
        return;
    }
   
    // 简化的状态打印
    const char *state_str;
    switch (info.state)
    {
    case BT_CONN_STATE_CONNECTED:    
        state_str = "CONNECTED";
        break;
    case BT_CONN_STATE_CONNECTING:
        state_str = "CONNECTING";
        break;
    case BT_CONN_STATE_DISCONNECTED:
        state_str = "DISCONNECTED";
        break;
    case BT_CONN_STATE_DISCONNECTING:
        state_str = "DISCONNECTING";
        break;
    default:                  
        state_str = "UNKNOWN";
        break;
    }
   
    printk("[Monitor] Connection state: %s\n", state_str);
}

void ble_init(void)  
{  
    int err = 0;  
    // 初始化定时器
    k_timer_init(&conn_monitor_timer, conn_monitor_timeout, NULL);
    // 初始化一个可延迟执行的工作项, 把 restart_adv_work 和它的执行函数 restart_adv_work_fn 绑定
    k_work_init_delayable(&restart_adv_work, restart_adv_work_fn);
    // 初始化蓝牙  
    err = bt_enable(NULL);  
    if (err != 0)  
    {  
        printk("Bluetooth init failed (err %d)\n", err);  
        return;  
    }  
    // 注册蓝牙初始化完成回调
    bt_ready();  
}

在main()中实现

int main(void)  
{  
    printf("Hello World! %s\n", CONFIG_BOARD_TARGET);
    ble_init();
    // led_init();
    while (1)  
    {  
        // led_toggle();
        k_sleep(K_SECONDS(1));
    }  
    return 0;  
}

注意:①,conn_monitor_timer 定时器现在是用来查询连接状态,之后可以用作一直查询温湿度数据;②,restart_adv_work 是一个一个可延迟的工作项,用来将重启广播的操作延迟到连接完全断开之后,确保资源被正确释放。如果在 bt_le_adv_stop 之后直接调用 bt_le_adv_start ,会报错  Restart advertising failed (err -12) ,错误码 -12 对应 -ENOMEM,表示内存不足。所以在两个函数之间要加延时确保释放完整。
         2,连接SHT30,需要使用I2C,刚好有示例 zephyr\samples\sensor\sht3xd,虽然是为LPI2C1,但也能直接用作I2C使用。查看数据手册找到支持LPI2C1的引脚。

3,数据手册IO信息.png

查看设备树文件 __repo__\zephyr\boards\nxp\frdm_mcxw71\frdm_mcxw71.dts,挂载了加速度计 fxls8974,需要创建 boards/frdm_mcxw71.overlay 文件,告诉系统 SHT30 连接在哪个 I2C 总线上

&lpi2c1 {
	status = "okay";
	pinctrl-0 = <&pinmux_lpi2c1>;
	pinctrl-names = "default";

	accelerometer: accelerometer@19 {
		status = "okay";
		compatible = "nxp,fxls8974";
		reg = <0x19>;
	};
};

arduino_i2c: &lpi2c1 {};

boards 目录下新增 frdm_mcxw71.overlay 文件,Zephyr 在编译时会合并 .dts 与 .overlay 等设备树源文件。合并规则是:在同一个节点下,新增内容会添加,已有内容会被覆盖(如果完全相同的属性)。

/* 使用已经使能的 lpi2c1 总线 */
&lpi2c1 {
    /* 在现有设备基础上添加 SHT30 */
    sht30@44 {
        compatible = "sensirion,sht3xd";
        reg = <0x44>;  /* SHT30 默认 I2C 地址*/
    };
};

sht30@44-设备节点名称,格式为 名称@地址。

compatible = "sensirion,sht3xd";-兼容性字符串,告诉系统使用哪个驱动程序来驱动这个设备。

  • sensirion:制造商名称(Sensirion)

  • sht3xd:设备型号/驱动名称

还需要在prj.conf文件中增加这些配置

# I2C-related configuration
CONFIG_STDOUT_CONSOLE=y
CONFIG_I2C=y
CONFIG_SENSOR=y
CONFIG_CBPRINTF_FP_SUPPORT=y

新建sht30.c文件实现获取温湿度数据,是用设备树开发确实简单了,就是不太好理解

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
#include <stdio.h>

#include "sht30.h"

static bool sht30_ready = false;
static const struct device *dev = NULL;

/**
 * @brief 初始化 SHT30
 */
void sht30_init(void)
{
    dev = DEVICE_DT_GET_ONE(sensirion_sht3xd);

    if (!device_is_ready(dev))
    {
        printf("Device %s is not ready\n", dev->name);
        return;
    }
    sht30_ready = true;
    printf("SHT30 initialized successfully\n");
}

/**
 * @brief 读取温湿度
 * @param temp_out 温度输出指针(摄氏度)
 * @param hum_out 湿度输出指针(百分比)
 * @return 0: 成功, 1: 失败
 */
uint8_t sht30_read(double *temp_out, double *hum_out)
{
    int rc = 0;
   
    if (sht30_ready == false) {
        printf("SHT30 not initialized\n");
        return 1;
    }
   
    struct sensor_value temp, hum;

    rc = sensor_sample_fetch(dev);
    if (rc == 0)
    {
        rc = sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, &temp);
    }
    if (rc == 0)
    {
        rc = sensor_channel_get(dev, SENSOR_CHAN_HUMIDITY, &hum);
    }
    if (rc != 0) {
        printf("SHT30 read failed: %d\n", rc);
        return 1;
    }
    *temp_out = sensor_value_to_double(&temp);
    *hum_out = sensor_value_to_double(&hum);
   
    return 0;
}

之后再main()中实现

int main(void)  
{  
    double temp = 0, hum = 0;
    printf("Hello World! %s\n", CONFIG_BOARD_TARGET);
    // ble_init();
    // led_init();
    sht30_init();
    while (1)  
    {  
        sht30_read(&temp, &hum);
        printf("Temperature: %.2f Cel, Humidity: %.2f %%RH\n", temp, hum);
        // led_toggle();
        k_sleep(K_SECONDS(1));
    }  
    return 0;  
}

输出log

Hello World! frdm_mcxw71/mcxw716c
SHT30 initialized successfully
Temperature: 26.79 Cel, Humidity: 53.59 %RH
*** Booting Zephyr OS build v4.4.0 ***
Temperature: 26.82 Cel, Humidity: 53.59 %RH
Temperature: 26.79 Cel, Humidity: 53.61 %RH
Temperature: 26.79 Cel, Humidity: 53.65 %RH
Temperature: 26.82 Cel, Humidity: 53.67 %RH
Temperature: 26.82 Cel, Humidity: 53.69 %RH
Temperature: 26.79 Cel, Humidity: 53.71 %RH





关键词: BLE     SHT30     温湿度     Zephyr    

共1条 1/1 1 跳转至

回复

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