Skip to content

Commit

Permalink
samples: i2s: Add i2s_codec example
Browse files Browse the repository at this point in the history
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 <[email protected]>
Signed-off-by: Vit Stanicek <[email protected]>
  • Loading branch information
hakehuang authored and carlescufi committed Aug 29, 2024
1 parent 446282c commit 8b914f6
Show file tree
Hide file tree
Showing 10 changed files with 384 additions and 0 deletions.
8 changes: 8 additions & 0 deletions samples/drivers/i2s/i2s_codec/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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 changes: 31 additions & 0 deletions samples/drivers/i2s/i2s_codec/Kconfig
Original file line number Diff line number Diff line change
@@ -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 changes: 33 additions & 0 deletions samples/drivers/i2s/i2s_codec/README.rst
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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";
};
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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 changes: 3 additions & 0 deletions samples/drivers/i2s/i2s_codec/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
CONFIG_I2S=y
CONFIG_AUDIO=y
CONFIG_AUDIO_DMIC=y
12 changes: 12 additions & 0 deletions samples/drivers/i2s/i2s_codec/sample.yaml
Original file line number Diff line number Diff line change
@@ -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 changes: 219 additions & 0 deletions samples/drivers/i2s/i2s_codec/src/main.c
Original file line number Diff line number Diff line change
@@ -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;
}
}

0 comments on commit 8b914f6

Please sign in to comment.