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 オーバーレイファイルの追加と conf ファイルの変更

XIAO nRF54L15 Sense IMU

6軸 IMU(慣性測定ユニット) LSM6DS3TR-C のようなセンサーは、加速度計とジャイロスコープを統合して、三次元空間における物体の動きと方向を測定します。具体的に、LSM6DS3TR-C には以下の機能があります:

加速度計機能:

  • X、Y、Z軸に沿った物体の加速度を測定します。物体の動き(例:静止、加速、減速)と傾きの変化(例:物体の角度)を感知できます。
  • 歩行、位置変化、振動などの検出に使用できます。

ジャイロスコープ機能:

  • 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;
}

次に、XIAO nRF54L15 を USB 経由でコンピューターに接続します。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 は、パルス密度変調(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
tip

このコマンドpython record.py -p **/dev/cu.usbmodemA0CBDDC33** -o output.wav -b 921600では、使用するシリアルポートに置き換える必要があります。

ステップ 3:

  • コマンドを実行すると、音声を録音するためにボタンを押すよう促されます。

音声を録音した後、ファイルはscriptsに保存されます

技術サポート & 製品ディスカッション

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

Loading Comments...