Uso de los sensores integrados en XIAO nRF54LM20A Sense

El XIAO nRF54LM20A Sense está equipado con abundantes sensores integrados para admitir aplicaciones en múltiples escenarios. Incluye el sensor de seis ejes LSM6DS3TR-C para reconocimiento de postura y el micrófono digital MEMS MSM261DGT006 que admite salida digital PDM y captación de sonido omnidireccional, lo que lo hace adecuado para escenarios de voz inteligente. Este artículo presenta los métodos de desarrollo y uso basados en los ricos periféricos integrados del XIAO nRF54LM20A.
- Este artículo se desarrolla sobre la base del sistema de compilación PlatformIO y Zephyr RTOS. Si no tienes experiencia previa con ellos, consulta Getting Started With SeeedStudio XIAO nRF54LM20A
Preparación de hardware
Este artículo se desarrolla basándose en el XIAO nRF54LM20A Sense, y necesitas preparar el hardware correspondiente con antelación.
| Seeed Studio XIAO nRF54LM20A Sense | Matriz RGB WS2812 6x10 para Seeed Studio XIAO |
|---|---|
![]() | ![]() |
IMU
El LSM6DS3TR-C es un sensor de seis ejes que integra un acelerómetro digital de 3 ejes y un giroscopio digital de 3 ejes, que pertenece a la unidad de medición inercial (IMU) iNEMO lanzada por STMicroelectronics. En el XIAO nRF54LM20A Sense, este sensor admite salida de datos activada por interrupción. Presenta un rango de aceleración a escala completa de ±2/±4/±8/±16 g y un rango de velocidad angular de ±125/±250/±500/±1000/±2000 dps, y admite un modo de bajo consumo persistente, lo que lo hace adecuado para diversos escenarios de detección de movimiento. El chip integrado se comunica con él mediante el protocolo I2C para adquirir los datos.
- Para obtener más información sobre el LSM6DS3TR-C, visita: Product overview for LSM6DS3TR-C y LSM6DS3TR-C Datasheet
Obtener datos de seis ejes
- Modifica el archivo del árbol de dispositivos
app.overlaypara vincular los pines de hardware utilizados por el LSM6DS3TR-C al árbol de dispositivos. Vincula IMU_SDA e IMU_SCL al nodo i2c30, que corresponde a P0.08 y P0.07 en el XIAO nRF54LM20A Sense. Vincula el pin de disparo de interrupción IMU_INT1 a P0.06.
- Para el pinout del XIAO nRF54LM20A, haz clic en XIAO nRF54LM20A Sense Pin List para ver los detalles.
&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;
};
- Modifica el archivo prj.conf para habilitar las configuraciones de I2C y de disparo por interrupción.
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
- Escribe un programa para enviar los datos adquiridos del acelerómetro digital de 3 ejes y del giroscopio digital de 3 ejes a través del puerto serie 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;
}
Si deseas verificar directamente el rendimiento de la IMU, clona el repositorio Platform-seeedboards, localiza el ejemplo zephyr-imu en el directorio examples, luego compila y flashea el programa para iniciar la prueba.
Resultado
Después de flashear el firmware, puedes abrir el asistente de puerto serie en tu PC para la visualización de datos. La frecuencia de disparo es de 12,5 Hz con un intervalo de 80 milisegundos.
- Acelerómetro digital de 3 ejes: Mide la aceleración a lo largo de los ejes X, Y y Z.
- Giroscopio digital de 3 ejes: Mide la velocidad angular alrededor de los ejes X, Y y Z.
- Configura la velocidad en baudios a 115200 cuando veas los datos a través del monitor serie.
- Especifica la velocidad en baudios como 115200 en el archivo de configuración platformio.ini para el monitor serie de PlatformIO IDE.
[env:seeed-xiao-nrf54lm20a]
platform = https://github.com/Seeed-Studio/platform-seeedboards.git
framework = zephyr
board = seeed-xiao-nrf54lm20a
monitor_speed = 115200

Aplicación
La IMU puede fusionar datos de aceleración de tres ejes para calcular los ángulos de actitud de cabeceo, guiñada y alabeo para el reconocimiento de postura. También puede trabajar con controladores correspondientes para realizar control de movimiento, o aplicarse en escenarios de bajo consumo como el despertar activado por actitud.
Océano electrónico
Este es un ejemplo basado en la IMU integrada de XIAO nRF54LM20A Sense. Recoge datos de actitud y fusiona información de aceleración para mapear los estados de movimiento en el panel de luz RGB, logrando efectos visuales de ritmo oceánico.
- Control de nivel de agua por inclinación — Ajusta la altura del nivel de agua mediante la inclinación de alabeo hacia la izquierda y derecha
- Animación de olas — Superposición de ondas de tres capas de frecuencia, propagación de ondas 2D y efecto de reflexión en los bordes
- Inercia del fluido — Superficie de agua con momento; una inclinación rápida provoca sobreimpulso y posterior vaivén de oscilación
- Detección de volteo — La pantalla se invierte automáticamente cuando la placa se voltea
- Color dinámico — Cambio de tono oceánico con degradado aleatorio para cada columna
Además, puedes modificar la configuración de la matriz RGB de la placa mediante definiciones de macros en main.c.
#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
Instrucciones de uso
-
Copia el contenido del programa correspondiente imu_ocean-main.c y pégalo en main.c.
-
Modifica el archivo del árbol de dispositivos
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;
};
};
- Habilita las configuraciones relacionadas con el uso de la 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
- Agita el dispositivo para activar el efecto visual de olas oceánicas.
- Al mismo tiempo, el puerto serie también emitirá los datos correspondientes de la IMU y la altura actual del nivel de agua de las olas.

