Skip to main content

Seeed Studio XIAO nRF54L15 Sense 内置传感器使用指南

以下示例代码专为 PlatformIO 设计,但也兼容 nRF Connect SDK。

tip

基于 VS Code,如果您想在 nRF Connect SDK 上使用以下案例,请参考提供的连接,添加 app.overlay 文件并修改 prj.conf 中的内容

XIAO nRF54L15 添加 overlay 文件并修改 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)中看到类似下面示例的输出。此串行输出显示实时加速度计和陀螺仪读数,为您的设备运动和方向提供关键洞察。

XIAO nRF54L15 BLE Advertising Power Consumption

来自 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
tip

在此命令 python record.py -p **/dev/cu.usbmodemA0CBDDC33** -o output.wav -b 921600 中,您需要将其替换为您的串口以供使用。

步骤 3:

  • 执行命令后,系统会提示您按下按钮开始录制声音。

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

技术支持与产品讨论

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

Loading Comments...