How to play pcm raw data on nrf52840 DK through I2S ?

Hello,

I'm using nrf52840 DK to test 1khz tone. The nRF Connect SDK version is v2.6.0.

I put a pcm raw data array of 1khz tone in the code and used i2s write to send the data to i2s out.

const struct device *const i2s_dev_tx = DEVICE_DT_GET(I2S_TX_NODE);
const struct device *const i2s_dev_rx = DEVICE_DT_GET(I2S_TX_NODE);
struct i2s_config config;
#define SAMPLE_FREQUENCY    48000
#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 100 ms. */
#define SAMPLES_PER_BLOCK   ((SAMPLE_FREQUENCY / 10) * NUMBER_OF_CHANNELS)
#define INITIAL_BLOCKS      2
#define TIMEOUT             1000
#define BLOCK_SIZE  (BYTES_PER_SAMPLE * SAMPLES_PER_BLOCK)
#define BLOCK_COUNT (INITIAL_BLOCKS + 2)
K_MEM_SLAB_DEFINE_STATIC(mem_slab, BLOCK_SIZE, BLOCK_COUNT, 4);
static int16_t data_buf_tmp[9600];

static bool configure_streams(const struct device *i2s_dev_tx,const struct i2s_config *config);

static void i2s_init(void){
	int ret;

	if (!device_is_ready(i2s_dev_tx)) {
		LOG_ERR("%s is not ready", i2s_dev_tx->name);
	}
	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;

	ret = configure_streams(i2s_dev_tx, &config);
	if (ret < 0) {
		LOG_ERR("Failed to configure TX stream: %d", ret);
	}
}
static bool configure_streams(const struct device *i2s_dev_tx,
			      const struct i2s_config *config)
{
	int ret;

	ret = i2s_configure(i2s_dev_tx, I2S_DIR_TX, config);
	if (ret < 0) {
		LOG_ERR("Failed to configure TX stream: %d\n", ret);
		return false;
	}

	return true;
}

static void *mem_block;
static bool prepare_transfer(const struct device *i2s_dev_tx)
{
	int ret;

	for (int i = 0; i < INITIAL_BLOCKS; ++i) {

		ret = k_mem_slab_alloc(&mem_slab, &mem_block, K_NO_WAIT);
		if (ret < 0) {
			LOG_ERR("Failed to allocate TX block %d: %d", i, ret);
			return false;
		}

		memset(mem_block, 0, BLOCK_SIZE);

		ret = i2s_write(i2s_dev_tx, mem_block, BLOCK_SIZE);
		if (ret < 0) {
			LOG_ERR("Failed to write block %d: %d", i, ret);
			return false;
		}
	}

	return true;
}

int main(void)
{
  int ret;
  i2s_init();

  for(;;){
    prepare_transfer(i2s_dev_tx);
    ret = i2s_trigger(i2s_dev_tx, I2S_DIR_TX, I2S_TRIGGER_START);
    if (ret < 0) {
      LOG_ERR("Failed to trigger command I2S_TRIGGER_START on TX: %d\n", ret);
    }
    // Put data into the buffer
    for (int i = 0; i < 9600; i++) {
      ((int16_t*)mem_block)[i] = data[i];
    }

    for (int i = 0; i < SAMPLES_PER_BLOCK; ++i) {
      int16_t *sample = &((int16_t *)mem_block)[i];
      *sample += data_buf_tmp[i];
      data_buf_tmp[i] = (*sample) / 2;
    }
  
    ret = i2s_write(i2s_dev_tx, mem_block, BLOCK_SIZE);
    if (ret < 0) {
      LOG_ERR("Failed to write data: %d", ret);
    }else{
      LOG_INF("Success to write data.");
    }
    ret = i2s_trigger(i2s_dev_tx, I2S_DIR_TX, I2S_TRIGGER_DROP);
    if (ret < 0) {
      LOG_ERR("Failed to trigger command I2S_TRIGGER_DROP on TX: %d\n", ret);
    }
  }
  return 0;
}

My pcm raw data array is an int16 stereo array, arranged as:
[left_data0,right_data0,left_data1,right_data1,....]

After testing, it is found that the output sound is distorted. Is there anything that needs to be adjusted?

