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

XIAO nRF54LM20A Sense の内蔵センサーの使用方法


XIAO nRF54LM20A Sense には、マルチシナリオのアプリケーションをサポートする豊富なオンボードセンサーが搭載されています。姿勢認識用の 6 軸センサー LSM6DS3TR-C、PDM デジタル出力と無指向性集音に対応したデジタル MEMS マイク MSM261DGT006 を備えており、インテリジェントボイスのシナリオに適しています。本記事では、XIAO nRF54LM20A の豊富なオンボード周辺機能に基づく開発および使用方法を紹介します。

ヒント

ハードウェアの準備

本記事は XIAO nRF54LM20A Sense をベースに開発されており、事前に関連ハードウェアを用意する必要があります。

Seeed Studio XIAO nRF54LM20A SenseSeeed Studio XIAO 用 6x10 RGB WS2812 マトリクス

IMU

LSM6DS3TR-C は、3 軸デジタル加速度センサーと 3 軸デジタルジャイロスコープを統合した 6 軸センサーで、STMicroelectronics が提供する iNEMO 慣性計測ユニット (IMU) に属します。XIAO nRF54LM20A Sense では、このセンサーは割り込みトリガによるデータ出力をサポートしています。加速度のフルスケール範囲は ±2/±4/±8/±16 g、角速度の範囲は ±125/±250/±500/±1000/±2000 dps で、持続的な低消費電力モードをサポートしており、さまざまなモーション検出シナリオに適しています。オンボードチップは I2C プロトコルを介してこのセンサーと通信し、データを取得します。

ヒント

6 軸データの取得

  1. デバイスツリーファイル app.overlay を編集し、LSM6DS3TR-C が使用するハードウェアピンをデバイスツリーにバインドします。IMU_SDA と IMU_SCL を i2c30 ノードにバインドし、XIAO nRF54LM20A Sense 上の P0.08 と P0.07 に対応させます。割り込みトリガピン IMU_INT1 を P0.06 にバインドします。
ヒント
/* Configure I2C30 for LSM6DS3TR-C */
&i2c30 {
pinctrl-0 = <&i2c30_default>;
pinctrl-1 = <&i2c30_sleep>;
pinctrl-names = "default", "sleep";
status = "okay";
clock-frequency = <I2C_BITRATE_STANDARD>;

lsm6ds3tr_c: lsm6ds3tr-c@6a {
compatible = "st,lsm6dsl";
reg = <0x6a>;
irq-gpios = <&gpio0 6 GPIO_ACTIVE_HIGH>;
status = "okay";
};
};

/* Pin control configuration for I2C30 */
&pinctrl {
i2c30_default: i2c30_default {
group1 {
psels = <NRF_PSEL(TWIM_SDA, 0, 8)>,
<NRF_PSEL(TWIM_SCL, 0, 7)>;
};
};

i2c30_sleep: i2c30_sleep {
group1 {
psels = <NRF_PSEL(TWIM_SDA, 0, 8)>,
<NRF_PSEL(TWIM_SCL, 0, 7)>;
low-power-enable;
};
};
};
  1. prj.conf ファイルを編集し、I2C と割り込みトリガの設定を有効にします。
CONFIG_STDOUT_CONSOLE=y
CONFIG_LOG=y
CONFIG_LOG_BACKEND_UART=y
CONFIG_LOG_DEFAULT_LEVEL=3
CONFIG_MAIN_STACK_SIZE=4096
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
CONFIG_GPIO=y
CONFIG_I2C=y
CONFIG_MFD=y
CONFIG_REGULATOR=y
CONFIG_SENSOR=y
CONFIG_LSM6DSL=y
CONFIG_SPI=y
CONFIG_LED_STRIP=y
CONFIG_WS2812_STRIP_SPI=y
CONFIG_CBPRINTF_FP_SUPPORT=y
CONFIG_CBPRINTF_COMPLETE=y
CONFIG_FAULT_DUMP=2
CONFIG_LOG_MODE_IMMEDIATE=y
  1. 取得した 3 軸デジタル加速度データと 3 軸デジタルジャイロスコープデータを USB シリアルポート経由で出力するプログラムを作成します。
main.c
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>
#include <stdio.h>

LOG_MODULE_REGISTER(lsm6ds3tr_c_imu, LOG_LEVEL_INF);

