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:
parent
446282cd22
commit
8b914f6805
10 changed files with 384 additions and 0 deletions
8
samples/drivers/i2s/i2s_codec/CMakeLists.txt
Normal file
8
samples/drivers/i2s/i2s_codec/CMakeLists.txt
Normal 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)
|
31
samples/drivers/i2s/i2s_codec/Kconfig
Normal file
31
samples/drivers/i2s/i2s_codec/Kconfig
Normal 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
|
33
samples/drivers/i2s/i2s_codec/README.rst
Normal file
33
samples/drivers/i2s/i2s_codec/README.rst
Normal 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.
|
|
@ -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
|
|
@ -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";
|
||||||
|
};
|
|
@ -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
|
|
@ -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";
|
||||||
|
};
|
3
samples/drivers/i2s/i2s_codec/prj.conf
Normal file
3
samples/drivers/i2s/i2s_codec/prj.conf
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
CONFIG_I2S=y
|
||||||
|
CONFIG_AUDIO=y
|
||||||
|
CONFIG_AUDIO_DMIC=y
|
12
samples/drivers/i2s/i2s_codec/sample.yaml
Normal file
12
samples/drivers/i2s/i2s_codec/sample.yaml
Normal 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"
|
219
samples/drivers/i2s/i2s_codec/src/main.c
Normal file
219
samples/drivers/i2s/i2s_codec/src/main.c
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue