メインコンテンツまでスキップ

XIAO nRF54LM20A Sense 向け Bluetooth LE

Bluetooth Low Energy(BLE)は、Bluetooth 4.0 で導入された低消費電力の無線通信規格です。断続的な少量データ伝送向けに設計されており、マイクロアンペアレベルの超低平均電流消費を維持しながら、数十メートルの範囲でワイヤレス接続を実現します。ウェアラブルデバイス、スマートホームセンサー、屋内測位、産業用 IoT などのシナリオで広く利用されています。

nRF54LM20A SoC を搭載した XIAO nRF54LM20A シリーズは、Bluetooth LE、Matter、Thread、Zigbee、および 2.4GHz 独自プロトコルをサポートし、低レイテンシーシナリオに最適な 4 Mbps のピークデータレートを実現します。さらに、Bluetooth Channel Sounding と Bluetooth Mesh にも対応しています。本記事では、基本的なブロードキャスト Beacon 送信と、Central デバイスと Peripheral デバイス間の BLE LED Button Service(LBS)接続という 2 つの実用的な例を通して、その BLE 機能を説明します。

ヒント

ハードウェアの準備

BLE LBS のサンプルを実行する場合は、作業を始める前に少なくとも 2 枚の XIAO nRF54LM20A Sense ボードを用意してください。

Seeed Studio XIAO nRF54LM20A Sense

Bluetooth アンテナ

このボードは外部 Bluetooth アンテナを使用します。より良い Bluetooth 信号品質を確保し、Bluetooth の使用体験を向上させるために、Bluetooth アンテナを取り付けることを推奨します。 接続方法は以下のとおりです。

Bluetooth antenna connection

アンテナの取り付け

Seeed Studio XIAO nRF54LM20A のパッケージには、専用の 2.4 GHz アンテナが同梱されています。最適な Bluetooth 性能を得るために、付属のアンテナをオンボードのアンテナコネクタに接続してください。

XIAO nRF54 シリーズ向け 2.4GHz FPC アンテナ A-04

アプリケーション

このセクションでは、実際のケースを通して、XIAO nRF54LM20A Sense における BLE の中核機能と使用方法を紹介します。

BLE Beacon

このサンプルでは、XIAO nRF54LM20A 上で BLE Beacon を実装します。起動後、デバイスは Manufacturer Specific Data を含むアドバタイジングパケットを継続的にブロードキャストします。パケットには 1 秒ごとにインクリメントされるカウンタが含まれており、nRF Connect を使用してデータの変化をリアルタイムに監視できます。

ソフトウェア

  1. app.overlay で関連するデバイスツリー設定を有効にし、BLE コントローラを Zephyr ネイティブ実装に切り替えます。
/* Enable Zephyr native BLE controller (LL SW Split) */
&bt_hci_controller {
status = "okay";
};

/ {
chosen {
zephyr,bt-hci = &bt_hci_controller;
};
};

  1. prj.conf で関連する Bluetooth 設定を有効にし、ログ出力モードを設定し、Bluetooth デバイス名を XIAO-Beacon に変更します。
# GPIO
CONFIG_GPIO=y
# XIAO nRF54LM20A can fault early with the MPU enabled in this toolchain/board package.
CONFIG_ARM_MPU=n
# Regulator (for power_en)
CONFIG_REGULATOR=y
# Logging
CONFIG_LOG=y
# UART for console logging
CONFIG_SERIAL=y
CONFIG_UART_ASYNC_API=y
CONFIG_UART_20_ASYNC=y
CONFIG_UART_21_ASYNC=y
CONFIG_UART_NRFX_UARTE_ENHANCED_RX=y
# BLE
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_DEVICE_NAME="XIAO-Beacon"
# Avoid GCC 8.2 rejecting the controller's optimized assert inline asm path.
CONFIG_BT_CTLR_ASSERT_OPTIMIZE_FOR_SIZE=n
CONFIG_BT_CTLR_ASSERT_DEBUG=n
CONFIG_BT_CTLR_ASSERT_OVERHEAD_START=n
# Disable auto-procedures to avoid LL Procedure Collision on nRF54L
CONFIG_BT_AUTO_PHY_UPDATE=n
CONFIG_BT_DATA_LEN_UPDATE=n
# Memory
CONFIG_HEAP_MEM_POOL_SIZE=8192
# System workqueue stack
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
# Assert level
CONFIG_ASSERT=y

  1. main.c でアドバタイジングデータ形式と更新ロジックを実装します。
