Pular para o conteúdo principal

Uso do Sensor integrado do Seeed Studio XIAO nRF54L15 Sense

O código de exemplo a seguir foi desenvolvido para o PlatformIO, mas também é compatível com o nRF Connect SDK.

dica

Com base no VS Code, se você quiser usar o caso a seguir no nRF Connect SDK, consulte o link fornecido, adicione o arquivo app.overlay e modifique o conteúdo em prj.conf

XIAO nRF54L15 Adicionar arquivo overlay e modificar o arquivo conf.

IMU do XIAO nRF54L15 Sense​

IMU de 6 eixos (Unidade de Medição Inercial) Sensores como o LSM6DS3TR-C integram acelerômetros e giroscópios para medir o movimento e a orientação de um objeto no espaço tridimensional. Especificamente, o LSM6DS3TR-C possui as seguintes características:

Função do acelerômetro:

  • Mede a aceleração de um objeto ao longo dos eixos X, Y e Z. É capaz de detectar o movimento do objeto (por exemplo, repouso, aceleração, desaceleração) e mudanças de inclinação (por exemplo, ângulo do objeto).
  • Pode ser usado para detectar marcha, mudanças de posição, vibrações, etc.

Função do giroscópio (Giroscópio):

  • Mede a velocidade angular de um objeto em torno dos eixos X, Y e Z, ou seja, a rotação do objeto.
  • Pode ser usado para detectar rotação, taxa de rotação e mudança de direção.
  • O ângulo do eixo X ( Roll ) é o ângulo na direção de rotação em torno do eixo X.
  • O ângulo do eixo Y ( Pitch ) é o ângulo na direção de rotação em torno do eixo Y.
  • O ângulo do eixo Z ( Yaw ) é o ângulo na direção de rotação em torno do eixo Z.

Driver do IMU​

Para simplificar sua experiência de desenvolvimento e garantir um início rápido com este programa de IMU, utilizamos a plataforma PlatformIO para escrever o código de driver necessário. O PlatformIO oferece um ambiente abrangente e eficiente para desenvolvimento embarcado, tornando-o uma escolha ideal para o XIAO nRF54L15 Sense.

Antes de prosseguir, certifique-se de que seu ambiente de desenvolvimento esteja configurado corretamente. Se você ainda não adicionou a placa de desenvolvimento Seeed Studio XIAO nRF54L15 à sua configuração do PlatformIO, consulte este link para obter instruções detalhadas sobre como configurá-la. Esta etapa essencial permitirá que o PlatformIO reconheça e compile corretamente o código para sua placa.

Quando seu ambiente estiver pronto, o driver do IMU permitirá que você leia dados brutos do sensor LSM6DS3TR-C. Esses dados incluem:

  • Valores brutos do acelerômetro (accel raw): Representando a aceleração ao longo dos eixos X, Y e Z.

  • Valores brutos do giroscópio (gyro raw): Indicando a velocidade angular em torno dos eixos X, Y e Z.

  • Contagem de acionamentos (trig_cnt): Um contador que é incrementado a cada nova amostra de dados.



Baixe o repositório para C:\Users\xxx\.platformio\platforms e abra a pasta examples\zephyr-imu no VS Code. Em seguida, clique em main.c e você verá o seguinte código:

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

Agora, conecte seu XIAO nRF54L15 ao computador via USB. No VS Code:

  • Compilar: Clique no ícone "Build" (marca de verificação) na barra de ferramentas do PlatformIO na parte inferior do VS Code, ou use a barra lateral do PlatformIO: PROJECT TASKS -> nome_do_seu_projeto -> General -> Build.

  • Enviar: Após uma compilação bem-sucedida, clique no ícone "Upload" (seta para a direita) na barra de ferramentas do PlatformIO, ou use a barra lateral do PlatformIO: PROJECT TASKS -> nome_do_seu_projeto -> General -> Upload.

Após um envio bem-sucedido, você deverá ver uma saída semelhante ao exemplo abaixo no Monitor de Dispositivo do PlatformIO (PROJECT TASKS -> nome_do_seu_projeto -> General -> Monitor). Esta saída serial exibe leituras em tempo real do acelerômetro e do giroscópio, fornecendo informações essenciais sobre o movimento e a orientação do seu dispositivo.

