之前我们成功通过 I2C 把 SHT30 的温湿度数据读出来了,接下来就是怎么通过蓝牙发给手机。
最开始我尝试直接把数据扔进蓝牙的无连接广播包(比如 Manufacturer Data)里,但实际测试发现一个非常头疼的问题:通用手机测试软件(比如 nRF Connect)非常死板,它严格遵循蓝牙底层规范,会把接收到的数据全部强制显示为 16 进制(Hex)格式。这导致每次看温度还得自己去查 ASCII 码表翻译,非常不直观。
后面原因分析及对策: 既然底层广播规范限制了纯文本的直观显示,那如果想在 APP 里直接看到类似 T:23.5C H:45.2% 这样肉眼能看懂的纯文本,最好的办法就是让开发板支持蓝牙连接,并创建一个 GATT 服务端(Server)。我们可以将读取到的浮点数转换为字符串,塞进特征值(Characteristic)里,通过 Notify(通知)的方式主动推送到手机。
修改配置文件 prj.conf为了支持手机连接,我们需要打开 Zephyr 的蓝牙外设模式相关配置:
代码段# 原有的 I2C 和打印配置保留 CONFIG_I2C=y CONFIG_PRINTK=y CONFIG_NRFX_TWIM=y CONFIG_CBPRINTF_FP_SUPPORT=y # 新增:蓝牙基础与外设模式配置 CONFIG_BT=y CONFIG_BT_DEVICE_NAME="SHT30_GATT" CONFIG_BT_PERIPHERAL=y # 开启外设模式,允许被手机连接 CONFIG_BT_GATT_CLIENT=n
主要实现代码在原本 read_gy_sht30 成功读取数据的基础上,我们加入自定义的 GATT 服务和 Notify 逻辑。
C#include <zephyr/kernel.h>#include <zephyr/drivers/i2c.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>#include <stdio.h>#include <string.h>/* I2C 及 SHT30 读取部分略(与之前完全一致) */// extern int read_gy_sht30(double *temp, double *hum);/* 1. 自定义 128-bit UUID */static struct bt_uuid_128 my_service_uuid = BT_UUID_INIT_128(
BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef0));static struct bt_uuid_128 my_char_uuid = BT_UUID_INIT_128(
BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef1));static char temp_string[32] = "No Data";static bool notify_enabled = false;/* 2. 监听手机是否开启了 Notify 订阅 */static void my_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) {
notify_enabled = (value == BT_GATT_CCC_NOTIFY);
}/* 3. 注册我们自己的 GATT 服务端 */BT_GATT_SERVICE_DEFINE(my_sensor_svc,
BT_GATT_PRIMARY_SERVICE(&my_service_uuid),
BT_GATT_CHARACTERISTIC(&my_char_uuid.uuid,
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_READ, NULL, NULL, temp_string),
BT_GATT_CCC(my_ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE)
);/* 连接状态管理 */struct bt_conn *current_conn = NULL;static void connected(struct bt_conn *conn, uint8_t err) { if (!err) current_conn = bt_conn_ref(conn);
}static void disconnected(struct bt_conn *conn, uint8_t reason) { if (current_conn) {
bt_conn_unref(current_conn);
current_conn = NULL;
notify_enabled = false;
}
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
};/* 蓝牙广播包内容设置 */static const struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1),
};int main(void) { double temperature, humidity;
// 初始化蓝牙
bt_enable(NULL); // 开启可连接广播
bt_le_adv_start(BT_LE_ADV_CONN_NAME_AD, ad, ARRAY_SIZE(ad), NULL, 0);
printk("BLE GATT Server Started...\n"); while (1) { if (read_gy_sht30(&temperature, &humidity) == 0) { /* 格式化为直观的纯文本字符串 */
snprintf(temp_string, sizeof(temp_string), "T:%.1fC H:%.1f%%", temperature, humidity); /* 如果手机已经连上,并且开启了 Notify 接收,就主动推过去 */
if (current_conn && notify_enabled) {
bt_gatt_notify(current_conn, &my_sensor_svc.attrs[2], temp_string, strlen(temp_string));
}
}
k_msleep(2000); // 也是两秒刷新一次
} return 0;
}最终效果验证将代码编译并烧录进去。通过手机打开 nRF Connect APP:
扫描找到 SHT30_GATT,点击 CONNECT 连接。
展开找到我们的自定义服务(Unknown Service),长按特征值右侧的数据区域,将格式选为 UTF-8 或者 String。
点击特征值旁边的 Notify(三个向下箭头)开启接收。
通过 APP 界面查看,成功将纯文本数据(例如 T:24.5C H:45.2%)读取并实时显示了出来,每两秒钟自动刷新一次数据


我要赚赏金