/* Use the LSM6DS3TR-C device defined in device tree */
#define IMU_NODE DT_NODELABEL(lsm6ds3tr_c)

static inline float out_ev(struct sensor_value *val)
{
return (val->val1 + (float)val->val2 / 1000000);
}

static void fetch_and_display(const struct device *dev)
{
struct sensor_value x, y, z;
static int sample_count;

sample_count++;

/* Fetch and display accelerometer data */
sensor_sample_fetch_chan(dev, SENSOR_CHAN_ACCEL_XYZ);
sensor_channel_get(dev, SENSOR_CHAN_ACCEL_X, &x);
sensor_channel_get(dev, SENSOR_CHAN_ACCEL_Y, &y);
sensor_channel_get(dev, SENSOR_CHAN_ACCEL_Z, &z);

LOG_INF("Sample #%d", sample_count);
LOG_INF("Accel - X: %.6f m/s^2, Y: %.6f m/s^2, Z: %.6f m/s^2",
(double)out_ev(&x), (double)out_ev(&y), (double)out_ev(&z));

/* Fetch and display gyroscope data */
sensor_sample_fetch_chan(dev, SENSOR_CHAN_GYRO_XYZ);
sensor_channel_get(dev, SENSOR_CHAN_GYRO_X, &x);
sensor_channel_get(dev, SENSOR_CHAN_GYRO_Y, &y);
sensor_channel_get(dev, SENSOR_CHAN_GYRO_Z, &z);

LOG_INF("Gyro - X: %.6f rad/s, Y: %.6f rad/s, Z: %.6f rad/s",
(double)out_ev(&x), (double)out_ev(&y), (double)out_ev(&z));
}

static int set_sampling_freq(const struct device *dev)
{
int ret = 0;
struct sensor_value odr_attr;

/* set accel/gyro sampling frequency to 12.5 Hz */
odr_attr.val1 = 12;
odr_attr.val2 = 500000;

ret = sensor_attr_set(dev, SENSOR_CHAN_ACCEL_XYZ,
SENSOR_ATTR_SAMPLING_FREQUENCY, &odr_attr);
if (ret != 0)
{
LOG_ERR("Cannot set sampling frequency for accelerometer.");
return ret;
}

ret = sensor_attr_set(dev, SENSOR_CHAN_GYRO_XYZ,
SENSOR_ATTR_SAMPLING_FREQUENCY, &odr_attr);
if (ret != 0)
{
LOG_ERR("Cannot set sampling frequency for gyro.");
return ret;
}

return 0;
}

#ifdef CONFIG_LSM6DSL_TRIGGER
static void trigger_handler(const struct device *dev,
const struct sensor_trigger *trig)
{
fetch_and_display(dev);
}

static void test_trigger_mode(const struct device *dev)
{
struct sensor_trigger trig;

if (set_sampling_freq(dev) != 0)
{
return;
}

trig.type = SENSOR_TRIG_DATA_READY;
trig.chan = SENSOR_CHAN_ACCEL_XYZ;

if (sensor_trigger_set(dev, &trig, trigger_handler) != 0)
{
LOG_ERR("Could not set sensor trigger");
return;
}

LOG_INF("LSM6DS3TR-C in trigger mode - waiting for data...");

/* Keep the application running */
while (1)
{
k_sleep(K_MSEC(1000));
}
}

#else
static void test_polling_mode(const struct device *dev)
{
if (set_sampling_freq(dev) != 0)
{
return;
}

LOG_INF("LSM6DS3TR-C in polling mode - sampling at 12.5 Hz");

while (1)
{
fetch_and_display(dev);
k_sleep(K_MSEC(80)); /* ~12.5 Hz sampling rate */
}
}
#endif

int main(void)
{
const struct device *const dev = DEVICE_DT_GET(IMU_NODE);
int ret;

LOG_INF("LSM6DS3TR-C IMU Data Acquisition System");
LOG_INF("========================================");

/* Check if device pointer is valid */
if (!device_is_ready(dev))
{
LOG_INF("IMU device %s not ready, attempting to initialize...", dev->name);
ret = device_init(dev);
if (ret < 0 && ret != -EALREADY)
{
LOG_ERR("Failed to initialize %s: %d", dev->name, ret);
return 1;
}
}

/* Final check - ensure device is ready */
if (!device_is_ready(dev))
{
LOG_ERR("%s: device not ready after init", dev->name);
return 1;
}

LOG_INF("IMU device initialized successfully");

#ifdef CONFIG_LSM6DSL_TRIGGER
LOG_INF("Running in interrupt-triggered mode");
test_trigger_mode(dev);
#else
LOG_INF("Running in polling mode");
test_polling_mode(dev);
#endif

return 0;
}

