Skip to main content

Usage of Seeed Studio XIAO nRF54L15 Sense built-in Sensor

XIAO nRF54L15 Sense IMU

6-Axis IMU (Inertial Measurement Unit) Sensors like the LSM6DS3TR-C integrate accelerometers and gyroscopes to measure the motion and orientation of an object in three-dimensional space. Specifically, the LSM6DS3TR-C has the following features:

Accelerometer function:

  • Measures the acceleration of an object along the X, Y, and Z axes. It is able to sense object motion (e.g., rest, acceleration, deceleration) and tilt changes (e.g., angle of the object).
  • It can be used to detect gait, position changes, vibrations, etc.

Gyroscope function (Gyroscope):

  • Measures the angular velocity of an object around the X, Y, and Z axes, i.e., the rotation of the object.
  • Can be used to detect rotation, rate of rotation, and change in direction.
  • The X-axis angle ( Roll ) is the angle in the direction of rotation around the X-axis.
  • The Y-axis angle ( Pitch ) is the angle in the direction of rotation around the Y-axis.
  • The Z-axis angle ( Yaw ) is the angle in the direction of rotation around the Z-axis.

IMU Driver

To simplify your development experience and ensure a quick start with this IMU program, we've leveraged the PlatformIO platform for writing the necessary driver code. PlatformIO offers a comprehensive and efficient environment for embedded development, making it an ideal choice for the XIAO nRF54L15 Sense.

Before proceeding, please ensure that your development environment is correctly set up. If you haven't yet added the Seeed Studio XIAO nRF54L15 development board to your PlatformIO configuration, kindly refer to this link for detailed instructions on how to configure it. This crucial step will enable PlatformIO to properly recognize and compile code for your board.

  • Once your environment is ready, the IMU driver will allow you to read raw sensor data from the LSM6DS3TR-C. This data includes:

  • Accelerometer raw values (accel raw): Representing the acceleration along the X, Y, and Z axes.

  • Gyroscope raw values (gyro raw): Indicating the angular velocity around the X, Y, and Z axes.

Trigger count (trig_cnt): A counter that increments with each new data sample.

Below is an example of the serial output you can expect to see from the IMU, as displayed in the PlatformIO Device Monitor. This output provides real-time readings of the accelerometer and gyroscope data, which are fundamental for understanding the motion and orientation of your device.

XIAO nRF54L15 BLE Advertising Power Consumption

Real-time IMU Data Output from PlatformIO Device Monitor, displaying raw accelerometer and gyroscope readings.

This raw data forms the basis for various applications, from simple motion detection to complex orientation tracking, by applying appropriate algorithms (e.g., filtering, sensor fusion).



#include <zephyr/drivers/sensor.h>
#include <zephyr/shell/shell.h>
#include <zephyr/drivers/regulator.h>
#include <nrfx.h>

static const struct device *const imu = DEVICE_DT_GET(DT_NODELABEL(lsm6dso));
static const struct device *const lsm6dso_reg = DEVICE_DT_GET(DT_NODELABEL(pdm_imu_pwr));