main.c
#include <stdio.h>

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/regulator.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(ble_beacon, LOG_LEVEL_INF);

/* Manufacturer Data configuration */
#define MANUF_COMPANY_ID 0x0059
#define MANUF_DATA_SIZE 8

static uint32_t manufacturer_counter;

static const uint8_t adv_flags[] __aligned(4) = {
BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR,
};

static const uint8_t adv_name[] __aligned(4) = CONFIG_BT_DEVICE_NAME;

static uint8_t manuf_data[MANUF_DATA_SIZE] __aligned(4);

static const struct bt_data ad[] __aligned(4) = {
BT_DATA(BT_DATA_FLAGS, adv_flags, sizeof(adv_flags)),
BT_DATA(BT_DATA_NAME_COMPLETE, adv_name, sizeof(adv_name) - 1),
BT_DATA(BT_DATA_MANUFACTURER_DATA, manuf_data, sizeof(manuf_data)),
};

/* Power enable regulator (GPIO1_12) - must be enabled before BLE init */
static const struct device *const power_en_dev =
DEVICE_DT_GET(DT_NODELABEL(power_en));

static void adv_update_work_handler(struct k_work *work);

static K_WORK_DELAYABLE_DEFINE(adv_update_work, adv_update_work_handler);

static void fill_manuf_data(uint32_t counter)
{
/* [Company ID (2B)][Counter (4B)][Custom (2B)] */
manuf_data[0] = MANUF_COMPANY_ID & 0xFF;
manuf_data[1] = (MANUF_COMPANY_ID >> 8) & 0xFF;
manuf_data[2] = (counter >> 0) & 0xFF;
manuf_data[3] = (counter >> 8) & 0xFF;
manuf_data[4] = (counter >> 16) & 0xFF;
manuf_data[5] = (counter >> 24) & 0xFF;
manuf_data[6] = 0xAA;
manuf_data[7] = 0xBB;
}

static int enable_power(void)
{
int ret;

if (!device_is_ready(power_en_dev)) {
LOG_ERR("power_en regulator is not ready");
return -ENODEV;
}

ret = regulator_enable(power_en_dev);
if (ret < 0 && ret != -EALREADY) {
LOG_ERR("Failed to enable power_en: %d", ret);
return ret;
}

k_sleep(K_MSEC(20));
LOG_INF("Power rail enabled");
return 0;
}

static void adv_update_work_handler(struct k_work *work)
{
int err;

manufacturer_counter++;
fill_manuf_data(manufacturer_counter);

err = bt_le_adv_update_data(ad, ARRAY_SIZE(ad), NULL, 0);
if (err < 0) {
LOG_ERR("Failed to update advertising data (err %d)", err);
} else {
LOG_INF("Manufacturer counter: %u", manufacturer_counter);
}

k_work_schedule(&adv_update_work, K_SECONDS(1));
}

int main(void)
{
int err;

LOG_INF("BLE Manufacturer Data Beacon");

/* Enable board power rail before BLE initialization */
err = enable_power();
if (err < 0) {
LOG_ERR("Power enable failed (err %d)", err);
return err;
}

LOG_INF("Initializing BLE...");

err = bt_enable(NULL);
if (err < 0) {
LOG_ERR("Bluetooth enable failed (err %d)", err);
return err;
}

LOG_INF("BLE initialized");

/* Initial advertising data with counter = 0 */
fill_manuf_data(0);

err = bt_le_adv_start(BT_LE_ADV_NCONN, ad, ARRAY_SIZE(ad), NULL, 0);
if (err < 0) {
LOG_ERR("Advertising failed to start (err %d)", err);
return err;
}

LOG_INF("BLE advertising started");

/* Schedule counter update after 1 second */
k_work_schedule(&adv_update_work, K_SECONDS(1));

for (;;) {
k_sleep(K_FOREVER);
}

return 0;
}