ヒント

IMU の性能を直接検証したい場合は、Platform-seeedboards リポジトリをクローンし、examples ディレクトリ内の zephyr-imu サンプルを見つけて、コンパイルして書き込むことでテストを開始できます。

結果

ファームウェアを書き込んだ後、PC 上でシリアルポートアシスタントを開いてデータを確認できます。トリガー周波数は 12.5 Hz、間隔は 80 ミリ秒です。

  • 3 軸デジタル加速度センサ:X、Y、Z 各軸方向の加速度を測定します。
  • 3 軸デジタルジャイロスコープ:X、Y、Z 各軸周りの角速度を測定します。

応用

IMU は 3 軸加速度データをフュージョンして、姿勢認識のためのピッチ、ヨー、ロールの姿勢角を算出できます。また、対応するコントローラと連携してモーションコントロールを実現したり、姿勢トリガによるウェイクアップなどの低消費電力シナリオに適用することもできます。

Electronic Ocean

これは XIAO nRF54LM20A Sense のオンボード IMU をベースにしたサンプルです。姿勢データを収集し、加速度情報をフュージョンして、動作状態を RGB ライトパネル上にマッピングし、視覚的な海のリズム効果を実現します。

  • 傾きによる水位制御 — 左右のロール傾きで水位の高さを調整
  • 波のアニメーション — 3 層の周波数を重ね合わせた波面、2D 波の伝播と端での反射効果
  • 流体慣性 — 慣性を持つ水面;急激な傾きでオーバーシュートが発生し、その後の揺り戻しが生じます
  • 反転検出 — ボードを反転させると表示が自動的にミラー反転
  • ダイナミックカラー — 各列ごとにランダムなグラデーションの海の色調に切り替え

さらに、main.c 内のマクロ定義を通じて、ボードの RGB 配列構成を変更することもできます。

#define COLS 10          // Number of matrix columns
#define ROWS 6 // Number of matrix rows
#define BRIGHTNESS 5 // Overall brightness (0-100)
#define WATER_CENTER 3.5f // Water level when placed horizontally
#define WATER_MIN 0.5f // Minimum water level
#define WATER_MAX 6.5f // Maximum water level
使用手順
  1. 対応するプログラム imu_ocean-main.c の内容をコピーし、main.c に貼り付けます。

  2. デバイスツリーファイル app.overlay を修正します。

&lsm6ds3tr_c {
zephyr,deferred-init;
};

/*
* The board DTS lists PMIC I2C on gpio1.15/16, but the actual XIAO
* nRF54LM20A Sense hardware uses gpio1.18 (SDA) and gpio1.17 (SCL).
* Override here to match the working reference example.
*/
&pmic_i2c {
sda-gpios = <&gpio1 18 GPIO_ACTIVE_HIGH>;
scl-gpios = <&gpio1 17 GPIO_ACTIVE_HIGH>;
};

/*
* Give LDO1 the label "imu_vdd" so main() can call regulator_enable().
* Voltage is 3.3 V as used by the reference example.
*/
&pmic {
regulators {
imu_vdd: LDO1 {
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
regulator-boot-on;
};
};
};

/* WS2812 LED strip on SPI24 (spi21/22 conflict with uart21/i2c22) */
&pinctrl {
spi24_ws2812_default: spi24_ws2812_default {
group1 {
psels = <NRF_PSEL(SPIM_MOSI, 1, 0)>,
<NRF_PSEL(SPIM_SCK, 1, 1)>;
};
};

spi24_ws2812_sleep: spi24_ws2812_sleep {
group1 {
psels = <NRF_PSEL(SPIM_MOSI, 1, 0)>,
<NRF_PSEL(SPIM_SCK, 1, 1)>;
low-power-enable;
};
};
};