static int cmd_imu_get(const struct shell *sh, size_t argc, char **argv)
{
int ret;
struct sensor_value accel_data[3];
struct sensor_value gyro_data[3];
struct sensor_value odr_attr;
/* set accel/gyro sampling frequency to 12.5 Hz */
odr_attr.val1 = 12.5;
odr_attr.val2 = 0;
regulator_enable(lsm6dso_reg);

k_sleep(K_MSEC(100));
if (!device_is_ready(imu)) {
shell_error(sh, "Device not ready\n");
return -ENODEV;
}

ret = sensor_attr_set(imu, SENSOR_CHAN_ACCEL_XYZ, SENSOR_ATTR_SAMPLING_FREQUENCY, &odr_attr);
if (ret)
{
shell_error(sh, "Failed to set accel sampling frequency\n");
return ret;
}

ret = sensor_attr_set(imu, SENSOR_CHAN_GYRO_XYZ, SENSOR_ATTR_SAMPLING_FREQUENCY, &odr_attr);
if (ret)
{
shell_error(sh, "Failed to set gyro sampling frequency\n");
return ret;
}
k_sleep(K_MSEC(500));
ret = sensor_sample_fetch(imu);
if (ret)
{
shell_error(sh, "Failed to fetch sample\n");
return ret;
}

ret = sensor_channel_get(imu, SENSOR_CHAN_ACCEL_XYZ, accel_data);
if (ret)
{
shell_error(sh, "Failed to get accel data\n");
return ret;
}

ret = sensor_channel_get(imu, SENSOR_CHAN_GYRO_XYZ, gyro_data);
if (ret)
{
shell_error(sh, "Failed to get gyro data\n");
return ret;
}

regulator_disable(lsm6dso_reg);
for (int i = 0; i < 3; i++) {
if (accel_data[i].val1 < 0 && accel_data[i].val2 > 0) {
accel_data[i].val2 = 1000000 - accel_data[i].val2;
}
if (gyro_data[i].val1 < 0 && gyro_data[i].val2 > 0) {
gyro_data[i].val2 = 1000000 - gyro_data[i].val2;
}
}

shell_print(sh, "accel data: %d.%06u, %d.%06u, %d.%06u",
accel_data[0].val1, (uint32_t)abs(accel_data[0].val2),
accel_data[1].val1, (uint32_t)abs(accel_data[1].val2),
accel_data[2].val1, (uint32_t)abs(accel_data[2].val2));
shell_print(sh, "gyro data: %d.%06u, %d.%06u, %d.%06u",
gyro_data[0].val1, (uint32_t)abs(gyro_data[0].val2),
gyro_data[1].val1, (uint32_t)abs(gyro_data[1].val2),
gyro_data[2].val1, (uint32_t)abs(gyro_data[2].val2));
return ret;
}

SHELL_STATIC_SUBCMD_SET_CREATE(sub_imu_cmds,
SHELL_CMD(get, NULL, "Get sensor data", cmd_imu_get),
SHELL_SUBCMD_SET_END);

SHELL_CMD_REGISTER(imu, &sub_imu_cmds, "IMU sensor", NULL);

int imu_init(void)
{
if (!device_is_ready(imu)) {
return -ENODEV;
}
regulator_disable(lsm6dso_reg);
return 0;
}

XIAO nRF54L15 Sense MIC

The MSM261DGT006 is a Digital Microphone (DMIC) that outputs Pulse Density Modulation (PDM) data, making it suitable for direct digital interfacing with microcontrollers like the XIAO nRF54L15 Sense. Our DMIC driver is specifically designed to handle this PDM output, convert it into usable audio samples, and process it for various applications.

The driver initiates the microphone, sets the appropriate sampling rate (e.g., 16000 Hz for standard audio), and configures the PDM clock frequency. It then continuously reads sample buffers from the microphone, allowing for real-time audio capture.

The output from the DMIC driver, when viewed in the PlatformIO Device Monitor, provides crucial information about the microphone's operation and the incoming audio data. Key messages you will observe include:

  • DMIC sample=: Indicates the start of the DMIC sampling process.

  • PCM output rate: 16000, channels: 1: Confirms the audio output settings, typically a sample rate of 16 kHz and a single channel (mono) audio.

  • dmic_nrf_pdm: PDM clock frequency: 1280000, actual PCM rate: 16000: Shows the internal PDM clock frequency and the resulting PCM audio sample rate.

  • got buffer 0x... of 3200 bytes: Confirms that the driver successfully received a buffer of audio data from the microphone. The hexadecimal address (e.g., 0x20004C8) and size in bytes (e.g., 3200 bytes) are shown. These buffers contain the raw audio samples that can then be processed or analyzed.

  • dmix_sample: Exiting: Indicates that the DMIC sampling process has been stopped.

Below is an example of the typical output you can expect to see in the PlatformIO Device Monitor when the DMIC driver is running, illustrating the successful capture and buffering of audio data.

DMIC Driver

XIAO nRF54L15 BLE Advertising Power Consumption

Real-time DMIC Data Output from PlatformIO Device Monitor, displaying microphone initialization and buffer reception

This raw audio data, once captured, can be used for a wide range of applications, including voice commands, sound event detection, environmental noise monitoring, and more complex audio processing tasks.



#include <stdio.h>

#include <zephyr/device.h>
#include <zephyr/kernel.h>
#include <zephyr/audio/dmic.h>
#include <zephyr/shell/shell.h>
#include <zephyr/sys/util.h>