結果

  1. ファームウェアを書き込んだ後、nRF Connect アプリをインストールして BLE デバイスをスキャン・検出します。

同時に、主要なモバイルアプリストアで nRF Connect アプリを検索してダウンロードすることができ、これによりスマートフォンで Bluetooth デバイスをスキャンして接続できます。

  1. ソフトウェアをインストールしたら、XIAO-Beacon という名前の Bluetooth デバイスをスキャンし、受信した Manufacturer Data を確認します。同時にシリアルポートを開き、出力ログを確認します。
  • 取得された Manufacturer Data は 16 進値 <0x0059> 0x03000000AABB です。プログラムコードを確認すると、0x03000000 の部分は現在のカウンタ値が 3 であることを示しています。
#define MANUF_COMPANY_ID    0x0059
static uint32_t manufacturer_counter;
...
manuf_data[0] = MANUF_COMPANY_ID & 0xFF;
manuf_data[1] = (MANUF_COMPANY_ID >> 8) & 0xFF;
manuf_data[2] = (manufacturer_counter >> 0) & 0xFF;
manuf_data[3] = (manufacturer_counter >> 8) & 0xFF;
manuf_data[4] = (manufacturer_counter >> 16) & 0xFF;
manuf_data[5] = (manufacturer_counter >> 24) & 0xFF;
manuf_data[6] = 0xAA;
manuf_data[7] = 0xBB;
  • シリアルポートツールを開き、カウンタ値が 1 行ずつ出力され、現在のカウントが 3 に達していることを確認します。

上記の結果から、XIAO nRF54LM20A Sense 上でカスタム BLE アドバタイジングパケットを送信するプロセスを明確に理解でき、BLE の動作特性に関するさらなる研究に役立ちます。特定のアプリケーションシナリオでは、実際に接続を確立することなく、アドバタイジングデータを用いてトリガ条件を判定することができます。

BLE LBS

このサンプルでは、2 枚の XIAO nRF54 ボードを使用して BLE LED Button Service (LBS) を実装します。1 枚は BLE Peripheral として動作し、カスタム LBS サービスをアドバタイズします。もう 1 枚は BLE Central として動作し、そのサービスをスキャンして自動的に接続し、GATT Write Characteristic を介して Peripheral 上の LED を制御します。

このサンプルで使用される led0sw0 エイリアスは、ボード定義ですでに提供されているため、追加の app.overlay ファイルは不要です。

ソフトウェア

BLE Central
  1. CMakeLists.txt でプロジェクトを設定します。

# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.13.1)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(ble-lbs-min-central)

target_sources(app PRIVATE src/main.c)



  1. prj.conf で Bluetooth 関連の設定を有効にします
CONFIG_GPIO=y
CONFIG_SERIAL=y
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y
CONFIG_PRINTK=y

CONFIG_LOG=y
CONFIG_LOG_BACKEND_UART=y
CONFIG_LOG_BUFFER_SIZE=2048

CONFIG_BT=y
CONFIG_BT_CENTRAL=y
CONFIG_BT_OBSERVER=y
CONFIG_BT_GATT_CLIENT=y
CONFIG_BT_CTLR_TX_PWR_PLUS_8=y
CONFIG_BT_DEVICE_NAME="zephyr_ble_lbs_central"

CONFIG_BT_BUF_ACL_RX_SIZE=255
CONFIG_BT_BUF_ACL_TX_SIZE=251
CONFIG_BT_BUF_CMD_TX_SIZE=255
CONFIG_BT_BUF_EVT_DISCARDABLE_SIZE=255
CONFIG_BT_L2CAP_TX_MTU=247

CONFIG_MAIN_STACK_SIZE=4096
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048

  1. main.c で BLE アプリケーションロジックを実装します。