&spi24 {
status = "okay";
pinctrl-0 = <&spi24_ws2812_default>;
pinctrl-1 = <&spi24_ws2812_sleep>;
pinctrl-names = "default", "sleep";

led_strip: ws2812@0 {
compatible = "worldsemi,ws2812-spi";
reg = <0>;
/*
* 8 MHz SPI: each clock = 125 ns, 8 clocks = 1 µs per WS2812 bit.
* 0xF8 = 11111000: T1H=625 ns T1L=375 ns
* 0xC0 = 11000000: T0H=250 ns T0L=750 ns
*/
spi-max-frequency = <8000000>;
spi-one-frame = <0xF8>;
spi-zero-frame = <0xC0>;
chain-length = <60>;
color-mapping = <1 0 2>;
reset-delay = <250>;
};
};

/ {
aliases {
led-strip = &led_strip;
};
};
  1. IMU 使用に関連する設定を有効にします
CONFIG_STDOUT_CONSOLE=y
CONFIG_LOG=y
CONFIG_LOG_BACKEND_UART=y
CONFIG_LOG_DEFAULT_LEVEL=3
CONFIG_MAIN_STACK_SIZE=4096
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
CONFIG_GPIO=y
CONFIG_I2C=y
CONFIG_MFD=y
CONFIG_REGULATOR=y
CONFIG_SENSOR=y
CONFIG_LSM6DSL=y
CONFIG_SPI=y
CONFIG_LED_STRIP=y
CONFIG_WS2812_STRIP_SPI=y
CONFIG_CBPRINTF_FP_SUPPORT=y
CONFIG_CBPRINTF_COMPLETE=y
CONFIG_FAULT_DUMP=2
CONFIG_LOG_MODE_IMMEDIATE=y
  • デバイスを振ると、海の波のビジュアルエフェクトがトリガーされます。

  • 同時に、シリアルポートからも対応する IMU データと現在の波の水位高さが出力されます。

IMU ウェイクアップ

このルーチンでは、電源投入後に RGB のグリーンチャネルが点灯して消灯し、その後システムは超低消費電力スリープモードに入ります。ボードがタップを検出すると、割り込みによって XIAO nRF54LM20A Sense がウェイクアップされます。タップイベントは記録され、シリアルポートを通じて出力されます。

IMU ウェイクアップ機能を実装するには、このルーチンをダウンロードしてください。

  1. imu-click-main.c プログラムをダウンロードし、その内容で main.c を置き換えます。

  2. デバイスツリーファイル app.overlay を修正し、必要なノード設定を追加します。

/*
* Disable PWM20 and PWM LEDs to release P1.22/23/24 as GPIO.
* The board DTS assigns these pins to PWM_OUT0/1/2 via pinctrl,
* which prevents gpio-leds from controlling them.
*/
&pwm20 {
status = "disabled";
};

&green_led {
gpios = <&gpio1 24 GPIO_ACTIVE_LOW>;
};

/* PMIC I2C pin configuration for NPM1300 power management */
&pmic_i2c {
sda-gpios = <&gpio1 18 GPIO_ACTIVE_HIGH>;
scl-gpios = <&gpio1 17 GPIO_ACTIVE_HIGH>;
status = "okay";
};

/* IMU power rail via PMIC LDO1 at 3.3V */
&pmic {
regulators {
imu_vdd: LDO1 {
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
regulator-boot-on;
};
};
};

/* Configure I2C30 for LSM6DS3TR-C */
&i2c30 {
pinctrl-0 = <&i2c30_default>;
pinctrl-1 = <&i2c30_sleep>;
pinctrl-names = "default", "sleep";
status = "okay";
clock-frequency = <I2C_BITRATE_STANDARD>;

lsm6ds3tr_c: lsm6ds3tr-c@6a {
compatible = "st,lsm6dsl";
reg = <0x6a>;
irq-gpios = <&gpio0 6 GPIO_ACTIVE_HIGH>;
status = "okay";
zephyr,deferred-init;
};
};

/* Pin control configuration for I2C30 */
&pinctrl {
i2c30_default: i2c30_default {
group1 {
psels = <NRF_PSEL(TWIM_SDA, 0, 8)>,
<NRF_PSEL(TWIM_SCL, 0, 7)>;
};
};

i2c30_sleep: i2c30_sleep {
group1 {
psels = <NRF_PSEL(TWIM_SDA, 0, 8)>,
<NRF_PSEL(TWIM_SCL, 0, 7)>;
low-power-enable;
};
};
};
  1. prj.conf で関連する IMU 設定を有効にします
