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

XIAO nRF54LM20A Sense には、マルチシナリオのアプリケーションをサポートする豊富なオンボードセンサーが搭載されています。姿勢認識用の 6 軸センサー LSM6DS3TR-C、PDM デジタル出力と無指向性集音に対応し、インテリジェントボイスシナリオに適したデジタル MEMS マイク MSM261DGT006 を備えています。本記事では、XIAO nRF54LM20A の豊富なオンボード周辺機能に基づく開発および使用方法を紹介します。
- 本記事は PlatformIO ビルドシステムと Zephyr RTOS をベースに開発されています。これらの使用経験がない場合は、Getting Started With SeeedStudio XIAO nRF54LM20A を参照してください。
ハードウェアの準備
本記事は XIAO nRF54LM20A Sense をベースに開発されており、事前に関連ハードウェアを準備する必要があります。
IMU
LSM6DS3TR-C は、3 軸デジタル加速度センサーと 3 軸デジタルジャイロスコープを統合した 6 軸センサーで、STMicroelectronics が提供する iNEMO 慣性計測ユニット (IMU) に属します。XIAO nRF54LM20A Sense では、このセンサーは割り込みトリガによるデータ出力をサポートします。加速度のフルスケールレンジは ±2/±4/±8/±16 g、角速度レンジは ±125/±250/±500/±1000/±2000 dps を備え、持続的な低消費電力モードをサポートしているため、さまざまな動作検知シナリオに適しています。オンボードチップは I2C プロトコルを介してこのセンサーと通信し、データを取得します。
- LSM6DS3TR-C の詳細については、Product overview for LSM6DS3TR-C および LSM6DS3TR-C Datasheet を参照してください。
6 軸データの取得
- デバイスツリーファイル
app.overlayを編集し、LSM6DS3TR-C が使用するハードウェアピンをデバイスツリーにバインドします。IMU_SDA と IMU_SCL を i2c30 ノードにバインドし、XIAO nRF54LM20A Sense 上の P0.08 および P0.07 に対応させます。割り込みトリガピン IMU_INT1 を P0.06 にバインドします。
- XIAO nRF54LM20A のピン配置については、XIAO nRF54LM20A Sense Pin List をクリックして詳細を確認してください。
&pmic_i2c {
sda-gpios = <&gpio1 18 GPIO_ACTIVE_HIGH>;
scl-gpios = <&gpio1 17 GPIO_ACTIVE_HIGH>;
status = "okay";
};
&pmic {
regulators {
imu_vdd: LDO1 {
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
/*
* Do not enable LDO1 during regulator driver init: the
* nPM1300 sits on gpio-i2c, which may not be ready yet.
* main() enables this rail before deferred IMU init.
* Use /delete-property/ so this also overrides board
* revisions that define regulator-boot-on themselves.
*/
/delete-property/ regulator-boot-on;
};
};
};
&lsm6ds3tr_c {
zephyr,deferred-init;
};
- prj.conf ファイルを編集し、I2C と割り込みトリガの設定を有効にします。
CONFIG_STDOUT_CONSOLE=y
CONFIG_LOG=y
CONFIG_LOG_BACKEND_UART=y
CONFIG_LOG_BACKEND_SHOW_COLOR=n
CONFIG_LOG_DEFAULT_LEVEL=3
CONFIG_MAIN_STACK_SIZE=2048
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_CBPRINTF_FP_SUPPORT=y
CONFIG_CBPRINTF_COMPLETE=y
- 取得した 3 軸デジタル加速度データと 3 軸デジタルジャイロスコープデータを USB シリアルポート経由で出力するプログラムを書きます。
main.c
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/drivers/regulator.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(zephyr_imu, LOG_LEVEL_INF);
#define IMU_NODE DT_ALIAS(imu0)
/*
* nrf54lm20a needs power_en (fixed regulator on gpio1.12) and imu_vdd
* (PMIC NPM1300 LDO1) enabled before the IMU can be used.
* nrf54l15 has pdm_imu_pwr with regulator-boot-on; power is already on.
*/
#if defined(DT_N_NODELABEL_power_en)
static const struct device *const power_en_dev =
DEVICE_DT_GET(DT_NODELABEL(power_en));
#endif
#if defined(DT_N_NODELABEL_imu_vdd)
static const struct device *const imu_vdd_dev =
DEVICE_DT_GET(DT_NODELABEL(imu_vdd));
#endif
static int enable_imu_power(void)
{
#if defined(DT_N_NODELABEL_power_en) || defined(DT_N_NODELABEL_imu_vdd)
int ret;
#endif
#if defined(DT_N_NODELABEL_power_en)
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;
}
#endif
#if defined(DT_N_NODELABEL_imu_vdd)
if (!device_is_ready(imu_vdd_dev)) {
LOG_ERR("imu_vdd regulator is not ready");
return -ENODEV;
}
ret = regulator_enable(imu_vdd_dev);
if (ret < 0 && ret != -EALREADY) {
LOG_ERR("Failed to enable imu_vdd: %d", ret);
return ret;
}
#endif
#if defined(DT_N_NODELABEL_power_en) || defined(DT_N_NODELABEL_imu_vdd)
/* Wait for power rail to stabilize */
k_sleep(K_MSEC(20));
#endif
return 0;
}
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 trig_cnt;
trig_cnt++;
/* lsm6dsl accel */
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("accel x:%f m/s^2 y:%f m/s^2 z:%f m/s^2",
(double)out_ev(&x), (double)out_ev(&y), (double)out_ev(&z));
/* lsm6dsl gyro */
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:%f rad/s y:%f rad/s z:%f rad/s",
(double)out_ev(&x), (double)out_ev(&y), (double)out_ev(&z));
LOG_INF("trig_cnt:%d", trig_cnt);
}
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 type and channel");
return;
}
while (1) {
k_sleep(K_MSEC(1000));
}
}
#else
static void test_polling_mode(const struct device *dev)
{
if (set_sampling_freq(dev) != 0) {
return;
}
while (1) {
fetch_and_display(dev);
k_sleep(K_MSEC(1000));
}
}
#endif
int main(void)
{
const struct device *const dev = DEVICE_DT_GET(IMU_NODE);
int ret;
/* On nrf54lm20a, enable power_en + imu_vdd before accessing IMU.
* On nrf54l15, these nodes don't exist; function returns immediately.
*/
ret = enable_imu_power();
if (ret < 0) {
LOG_ERR("Failed to enable IMU power: %d", ret);
return 0;
}
/* On nrf54lm20a, IMU has zephyr,deferred-init; must init manually.
* On nrf54l15, device auto-inits at boot; device_is_ready() is true.
*/
if (!device_is_ready(dev)) {
ret = device_init(dev);
if (ret < 0 && ret != -EALREADY) {
LOG_ERR("Failed to initialize %s: %d", dev->name, ret);
return 0;
}
}
if (!device_is_ready(dev)) {
LOG_ERR("%s: device not ready.", dev->name);
return 0;
}
#ifdef CONFIG_LSM6DSL_TRIGGER
LOG_INF("Testing LSM6DSL sensor in trigger mode.");
test_trigger_mode(dev);
#else
LOG_INF("Testing LSM6DSL sensor 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 各軸周りの角速度を測定します。
- シリアルモニタでデータを確認する際は、ボーレートを 115200 に設定してください。
- PlatformIO IDE のシリアルモニタ用に、platformio.ini 設定ファイル内でもボーレートを 115200 に指定してください。
[env:seeed-xiao-nrf54lm20a]
platform = https://github.com/Seeed-Studio/platform-seeedboards.git
framework = zephyr
board = seeed-xiao-nrf54lm20a
monitor_speed = 115200

応用
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
使用手順
-
対応するプログラム imu_ocean-main.c の内容をコピーし、main.c に貼り付けます。
-
デバイスツリーファイル
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;
};
};
- 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 ウェイクアップ機能を実装するには、このルーチンをダウンロードしてください。
-
imu-click-main.c プログラムをダウンロードし、その内容で main.c を置き換えます。
-
デバイスツリーファイル
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;
};
};
};
- 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 アラームによってシステムがウェイクアップし、カウントを継続します。
-
rtc-main.c を main.c ファイルにコピーします。RTC 関数を使用してタイムスタンプを出力します。
-
デバイスツリー
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>;
};
- prj.conf ファイルを編集して、関連する RTC 設定を有効にします。
# 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 上で受信用スクリプトを実行し、音声ファイルを
./recordingsディレクトリに保存します。 - 送信が完了すると RGB-G LED は消灯します。
-
mic-main.c のプログラムを
main.cにコピーします。 -
デバイスツリーファイル
app.overlayを変更して、BLE ノードをバインドします。
/*
* XIAO nRF54LM20A BLE recorder devicetree overlay
*
* Logging uses the board's UART20 debug bridge at 115200 baud.
*/
/* Keep the board debug UART configuration explicit and reproducible. */
&uart20 {
current-speed = <115200>;
status = "okay";
};
/* Enable the PDM microphone interface. */
&pdm20 {
status = "okay";
};
/* Enable the external 8 MB SPI NOR flash used for PCM storage. */
&py25q64 {
status = "okay";
};
/ {
chosen {
zephyr,bt-hci = &bt_hci_controller;
};
};
/* dmic_vdd is the board-defined nPM1300 LDO1 3.3 V supply. The application
* enables it at runtime. power_en is defined by the board devicetree.
*/
&dmic_vdd {
/delete-property/ regulator-boot-on;
};
prj.confファイルを変更して、Bluetooth とマイクロフォン用の設定を有効にし、Bluetooth デバイス名を XIAO-MIC に設定します。
# ===== Audio / DMIC =====
CONFIG_AUDIO=y
CONFIG_AUDIO_DMIC=y
# ===== GPIO =====
CONFIG_GPIO=y
# I2C and PMIC support for the nPM1300 microphone supply
CONFIG_I2C=y
CONFIG_MFD=y
CONFIG_REGULATOR=y
# Logging over the board UART debug bridge
CONFIG_LOG=y
CONFIG_LOG_BACKEND_UART=y
# Serial and console
CONFIG_SERIAL=y
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y
# Disable the Arm MPU for compatibility with early LM20A silicon.
CONFIG_ARM_MPU=n
# ===== 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
CONFIG_BT_LOG_LEVEL_ERR=y
CONFIG_BT_AUTO_PHY_UPDATE=n
CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS=n
CONFIG_BT_CTLR_CONN_PARAM_REQ=n
CONFIG_BT_DATA_LEN_UPDATE=n
CONFIG_BT_BUF_ACL_TX_SIZE=251
CONFIG_BT_BUF_ACL_TX_COUNT=32
CONFIG_BT_BUF_EVT_RX_COUNT=33
CONFIG_BT_BUF_ACL_RX_SIZE=251
CONFIG_BT_L2CAP_TX_MTU=247
CONFIG_BT_L2CAP_TX_BUF_COUNT=24
CONFIG_BT_L2CAP_TX_FRAG_COUNT=12
CONFIG_BT_ATT_TX_COUNT=24
CONFIG_BT_CONN_TX_MAX=32
CONFIG_BT_ZEPHYR_NUS=y
CONFIG_BT_ZEPHYR_NUS_DEFAULT_INSTANCE=y
# Memory
CONFIG_HEAP_MEM_POOL_SIZE=16384
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096
# External SPI NOR flash (8 MB PY25Q64HA)
CONFIG_SPI=y
CONFIG_SPI_NOR=y
CONFIG_FLASH=y
CONFIG_FLASH_PAGE_LAYOUT=y
# Assertions
CONFIG_ASSERT=y
CONFIG_BT_CTLR_ASSERT_OPTIMIZE_FOR_SIZE=n
結果
プログラムをコンパイルして書き込み、その後 Windows コンピュータ上でスクリプトを使用して Bluetooth 経由で録音音声を受信します。
- Python スクリプトを実行します
実行前に必要な依存ライブラリをインストールします。
pip install bleak
Python スクリプトファイルをコピーします。
ble_recorder_receiver.py
#!/usr/bin/env python3
"""
BLE recorder receiver for the XIAO nRF54LM20A.
Continuously scans for "XIAO-MIC", connects, subscribes to NUS notifications,
saves WAV data, and reconnects automatically. Each RIFF header starts a new file.
Usage: python ble_recorder_receiver.py
Dependency: pip install bleak
Firmware operation:
Press BOOT once to begin recording (green LED on).
Press BOOT again, or wait 10 seconds, to begin transfer (green LED blinking).
The green LED turns off when the transfer is complete.
"""
import asyncio
import os
import sys
from datetime import datetime
from bleak import BleakClient, BleakError, BleakScanner
NUS_SERVICE_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e"
NUS_TX_CHAR_UUID = "6e400003-b5a3-f393-e0a9-e50e24dcca9e" # Device-to-host notifications
DEVICE_NAME = "XIAO-MIC"
OUTPUT_DIR = "./recordings"
SCAN_TIMEOUT = 10.0
RECONNECT_DELAY = 2.0
RIFF_MAGIC = b"RIFF"
WAV_HEADER_SIZE = 44
WAV_DATA_SIZE_OFFSET = 40
class RecordingWriter:
"""Write NUS notification data to WAV files, splitting on RIFF headers."""
def __init__(self):
self.path = None
self.file = None
self.total = 0
self.expected_total = None
self.header = bytearray()
def _open(self):
os.makedirs(OUTPUT_DIR, exist_ok=True)
stamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
self.path = os.path.join(OUTPUT_DIR, f"recording_{stamp}.wav")
self.file = open(self.path, "wb")
self.total = 0
self.expected_total = None
self.header.clear()
print(f"\nNew recording -> {self.path}")
def on_notification(self, _sender, data):
# Finalize the current file before starting a new RIFF stream.
if data[:4] == RIFF_MAGIC and self.total > 0:
self.close()
if self.file is None:
self._open()
self.file.write(data)
self.total += len(data)
if len(self.header) < WAV_HEADER_SIZE:
needed = WAV_HEADER_SIZE - len(self.header)
self.header.extend(data[:needed])
if len(self.header) == WAV_HEADER_SIZE and self.header[:4] == RIFF_MAGIC:
data_size = int.from_bytes(
self.header[WAV_DATA_SIZE_OFFSET:WAV_HEADER_SIZE],
byteorder="little",
)
self.expected_total = WAV_HEADER_SIZE + data_size
sys.stdout.write(f"\rReceiving: {self.total} bytes")
sys.stdout.flush()
if self.expected_total is not None and self.total >= self.expected_total:
self.close()
def close(self):
if self.file:
self.file.flush()
self.file.close()
self.file = None
if self.total > 44:
tag = "WAV, playable"
elif self.total > 0:
tag = "incomplete (header only)"
else:
tag = "empty"
print(f"\nSaved: {self.path} ({self.total} bytes) [{tag}]")
self.total = 0
self.expected_total = None
self.header.clear()
async def connect_and_receive(writer):
while True:
print(f"\nScanning for '{DEVICE_NAME}'...")
device = await BleakScanner.find_device_by_name(DEVICE_NAME,
timeout=SCAN_TIMEOUT)
if device is None:
print(f"Not found, retrying in {RECONNECT_DELAY:.0f}s"
" (check: board powered on, Bluetooth enabled)")
await asyncio.sleep(RECONNECT_DELAY)
continue
print(f"Found: {device.name} ({device.address}), connecting...")
disconnect_event = asyncio.Event()
def on_disconnect(_c):
disconnect_event.set()
try:
async with BleakClient(
device.address,
disconnected_callback=on_disconnect,
timeout=30.0,
services=[NUS_SERVICE_UUID],
) as client:
print("Connected")
await client.start_notify(NUS_TX_CHAR_UUID,
writer.on_notification)
print("Subscribed to NUS, waiting for audio data...")
print("(Press BOOT on the board to record; press again or wait 10s to transfer)")
await disconnect_event.wait()
except BleakError as e:
print(f"\nBLE error: {e}")
except asyncio.TimeoutError:
print("\nConnection timed out")
writer.close()
print(f"Disconnected, reconnecting in {RECONNECT_DELAY:.0f}s...")
async def main():
print(f"=== BLE receiver '{DEVICE_NAME}', Ctrl+C to quit ===\n")
writer = RecordingWriter()
try:
await connect_and_receive(writer)
except KeyboardInterrupt:
pass
finally:
writer.close()
print("Stopped.")
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
print("\nStopped.")
スクリプトの実行コマンド:
python ble_recorder_receiver.py
BLE UUID はすでに Python プログラム内で設定されているため、スクリプトを実行すると自動的に接続されます。
- 結果を確認する
- BOOT キーを押して録音を開始します。緑色の RGB LED が点灯している場合は、録音中であることを示します。マイクに向かって大きな声で話し、再度 BOOT キーを押して録音を停止します。緑色の RGB LED が点滅している場合は、オーディオファイルを送信中であることを意味します。

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

- 受信したオーディオファイルとそのバイトサイズが表示されます。

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