main.c
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/atomic.h>

#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>

#include <string.h>

LOG_MODULE_REGISTER(app, LOG_LEVEL_INF);

#define BT_UUID_LBS_MIN_VAL BT_UUID_128_ENCODE(0x8e7f1a23, 0x4b2c, 0x11ee, 0xbe56, 0x0242ac120002)
#define BT_UUID_LBS_MIN BT_UUID_DECLARE_128(BT_UUID_LBS_MIN_VAL)

#define BT_UUID_LBS_MIN_WRITE_VAL \
BT_UUID_128_ENCODE(0x8e7f1a24, 0x4b2c, 0x11ee, 0xbe56, 0x0242ac120002)
#define BT_UUID_LBS_MIN_WRITE BT_UUID_DECLARE_128(BT_UUID_LBS_MIN_WRITE_VAL)

#define LED0_NODE DT_ALIAS(led0)
#define SW0_NODE DT_ALIAS(sw0)

static const struct gpio_dt_spec led0 = GPIO_DT_SPEC_GET_OR(LED0_NODE, gpios, {0});
static const struct gpio_dt_spec sw0 = GPIO_DT_SPEC_GET_OR(SW0_NODE, gpios, {0});

static struct bt_conn *default_conn;
static struct bt_conn *discover_conn;
static struct bt_gatt_discover_params discover_params;
static struct bt_gatt_write_params write_params;
static struct gpio_callback sw0_cb;
static struct k_work button_work;
static struct k_work_delayable debounce_work;
static struct k_work_delayable blink_work;
static atomic_t write_busy;

static uint16_t svc_start_handle;
static uint16_t svc_end_handle;
static uint16_t write_handle;
static uint8_t remote_led_state;
static uint8_t blink_led_state;
static bool blink_active;

static bool gpio_ready(const struct gpio_dt_spec *spec)
{
return spec->port != NULL && device_is_ready(spec->port);
}

static void status_led_apply(uint8_t value)
{
if (!gpio_ready(&led0)) {
return;
}

(void)gpio_pin_set_dt(&led0, value ? 1 : 0);
}

static void blink_handler(struct k_work *work)
{
ARG_UNUSED(work);

if (!blink_active) {
return;
}

blink_led_state = blink_led_state ? 0U : 1U;
status_led_apply(blink_led_state);
k_work_reschedule(&blink_work, K_MSEC(500));
}

static int init_status_led(void)
{
int err;

k_work_init_delayable(&blink_work, blink_handler);

if (!gpio_ready(&led0)) {
return -ENODEV;
}

err = gpio_pin_configure_dt(&led0, GPIO_OUTPUT_INACTIVE);
if (err) {
return err;
}

status_led_apply(0U);
return 0;
}

static void start_blink(void)
{
if (!gpio_ready(&led0)) {
return;
}

blink_active = true;
k_work_reschedule(&blink_work, K_NO_WAIT);
}

static void stop_blink(void)
{
blink_active = false;
(void)k_work_cancel_delayable(&blink_work);
blink_led_state = 0U;
status_led_apply(0U);
}

static bool ad_has_uuid(struct bt_data *data, void *user_data)
{
bool *found = user_data;
struct bt_uuid_128 uuid;

if (data->type != BT_DATA_UUID128_ALL && data->type != BT_DATA_UUID128_SOME) {
return true;
}

if ((data->data_len % 16U) != 0U) {
return true;
}

for (size_t i = 0; i < data->data_len; i += 16U) {
memcpy(uuid.val, &data->data[i], 16U);
uuid.uuid.type = BT_UUID_TYPE_128;
if (bt_uuid_cmp(&uuid.uuid, BT_UUID_LBS_MIN) == 0) {
*found = true;
return false;
}
}

return true;
}

static void start_scan(void);

