Skip to main content

XIAO nRF54LM20A Sense 板载传感器的使用


XIAO nRF54LM20A Sense 配备了丰富的板载传感器,可支持多场景应用。它集成了用于姿态识别的 LSM6DS3TR-C 六轴传感器,以及支持 PDM 数字输出和全向拾音的 MSM261DGT006 数字 MEMS 麦克风,适用于智能语音场景。本文将介绍基于 XIAO nRF54LM20A 丰富板载外设的开发与使用方法。

tip

硬件准备

本文基于 XIAO nRF54LM20A Sense 进行开发,你需要提前准备好相关硬件。

Seeed Studio XIAO nRF54LM20A Sense6x10 RGB WS2812 Matrix for Seeed Studio XIAO

IMU

LSM6DS3TR-C 是一款集成 3 轴数字加速度计和 3 轴数字陀螺仪的六轴传感器,属于意法半导体推出的 iNEMO 惯性测量单元(IMU)。在 XIAO nRF54LM20A Sense 上,该传感器支持中断触发数据输出。它具有 ±2/±4/±8/±16 g 的加速度全量程范围和 ±125/±250/±500/±1000/±2000 dps 的角速度范围,并支持持续低功耗模式,适用于多种运动检测场景。板载芯片通过 I2C 协议与其通信以获取数据。

tip

获取六轴数据

  1. 修改设备树文件 app.overlay,将 LSM6DS3TR-C 使用的硬件引脚绑定到设备树中。将 IMU_SDA 和 IMU_SCL 绑定到 i2c30 节点,对应 XIAO nRF54LM20A Sense 上的 P0.08 和 P0.07。将中断触发引脚 IMU_INT1 绑定到 P0.06。
tip
/* 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. 编写程序,通过 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;
}

tip

如果你想直接验证 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
使用说明
  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。
  • 同时,轻敲事件信息也会通过串口输出。

tip

感应位置仅供参考,准确的轻敲位置识别取决于 IMU 融合控制算法。

RTC

XIAO nRF54LM20A Sense 采用的芯片内置 GRTC 硬件资源,无需额外的 RTC 模块即可实现 RTC 功能。

RTC 支持时间戳计数,即使断电也能记录运行时间,便于日志记录和时间追踪。

本节介绍一个在 XIAO nRF54LM20A Sense 上实现的示例程序。上电后,通过 RTC 获取从编译时间开始的时间戳,并每秒打印一次数据。进入 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. 编辑 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。适用于可穿戴设备、智能设备、语音识别、音频录制以及其他需要声学感知功能的应用场景。

tip

在 XIAO nRF54LM20A 系列中,只有 XIAO nRF54M20A Sense 配备了麦克风,位于开发板的左下角。

音频录制与 BLE 上传

本节通过语音示例演示麦克风功能。具体流程如下:

  • 按下 BOOT 按键,RGB-G LED 常亮并开始录音;再次按下停止录音(最长 10 秒)。
  • 录音结束后,音频文件将通过蓝牙发送到上位机。传输过程中 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 文件以启用蓝牙和麦克风的相关配置,并将蓝牙设备名称设置为 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 电脑上借助脚本通过蓝牙接收录制的音频。

  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
tip

BLE UUID 已在 Python 程序中配置好,因此运行脚本后会自动连接。

  1. 检查结果
  • 按下 BOOT 键开始录音,绿色常亮的 RGB LED 表示正在录音。你可以对着麦克风大声说话,然后再次按下 BOOT 键停止录音。绿色闪烁的 RGB LED 表示正在传输音频文件。

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

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

技术支持与产品讨论

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

Loading Comments...