diff --git a/samples/drivers/i2s/output/CMakeLists.txt b/samples/drivers/i2s/output/CMakeLists.txt new file mode 100644 index 0000000000..100cc967f2 --- /dev/null +++ b/samples/drivers/i2s/output/CMakeLists.txt @@ -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_output) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/drivers/i2s/output/README.rst b/samples/drivers/i2s/output/README.rst new file mode 100644 index 0000000000..d8e10d81e0 --- /dev/null +++ b/samples/drivers/i2s/output/README.rst @@ -0,0 +1,36 @@ +.. zephyr:code-sample:: i2s-output + :name: I2S output + :relevant-api: i2s_interface + + Send I2S output stream + +Overview +******** + +This sample demonstrates how to use an I2S driver to send an output stream of +audio data. Currently, no codec is used with this sample. The I2S output can +be verified with a signal analyzer. + +The sample will send a short burst of audio data, consisting of a sine wave. +The I2S TX queue will then be drained, and audio output will stop. + +Requirements +************ + +The I2S device to be used by the sample is specified by defining +a devicetree alias named ``i2s_tx`` + +This sample has been tested on :ref:`mimxrt1060_evk` (mimxrt1060_evkb) + +Building and Running +******************** + +The code can be found in :zephyr_file:`samples/drivers/i2s/output`. + +To build and flash the application: + +.. zephyr-app-commands:: + :zephyr-app: samples/drivers/i2s/output + :board: mimxrt1060_evkb + :goals: build flash + :compact: diff --git a/samples/drivers/i2s/output/boards/mimxrt1060_evkb.conf b/samples/drivers/i2s/output/boards/mimxrt1060_evkb.conf new file mode 100644 index 0000000000..c27a69285c --- /dev/null +++ b/samples/drivers/i2s/output/boards/mimxrt1060_evkb.conf @@ -0,0 +1,2 @@ +# Raise DMA TCD Queue size, as this is required by the I2S SAI driver +CONFIG_DMA_TCD_QUEUE_SIZE=4 diff --git a/samples/drivers/i2s/output/boards/mimxrt1060_evkb.overlay b/samples/drivers/i2s/output/boards/mimxrt1060_evkb.overlay new file mode 100644 index 0000000000..92ffdea0a3 --- /dev/null +++ b/samples/drivers/i2s/output/boards/mimxrt1060_evkb.overlay @@ -0,0 +1,11 @@ +/* + * Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + i2s-tx = &sai1; + }; +}; diff --git a/samples/drivers/i2s/output/prj.conf b/samples/drivers/i2s/output/prj.conf new file mode 100644 index 0000000000..c1378264b9 --- /dev/null +++ b/samples/drivers/i2s/output/prj.conf @@ -0,0 +1 @@ +CONFIG_I2S=y diff --git a/samples/drivers/i2s/output/sample.yaml b/samples/drivers/i2s/output/sample.yaml new file mode 100644 index 0000000000..8763b5128b --- /dev/null +++ b/samples/drivers/i2s/output/sample.yaml @@ -0,0 +1,9 @@ +sample: + description: I2S Output Sample + name: i2s_output +common: + tags: drivers + depends_on: i2s +tests: + sample.drivers.i2s.output: + filter: dt_alias_exists("i2s-tx") diff --git a/samples/drivers/i2s/output/src/main.c b/samples/drivers/i2s/output/src/main.c new file mode 100644 index 0000000000..34f1629ceb --- /dev/null +++ b/samples/drivers/i2s/output/src/main.c @@ -0,0 +1,128 @@ +/* + * Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#define SAMPLE_NO 64 + +/* The data represent a sine wave */ +static int16_t data[SAMPLE_NO] = { + 3211, 6392, 9511, 12539, 15446, 18204, 20787, 23169, + 25329, 27244, 28897, 30272, 31356, 32137, 32609, 32767, + 32609, 32137, 31356, 30272, 28897, 27244, 25329, 23169, + 20787, 18204, 15446, 12539, 9511, 6392, 3211, 0, + -3212, -6393, -9512, -12540, -15447, -18205, -20788, -23170, + -25330, -27245, -28898, -30273, -31357, -32138, -32610, -32767, + -32610, -32138, -31357, -30273, -28898, -27245, -25330, -23170, + -20788, -18205, -15447, -12540, -9512, -6393, -3212, -1, +}; + +/* Fill buffer with sine wave on left channel, and sine wave shifted by + * 90 degrees on right channel. "att" represents a power of two to attenuate + * the samples by + */ +static void fill_buf(int16_t *tx_block, int att) +{ + int r_idx; + + for (int i = 0; i < SAMPLE_NO; i++) { + /* Left channel is sine wave */ + tx_block[2 * i] = data[i] / (1 << att); + /* Right channel is same sine wave, shifted by 90 degrees */ + r_idx = (i + (ARRAY_SIZE(data) / 4)) % ARRAY_SIZE(data); + tx_block[2 * i + 1] = data[r_idx] / (1 << att); + } +} + +#define NUM_BLOCKS 20 +#define BLOCK_SIZE (2 * sizeof(data)) + +#ifdef CONFIG_NOCACHE_MEMORY + #define MEM_SLAB_CACHE_ATTR __nocache +#else + #define MEM_SLAB_CACHE_ATTR +#endif /* CONFIG_NOCACHE_MEMORY */ + +static char MEM_SLAB_CACHE_ATTR __aligned(WB_UP(32)) + _k_mem_slab_buf_tx_0_mem_slab[(NUM_BLOCKS) * WB_UP(BLOCK_SIZE)]; + +static STRUCT_SECTION_ITERABLE(k_mem_slab, tx_0_mem_slab) = + Z_MEM_SLAB_INITIALIZER(tx_0_mem_slab, _k_mem_slab_buf_tx_0_mem_slab, + WB_UP(BLOCK_SIZE), NUM_BLOCKS); + +int main(void) +{ + void *tx_block[NUM_BLOCKS]; + struct i2s_config i2s_cfg; + int ret; + uint32_t tx_idx; + const struct device *dev_i2s = DEVICE_DT_GET(DT_ALIAS(i2s_tx)); + + if (!device_is_ready(dev_i2s)) { + printf("I2S device not ready\n"); + return -ENODEV; + } + /* Configure I2S stream */ + i2s_cfg.word_size = 16U; + i2s_cfg.channels = 2U; + i2s_cfg.format = I2S_FMT_DATA_FORMAT_I2S; + i2s_cfg.frame_clk_freq = 44100; + i2s_cfg.block_size = BLOCK_SIZE; + i2s_cfg.timeout = 2000; + /* Configure the Transmit port as Master */ + i2s_cfg.options = I2S_OPT_FRAME_CLK_MASTER + | I2S_OPT_BIT_CLK_MASTER; + i2s_cfg.mem_slab = &tx_0_mem_slab; + ret = i2s_configure(dev_i2s, I2S_DIR_TX, &i2s_cfg); + if (ret < 0) { + printf("Failed to configure I2S stream\n"); + return ret; + } + + /* Prepare all TX blocks */ + for (tx_idx = 0; tx_idx < NUM_BLOCKS; tx_idx++) { + ret = k_mem_slab_alloc(&tx_0_mem_slab, &tx_block[tx_idx], + K_FOREVER); + if (ret < 0) { + printf("Failed to allocate TX block\n"); + return ret; + } + fill_buf((uint16_t *)tx_block[tx_idx], tx_idx % 3); + } + + tx_idx = 0; + /* Send first block */ + ret = i2s_write(dev_i2s, tx_block[tx_idx++], BLOCK_SIZE); + if (ret < 0) { + printf("Could not write TX buffer %d\n", tx_idx); + return ret; + } + /* Trigger the I2S transmission */ + ret = i2s_trigger(dev_i2s, I2S_DIR_TX, I2S_TRIGGER_START); + if (ret < 0) { + printf("Could not trigger I2S tx\n"); + return ret; + } + + for (; tx_idx < NUM_BLOCKS; ) { + ret = i2s_write(dev_i2s, tx_block[tx_idx++], BLOCK_SIZE); + if (ret < 0) { + printf("Could not write TX buffer %d\n", tx_idx); + return ret; + } + } + /* Drain TX queue */ + ret = i2s_trigger(dev_i2s, I2S_DIR_TX, I2S_TRIGGER_DRAIN); + if (ret < 0) { + printf("Could not trigger I2S tx\n"); + return ret; + } + printf("All I2S blocks written\n"); + return 0; +}