static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type,
struct net_buf_simple *ad)
{
bool found = false;
int err;

ARG_UNUSED(rssi);

if (default_conn != NULL) {
return;
}

if (type != BT_GAP_ADV_TYPE_ADV_IND &&
type != BT_GAP_ADV_TYPE_ADV_DIRECT_IND &&
type != BT_GAP_ADV_TYPE_ADV_SCAN_IND &&
type != BT_GAP_ADV_TYPE_SCAN_RSP) {
return;
}

bt_data_parse(ad, ad_has_uuid, &found);
if (!found) {
return;
}

{
char addr_str[BT_ADDR_LE_STR_LEN];

bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));
LOG_INF("LBS adv matched from %s (type=0x%02x)", addr_str, type);
}

err = bt_le_scan_stop();
if (err) {
LOG_WRN("scan stop failed: %d", err);
}

err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, BT_LE_CONN_PARAM_DEFAULT,
&default_conn);
if (err) {
LOG_ERR("create conn failed: %d", err);
start_scan();
} else {
LOG_INF("connecting to matching peripheral");
}
}

static void start_scan(void)
{
int err = bt_le_scan_start(BT_LE_SCAN_ACTIVE, device_found);

if (err) {
LOG_ERR("scan start failed: %d", err);
return;
}

LOG_INF("scanning");
}

static uint8_t discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
if (attr == NULL) {
LOG_INF("discover complete (attr=NULL) write_handle=0x%04x", write_handle);
memset(params, 0, sizeof(*params));
if (discover_conn) {
bt_conn_unref(discover_conn);
discover_conn = NULL;
}
return BT_GATT_ITER_STOP;
}

if (params->type == BT_GATT_DISCOVER_PRIMARY) {
const struct bt_gatt_service_val *svc = attr->user_data;

svc_start_handle = attr->handle;
svc_end_handle = svc->end_handle;
LOG_INF("primary svc found: start=0x%04x end=0x%04x",
svc_start_handle, svc_end_handle);

memset(params, 0, sizeof(*params));
/* Discover all characteristics in the service, then match the write
* characteristic in code. Filtering by the 128-bit UUID at ATT level
* can return nothing even when the characteristic exists.
*/
params->uuid = NULL;
params->func = discover_func;
params->start_handle = svc_start_handle + 1U;
params->end_handle = svc_end_handle;
params->type = BT_GATT_DISCOVER_CHARACTERISTIC;

if (bt_gatt_discover(conn, params)) {
LOG_ERR("characteristic discover failed");
}

return BT_GATT_ITER_STOP;
}

if (params->type == BT_GATT_DISCOVER_CHARACTERISTIC) {
const struct bt_gatt_chrc *chrc = attr->user_data;
char uuid_str[37];

bt_uuid_to_str(chrc->uuid, uuid_str, sizeof(uuid_str));
LOG_INF("chrc: value_handle=0x%04x props=0x%02x uuid=%s",
chrc->value_handle, chrc->properties, uuid_str);

if (bt_uuid_cmp(chrc->uuid, BT_UUID_LBS_MIN_WRITE) == 0) {
write_handle = chrc->value_handle;
LOG_INF("write handle found: 0x%04x", write_handle);
return BT_GATT_ITER_STOP;
}
}

return BT_GATT_ITER_CONTINUE;
}

static void discover_lbs_service(struct bt_conn *conn)
{
svc_start_handle = 0U;
svc_end_handle = 0U;
write_handle = 0U;

if (discover_conn) {
bt_conn_unref(discover_conn);
}

discover_conn = bt_conn_ref(conn);

memset(&discover_params, 0, sizeof(discover_params));
discover_params.uuid = BT_UUID_LBS_MIN;
discover_params.func = discover_func;
discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
discover_params.type = BT_GATT_DISCOVER_PRIMARY;

if (bt_gatt_discover(conn, &discover_params)) {
LOG_ERR("service discover failed");
} else {
LOG_INF("discovering LBS service");
}
}

static void write_cb(struct bt_conn *conn, uint8_t err, struct bt_gatt_write_params *params)
{
ARG_UNUSED(conn);
ARG_UNUSED(params);

atomic_set(&write_busy, 0);

if (err) {
LOG_ERR("write failed: 0x%02x", err);
return;
}

LOG_INF("write ok");
}