#define BITS_PER_BYTE 8

#define SAMPLE_RATE_HZ 16000
#define SAMPLE_BITS 16
#define TIMEOUT_MS 1000
#define CAPTURE_MS 500
#define BLOCK_SIZE ((SAMPLE_BITS / BITS_PER_BYTE) * (SAMPLE_RATE_HZ * CAPTURE_MS) / 1000)
#define BLOCK_COUNT 4

static const struct device *const dmic = DEVICE_DT_GET(DT_ALIAS(dmic20));
static const struct device *const pdm_reg = DEVICE_DT_GET(DT_NODELABEL(pdm_imu_pwr));

K_MEM_SLAB_DEFINE_STATIC(mem_slab, BLOCK_SIZE, BLOCK_COUNT, 4);

static struct pcm_stream_cfg stream = {
.pcm_rate = SAMPLE_RATE_HZ,
.pcm_width = SAMPLE_BITS,
.block_size = BLOCK_SIZE,
.mem_slab = &mem_slab,
};

static struct dmic_cfg cfg = {
.io =
{
.min_pdm_clk_freq = 1000000,
.max_pdm_clk_freq = 3500000,
.min_pdm_clk_dc = 40,
.max_pdm_clk_dc = 60,
},
.streams = &stream,
.channel =
{
.req_num_streams = 1,
.req_num_chan = 1,
},
};

static bool initialized;

static int cmd_mic_capture(const struct shell *sh, size_t argc, char **argv)
{
int ret,time = 1;
void *buffer;
uint32_t size;
int16_t max_data = 0, min_data = 0;

if (argc > 1) {
time = atoi(argv[1]);
}
time *= (1000 / CAPTURE_MS);
if (!initialized) {
shell_error(sh, "Microphone module not initialized");
return -EPERM;
}
regulator_enable(pdm_reg);
shell_print(sh, "S");
ret = dmic_configure(dmic, &cfg);
if (ret < 0) {
shell_error(sh, "Failed to configure DMIC(%d)", ret);
return ret;
}
ret = dmic_trigger(dmic, DMIC_TRIGGER_START);
if (ret < 0) {
shell_error(sh, "START trigger failed (%d)", ret);
return ret;
}
for (int i = 0; i < time; i++) {
ret = dmic_read(dmic, 0, &buffer, &size, TIMEOUT_MS);
if (ret < 0) {
shell_error(sh, "DMIC read failed (%d)", ret);
k_mem_slab_free(&mem_slab, buffer);
dmic_trigger(dmic, DMIC_TRIGGER_STOP);
regulator_disable(pdm_reg);
return ret;
}

for (int j = 0; j < size / sizeof(int16_t); j++)
{
if (((int16_t *)buffer)[j] > max_data) {
max_data = ((int16_t *)buffer)[j];
}
if (((int16_t *)buffer)[j] < min_data) {
min_data = ((int16_t *)buffer)[j];
}
shell_print(sh, "%d", ((int16_t *)buffer)[j]);
}
k_mem_slab_free(&mem_slab, buffer);
}
ret = dmic_trigger(dmic, DMIC_TRIGGER_STOP);
if (ret < 0) {
shell_error(sh, "STOP trigger failed (%d)", ret);
regulator_disable(pdm_reg);
return ret;
}
shell_print(sh, "E");
shell_print(sh, "audio data Max: %d Min: %d", max_data, min_data);
regulator_disable(pdm_reg);
return 0;
}

SHELL_STATIC_SUBCMD_SET_CREATE(sub_mic_cmds,
SHELL_CMD(capture, NULL, "Capture microphone data", cmd_mic_capture),
SHELL_SUBCMD_SET_END);

SHELL_CMD_REGISTER(mic, &sub_mic_cmds, "Microphone", NULL);

int mic_init(void)
{
if (!device_is_ready(dmic)) {
return -ENODEV;
}

cfg.channel.req_chan_map_lo = dmic_build_channel_map(0, 0, PDM_CHAN_LEFT);

initialized = true;

return 0;
}

Tech Support & Product Discussion

Thank you for choosing our products! We are here to provide you with different support to ensure that your experience with our products is as smooth as possible. We offer several communication channels to cater to different preferences and needs.

Loading Comments...