/** * Copyright (c) 2015 - 2020, Nordic Semiconductor ASA * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form, except as embedded into a Nordic * Semiconductor ASA integrated circuit in a product or a software update for * such product, must reproduce the above copyright notice, this list of * conditions and the following disclaimer in the documentation and/or other * materials provided with the distribution. * * 3. Neither the name of Nordic Semiconductor ASA nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * 4. This software, with or without modification, must only be used with a * Nordic Semiconductor ASA integrated circuit. * * 5. Any software provided in binary form under this license must not be reverse * engineered, decompiled, modified and/or disassembled. * * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ /** @file * @defgroup i2s_example_main main.c * @{ * @ingroup i2s_example * * @brief I2S Example Application main file. * * This file contains the source code for a sample application using I2S. */ #include #include "nrf_drv_i2s.h" #include "nrf_delay.h" #include "app_util_platform.h" #include "app_error.h" #include "boards.h" #include "nrf_log.h" #include "nrf_log_ctrl.h" #include "nrf_log_default_backends.h" #define LED_OK BSP_BOARD_LED_0 #define LED_ERROR BSP_BOARD_LED_1 #define I2S_DATA_BLOCK_WORDS 512 static uint32_t m_buffer_rx[2][I2S_DATA_BLOCK_WORDS]; static uint32_t m_buffer_tx[2][I2S_DATA_BLOCK_WORDS]; // Delay time between consecutive I2S transfers performed in the main loop // (in milliseconds). #define PAUSE_TIME 500 // Number of blocks of data to be contained in each transfer. #define BLOCKS_TO_TRANSFER 20 static uint8_t volatile m_blocks_transferred = 0; static uint8_t m_zero_samples_to_ignore = 0; static uint16_t m_sample_value_to_send; static uint16_t m_sample_value_expected; static bool m_error_encountered; static uint32_t * volatile mp_block_to_fill = NULL; static uint32_t const * volatile mp_block_to_check = NULL; static void prepare_tx_data(uint32_t * p_block) { // These variables will be both zero only at the very beginning of each // transfer, so we use them as the indication that the re-initialization // should be performed. if (m_blocks_transferred == 0 && m_zero_samples_to_ignore == 0) { // Number of initial samples (actually pairs of L/R samples) with zero // values that should be ignored - see the comment in 'check_samples'. m_zero_samples_to_ignore = 2; m_sample_value_to_send = 0xCAFE; m_sample_value_expected = 0xCAFE; m_error_encountered = false; } // [each data word contains two 16-bit samples] uint16_t i; for (i = 0; i < I2S_DATA_BLOCK_WORDS; ++i) { uint16_t sample_l = m_sample_value_to_send - 1; uint16_t sample_r = m_sample_value_to_send + 1; ++m_sample_value_to_send; uint32_t * p_word = &p_block[i]; ((uint16_t *)p_word)[0] = sample_l; ((uint16_t *)p_word)[1] = sample_r; } } static bool check_samples(uint32_t const * p_block) { // [each data word contains two 16-bit samples] uint16_t i; for (i = 0; i < I2S_DATA_BLOCK_WORDS; ++i) { uint32_t const * p_word = &p_block[i]; uint16_t actual_sample_l = ((uint16_t const *)p_word)[0]; uint16_t actual_sample_r = ((uint16_t const *)p_word)[1]; // Normally a couple of initial samples sent by the I2S peripheral // will have zero values, because it starts to output the clock // before the actual data is fetched by EasyDMA. As we are dealing // with streaming the initial zero samples can be simply ignored. if (m_zero_samples_to_ignore > 0 && actual_sample_l == 0 && actual_sample_r == 0) { --m_zero_samples_to_ignore; } else { m_zero_samples_to_ignore = 0; uint16_t expected_sample_l = m_sample_value_expected - 1; uint16_t expected_sample_r = m_sample_value_expected + 1; ++m_sample_value_expected; if (actual_sample_l != expected_sample_l || actual_sample_r != expected_sample_r) { NRF_LOG_INFO("%3u: %04x/%04x, expected: %04x/%04x (i: %u)", m_blocks_transferred, actual_sample_l, actual_sample_r, expected_sample_l, expected_sample_r, i); return false; } } } NRF_LOG_INFO("%3u: OK", m_blocks_transferred); return true; } static void check_rx_data(uint32_t const * p_block) { ++m_blocks_transferred; if (!m_error_encountered) { m_error_encountered = !check_samples(p_block); } if (m_error_encountered) { bsp_board_led_off(LED_OK); bsp_board_led_invert(LED_ERROR); } else { bsp_board_led_off(LED_ERROR); bsp_board_led_invert(LED_OK); } } static void data_handler(nrf_drv_i2s_buffers_t const * p_released, uint32_t status) { // 'nrf_drv_i2s_next_buffers_set' is called directly from the handler // each time next buffers are requested, so data corruption is not // expected. ASSERT(p_released); // When the handler is called after the transfer has been stopped // (no next buffers are needed, only the used buffers are to be // released), there is nothing to do. if (!(status & NRFX_I2S_STATUS_NEXT_BUFFERS_NEEDED)) { return; } // First call of this handler occurs right after the transfer is started. // No data has been transferred yet at this point, so there is nothing to // check. Only the buffers for the next part of the transfer should be // provided. if (!p_released->p_rx_buffer) { nrf_drv_i2s_buffers_t const next_buffers = { .p_rx_buffer = m_buffer_rx[1], .p_tx_buffer = m_buffer_tx[1], }; APP_ERROR_CHECK(nrf_drv_i2s_next_buffers_set(&next_buffers)); mp_block_to_fill = m_buffer_tx[1]; } else { mp_block_to_check = p_released->p_rx_buffer; // The driver has just finished accessing the buffers pointed by // 'p_released'. They can be used for the next part of the transfer // that will be scheduled now. APP_ERROR_CHECK(nrf_drv_i2s_next_buffers_set(p_released)); // The pointer needs to be typecasted here, so that it is possible to // modify the content it is pointing to (it is marked in the structure // as pointing to constant data because the driver is not supposed to // modify the provided data). mp_block_to_fill = (uint32_t *)p_released->p_tx_buffer; } } void app_error_fault_handler(uint32_t id, uint32_t pc, uint32_t info) { bsp_board_leds_on(); app_error_save_and_stop(id, pc, info); } int main(void) { uint32_t err_code = NRF_SUCCESS; bsp_board_init(BSP_INIT_LEDS); err_code = NRF_LOG_INIT(NULL); APP_ERROR_CHECK(err_code); NRF_LOG_DEFAULT_BACKENDS_INIT(); NRF_LOG_INFO("I2S loopback example started."); nrf_drv_i2s_config_t config = NRF_DRV_I2S_DEFAULT_CONFIG; // In Master mode the MCK frequency and the MCK/LRCK ratio should be // set properly in order to achieve desired audio sample rate (which // is equivalent to the LRCK frequency). // For the following settings we'll get the LRCK frequency equal to // 15873 Hz (the closest one to 16 kHz that is possible to achieve). config.sdin_pin = I2S_SDIN_PIN; config.sdout_pin = I2S_SDOUT_PIN; config.mck_setup = NRF_I2S_MCK_32MDIV21; config.ratio = NRF_I2S_RATIO_96X; config.channels = NRF_I2S_CHANNELS_STEREO; err_code = nrf_drv_i2s_init(&config, data_handler); APP_ERROR_CHECK(err_code); for (;;) { m_blocks_transferred = 0; mp_block_to_fill = NULL; mp_block_to_check = NULL; prepare_tx_data(m_buffer_tx[0]); nrf_drv_i2s_buffers_t const initial_buffers = { .p_tx_buffer = m_buffer_tx[0], .p_rx_buffer = m_buffer_rx[0], }; err_code = nrf_drv_i2s_start(&initial_buffers, I2S_DATA_BLOCK_WORDS, 0); APP_ERROR_CHECK(err_code); do { // Wait for an event. __WFE(); // Clear the event register. __SEV(); __WFE(); if (mp_block_to_fill) { prepare_tx_data(mp_block_to_fill); mp_block_to_fill = NULL; } if (mp_block_to_check) { check_rx_data(mp_block_to_check); mp_block_to_check = NULL; } } while (m_blocks_transferred < BLOCKS_TO_TRANSFER); nrf_drv_i2s_stop(); NRF_LOG_FLUSH(); bsp_board_leds_off(); nrf_delay_ms(PAUSE_TIME); } } /** @} */