static void button_work_handler(struct k_work *work)
{
uint8_t next_state;
int err;

ARG_UNUSED(work);

if (default_conn == NULL || write_handle == 0U) {
return;
}

if (!atomic_cas(&write_busy, 0, 1)) {
return;
}

next_state = remote_led_state ? 0U : 1U;
remote_led_state = next_state;
LOG_INF("button press -> write 0x%02x", remote_led_state);

write_params.handle = write_handle;
write_params.offset = 0U;
write_params.data = &remote_led_state;
write_params.length = sizeof(remote_led_state);
write_params.func = write_cb;

err = bt_gatt_write(default_conn, &write_params);
if (err) {
atomic_set(&write_busy, 0);
LOG_ERR("write start failed: %d", err);
} else {
LOG_INF("write started");
}
}

static void debounce_handler(struct k_work *work)
{
ARG_UNUSED(work);

if (gpio_pin_get_dt(&sw0) > 0) {
LOG_INF("button debounced");
k_work_submit(&button_work);
}
}

static void sw0_isr(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
ARG_UNUSED(dev);
ARG_UNUSED(cb);
ARG_UNUSED(pins);

k_work_reschedule(&debounce_work, K_MSEC(30));
}

static int init_button(void)
{
int err;

if (!gpio_ready(&sw0)) {
return -ENODEV;
}

err = gpio_pin_configure_dt(&sw0, GPIO_INPUT);
if (err) {
return err;
}

k_work_init(&button_work, button_work_handler);
k_work_init_delayable(&debounce_work, debounce_handler);

gpio_init_callback(&sw0_cb, sw0_isr, BIT(sw0.pin));
err = gpio_add_callback(sw0.port, &sw0_cb);
if (err) {
return err;
}

return gpio_pin_interrupt_configure_dt(&sw0, GPIO_INT_EDGE_TO_ACTIVE);
}

static void connected(struct bt_conn *conn, uint8_t err)
{
if (err) {
LOG_ERR("connect failed: 0x%02x %s", err, bt_hci_err_to_str(err));
if (default_conn) {
bt_conn_unref(default_conn);
default_conn = NULL;
}
start_scan();
return;
}

LOG_INF("connected");
stop_blink();
discover_lbs_service(conn);
}

static void disconnected(struct bt_conn *conn, uint8_t reason)
{
ARG_UNUSED(conn);

LOG_INF("disconnected: 0x%02x %s", reason, bt_hci_err_to_str(reason));

if (default_conn) {
bt_conn_unref(default_conn);
default_conn = NULL;
}

write_handle = 0U;
atomic_set(&write_busy, 0);
start_blink();
start_scan();
}

BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
};

int main(void)
{
int err;

remote_led_state = 0U;

err = init_status_led();
if (err) {
LOG_WRN("status led init failed: %d", err);
}

err = init_button();
if (err) {
LOG_WRN("button init failed: %d", err);
}

err = bt_enable(NULL);
if (err) {
LOG_ERR("bt enable failed: %d", err);
return err;
}

LOG_INF("bluetooth initialized");
start_blink();
start_scan();

for (;;) {
k_sleep(K_FOREVER);
}
}

  1. platformio.ini で PlatformIO プロジェクトを設定します。
[env:seeed-xiao-nrf54lm20a]
platform = https://github.com/Seeed-Studio/platform-seeedboards.git
framework = zephyr
board = seeed-xiao-nrf54lm20a
platform_packages =
platformio/toolchain-gccarmnoneeabi@~1.90201.0
monitor_speed = 115200

BLE ペリフェラル
  1. CMakeLists.txt でプロジェクトを設定します。
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.13.1)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(ble-lbs-min-peripheral)

target_sources(app PRIVATE src/main.c)



  1. prj.conf で Bluetooth 関連の設定を有効にします
CONFIG_GPIO=y
CONFIG_SERIAL=y
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y
CONFIG_PRINTK=y

