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