Parents
  • Hi,

    There are several potential sources for distortion (this thread has a good discussion on that). Can you describe the distortion or share a sound recording? Also, how is the data fomated/encoded? Can you share the data buffer you are playing out with this code for reference?

  • I have solved this problem by just changing this line

    ret = i2s_trigger(i2s_dev_tx, I2S_DIR_TX, I2S_TRIGGER_DROP);

    modify to

    ret = i2s_trigger(i2s_dev_tx, I2S_DIR_TX, I2S_TRIGGER_DRAIN);

    The output sound is no longer distorted and now I can hear a normal 1khz tone.

    But I have now encountered a new problem. When I want to play 1khz tone continuously, reset or noise will occur.

    When my code is set as follows:

    for(;;){
        prepare_transfer(i2s_dev_tx);
        // Put data into the tx buffer
        ...
        ret = i2s_trigger(i2s_dev_tx, I2S_DIR_TX, I2S_TRIGGER_START);
        if (ret < 0) {
          LOG_ERR("Failed to trigger command I2S_TRIGGER_START on TX: %d\n", ret);
        }
        ret = i2s_write(i2s_dev_tx, mem_block, BLOCK_SIZE);
        if (ret < 0) {
          LOG_ERR("Failed to write data: %d", ret);
        }else{
          LOG_INF("Success to write data.");
        }
        ret = i2s_trigger(i2s_dev_tx, I2S_DIR_TX, I2S_TRIGGER_DRAIN);
        if (ret < 0) {
          LOG_ERR("Failed to trigger command I2S_TRIGGER_DROP on TX: %d\n", ret);
        }
      }

    After playing for a period of time, an error will occur causing a reset. The error log is as follows:

    *** Booting nRF Connect SDK v3.5.99-ncs1 ***
    [00:00:00.330,932] <inf> i2s_nrfx: I2S MCK frequency: 1523809, actual PCM rate: 47619
    [00:00:00.333,801] <inf> central_test: loop times:1
    [00:00:00.340,179] <inf> central_test: Success to write data.
    [00:00:00.340,209] <inf> central_test: loop times:2
    [00:00:00.342,041] <err> i2s_nrfx: Cannot write in state: 3
    [00:00:00.342,071] <err> central_test: Failed to write block 0: -5
    [00:00:00.344,757] <err> central_test: Failed to trigger command I2S_TRIGGER_START on TX: -5
    
    [00:00:00.344,787] <err> i2s_nrfx: Cannot write in state: 3
    [00:00:00.344,787] <err> central_test: Failed to write data: -5
    [00:00:00.344,787] <err> central_test: Failed to trigger command I2S_TRIGGER_DROP on TX: -5
    
    [00:00:00.344,818] <inf> central_test: loop times:3
    [00:00:00.346,618] <err> i2s_nrfx: Cannot write in state: 3
    [00:00:00.346,649] <err> central_test: Failed to write block 0: -5
    [00:00:00.349,334] <err> central_test: Failed to trigger command I2S_TRIGGER_START on TX: -5
    
    [00:00:00.349,365] <err> i2s_nrfx: Cannot write in state: 3
    [00:00:00.349,365] <err> central_test: Failed to write data: -5
    [00:00:00.349,365] <err> central_test: Failed to trigger command I2S_TRIGGER_DROP on TX: -5
    
    [00:00:00.349,395] <inf> central_test: loop times:4
    [00:00:00.349,395] <err> central_test: Failed to allocate TX block 0: -12
    [00:00:00.349,395] <err> os: ***** MPU FAULT *****
    [00:00:00.349,426] <err> os:   Data Access Violation
    [00:00:00.349,426] <err> os:   MMFAR Address: 0x0
    [00:00:00.349,426] <err> os: r0/a1:  0x00000000  r1/a2:  0x2000139c  r2/a3:  0x00000001
    [00:00:00.349,456] <err> os: r3/a4:  0x00000000 r12/ip:  0x00000000 r14/lr:  0x00001c61
    [00:00:00.349,456] <err> os:  xpsr:  0x81000000
    [00:00:00.349,487] <err> os: Faulting instruction address (r15/pc): 0x00000d3e
    [00:00:00.349,517] <err> os: >>> ZEPHYR FATAL ERROR 19: Unknown error on CPU 0
    [00:00:00.349,548] <err> os: Current thread: 0x20000898 (unknown)
    [00:00:01.292,724] <err> fatal_error: Resetting system

    When I move prepare_transfer outside the for-loop, although it is no longer reset, there is noise.

    The error log is as follows:

    [00:00:00.393,951] <err> central_test: Failed to trigger command I2S_TRIGGER_START on TX: -5
    
    [00:00:00.393,981] <err> i2s_nrfx: Cannot write in state: 3
    [00:00:00.393,981] <err> central_test: Failed to write data: -5
    [00:00:00.393,981] <err> central_test: Failed to trigger command I2S_TRIGGER_DROP on TX: -5

    When I move i2s_trigger outside the for-loop

    An error will occur after i2s_write is played several times.

    *** Booting nRF Connect SDK v3.5.99-ncs1 ***
    [00:00:00.386,260] <inf> i2s_nrfx: I2S MCK frequency: 1523809, actual PCM rate: 47619
    [00:00:00.395,660] <inf> central_test: Success to write data.
    [00:00:00.395,660] <inf> central_test: Success to write data.
    [00:00:00.396,026] <inf> central_test: Success to write data.
    [00:00:00.396,057] <inf> central_test: Success to write data.
    [00:00:00.496,856] <inf> central_test: Success to write data.
    [00:00:01.496,948] <err> central_test: Failed to write data: -11
    [00:00:02.497,100] <err> central_test: Failed to write data: -11
    [00:00:03.497,253] <err> central_test: Failed to write data: -11
    [00:00:04.497,375] <err> central_test: Failed to write data: -11

    How to set up so that i2s write can continuously play 1khz tone?

  • Hi,

    I am glad to hear you resovled the first issue with distortion.

    Regarding the second issue, calling your prepare_transfer() in the main loop allocats memory with k_mem_slab_alloc(). As this is never freeed, calling this function in a loop will cause a memory leak, so that it fails is expected, so moving it out of the loop is needed.

    I assume the noise you hear is due to a pause in the playout? The sequence you play out should be so that the start of the sequence match the end (with no step in the signal) so that when played back-to-back it gives a perfect sine wave. However, in this approach you are doing separate transactions (stopping and re-starting and that takes some CPU time. That is fixable though, as the I2S peripheral's transmit and receive pointer registers are double buffered (see specification), so to play out continiously you can start, and then supply a new buffer regularily (if a simple sine way you could point to the same buffer again and again). Then you should not trigger I2S_TRIGGER_DRAIN, as this will stop transmission, which is not what you want if you want to send a continious signal. Essentially, it should be enough to call i2s_write() in a loop after starting.

  • I assume the noise you hear is due to a pause in the playout?

    Yes, according to the frequency of noise, I think it is caused by I2S_TRIGGER_DRAIN.

    I moved the i2s trigger outside the for-loop so that only i2s_write is in the loop.

      ret = i2s_trigger(i2s_dev_tx, I2S_DIR_TX, I2S_TRIGGER_START);
      if (ret < 0) {
        LOG_ERR("Failed to trigger command I2S_TRIGGER_START on TX: %d\n", ret);
      }
      for(;;){
        ret = i2s_write(i2s_dev_tx, mem_block, BLOCK_SIZE);
        if (ret < 0) {
          LOG_ERR("Failed to write data: %d", ret);
        }else{
          LOG_INF("Success to write data.");
        }
      }
      ret = i2s_trigger(i2s_dev_tx, I2S_DIR_TX, I2S_TRIGGER_DRAIN);
      if (ret < 0) {
        LOG_ERR("Failed to trigger command I2S_TRIGGER_DRAIN on TX: %d\n", ret);
      }

    But after playing it several times, an -EAGAIN error appeared. After that, the sound could no longer be played.

    *** Booting nRF Connect SDK v3.5.99-ncs1 ***
    [00:00:00.386,260] <inf> i2s_nrfx: I2S MCK frequency: 1523809, actual PCM rate: 47619
    [00:00:00.395,660] <inf> central_test: Success to write data.
    [00:00:00.395,660] <inf> central_test: Success to write data.
    [00:00:00.396,026] <inf> central_test: Success to write data.
    [00:00:00.396,057] <inf> central_test: Success to write data.
    [00:00:00.496,856] <inf> central_test: Success to write data.
    [00:00:01.496,948] <err> central_test: Failed to write data: -11
    [00:00:02.497,100] <err> central_test: Failed to write data: -11
    [00:00:03.497,253] <err> central_test: Failed to write data: -11
    [00:00:04.497,375] <err> central_test: Failed to write data: -11

  • Hi,

    I see. If you call i2s_write() with a short timeotu, -EAGAIN (-11) is expected to happen from time to time. But it should succeed again after a buffer is played out so that there is room for queueing another. Does providing a larger timeout in the i2s_config struct you pass to i2s_configure() make a difference? Also, could there be other threads or interrupts in your application that is preventing the I2S driver from doing it's work on time?

    This thread discusses some of the same issues.

  • I tried increasing or decreasing i2s timeout, but the test results did not change.

    SEGGER J-Link V7.94e - Real time terminal output
    SEGGER J-Link (unknown) V1.0, SN=683518542
    Process: JLink.exe
    *** Booting nRF Connect SDK v3.5.99-ncs1 ***
    [00:00:00.371,582] <inf> i2s_nrfx: I2S MCK frequency: 1523809, actual PCM rate: 47619
    [00:00:00.383,789] <inf> central_test: Success to write data.
    [00:00:00.383,789] <inf> central_test: Success to write data.
    [00:00:00.384,155] <inf> central_test: Success to write data.
    [00:00:00.384,185] <inf> central_test: Success to write data.
    [00:00:00.484,954] <inf> central_test: Success to write data.
    [00:00:10.485,076] <err> central_test: Failed to write data: -11
    [00:00:20.485,168] <err> central_test: Failed to write data: -11

    There are no other threads or interrupts in my application, only the i2s write is being executed.

    I tried not using a for-loop but doing i2s_write every 100ms and the results were the same as using a for-loop.

    An error always occurs after i2s writes successfully 4 to 5 times, and once it occurs, it cannot be written successfully again. Does this mean that the buffer has been occupied and cannot be released?

    But I only write 100ms of data at a time, and the buffer should be released after the playback is completed. However, the buffer is actually always occupied. Is there any way to manually delete the data in the buffer?

  • I see. I am not sure what the issue is to be honest. Can you see from the duration of the signal that is played out if it is only from the first call to i2s_write(), or if you get all 5 played out? Or is the duration the same if you test with (say) just a single call to i2s_write()?

    Do you see a different behaviour if yo use i2s_buf_write() instead?

    This thread discuss a similar issue.

Reply Children
Related