XIAO nRF54L15 BLE Advertising Power Consumption

Saída de dados do IMU em tempo real do Monitor de Dispositivo do PlatformIO, exibindo leituras brutas do acelerômetro e do giroscópio.

Esses dados brutos formam a base para diversas aplicações, desde a simples detecção de movimento até o rastreamento complexo de orientação, mediante a aplicação de algoritmos adequados (por exemplo, filtragem, fusão de sensores).

MIC do XIAO nRF54L15 Sense​

O MSM261DGT006 é um Microfone Digital (DMIC) que emite dados de Modulação por Densidade de Pulso (PDM), tornando-o adequado para interface digital direta com microcontroladores como o XIAO nRF54L15 Sense. Nosso driver DMIC foi especificamente projetado para lidar com essa saída PDM, convertê-la em amostras de áudio utilizáveis e processá-la para diversas aplicações.

O driver inicia o microfone, define a taxa de amostragem apropriada (por exemplo, 16000 Hz para áudio padrão) e configura a frequência do clock PDM. Em seguida, lê continuamente buffers de amostras do microfone, permitindo a captura de áudio em tempo real.

A saída do driver DMIC, quando visualizada no Monitor de Dispositivo do PlatformIO, fornece informações essenciais sobre o funcionamento do microfone e os dados de áudio recebidos. As principais mensagens que você observará incluem:

  • DMIC sample=: Indica o início do processo de amostragem do DMIC.

  • PCM output rate: 16000, channels: 1: Confirma as configurações de saída de áudio, tipicamente uma taxa de amostragem de 16 kHz e um único canal (mono) de áudio.

  • dmic_nrf_pdm: PDM clock frequency: 1280000, actual PCM rate: 16000: Mostra a frequência interna do clock PDM e a taxa de amostragem de áudio PCM resultante.

  • got buffer 0x... of 3200 bytes: Confirma que o driver recebeu com sucesso um buffer de dados de áudio do microfone. O endereço hexadecimal (ex.: 0x20004C8) e o tamanho em bytes (ex.: 3200 bytes) são exibidos. Esses buffers contêm as amostras de áudio brutas que podem ser processadas ou analisadas.

  • dmix_sample: Exiting: Indica que o processo de amostragem do DMIC foi interrompido.

Abaixo está um exemplo da saída típica que você pode esperar ver no Monitor de Dispositivo do PlatformIO quando o driver DMIC está em execução, ilustrando a captura e o armazenamento em buffer de dados de áudio com sucesso.

Driver DMIC​

Esses dados de áudio brutos, uma vez capturados, podem ser usados para uma ampla gama de aplicações, incluindo comandos de voz, detecção de eventos sonoros, monitoramento de ruído ambiental e tarefas de processamento de áudio mais complexas.

O exemplo de código a seguir demonstra como gravar áudio usando o botão de pressão na placa XIAO nRF54L15 e salvar o arquivo WAV gravado em um computador.


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

Em seguida, abra o terminal no diretório da pasta scripts e execute as seguintes operações, desde que o programa já tenha sido gravado.

Passo 1:

  • python3 -m pip install pyserial

Passo 2:

  • python record.py -p /dev/cu.usbmodemA0CBDDC33 -o output.wav -b 921600
dica

Neste comando python record.py -p **/dev/cu.usbmodemA0CBDDC33** -o output.wav -b 921600, você precisa substituir pela sua porta serial para uso.

Passo 3:

  • Após executar o comando, você será solicitado a pressionar o botão para gravar o som.

Após gravar o áudio, o arquivo será salvo nos scripts

Suporte Técnico e Discussão de Produtos​

Obrigado por escolher nossos produtos! Estamos aqui para fornecer diferentes tipos de suporte para garantir que sua experiência com nossos produtos seja a mais tranquila possível. Oferecemos vários canais de comunicação para atender a diferentes preferências e necessidades.

Loading Comments...