CONFIG_LOG=y
CONFIG_LOG_BACKEND_UART=y
CONFIG_LOG_BUFFER_SIZE=2048

CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_CTLR_TX_PWR_PLUS_8=y
CONFIG_BT_DEVICE_NAME="zephyr_ble_lbs"

CONFIG_MAIN_STACK_SIZE=4096
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048



  1. main.c で BLE アプリケーションロジックを実装します。
main.c
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/logging/log.h>

#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>

LOG_MODULE_REGISTER(app, LOG_LEVEL_INF);

#define BT_UUID_LBS_MIN_VAL BT_UUID_128_ENCODE(0x8e7f1a23, 0x4b2c, 0x11ee, 0xbe56, 0x0242ac120002)

#define BT_UUID_LBS_MIN_WRITE_VAL \
BT_UUID_128_ENCODE(0x8e7f1a24, 0x4b2c, 0x11ee, 0xbe56, 0x0242ac120002)

#define BT_UUID_LBS_MIN_READ_VAL \
BT_UUID_128_ENCODE(0x8e7f1a25, 0x4b2c, 0x11ee, 0xbe56, 0x0242ac120003)

static const struct bt_uuid_128 lbs_min_uuid __aligned(4) =
BT_UUID_INIT_128(BT_UUID_LBS_MIN_VAL);
static const struct bt_uuid_128 lbs_min_write_uuid __aligned(4) =
BT_UUID_INIT_128(BT_UUID_LBS_MIN_WRITE_VAL);
static const struct bt_uuid_128 lbs_min_read_uuid __aligned(4) =
BT_UUID_INIT_128(BT_UUID_LBS_MIN_READ_VAL);

#define BT_UUID_LBS_MIN ((const struct bt_uuid *)&lbs_min_uuid.uuid)
#define BT_UUID_LBS_MIN_WRITE ((const struct bt_uuid *)&lbs_min_write_uuid.uuid)
#define BT_UUID_LBS_MIN_READ ((const struct bt_uuid *)&lbs_min_read_uuid.uuid)

#define LED0_NODE DT_ALIAS(led0)

static const struct gpio_dt_spec led0 = GPIO_DT_SPEC_GET_OR(LED0_NODE, gpios, {0});
static struct k_work_delayable blink_work;
static uint8_t led_state __aligned(4);
static uint8_t blink_led_state __aligned(4);
static bool blink_active;

static bool gpio_ready(const struct gpio_dt_spec *spec)
{
return spec->port != NULL && device_is_ready(spec->port);
}

static void led_apply(uint8_t value)
{
if (!gpio_ready(&led0)) {
return;
}

(void)gpio_pin_set_dt(&led0, value ? 1 : 0);
}

static void blink_handler(struct k_work *work)
{
ARG_UNUSED(work);

if (!blink_active) {
return;
}

blink_led_state = blink_led_state ? 0U : 1U;
led_apply(blink_led_state);
k_work_reschedule(&blink_work, K_MSEC(500));
}

static void start_blink(void)
{
if (!gpio_ready(&led0)) {
return;
}

blink_active = true;
k_work_reschedule(&blink_work, K_NO_WAIT);
}

static void stop_blink(void)
{
blink_active = false;
(void)k_work_cancel_delayable(&blink_work);
blink_led_state = 0U;
led_apply(led_state);
}

static ssize_t read_led(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
const uint8_t *value = attr->user_data;

return bt_gatt_attr_read(conn, attr, buf, len, offset, value, sizeof(*value));
}

static ssize_t write_led(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;

ARG_UNUSED(conn);
ARG_UNUSED(attr);
ARG_UNUSED(flags);

if (len != 1U) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}

if (offset != 0U) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}

value = ((const uint8_t *)buf)[0];
if (value != 0U && value != 1U) {
return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED);
}

led_state = value;
led_apply(led_state);
LOG_INF("remote led state=%u", led_state);

return len;
}

