我们根据Zephyr SDK中的例程进行工程创建,实际上蓝牙功能是我们这次任务实现的最基础功能,我们这一次要做一个功能能,就是通过蓝牙输出本次的开发板型号“FRDM-MCXW72”并带有发送的次数编号,间隔是2s。
我们先创建基础工程:
Zephyr官方在samples/bluetooth/peripheral下放了一个非常完整的BLE外设demo,包含了BAS(电池服务)、HRS(心率服务)、IAS(报警服务)、CTS(时间服务)等一堆示例功能。
基本目录如下:peripheral/ ├── CMakeLists.txt ├── prj.conf ← 蓝牙相关的 Kconfig 配置 ├── sample.yaml ├── boards/ ← 板级 overlay(默认不带,先用 SDK 自带) └── src/ └── main.c ← 我们要改的主入口
prj.conf蓝牙配置
demo自带的prj.conf把上面提到的服务全打开了,配置有20多行。我们这次任务很简单——只做"自定义GATT服务 + 定时透传编号"——所以配置反而要精简。
精简后的 prj.conf:
CONFIG_BT=y CONFIG_BT_PERIPHERAL=y CONFIG_LOG=y CONFIG_BT_DEVICE_NAME="BLE test" 注意我们没有开CONFIG_BT_SETTINGS/CONFIG_BT_SMP/CONFIG_BT_BAS等,因为这次不需要绑定配对,也不需要演示服务,留着只会拖大固件体积。
自定义一个 GATT 服务
我们做的事情可以概括成一句话:手机连上板子后,板子每 2 秒往手机推一个字符串 "FRDM-MCXW72 #N",N 是个递增的计数器。 拆成 Zephyr 的几个概念:Service(服务):一组相关功能的集合,对应一个UUID
Characteristic(特征):服务里实际的数据项,可读可写可通知
Notify(通知):服务器主动推数据给手机(需要手机先订阅)
服务的UUID
我们用128-bit自定义UUID(避开蓝牙SIG标准UUID就行):
#define BT_UUID_COUNT_SERVICE_VAL \ BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef0) #define BT_UUID_COUNT_CHAR_VAL \ BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef1)
服务定义
BT_GATT_SERVICE_DEFINE是Zephyr的一个很方便的宏,会自动展开成 GATT表项。用法像写表一样直观:
BT_GATT_SERVICE_DEFINE(count_svc, BT_GATT_PRIMARY_SERVICE(&count_svc_uuid), BT_GATT_CHARACTERISTIC(&count_char_uuid.uuid, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_READ, read_count, NULL, count_buf), BT_GATT_CCC(count_ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), );
周期任务
每2秒触发一次"更新数据 + 发送通知",用k_work_delayable(这是Zephyr内核标准的工作队列机制):
static void count_work_handler(struct k_work *work) { int err; count_len = (uint16_t)snprintf((char *)count_buf, sizeof(count_buf), "FRDM-MCXW72 #%u", (unsigned int)send_count); send_count++; printk("Notify: %s\n", count_buf); if (notify_enabled) { err = bt_gatt_notify(NULL, &count_svc.attrs[2], count_buf, count_len); if (err) { printk("bt_gatt_notify failed (err %d)\n", err); } } if (streaming) { k_work_schedule(&count_work, K_MSEC(NOTIFY_INTERVAL_MS)); } } K_WORK_DELAYABLE_DEFINE(count_work, count_work_handler);连接状态
BT_CONN_CB_DEFINE是Zephyr的连接回调注册宏,自动展开成一个struct bt_conn_cb,我们要关心两件事:连接成功、断开连接。
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)); return; } printk("Connected\n"); if (!streaming) { streaming = true; k_work_schedule(&count_work, K_MSEC(NOTIFY_INTERVAL_MS)); } } static void disconnected(struct bt_conn *conn, uint8_t reason) { streaming = false; (void)k_work_cancel_delayable(&count_work); printk("Disconnected (reason 0x%02x %s)\n", reason, bt_hci_err_to_str(reason)); } BT_CONN_CB_DEFINE(conn_callbacks) = { .connected = connected, .disconnected = disconnected, };关键的"在连接成功后才开始发送"这个时序就是在这里实现的——bt_ready里只启动广播,不开定时器;定时器在connected回调里启动。
main函数
main反而是整个工程里最简短的:
int main(void) { int err = bt_enable(bt_ready); if (err) { printk("Bluetooth enable failed (err %d)\n", err); } return 0; }测试:
这里我们找了一个微信小程序专门用于蓝牙测试的,可以看到手机蓝牙开启后可以看到我们的蓝牙名称“BLE test”,具体的服务可以收到对应的数据:

这时板子串口会开始每 2 秒打印一次:

我要赚赏金
