samples: i2s: Add i2s_codec example

This example demonstrates I2S audio playback combined with the codec
API. It can use the dmic API for audio input.

Signed-off-by: Hake Huang <hake.huang@oss.nxp.com>
Signed-off-by: Vit Stanicek <vit.stanicek@nxp.com>
This commit is contained in:
Hake Huang 2024-03-03 00:41:04 +08:00 committed by Carles Cufí
commit 8b914f6805
10 changed files with 384 additions and 0 deletions

View file

@ -0,0 +1,8 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(i2s_rx_tx)
target_sources(app PRIVATE src/main.c)

View file

@ -0,0 +1,31 @@
# Copyright (c) 2024 NXP
# SPDX-License-Identifier: Apache-2.0
source "Kconfig.zephyr"
config I2S_INIT_BUFFERS
int "Initial count of audio data blocks"
default 2
help
Controls the initial count of audio data blocks, which are (optionally)
filled by data from the DMIC peripheral and played back by the I2S
output perihperal.
config SAMPLE_FREQ
int "Sample rate"
default 48000
help
Sample frequency of the system.
config USE_DMIC
bool "Use DMIC as an audio input"
if USE_DMIC
config DMIC_CHANNELS
int "Number of DMIC channels"
default 1
help
Count of DMIC channels to capture and process.
endif

View file

@ -0,0 +1,33 @@
.. zephyr:code-sample:: i2s_codec
:name: I2S codec
:relevant-api: i2s_interface
Process an audio stream to codec.
Overview
********
This sample demonstrates how to use an I2S driver in a simple processing of
an audio stream. It configures and starts from memory buffer or from DMIC to
record i2s data and send to codec with DMA.
Requirements
************
This sample has been tested on mimxrt595_evk/mimxrt595s/cm33
Building and Running
********************
The code can be found in :zephyr_file:`samples/drivers/i2s/i2s_codec`.
To build and flash the application:
.. zephyr-app-commands::
:zephyr-app: samples/drivers/i2s/i2s_codec
:board: mimxrt595_evk/mimxrt595s/cm33
:goals: build flash
:compact:
To run you can connect earphones to the lineout connect and hear the sound
from DMIC or from memory buffer.

View file

@ -0,0 +1,16 @@
#
# Copyright 2024 NXP
#
# SPDX-License-Identifier: Apache-2.0
#
CONFIG_DMA=y
CONFIG_I2S_MCUX_FLEXCOMM=y
CONFIG_I3C=y
CONFIG_HEAP_MEM_POOL_SIZE=81920
CONFIG_AUDIO=y
CONFIG_AUDIO_CODEC=y
CONFIG_AUDIO_CODEC_WM8904=y
CONFIG_I2S_INIT_BUFFERS=4
CONFIG_SAMPLE_FREQ=16000
CONFIG_USE_DMIC=y

View file

@ -0,0 +1,24 @@
/ {
aliases {
i2s-codec-rx = &i2s0;
i2s-codec-tx = &i2s1;
};
};
&i3c0 {
clk-divider = <20>;
status = "okay";
};
&i2s0 {
status = "okay";
};
&i2s1 {
status = "okay";
};
dmic_dev: &dmic0 {
status = "okay";
};

View file

@ -0,0 +1,16 @@
#
# Copyright 2024 NXP
#
# SPDX-License-Identifier: Apache-2.0
#
CONFIG_DMA=y
CONFIG_I2S_MCUX_FLEXCOMM=y
CONFIG_I3C=y
CONFIG_HEAP_MEM_POOL_SIZE=81920
CONFIG_AUDIO=y
CONFIG_AUDIO_CODEC=y
CONFIG_AUDIO_CODEC_WM8904=y
CONFIG_I2S_INIT_BUFFERS=4
CONFIG_SAMPLE_FREQ=16000
CONFIG_USE_DMIC=y

View file