CONFIG_STDOUT_CONSOLE=y
CONFIG_LOG=y
CONFIG_LOG_BACKEND_UART=y
CONFIG_LOG_DEFAULT_LEVEL=3
CONFIG_MAIN_STACK_SIZE=4096
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
CONFIG_GPIO=y
CONFIG_I2C=y
CONFIG_MFD=y
CONFIG_REGULATOR=y
CONFIG_SENSOR=y
CONFIG_LSM6DSL=y
CONFIG_LSM6DSL_TRIGGER_GLOBAL_THREAD=y
CONFIG_SPI=y
CONFIG_LED_STRIP=y
CONFIG_WS2812_STRIP_SPI=y
CONFIG_CBPRINTF_FP_SUPPORT=y
CONFIG_CBPRINTF_COMPLETE=y
CONFIG_FAULT_DUMP=2
CONFIG_LOG_MODE_IMMEDIATE=y

  • 書き込みと電源投入後、RGB-G LED が短時間点滅します。ボード上の任意の場所をタップすると RGB-G LED が点灯します。
  • 同時に、タップイベント情報もシリアルポート経由で出力されます。

ヒント

検知位置はあくまで参考です。正確なタップ位置の認識は、IMU フュージョン制御アルゴリズムに依存します。

RTC

XIAO nRF54LM20A Sense に採用されているチップには GRTC ハードウェアリソースが内蔵されており、追加の RTC モジュールなしで RTC 機能を実現できます。

RTC はタイムスタンプカウントをサポートしており、電源断後も動作時間を記録できるため、ログ記録や時間追跡に役立ちます。

このセクションでは、XIAO nRF54LM20A Sense 上で実装されたサンプルプログラムを紹介します。電源投入後、RTC を介してコンパイル時刻からのタイムスタンプを取得し、1 秒ごとにデータを出力します。System OFF モードに入った後は、RTC アラームによってシステムがウェイクアップされ、カウントを継続します。

  1. rtc-main.c を main.c ファイルにコピーします。RTC 機能を使用してタイムスタンプを出力します。

  2. デバイスツリー app.overlay を修正して、RTC ノードを有効にします。

/ {
cpuapp_sram@2007ec00 {
compatible = "zephyr,memory-region", "mmio-sram";
reg = <0x2007ec00 DT_SIZE_K(4)>;
zephyr,memory-region = "RetainedMem";
status = "okay";

retainedmem0: retainedmem {
compatible = "zephyr,retained-ram";
status = "okay";
};
};

aliases {
retainedmemdevice = &retainedmem0;
};
};

&cpuapp_sram {
/* Shrink SRAM to avoid overlap with retained memory region:
* 511 - 4 = 507 KB = 0x7EC00
*/
reg = <0x20000000 DT_SIZE_K(507)>;
ranges = <0x0 0x20000000 0x7ec00>;
};
  1. 関連する RTC 設定を有効にするために prj.conf ファイルを編集します。
# Console and serial
CONFIG_SERIAL=y
CONFIG_CONSOLE=y
CONFIG_PRINTK=y

# Power management and System OFF
CONFIG_PM=y
CONFIG_PM_DEVICE=y
CONFIG_POWEROFF=y

# Hardware info (reset cause detection)
CONFIG_HWINFO=y

# Retained memory (survives System OFF)
CONFIG_RETAINED_MEM=y

# CRC for retained data validation
CONFIG_CRC=y

# Newlib C library (required for sscanf, strcmp etc.)
# Note: mktime() and gmtime() are NOT used — custom tm_to_unix()
# and unix_to_tm() avoid newlib's TZ environment dependency.
CONFIG_NEWLIB_LIBC=y

結果

  • プログラムはコンパイルおよび書き込み時点からカウントを開始します。シリアルポートツールを開いて動作状況を確認すると、期待されるすべての機能が実装されていることが分かります。

MIC

XIAO nRF54LM20A Sense には、音声入力用に MSM261DGT006 デジタル MEMS マイクが搭載されています。PDM インターフェースを介して直接接続され、ADC を必要としません。ウェアラブルデバイス、スマートデバイス、音声認識、音声録音など、音響センシング機能を必要とするアプリケーションシナリオに適しています。

ヒント

