Seeed Studio XIAO nRF54L15 Sense 内置传感器使用指南
以下示例代码专为 PlatformIO 设计,但也兼容 nRF Connect SDK。
基于 VS Code,如果您想在 nRF Connect SDK 上使用以下案例,请参考提供的连接,添加 app.overlay 文件并修改 prj.conf 中的内容
XIAO nRF54L15 Sense IMU
6 轴 IMU(惯性测量单元) 传感器如 LSM6DS3TR-C 集成了加速度计和陀螺仪,用于测量物体在三维空间中的运动和方向。具体来说,LSM6DS3TR-C 具有以下特性:
加速度计功能:
- 测量物体沿 X、Y 和 Z 轴的加速度。能够感知物体运动(例如静止、加速、减速)和倾斜变化(例如物体的角度)。
- 可用于检测步态、位置变化、振动等。

陀螺仪功能(Gyroscope):
- 测量物体绕 X、Y 和 Z 轴的角速度,即物体的旋转。
- 可用于检测旋转、旋转速率和方向变化。

- X 轴角度(Roll) 是绕 X 轴旋转方向的角度。
- Y 轴角度(Pitch) 是绕 Y 轴旋转方向的角度。
- Z 轴角度(Yaw) 是绕 Z 轴旋转方向的角度。
IMU 驱动程序
为了简化您的开发体验并确保快速启动此 IMU 程序,我们利用 PlatformIO 平台编写了必要的驱动程序代码。PlatformIO 为嵌入式开发提供了全面高效的环境,是 XIAO nRF54L15 Sense 的理想选择。
在继续之前,请确保您的开发环境已正确设置。如果您尚未将 Seeed Studio XIAO nRF54L15 开发板添加到您的 PlatformIO 配置中,请参考此链接获取如何配置的详细说明。这个关键步骤将使 PlatformIO 能够正确识别并为您的开发板编译代码。
一旦您的环境准备就绪,IMU 驱动程序将允许您从 LSM6DS3TR-C 读取原始传感器数据。这些数据包括:
-
加速度计原始值(accel raw):表示沿 X、Y 和 Z 轴的加速度。
-
陀螺仪原始值(gyro raw):表示绕 X、Y 和 Z 轴的角速度。
-
触发计数(trig_cnt):随每个新数据样本递增的计数器。
将仓库下载到 C:\Users\xxx\.platformio\platforms 并在 VS Code 中打开 examples\zephyr-imu 文件夹。然后点击 main.c,您将看到以下代码:
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(zephyr_imu, LOG_LEVEL_INF);
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.5;
odr_attr.val2 = 0;
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;
}
}
#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(DT_ALIAS(imu0));
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;
}
现在,通过 USB 将您的 XIAO nRF54L15 连接到计算机。在 VS Code 中:
-
构建:点击 VS Code 底部 PlatformIO 工具栏中的"Build"图标(对勾),或使用 PlatformIO 侧边栏:PROJECT TASKS -> your_project_name -> General -> Build。
-
上传:构建成功后,点击 PlatformIO 工具栏中的"Upload"图标(右箭头),或使用 PlatformIO 侧边栏:PROJECT TASKS -> your_project_name -> General -> Upload。
上传成功后,您应该在 PlatformIO Device Monitor(PROJECT TASKS -> your_project_name -> General -> Monitor)中看到类似下面示例的输出。此串行输出显示实时加速度计和陀螺仪读数,为您的设备运动和方向提供关键洞察。

