目前已经能实现蓝牙连接和蓝牙传输,但是有个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的引脚。

查看设备树文件 __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
我要赚赏金