XIAO nRF54LM20A シリーズの中で、マイクを搭載しているのは XIAO nRF54M20A Sense のみであり、開発ボードの左下隅に配置されています。

音声録音と BLE アップロード

このセクションでは、音声サンプルを通してマイク機能をデモします。具体的な手順は次のとおりです。

  • BOOT ボタンを押すと RGB-G LED が点灯し、録音を開始します。もう一度押すと録音が停止します(最大 10 秒)。
  • 録音後、音声ファイルは Bluetooth を介してホストコンピュータに送信されます。送信中は RGB-G LED が点滅します。
  • Windows 上で受信スクリプトを実行し、音声ファイルをデスクトップに保存します。
  • 送信が完了すると RGB-G LED は消灯します。
  1. mic-main.c からプログラムをコピーして main.c に貼り付けます。

  2. デバイスツリーファイル app.overlay を修正して、BLE ノードをバインドします。


dmic_dev: &pdm20 {
status = "okay";
};

/* Disable Nordic SoftDevice Controller (not available in mainline Zephyr) */
&bt_hci_sdc {
status = "disabled";
};

/* Enable Zephyr native BLE controller (LL SW Split) */
&bt_hci_controller {
status = "okay";
};

&pwm20 {
status = "disabled";
};

&pmic_i2c {
sda-gpios = <&gpio1 18 GPIO_ACTIVE_HIGH>;
scl-gpios = <&gpio1 17 GPIO_ACTIVE_HIGH>;
status = "okay";
};

&pmic {
regulators {
dmic_vdd: LDO1 {
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
regulator-boot-on;
};
};
};

&uart20 {
current-speed = <921600>;
};

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

leds {
compatible = "gpio-leds";
led2: led_2 {
gpios = <&gpio1 24 GPIO_ACTIVE_LOW>;
};
};
};
  1. shturl.c ファイルを修正して、Bluetooth とマイクの設定を有効にし、Bluetooth デバイス名を XIAO MIC に設定します。
# Audio / DMIC
CONFIG_AUDIO=y
CONFIG_AUDIO_DMIC=y

# GPIO
CONFIG_GPIO=y

# I2C / PMIC
CONFIG_I2C=y
CONFIG_MFD=y
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-MIC"
CONFIG_BT_DEVICE_APPEARANCE=833
CONFIG_BT_MAX_CONN=1
CONFIG_BT_MAX_PAIRED=1

# Disable auto-procedures to avoid LL Procedure Collision (reason 35)
# on nRF54L with Zephyr native BLE controller
CONFIG_BT_AUTO_PHY_UPDATE=n
CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS=n
CONFIG_BT_CTLR_CONN_PARAM_REQ=n

# Disable data length auto-update (can also cause LL races)
CONFIG_BT_DATA_LEN_UPDATE=n

# BLE buffer tuning for NUS notifications (244-byte chunks at MTU 247)
CONFIG_BT_BUF_ACL_TX_SIZE=251
CONFIG_BT_BUF_ACL_TX_COUNT=10
CONFIG_BT_BUF_EVT_RX_COUNT=15
CONFIG_BT_BUF_ACL_RX_SIZE=251
CONFIG_BT_L2CAP_TX_MTU=247
CONFIG_BT_L2CAP_TX_BUF_COUNT=10
CONFIG_BT_L2CAP_TX_FRAG_COUNT=6
CONFIG_BT_ATT_TX_COUNT=10
CONFIG_BT_CONN_TX_MAX=10

# BLE NUS
CONFIG_BT_ZEPHYR_NUS=y
CONFIG_BT_ZEPHYR_NUS_DEFAULT_INSTANCE=y

# Memory
CONFIG_HEAP_MEM_POOL_SIZE=8192

# System workqueue stack
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048

# Assert level
CONFIG_ASSERT=y

結果

プログラムをコンパイルして書き込み、その後 Windows コンピュータ上でスクリプトを使用して Bluetooth 経由で録音された音声を受信します。

  1. Python スクリプトを実行します

実行前に必要な依存ライブラリをインストールします。

pip install bleak 

Python スクリプトファイルをコピーします。

ble_recorder_receiver.py
"""
BLE Audio Receiver for XIAO nRF54LM20A BLE Audio Recorder

Connects to "XIAO-MIC" via BLE, subscribes to Nordic UART Service (NUS)
notifications, receives WAV audio data, and saves it to a file.

Requirements: pip install bleak

Usage: python ble_recorder_receiver.py
"""

