/* * Copyright (c) 2021 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include "codec.h" #include #include #include #include #include #if DT_NODE_EXISTS(DT_NODELABEL(i2s_rxtx)) #define I2S_RX_NODE DT_NODELABEL(i2s_rxtx) #define I2S_TX_NODE I2S_RX_NODE #else #define I2S_RX_NODE DT_NODELABEL(i2s_rx) #define I2S_TX_NODE DT_NODELABEL(i2s_tx) #endif /* Reduce echo delay when running on low ram devices */ #if CONFIG_SRAM_SIZE <= 48 #define ECHO_DELAY 30 #else #define ECHO_DELAY 10 #endif #define SAMPLE_FREQUENCY 44100 #define SAMPLE_BIT_WIDTH 16 #define BYTES_PER_SAMPLE sizeof(int16_t) #define NUMBER_OF_CHANNELS 2 /* Such block length provides an echo with the delay of 100ms or 33.33ms */ #define SAMPLES_PER_BLOCK ((SAMPLE_FREQUENCY / ECHO_DELAY) * NUMBER_OF_CHANNELS) #define INITIAL_BLOCKS 2 #define TIMEOUT 1000 #define SW0_NODE DT_ALIAS(sw0) #ifdef CONFIG_TOGGLE_ECHO_EFFECT_SW0 static struct gpio_dt_spec sw0_spec = GPIO_DT_SPEC_GET(SW0_NODE, gpios); #endif #define SW1_NODE DT_ALIAS(sw1) #ifdef CONFIG_STOP_START_STREAMS_SW1 static struct gpio_dt_spec sw1_spec = GPIO_DT_SPEC_GET(SW1_NODE, gpios); #endif #define BLOCK_SIZE (BYTES_PER_SAMPLE * SAMPLES_PER_BLOCK) #define BLOCK_COUNT (INITIAL_BLOCKS + 4) K_MEM_SLAB_DEFINE_STATIC(mem_slab, BLOCK_SIZE, BLOCK_COUNT, 4); static int16_t echo_block[SAMPLES_PER_BLOCK]; static volatile bool echo_enabled = true; static K_SEM_DEFINE(toggle_transfer, 1, 1); #ifdef CONFIG_TOGGLE_ECHO_EFFECT_SW0 static void sw0_handler(const struct device *dev, struct gpio_callback *cb, uint32_t pins) { bool enable = !echo_enabled; echo_enabled = enable; printk("Echo %sabled\n", (enable ? "en" : "dis")); } #endif #ifdef CONFIG_STOP_START_STREAMS_SW1 static void sw1_handler(const struct device *dev, struct gpio_callback *cb, uint32_t pins) { k_sem_give(&toggle_transfer); } #endif static bool init_buttons(void) { int ret; #ifdef CONFIG_TOGGLE_ECHO_EFFECT_SW0 static struct gpio_callback sw0_cb_data; if (!gpio_is_ready_dt(&sw0_spec)) { printk("%s is not ready\n", sw0_spec.port->name); return false; } ret = gpio_pin_configure_dt(&sw0_spec, GPIO_INPUT); if (ret < 0) { printk("Failed to configure %s pin %d: %d\n", sw0_spec.port->name, sw0_spec.pin, ret); return false; } ret = gpio_pin_interrupt_configure_dt(&sw0_spec, GPIO_INT_EDGE_TO_ACTIVE); if (ret < 0) { printk("Failed to configure interrupt on %s pin %d: %d\n", sw0_spec.port->name, sw0_spec.pin, ret); return false; } gpio_init_callback(&sw0_cb_data, sw0_handler, BIT(sw0_spec.pin)); gpio_add_callback(sw0_spec.port, &sw0_cb_data); printk("Press \"%s\" to toggle the echo effect\n", sw0_spec.port->name); #endif #ifdef CONFIG_STOP_START_STREAMS_SW1 static struct gpio_callback sw1_cb_data; if (!gpio_is_ready_dt(&sw1_spec)) { printk("%s is not ready\n", sw1_spec.port->name); return false; } ret = gpio_pin_configure_dt(&sw1_spec, GPIO_INPUT); if (ret < 0) { printk("Failed to configure %s pin %d: %d\n", sw1_spec.port->name, sw1_spec.pin, ret); return false; } ret = gpio_pin_interrupt_configure_dt(&sw1_spec, GPIO_INT_EDGE_TO_ACTIVE); if (ret < 0) { printk("Failed to configure interrupt on %s pin %d: %d\n", sw1_spec.port->name, sw1_spec.pin, ret); return false; } gpio_init_callback(&sw1_cb_data, sw1_handler, BIT(sw1_spec.pin)); gpio_add_callback(sw1_spec.port, &sw1_cb_data); printk("Press \"%s\" to stop/restart I2S streams\n", sw1_spec.port->name); #endif (void)ret; return true; } static void process_block_data(void *mem_block, uint32_t number_of_samples) { static bool clear_echo_block; if (echo_enabled) { for (int i = 0; i < number_of_samples; ++i) { int16_t *sample = &((int16_t *)mem_block)[i]; *sample += echo_block[i]; echo_block[i] = (*sample) / 2; } clear_echo_block = true; } else if (clear_echo_block) { clear_echo_block = false; memset(echo_block, 0, sizeof(echo_block)); } } static bool configure_streams(const struct device *i2s_dev_rx, const struct device *i2s_dev_tx, const struct i2s_config *config) { int ret; if (i2s_dev_rx == i2s_dev_tx) { ret = i2s_configure(i2s_dev_rx, I2S_DIR_BOTH, config); if (ret == 0) { return true; } /* -ENOSYS means that the RX and TX streams need to be * configured separately. */ if (ret != -ENOSYS) { printk("Failed to configure streams: %d\n", ret); return false; } } ret = i2s_configure(i2s_dev_rx, I2S_DIR_RX, config); if (ret < 0) { printk("Failed to configure RX stream: %d\n", ret); return false; } ret = i2s_configure(i2s_dev_tx, I2S_DIR_TX, config); if (ret < 0) { printk("Failed to configure TX stream: %d\n", ret); return false; } return true; } static bool prepare_transfer(const struct device *i2s_dev_rx, const struct device *i2s_dev_tx) { int ret; for (int i = 0; i < INITIAL_BLOCKS; ++i) { void *mem_block; ret = k_mem_slab_alloc(&mem_slab, &mem_block, K_NO_WAIT); if (ret < 0) { printk("Failed to allocate TX block %d: %d\n", i, ret); return false; } memset(mem_block, 0, BLOCK_SIZE); ret = i2s_write(i2s_dev_tx, mem_block, BLOCK_SIZE); if (ret < 0) { printk("Failed to write block %d: %d\n", i, ret); return false; } } return true; } static bool trigger_command(const struct device *i2s_dev_rx, const struct device *i2s_dev_tx, enum i2s_trigger_cmd cmd) { int ret; if (i2s_dev_rx == i2s_dev_tx) { ret = i2s_trigger(i2s_dev_rx, I2S_DIR_BOTH, cmd); if (ret == 0) { return true; } /* -ENOSYS means that commands for the RX and TX streams need * to be triggered separately. */ if (ret != -ENOSYS) { printk("Failed to trigger command %d: %d\n", cmd, ret); return false; } } ret = i2s_trigger(i2s_dev_rx, I2S_DIR_RX, cmd); if (ret < 0) { printk("Failed to trigger command %d on RX: %d\n", cmd, ret); return false; } ret = i2s_trigger(i2s_dev_tx, 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_rx = DEVICE_DT_GET(I2S_RX_NODE); const struct device *const i2s_dev_tx = DEVICE_DT_GET(I2S_TX_NODE); struct i2s_config config; printk("I2S echo sample\n"); #if DT_ON_BUS(DT_NODELABEL(wm8731), i2c) if (!init_wm8731_i2c()) { return 0; } #elif DT_NODE_HAS_STATUS(DT_NODELABEL(audio_codec), okay) const struct device *const codec_dev = DEVICE_DT_GET(DT_NODELABEL(audio_codec)); struct audio_codec_cfg audio_cfg; audio_cfg.dai_route = AUDIO_ROUTE_PLAYBACK_CAPTURE; audio_cfg.dai_type = AUDIO_DAI_TYPE_I2S; audio_cfg.dai_cfg.i2s.word_size = SAMPLE_BIT_WIDTH; audio_cfg.dai_cfg.i2s.channels = NUMBER_OF_CHANNELS; 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); #endif #if DT_ON_BUS(MAX9867_NODE, i2c) if (!init_max9867_i2c()) { return 0; } #endif if (!init_buttons()) { return 0; } if (!device_is_ready(i2s_dev_rx)) { printk("%s is not ready\n", i2s_dev_rx->name); return 0; } if (i2s_dev_rx != i2s_dev_tx && !device_is_ready(i2s_dev_tx)) { printk("%s is not ready\n", i2s_dev_tx->name); return 0; } config.word_size = SAMPLE_BIT_WIDTH; config.channels = NUMBER_OF_CHANNELS; config.format = I2S_FMT_DATA_FORMAT_I2S; /* * On MAX32655FTHR, MAX9867 MCLK is connected to external 12.2880 crystal * thus using slave mode */ #if CONFIG_BOARD_MAX32655FTHR_MAX32655_M4 config.options = I2S_OPT_BIT_CLK_SLAVE | I2S_OPT_FRAME_CLK_SLAVE; #else config.options = I2S_OPT_BIT_CLK_MASTER | I2S_OPT_FRAME_CLK_MASTER; #endif config.frame_clk_freq = SAMPLE_FREQUENCY; config.mem_slab = &mem_slab; config.block_size = BLOCK_SIZE; config.timeout = TIMEOUT; if (!configure_streams(i2s_dev_rx, i2s_dev_tx, &config)) { return 0; } for (;;) { k_sem_take(&toggle_transfer, K_FOREVER); if (!prepare_transfer(i2s_dev_rx, i2s_dev_tx)) { return 0; } if (!trigger_command(i2s_dev_rx, i2s_dev_tx, I2S_TRIGGER_START)) { return 0; } printk("Streams started\n"); while (k_sem_take(&toggle_transfer, K_NO_WAIT) != 0) { void *mem_block; uint32_t block_size; int ret; ret = i2s_read(i2s_dev_rx, &mem_block, &block_size); if (ret < 0) { printk("Failed to read data: %d\n", ret); break; } process_block_data(mem_block, SAMPLES_PER_BLOCK); ret = i2s_write(i2s_dev_tx, mem_block, block_size); if (ret < 0) { printk("Failed to write data: %d\n", ret); break; } } if (!trigger_command(i2s_dev_rx, i2s_dev_tx, I2S_TRIGGER_DROP)) { return 0; } printk("Streams stopped\n"); } }