XIAO nRF54LM20A Sense 板载传感器的使用

XIAO nRF54LM20A Sense 配备了丰富的板载传感器,可支持多场景应用。它集成了用于姿态识别的 LSM6DS3TR-C 六轴传感器,以及支持 PDM 数字输出和全向拾音的 MSM261DGT006 数字 MEMS 麦克风,适用于智能语音场景。本文将介绍基于 XIAO nRF54LM20A 丰富板载外设的开发与使用方法。
- 本文基于 PlatformIO 构建系统和 Zephyr RTOS 进行开发。如果你之前没有相关经验,请参考 Getting Started With SeeedStudio XIAO nRF54LM20A
硬件准备
本文基于 XIAO nRF54LM20A Sense 进行开发,你需要提前准备好相关硬件。
IMU
LSM6DS3TR-C 是一款集成 3 轴数字加速度计和 3 轴数字陀螺仪的六轴传感器,属于意法半导体推出的 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
获取六轴数据
- 修改设备树文件
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 查看详情。
/* 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;
};
};
};
- 修改 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
- 编写程序,通过 USB 串口输出获取到的 3 轴数字加速度计数据和 3 轴数字陀螺仪数据。
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 示例,然后编译并烧录程序即可开始测试。
结果
烧录固件后,你可以在电脑上打开串口助手进行数据查看。触发频率为 12.5 Hz,间隔为 80 毫秒。
- 三轴数字加速度计:测量 X、Y 和 Z 轴方向的加速度。
- 三轴数字陀螺仪:测量绕 X、Y 和 Z 轴的角速度。

应用
IMU 可以融合三轴加速度数据来计算俯仰、偏航和横滚姿态角,用于姿态识别。它还可以与相应的控制器配合实现运动控制,或应用于姿态触发唤醒等低功耗场景。
电子海洋
这是一个基于 XIAO nRF54LM20A Sense 板载 IMU 的示例。它采集姿态数据并融合加速度信息,将运动状态映射到 RGB 灯板上,实现可视化的海洋律动效果。
- 倾斜水位控制 — 通过左右横滚倾斜调节水位高度
- 波浪动画 — 三层频率叠加的波面,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 获取从编译时间开始的时间戳,并每秒打印一次数据。进入 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 秒)。
- 录音结束后,音频文件将通过蓝牙发送到上位机。传输过程中 RGB-G LED 闪烁。
- 在 Windows 上运行接收脚本,将音频文件保存到桌面。
- 传输完成后 RGB-G LED 熄灭。
-
将 mic-main.c 中的程序复制到
main.c中。 -
修改设备树文件
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>;
};
};
};
- 修改 shturl.c 文件以启用蓝牙和麦克风的相关配置,并将蓝牙设备名称设置为 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 电脑上借助脚本通过蓝牙接收录制的音频。
- 运行 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 程序中配置好,因此运行脚本后会自动连接。
- 检查结果
- 按下 BOOT 键开始录音,绿色常亮的 RGB LED 表示正在录音。你可以对着麦克风大声说话,然后再次按下 BOOT 键停止录音。绿色闪烁的 RGB LED 表示正在传输音频文件。

- 打开串口,将会打印日志。请将波特率设置为 921600。

- 将显示接收到的音频文件及其字节大小。

技术支持与产品讨论
感谢您选择我们的产品!我们将为您提供多种支持,以确保您在使用我们产品时拥有尽可能顺畅的体验。我们提供多种沟通渠道,以满足不同的偏好和需求。