import asyncio
import sys
import os
from datetime import datetime

from bleak import BleakScanner, BleakClient, BleakError

# Nordic UART Service (NUS) UUIDs
NUS_SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
NUS_TX_CHAR_UUID = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" # Notify (device -> host)

DEVICE_NAME = "XIAO-MIC"
OUTPUT_DIR = "./recordings"


def make_output_path():
os.makedirs(OUTPUT_DIR, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
return os.path.join(OUTPUT_DIR, f"recording_{timestamp}.wav")


async def main():
output_path = make_output_path()
total_bytes = 0
transfer_complete = asyncio.Event()
connected = False

def notification_handler(sender, data):
nonlocal total_bytes
with open(output_path, "ab") as f:
f.write(data)
total_bytes += len(data)
sys.stdout.write(f"\rReceived: {total_bytes} bytes")
sys.stdout.flush()

def disconnected_callback(client):
nonlocal connected
connected = False
print("\nDevice disconnected")
transfer_complete.set()

client = None
try:
# Step 1: scan with active scanning (find_device_by_name does active scan)
print(f"Scanning for '{DEVICE_NAME}'...")
device = await BleakScanner.find_device_by_name(
DEVICE_NAME, timeout=10.0,
)

if device is None:
print(f"Device '{DEVICE_NAME}' not found. Check:")
print(" 1. XIAO is powered on")
print(" 2. PC Bluetooth is enabled")
sys.exit(1)

print(f"Found: {device.name} ({device.address})")

# Step 2: connect with service UUID filtering
# By specifying the NUS service UUID, we help Windows discover only what we need
print("Connecting (this may take up to 30s on Windows)...")

client = BleakClient(
device.address,
disconnected_callback=disconnected_callback,
timeout=30.0,
services=[NUS_SERVICE_UUID],
)
await client.connect()
connected = True
print("Connected")

# Step 3: subscribe to notifications
await client.start_notify(NUS_TX_CHAR_UUID, notification_handler)
print("Subscribed to NUS TX notifications")
print(f"Saving to: {output_path}")
print()
print("Waiting for audio data... Press Ctrl+C to stop.")
print("On the XIAO: press BOOT button once to start recording,")
print("press again (or wait 10s) to stop and transfer.\n")

try:
await asyncio.wait_for(
transfer_complete.wait(),
timeout=600.0,
)
except asyncio.TimeoutError:
print("\nTimeout: no activity for 10 minutes")
except KeyboardInterrupt:
print("\nStopped by user")

except (BleakError, asyncio.TimeoutError) as e:
print(f"\nBLE error: {e}")
print()
print("Windows BLE workarounds:")
print(" 1. Windows Settings > Bluetooth & devices > Devices")
print(" Remove 'XIAO-MIC' if listed")
print(" 2. Toggle Bluetooth OFF then ON")
print(" 3. Reset XIAO board (replug USB)")
print(" 4. Reboot PC if all else fails")
sys.exit(1)
finally:
if client and connected:
try:
await client.stop_notify(NUS_TX_CHAR_UUID)
await client.disconnect()
except Exception:
pass

file_size = os.path.getsize(output_path) if os.path.exists(output_path) else 0
print(f"\n{'='*50}")
print(f"Saved: {output_path}")
print(f"File size: {file_size} bytes")
if file_size > 44:
print("Valid WAV file, ready to play")
elif file_size > 0:
print("File may be incomplete (header only)")
else:
print("No data received")
print(f"{'='*50}")


if __name__ == "__main__":
asyncio.run(main())

スクリプトの実行コマンド:

python ble_recorder_receiver.py
ヒント

BLE UUID はすでに Python プログラム内で設定されているため、スクリプトを実行すると自動的に接続されます。

  1. 結果を確認する
  • BOOT キーを押して録音を開始します。緑色の RGB LED が点灯している間は録音中であることを示します。マイクに向かって大きな声で話し、その後もう一度 BOOT キーを押して録音を停止します。緑色の RGB LED が点滅している場合は、音声ファイルを送信中であることを意味します。

  • シリアルポートを開くとログが出力されます。ボーレートを 921600 に設定してください。

  • 受信した音声ファイルとそのバイトサイズが表示されます。

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

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

Loading Comments...