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のようなセンサーは、加速度計とジャイロスコープを統合して、3次元空間における物体の動きと方向を測定します。具体的に、LSM6DS3TR-Cには以下の機能があります:
加速度計機能:
- X、Y、Z軸に沿った物体の加速度を測定します。物体の動き(例:静止、加速、減速)や傾きの変化(例:物体の角度)を感知することができます。
- 歩行、位置変化、振動などの検出に使用できます。

ジャイロスコープ機能:
- X、Y、Z軸周りの物体の角速度、つまり物体の回転を測定します。
- 回転、回転速度、方向の変化の検出に使用できます。

- **X軸角度(ロール)**は、X軸周りの回転方向の角度です。
- **Y軸角度(ピッチ)**は、Y軸周りの回転方向の角度です。
- **Z軸角度(ヨー)**は、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):新しいデータサンプルごとに増加するカウンター。
以下は、PlatformIO Device Monitorに表示されるIMUからのシリアル出力の例です。この出力は、デバイスの動きと方向を理解するための基本となる加速度計とジャイロスコープデータのリアルタイム読み取り値を提供します。

PlatformIO Device Monitorからのリアルタイム IMU データ出力、生の加速度計とジャイロスコープの読み取り値を表示。
この生データは、適切なアルゴリズム(例:フィルタリング、センサーフュージョン)を適用することで、単純な動き検出から複雑な方向追跡まで、さまざまなアプリケーションの基礎となります。
#include <stdio.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/logging/log.h>
#include <zephyr/devicetree.h>
LOG_MODULE_REGISTER(lsm6dso_i2c_example, LOG_LEVEL_INF);
// --- LSM6DSO I2C address and register definitions ---
#define LSM6DSO_I2C_ADDR 0x6A // LSM6DSO I2C device address
#define LSM6DSO_REG_WHO_AM_I 0x0F // Identification register
#define LSM6DSO_WHO_AM_I_VAL 0x6A // Expected WHO_AM_I value
#define LSM6DSO_REG_CTRL1_XL 0x10 // Accelerometer control register
#define LSM6DSO_REG_CTRL2_G 0x11 // Gyroscope control register
// Accelerometer/gyroscope data output registers (low byte first)
#define LSM6DSO_REG_OUTX_L_XL 0x28 // Accelerometer X axis low byte
#define LSM6DSO_REG_OUTX_L_G 0x22 // Gyroscope X axis low byte
// --- Data structure definitions ---
// Structure for storing raw sensor data
struct lsm6dso_raw_data {
int16_t accel_x;
int16_t accel_y;
int16_t accel_z;
int16_t gyro_x;
int16_t gyro_y;
int16_t gyro_z;
};
// --- Helper functions ---
/**
* @brief Write a single byte to an LSM6DSO register via I2C.
*/
static int lsm6dso_i2c_reg_write_byte(const struct device *i2c_dev, uint8_t reg_addr, uint8_t value)
{
uint8_t tx_buf[2] = {reg_addr, value};
return i2c_write(i2c_dev, tx_buf, sizeof(tx_buf), LSM6DSO_I2C_ADDR);
}
/**
* @brief Read a single byte from an LSM6DSO register via I2C.
*/
static int lsm6dso_i2c_reg_read_byte(const struct device *i2c_dev, uint8_t reg_addr, uint8_t *value)
{
return i2c_reg_read_byte(i2c_dev, LSM6DSO_I2C_ADDR, reg_addr, value);
}
/**
* @brief Read multiple consecutive bytes from LSM6DSO register via I2C.
*/
static int lsm6dso_i2c_reg_read_bytes(const struct device *i2c_dev, uint8_t reg_addr, uint8_t *data, uint8_t len)
{
return i2c_burst_read(i2c_dev, LSM6DSO_I2C_ADDR, reg_addr, data, len);
}
// --- LSM6DSO driver core functionality ---
/**
* @brief Initialize the LSM6DSO sensor.
* Check WHO_AM_I and set ODR for accelerometer and gyroscope.
*/
static int lsm6dso_init(const struct device *i2c_dev)
{
uint8_t who_am_i = 0;
int ret;
// Verify device ID
ret = lsm6dso_i2c_reg_read_byte(i2c_dev, LSM6DSO_REG_WHO_AM_I, &who_am_i);
if (ret != 0) {
LOG_ERR("Failed to read WHO_AM_I register (err: %d)", ret);
return ret;
}
if (who_am_i != LSM6DSO_WHO_AM_I_VAL) {
LOG_ERR("Invalid WHO_AM_I: 0x%02x, expected 0x%02x", who_am_i, LSM6DSO_WHO_AM_I_VAL);
return -ENODEV;
}
LOG_INF("LSM6DSO WHO_AM_I check passed. ID: 0x%02x", who_am_i);
// Set accelerometer ODR (12.5 Hz) and 2g range (0x20)
ret = lsm6dso_i2c_reg_write_byte(i2c_dev, LSM6DSO_REG_CTRL1_XL, 0x20);
if (ret != 0) {
LOG_ERR("Failed to set CTRL1_XL register (err: %d)", ret);
return ret;
}
// Set gyroscope ODR (12.5 Hz) and 250dps range (0x20)
ret = lsm6dso_i2c_reg_write_byte(i2c_dev, LSM6DSO_REG_CTRL2_G, 0x20);
if (ret != 0) {
LOG_ERR("Failed to set CTRL2_G register (err: %d)", ret);
return ret;
}
LOG_INF("LSM6DSO initialized successfully.");
return 0;
}
/**
* @brief Fetch raw accelerometer and gyroscope data from LSM6DSO sensor.
* @param i2c_dev Pointer to I2C device structure.
* @param raw_data_out Pointer to structure for storing raw data.
* @return 0 on success, negative value on failure.
*/
static int lsm6dso_fetch_raw_data(const struct device *i2c_dev, struct lsm6dso_raw_data *raw_data_out)
{
uint8_t accel_data[6];
uint8_t gyro_data[6];
int ret;
// Read accelerometer data (6 bytes)
ret = lsm6dso_i2c_reg_read_bytes(i2c_dev, LSM6DSO_REG_OUTX_L_XL, accel_data, 6);
if (ret != 0) {
LOG_ERR("Failed to read accelerometer data (err: %d).", ret);
return ret;
}
// Raw data is 16-bit signed integer, low byte first
raw_data_out->accel_x = (int16_t)(accel_data[0] | (accel_data[1] << 8));
raw_data_out->accel_y = (int16_t)(accel_data[2] | (accel_data[3] << 8));
raw_data_out->accel_z = (int16_t)(accel_data[4] | (accel_data[5] << 8));
// Read gyroscope data (6 bytes)
ret = lsm6dso_i2c_reg_read_bytes(i2c_dev, LSM6DSO_REG_OUTX_L_G, gyro_data, 6);
if (ret != 0) {
LOG_ERR("Failed to read gyroscope data (err: %d).", ret);
return ret;
}
// Raw data is 16-bit signed integer, low byte first
raw_data_out->gyro_x = (int16_t)(gyro_data[0] | (gyro_data[1] << 8));
raw_data_out->gyro_y = (int16_t)(gyro_data[2] | (gyro_data[3] << 8));
raw_data_out->gyro_z = (int16_t)(gyro_data[4] | (gyro_data[5] << 8));
return 0;
}
/**
* @brief Display raw accelerometer and gyroscope data.
* @param raw_data Pointer to structure containing raw data.
* @param count Polling counter.
*/
static void lsm6dso_display_raw_data(const struct lsm6dso_raw_data *raw_data, int count)
{
printf("accel raw: X:%d Y:%d Z:%d (LSB)\n",
raw_data->accel_x, raw_data->accel_y, raw_data->accel_z);
printf("gyro raw: X:%d Y:%d Z:%d (LSB)\n",
raw_data->gyro_x, raw_data->gyro_y, raw_data->gyro_z);
printf("trig_cnt:%d\n\n", count);
}
// --- Main function ---
int main(void)
{
const struct device *i2c_dev = DEVICE_DT_GET(DT_NODELABEL(i2c30));
struct lsm6dso_raw_data sensor_data;
static int trig_cnt = 0; // Ensure only initialized once in main scope
if (!device_is_ready(i2c_dev)) {
LOG_ERR("I2C device %s is not ready!", i2c_dev->name);
return 0;
}
LOG_INF("I2C device %s is ready.", i2c_dev->name);
if (lsm6dso_init(i2c_dev) != 0) {
LOG_ERR("Failed to initialize LSM6DSO sensor.");
return 0;
}
printf("Testing LSM6DSO sensor in polling mode (custom I2C driver) - Raw Data Output.\n\n");
while (1) {
trig_cnt++; // Increment counter at the start of each loop
// Fetch raw data
if (lsm6dso_fetch_raw_data(i2c_dev, &sensor_data) == 0) {
// Display raw data
lsm6dso_display_raw_data(&sensor_data, trig_cnt);
} else {
LOG_ERR("Failed to fetch data.");
}
k_sleep(K_MSEC(1000)); // Read once every second
}
return 0;
}
XIAO nRF54L15 Sense MIC
MSM261DGT006は、パルス密度変調(PDM)データを出力するデジタルマイクロフォン(DMIC)で、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:
ドライバーがマイクロフォンからオーディオデータのバッファを正常に受信したことを確認します。16進アドレス(例: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に保存されます

技術サポート & 製品ディスカッション
弊社製品をお選びいただき、ありがとうございます!弊社製品での体験が可能な限りスムーズになるよう、さまざまなサポートを提供いたします。異なる好みやニーズに対応するため、複数のコミュニケーションチャンネルを用意しています。