Despertar por IMU
En esta rutina, el canal verde de los LEDs RGB se enciende y apaga después del encendido, luego el sistema entra en modo de suspensión de ultra bajo consumo. Cuando la placa detecta un toque, XIAO nRF54LM20A Sense se despertará mediante una interrupción. El evento de toque se registrará e imprimirá a través del puerto serie.
Descarga la rutina para implementar la función de despertar por IMU.
-
Descarga el programa imu-click-main.c y reemplaza con él el contenido de main.c.
-
Modifica el archivo del árbol de dispositivos
app.overlayy añade las configuraciones de nodo requeridas.
/*
* 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;
};
};
};
- Habilita las configuraciones relevantes de la IMU en prj.conf
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
- Después de flashear y encender, el LED RGB-G parpadeará brevemente. Toca en cualquier lugar de la placa para encender el LED RGB-G.

- Al mismo tiempo, la información del evento de toque también se emitirá a través del puerto serie.

La posición de detección es solo de referencia. El reconocimiento preciso de la posición del toque depende del algoritmo de control de fusión de la IMU.
RTC
El chip adoptado por XIAO nRF54LM20A Sense está equipado con recursos de hardware GRTC integrados, lo que permite funciones de RTC sin módulos RTC adicionales.
El RTC admite el conteo de marcas de tiempo y puede registrar el tiempo de funcionamiento incluso después de un fallo de alimentación, lo que facilita el registro de registros y el seguimiento del tiempo.
Esta sección presenta un programa de ejemplo implementado en XIAO nRF54LM20A Sense. Después de encenderlo, obtiene marcas de tiempo a partir de la hora de compilación mediante el RTC y muestra los datos cada segundo. Tras entrar en el modo System OFF, el sistema será despertado por la alarma del RTC para continuar el conteo.
-
Copia rtc-main.c en el archivo main.c. Utiliza las funciones del RTC para imprimir la marca de tiempo.
-
Modifica el árbol de dispositivos
app.overlaypara habilitar el nodo 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>;
};
- Edita el archivo prj.conf para habilitar las configuraciones relacionadas con el 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
Resultado
- El programa comienza a contar desde el momento de la compilación y la grabación en la memoria. Abre la herramienta de puerto serie para observar el efecto de ejecución, y todas las funciones esperadas se implementan.

MIC
El XIAO nRF54LM20A Sense está equipado con el micrófono digital MEMS MSM261DGT006 para entrada de voz. Se conecta directamente a través de la interfaz PDM sin necesidad de un ADC. Es adecuado para dispositivos portátiles, dispositivos inteligentes, reconocimiento de voz, grabación de audio y otros escenarios de aplicación que requieren funciones de detección acústica.
Entre la serie XIAO nRF54LM20A, solo el XIAO nRF54M20A Sense está equipado con un micrófono, que se encuentra en la esquina inferior izquierda de la placa de desarrollo.
Grabación de audio y carga por BLE
Esta sección demuestra la función del micrófono mediante un ejemplo de voz. El proceso específico es el siguiente:
- Pulsa el botón BOOT, el LED RGB-G permanecerá encendido y comenzará la grabación; púlsalo de nuevo para detener la grabación (máximo 10 segundos).
- Después de la grabación, el archivo de audio se enviará al ordenador host mediante Bluetooth. El LED RGB-G parpadea durante la transmisión.
- Ejecuta el script de recepción en Windows para guardar el archivo de audio en el directorio
./recordings. - El LED RGB-G se apaga después de que se complete la transmisión.
-
Copia el programa desde mic-main.c en
main.c. -
Modifica el archivo de árbol de dispositivos
app.overlaypara enlazar el nodo 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;
};
- Modifica el archivo
prj.confpara habilitar las configuraciones de Bluetooth y micrófono, y establece el nombre del dispositivo Bluetooth como 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
Resultado
Compila y graba el programa en la placa, luego utiliza un ordenador con Windows para recibir el audio grabado mediante Bluetooth con la ayuda de scripts.
- Ejecuta el script de Python
Instala las bibliotecas dependientes necesarias antes de la ejecución:
pip install bleak
Copia el archivo de script de 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.")
Comando de ejecución del script:
python ble_recorder_receiver.py
El UUID BLE ya está configurado en el programa de Python, por lo que se conectará automáticamente después de ejecutar el script.
- Verificar el resultado
- Pulsa la tecla BOOT para iniciar la grabación. El LED RGB verde fijo indica que la grabación está en curso. Puedes hablar en voz alta hacia el micrófono y luego pulsar de nuevo la tecla BOOT para detener la grabación. El LED RGB verde parpadeando significa que se está transmitiendo el archivo de audio.

- Abre el puerto serie, se imprimirá el registro. Configura la velocidad en baudios a 921600.

- Se mostrarán el archivo de audio recibido y su tamaño en bytes.

Soporte técnico y debate sobre el producto
Gracias por elegir nuestros productos. Estamos aquí para ofrecerte diferentes tipos de soporte y garantizar que tu experiencia con nuestros productos sea lo más fluida posible. Ofrecemos varios canales de comunicación para adaptarnos a distintas preferencias y necesidades.