BT_GATT_SERVICE_DEFINE(lbs_min_svc,
BT_GATT_PRIMARY_SERVICE(BT_UUID_LBS_MIN),
BT_GATT_CHARACTERISTIC(BT_UUID_LBS_MIN_WRITE, BT_GATT_CHRC_WRITE,
BT_GATT_PERM_WRITE, NULL, write_led, NULL),
BT_GATT_CHARACTERISTIC(BT_UUID_LBS_MIN_READ, BT_GATT_CHRC_READ,
BT_GATT_PERM_READ, read_led, NULL, &led_state),
);

static const struct bt_data ad[] __aligned(4) = {
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),
};

static const struct bt_data sd[] __aligned(4) = {
BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_LBS_MIN_VAL),
};

static void connected(struct bt_conn *conn, uint8_t err)
{
ARG_UNUSED(conn);

if (err) {
LOG_ERR("connect failed: 0x%02x %s", err, bt_hci_err_to_str(err));
return;
}

LOG_INF("connected");
stop_blink();
}

static void disconnected(struct bt_conn *conn, uint8_t reason)
{
ARG_UNUSED(conn);

LOG_INF("disconnected: 0x%02x %s", reason, bt_hci_err_to_str(reason));
start_blink();
}

BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
};

int main(void)
{
int err;

k_work_init_delayable(&blink_work, blink_handler);

led_state = 0U;
if (gpio_ready(&led0)) {
err = gpio_pin_configure_dt(&led0, GPIO_OUTPUT_INACTIVE);
if (err == 0) {
led_apply(led_state);
}
}

err = bt_enable(NULL);
if (err) {
LOG_ERR("bt enable failed: %d", err);
return err;
}

LOG_INF("bluetooth initialized");

err = bt_le_adv_start(BT_LE_ADV_CONN_FAST_1, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
if (err) {
LOG_ERR("advertising failed: %d", err);
return err;
}

LOG_INF("advertising");
start_blink();

for (;;) {
k_sleep(K_FOREVER);
}
}


  1. platformio.ini で PlatformIO プロジェクトを設定します。
[env:seeed-xiao-nrf54lm20a]
platform = https://github.com/Seeed-Studio/platform-seeedboards.git
framework = zephyr
board = seeed-xiao-nrf54lm20a
platform_packages =
platformio/toolchain-gccarmnoneeabi@~1.90201.0
monitor_speed = 115200


結果

  1. ペリフェラル用ファームウェアを 1 枚の XIAO ボードに、セントラル用ファームウェアを別のボードに書き込みます。

  2. 両方のボードをリセットします。接続が確立される前は、ペリフェラルの LED はアドバタイジングを示すために点滅し、セントラルの LED はスキャン中であることを示すために点滅します。

  3. セントラルがペリフェラルを検出すると、2 枚のボードは自動的に接続されます。接続が確立された後は、両方の LED の点滅が止まります。

  4. セントラルボード上の BOOT ボタンを押します。セントラルは GATT Write Characteristic を介してペリフェラルに 0 または 1 を書き込み、ペリフェラルはそれに応じて LED を更新します。

2 枚の XIAO ボード間の BLE LBS 通信

このサンプルを通して、BLE アドバタイジング、スキャン、自動接続、GATT サービスディスカバリ、そして一方の開発ボード上のボタンを使って別の開発ボード上の LED をリモート制御するという、基本的な通信プロセスを含む、完全な BLE セントラルおよびペリフェラルアプリケーションの構築方法を学ぶことができます。

まとめ

このサンプルでは、BLE アドバタイジング、スキャン、自動接続、GATT サービスディスカバリ、および GATT Write Characteristic を介したリモート LED 制御を含む、BLE セントラルおよびペリフェラルアプリケーションの構築方法を示しました。

技術サポート & 製品ディスカッション

当社の製品をお選びいただきありがとうございます。私たちは、製品をできるだけスムーズにご利用いただけるよう、さまざまなサポートを提供しています。お好みやニーズに応じてお選びいただける、複数のコミュニケーションチャネルをご用意しています。

Loading Comments...