@ -0,0 +1,22 @@
/ {
aliases {
i2s-codec-rx = &i2s0;
i2s-codec-tx = &i2s1;
};
};
&i3c0 {
status = "okay";
};
&i2s0 {
status = "okay";
};
&i2s1 {
status = "okay";
};
dmic_dev: &dmic0 {
status = "okay";
};

View file

@ -0,0 +1,3 @@
CONFIG_I2S=y
CONFIG_AUDIO=y
CONFIG_AUDIO_DMIC=y

View file

@ -0,0 +1,12 @@
sample:
name: codec sample
tests:
sample.drivers.i2s.codec:
tags: i2s
platform_allow:
- mimxrt595_evk/mimxrt595s/cm33
harness: console
harness_config:
type: one_line
regex:
- "start streams"

View file

@ -0,0 +1,219 @@
/*
* Copyright 2024 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/audio/dmic.h>
#include <zephyr/drivers/i2s.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/audio/codec.h>
#include <string.h>
#define I2S_CODEC_TX DT_ALIAS(i2s_codec_tx)
#define SAMPLE_FREQUENCY CONFIG_SAMPLE_FREQ
#define SAMPLE_BIT_WIDTH (16U)
#define BYTES_PER_SAMPLE sizeof(int16_t)
#if CONFIG_USE_DMIC
#define NUMBER_OF_CHANNELS CONFIG_DMIC_CHANNELS
#else
#define NUMBER_OF_CHANNELS (2U)
#endif
/* Such block length provides an echo with the delay of 100 ms. */
#define SAMPLES_PER_BLOCK ((SAMPLE_FREQUENCY / 10) * NUMBER_OF_CHANNELS)
#define INITIAL_BLOCKS CONFIG_I2S_INIT_BUFFERS
#define TIMEOUT (2000U)
#define BLOCK_SIZE (BYTES_PER_SAMPLE * SAMPLES_PER_BLOCK)
#define BLOCK_COUNT (INITIAL_BLOCKS + 32)
K_MEM_SLAB_DEFINE_STATIC(mem_slab, BLOCK_SIZE, BLOCK_COUNT, 4);
static bool configure_tx_streams(const struct device *i2s_dev, struct i2s_config *config)
{
int ret;
ret = i2s_configure(i2s_dev, I2S_DIR_TX, config);
if (ret < 0) {
printk("Failed to configure codec stream: %d\n", ret);
return false;
}
return true;
}
static bool trigger_command(const struct device *i2s_dev_codec,
enum i2s_trigger_cmd cmd)
{
int ret;
ret = i2s_trigger(i2s_dev_codec, I2S_DIR_TX, cmd);
if (ret < 0) {
printk("Failed to trigger command %d on TX: %d\n", cmd, ret);
return false;
}
return true;
}
int main(void)
{
const struct device *const i2s_dev_codec = DEVICE_DT_GET(I2S_CODEC_TX);
#if CONFIG_USE_DMIC
const struct device *const dmic_dev = DEVICE_DT_GET(DT_NODELABEL(dmic_dev));
#endif
const struct device *const codec_dev = DEVICE_DT_GET(DT_NODELABEL(audio_codec));
struct i2s_config config;
struct audio_codec_cfg audio_cfg;
int ret;
#if CONFIG_USE_DMIC
struct pcm_stream_cfg stream = {
.pcm_width = SAMPLE_BIT_WIDTH,
.mem_slab = &mem_slab,
};
struct dmic_cfg cfg = {
.io = {
/* These fields can be used to limit the PDM clock
* configurations that the driver is allowed to use
* to those supported by the microphone.
*/
.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,
},
};
#endif
printk("codec sample\n");
#if CONFIG_USE_DMIC
if (!device_is_ready(dmic_dev)) {
printk("%s is not ready", dmic_dev->name);
return 0;
}
#endif
if (!device_is_ready(i2s_dev_codec)) {
printk("%s is not ready\n", i2s_dev_codec->name);
return 0;
}
if (!device_is_ready(codec_dev)) {
printk("%s is not ready", codec_dev->name);
return 0;
}
audio_cfg.dai_route = AUDIO_ROUTE_PLAYBACK;
audio_cfg.dai_type = AUDIO_DAI_TYPE_I2S;
audio_cfg.dai_cfg.i2s.word_size = SAMPLE_BIT_WIDTH;
audio_cfg.dai_cfg.i2s.channels = 2;
audio_cfg.dai_cfg.i2s.format = I2S_FMT_DATA_FORMAT_I2S;
audio_cfg.dai_cfg.i2s.options = I2S_OPT_FRAME_CLK_MASTER;
audio_cfg.dai_cfg.i2s.frame_clk_freq = SAMPLE_FREQUENCY;
audio_cfg.dai_cfg.i2s.mem_slab = &mem_slab;
audio_cfg.dai_cfg.i2s.block_size = BLOCK_SIZE;
audio_codec_configure(codec_dev, &audio_cfg);
k_msleep(1000);
#if CONFIG_USE_DMIC
cfg.channel.req_num_chan = 2;
cfg.channel.req_chan_map_lo =
dmic_build_channel_map(0, 0, PDM_CHAN_LEFT) |
dmic_build_channel_map(1, 0, PDM_CHAN_RIGHT);
cfg.streams[0].pcm_rate = SAMPLE_FREQUENCY;
cfg.streams[0].block_size = BLOCK_SIZE;
printk("PCM output rate: %u, channels: %u\n",
cfg.streams[0].pcm_rate, cfg.channel.req_num_chan);
ret = dmic_configure(dmic_dev, &cfg);
if (ret < 0) {
printk("Failed to configure the driver: %d", ret);
return ret;
}
#endif
config.word_size = SAMPLE_BIT_WIDTH;
config.channels = NUMBER_OF_CHANNELS;
config.format = I2S_FMT_DATA_FORMAT_I2S;
config.options = I2S_OPT_BIT_CLK_MASTER | I2S_OPT_FRAME_CLK_MASTER;
config.frame_clk_freq = SAMPLE_FREQUENCY;
config.mem_slab = &mem_slab;
config.block_size = BLOCK_SIZE;
config.timeout = TIMEOUT;
if (!configure_tx_streams(i2s_dev_codec, &config)) {
printk("failure to config streams\n");
return 0;
}
printk("start streams\n");
for (;;) {
bool started = false;
#if CONFIG_USE_DMIC
ret = dmic_trigger(dmic_dev, DMIC_TRIGGER_START);
if (ret < 0) {
printk("START trigger failed: %d", ret);
return ret;
}
#endif
while (1) {
void *mem_block;
uint32_t block_size = BLOCK_SIZE;
int ret;
int i;
for (i = 0; i < 2; i++) {
#if CONFIG_USE_DMIC
ret = dmic_read(dmic_dev, 0,
&mem_block, &block_size, TIMEOUT);
if (ret < 0) {
printk("read failed: %d", ret);
break;
}
#else
ret = k_mem_slab_alloc(&mem_slab,
&mem_block, Z_TIMEOUT_TICKS(TIMEOUT));
if (ret < 0) {
printk("Failed to allocate TX block\n");
return 0;
}
#endif
ret = i2s_write(i2s_dev_codec, mem_block, block_size);
if (ret < 0) {
printk("Failed to write data: %d\n", ret);
break;
}
}
if (ret < 0) {
printk("error %d\n", ret);
break;
}
if (!started) {
i2s_trigger(i2s_dev_codec, I2S_DIR_TX, I2S_TRIGGER_START);
started = true;
}
}
if (!trigger_command(i2s_dev_codec,
I2S_TRIGGER_DROP)) {
printk("Send I2S trigger DRAIN failed: %d", ret);
return 0;
}
#if CONFIG_USE_DMIC
ret = dmic_trigger(dmic_dev, DMIC_TRIGGER_STOP);
if (ret < 0) {
printk("STOP trigger failed: %d", ret);
return 0;
}
#endif
printk("Streams stopped\n");
return 0;
}
}