来自 PlatformIO Device Monitor 的实时 IMU 数据输出,显示原始加速度计和陀螺仪读数。
这些原始数据通过应用适当的算法(例如滤波、传感器融合),为从简单运动检测到复杂方向跟踪的各种应用奠定了基础。
XIAO nRF54L15 Sense MIC
MSM261DGT006 是一个数字麦克风(DMIC),输出脉冲密度调制(PDM)数据,适合与 XIAO nRF54L15 Sense 等微控制器直接数字接口。我们的 DMIC 驱动程序专门设计用于处理此 PDM 输出,将其转换为可用的音频样本,并为各种应用进行处理。
驱动程序初始化麦克风,设置适当的采样率(例如,标准音频为 16000 Hz),并配置 PDM 时钟频率。然后它持续从麦克风读取样本缓冲区,允许实时音频捕获。
在 PlatformIO Device Monitor 中查看时,DMIC 驱动程序的输出提供了关于麦克风操作和传入音频数据的重要信息。您将观察到的关键消息包括:
-
DMIC sample=::表示 DMIC 采样过程的开始。 -
PCM output rate:16000, channels: 1:确认音频输出设置,通常为 16 kHz 采样率和单声道音频。 -
dmic_nrf_pdm:PDM clock frequency: 1280000, actual PCM rate: 16000:显示内部 PDM 时钟频率和生成的 PCM 音频采样率。 -
got buffer 0x... of 3200 bytes:确认驱动程序成功从麦克风接收到音频数据缓冲区。显示十六进制地址(例如 0x20004C8)和字节大小(例如 3200 字节)。这些缓冲区包含可以进行处理或分析的原始音频样本。 -
dmix_sample: Exiting:表示 DMIC 采样过程已停止。
以下是在 DMIC 驱动程序运行时,您可以在 PlatformIO Device Monitor 中看到的典型输出示例,展示了音频数据的成功捕获和缓冲。
DMIC 驱动程序
一旦捕获,这些原始音频数据可用于广泛的应用,包括语音命令、声音事件检测、环境噪声监测以及更复杂的音频处理任务。
以下代码示例演示了如何使用 XIAO nRF54L15 开发板上的按钮录制音频,并将录制的 WAV 文件保存到计算机上。
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/audio/dmic.h>
#include <zephyr/sys/util.h>
#include <zephyr/logging/log.h>
#include <zephyr/drivers/uart.h>
LOG_MODULE_REGISTER(mic_capture_sample, LOG_LEVEL_INF);
#define RECORD_TIME_S 10 // Recording duration (seconds)
#define SAMPLE_RATE_HZ 16000 // Sample rate (Hz)
#define SAMPLE_BIT_WIDTH 16 // Sample bit width (bits)
#define BYTES_PER_SAMPLE (SAMPLE_BIT_WIDTH / 8) // Bytes per sample
#define READ_TIMEOUT_MS 1000 // DMIC read timeout (ms)
#define CHUNK_DURATION_MS 100 // Duration of each chunk (ms)
#define CHUNK_SIZE_BYTES (BYTES_PER_SAMPLE * (SAMPLE_RATE_HZ * CHUNK_DURATION_MS) / 1000) // Chunk size (bytes)
#define CHUNK_COUNT 8 // Number of blocks in memory pool
#define TOTAL_CHUNKS (RECORD_TIME_S * 1000 / CHUNK_DURATION_MS) // Total number of chunks
static const struct device *const dmic_dev = DEVICE_DT_GET(DT_ALIAS(dmic20)); // DMIC device handle
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios); // LED device descriptor
static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET(DT_ALIAS(sw0), gpios); // Button device descriptor
static const struct device *const console_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_console)); // Console UART device
K_MEM_SLAB_DEFINE_STATIC(mem_slab, CHUNK_SIZE_BYTES, CHUNK_COUNT, 4); // Audio data memory pool
K_MSGQ_DEFINE(audio_msgq, sizeof(void *), CHUNK_COUNT, 4);
static K_SEM_DEFINE(tx_done_sem, 0, 1); // Button semaphore
static K_SEM_DEFINE(button_sem, 0, 1); // UART TX done semaphore
static const uint8_t packet_start[] = {0xAA, 0x55, 'S', 'T', 'A', 'R', 'T'}; // Packet start marker
static const uint8_t packet_end[] = {0xAA, 0x55, 'E', 'N', 'D'}; // Packet end marker
static struct gpio_callback button_cb_data;
/**
* @brief UART callback function
*
* @param dev UART device pointer
* @param evt UART event
* @param user_data User data (unused)
*/
static void uart_tx_callback(const struct device *dev, struct uart_event *evt, void *user_data)
{
if (evt->type == UART_TX_DONE) {
k_sem_give(&tx_done_sem);
}
}
/**
* @brief Button interrupt callback function
*
* @param dev Button device pointer
* @param cb Callback structure pointer
* @param pins Triggered pins
*/
void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
k_sem_give(&button_sem);
}
/**
* @brief Send a data packet via UART (polling, for small packets)
*
* @param data Data pointer
* @param len Data length
*/
static void send_packet_poll(const uint8_t *data, size_t len)
{
for (size_t i = 0; i < len; i++) {
uart_poll_out(console_dev, data[i]);
}
}
/**
* @brief UART writer thread function
*
* This thread continuously reads audio data from the message queue and sends it via UART.
* It waits for the semaphore to signal that the previous transmission is done before sending the next chunk.
*/
void uart_writer_thread(void *p1, void *p2, void *p3)
{
uart_callback_set(console_dev, uart_tx_callback, NULL);
while (true) {
void *buffer;
k_msgq_get(&audio_msgq, &buffer, K_FOREVER);
if (buffer == NULL) {
send_packet_poll(packet_end, sizeof(packet_end));
continue;
}
uart_tx(console_dev, buffer, CHUNK_SIZE_BYTES, SYS_FOREVER_US);
k_sem_take(&tx_done_sem, K_FOREVER);
k_mem_slab_free(&mem_slab, buffer);
}
}
K_THREAD_DEFINE(uart_writer_tid, 2048, uart_writer_thread, NULL, NULL, NULL,
K_PRIO_COOP(7), 0, 0);
static struct pcm_stream_cfg stream_cfg = {
.pcm_rate = SAMPLE_RATE_HZ,
.pcm_width = SAMPLE_BIT_WIDTH,
.block_size = CHUNK_SIZE_BYTES,
.mem_slab = &mem_slab,
}; // PCM stream configuration
static struct dmic_cfg dmic_config = {
.io = {
.min_pdm_clk_freq = 1000000,
.max_pdm_clk_freq = 3500000,
.min_pdm_clk_dc = 40,
.max_pdm_clk_dc = 60,
},
.streams = &stream_cfg,
.channel = {
.req_num_streams = 1,
.req_num_chan = 1,
},
}; // DMIC configuration
/**
* @brief Record audio from DMIC and stream it via UART
*
* @return 0 on success, negative error code on failure
*/
static int record_and_stream_audio(void)
{
int ret;
void *buffer;
uint32_t size;
k_msgq_purge(&audio_msgq);
ret = dmic_configure(dmic_dev, &dmic_config);
if (ret < 0) {
LOG_ERR("Failed to configure DMIC: %d", ret);
return ret;
}
ret = dmic_trigger(dmic_dev, DMIC_TRIGGER_START);
if (ret < 0) {
LOG_ERR("Failed to start DMIC: %d", ret);
return ret;
}
ret = dmic_read(dmic_dev, 0, &buffer, &size, READ_TIMEOUT_MS);
if (ret < 0) {
LOG_WRN("Failed to read discard chunk: %d", ret);
} else {
k_mem_slab_free(&mem_slab, buffer);
}
send_packet_poll(packet_start, sizeof(packet_start));
for (int i = 0; i < TOTAL_CHUNKS; i++) {
ret = dmic_read(dmic_dev, 0, &buffer, &size, READ_TIMEOUT_MS);
if (ret < 0) {
LOG_ERR("Failed to read from DMIC: %d", ret);
break;
}
ret = k_msgq_put(&audio_msgq, &buffer, K_MSEC(500));
if (ret != 0) {
LOG_ERR("Failed to queue buffer. UART thread might be too slow.");
k_mem_slab_free(&mem_slab, buffer);
break;
}
}
(void)dmic_trigger(dmic_dev, DMIC_TRIGGER_STOP);
void *end_marker = NULL;
k_msgq_put(&audio_msgq, &end_marker, K_NO_WAIT);
LOG_INF("Audio capture finished and data queued.");
return 0;
}
/**
* @brief Main function, initializes peripherals and waits for button to trigger recording in a loop
*
* @return Always returns 0
*/
int main(void)
{
int ret;
// Check if all required devices are ready
if (!device_is_ready(dmic_dev) || !device_is_ready(led.port) ||
!device_is_ready(button.port) || !device_is_ready(console_dev)) {
LOG_ERR("A required device is not ready.");
return -ENODEV;
}
// Configure DMIC channel mapping
dmic_config.channel.req_chan_map_lo = dmic_build_channel_map(0, 0, PDM_CHAN_LEFT);
// Configure LED as output
ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
if (ret < 0) { return ret; }
// Configure button as input and enable interrupt
ret = gpio_pin_configure_dt(&button, GPIO_INPUT);
if (ret < 0) { return ret; }
ret = gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_TO_ACTIVE);
if (ret < 0) { return ret; }
gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin));
gpio_add_callback(button.port, &button_cb_data);
LOG_INF("Zephyr Audio Streamer Ready.");
LOG_INF("Press button SW0 to start recording...");
// Main loop, wait for button to trigger recording
while (1) {
k_sem_take(&button_sem, K_FOREVER);
LOG_INF("Button pressed, starting capture...");
gpio_pin_set_dt(&led, 0);
record_and_stream_audio();
gpio_pin_set_dt(&led, 1);
LOG_INF("\nPress button SW0 to start recording again...");
}
return 0;
}
接下来,在 scripts 文件夹目录中打开终端并执行以下操作,前提是程序已经烧录完成。
步骤 1:
python3 -m pip install pyserial
步骤 2:
python record.py -p /dev/cu.usbmodemA0CBDDC33 -o output.wav -b 921600
在此命令 python record.py -p **/dev/cu.usbmodemA0CBDDC33** -o output.wav -b 921600 中,您需要将其替换为您的串口以供使用。
步骤 3:
- 执行命令后,系统会提示您按下按钮开始录制声音。

录制音频后,文件将保存在 scripts 中

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