Page 1 of 1

Samples swapped for mono I2S driving built-in ESP32 DAC (v4.4.4)

Posted: Sat Mar 04, 2023 6:56 pm
by danjulio
I have a very simple ESP32 program (attached) that attempts to output a 500 Hz sine wave to the GPIO25 DAC output using I2S. I configure I2S for mono (one-channel) operation. The sine wave comes from a table-lookup. The sine wave table is 32 entries (for one period). I2S is configured for 16 kHz operation so 16000 / 32 = 500 Hz. A buffer is filled with an even number of table entries to pass repeatedly to i2s_write.

When I run this it appears that every two entries are swapped when they are output to the DAC as shown with the following oscilloscope trace from the DAC output.
scope_0.png
scope_0.png (25.82 KiB) Viewed 2405 times
When I swap every other two entries (with the compiler define SWAP_WORDS) the output looks correct.
scope_2.png
scope_2.png (24.93 KiB) Viewed 2405 times
What is going on? Am I doing something wrong? It is interesting to note that the output is correct if I configure I2S for two channel operation, enable both DAC outputs, and load each sample twice (one for each channel).

The code is as follows (entire IDF project attached - compiled with IDF v4.4.4)

Code: Select all

[Codebox=c file=i2s_dac.c]
#include <stdio.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "driver/i2s.h"


/* Enable to swap 16-bit words as a kludge work-around */
//#define SWAP_WORDS

/* Prepare for 32 sample values */
#define NUM_SINE_SAMPLES 32

/* Sine Wave Sample */
const int16_t sine_data_frame[NUM_SINE_SAMPLES] = {
	  6392,  12539,  18204,  23169,  27244,  30272,  32137,  32767,  32137,
	 30272,  27244,  23169,  18204,  12539,   6392,      0,  -6393, -12540,
	-18205, -23170, -27245, -30273, -32138, -32767, -32138, -30273, -27245,
	-23170, -18205, -12540,  -6393,     -1,
};


esp_err_t app_main(void)
{
	int16_t i2s_write_buf[512];
	size_t bytes_to_write;
	size_t bytes_written;
	size_t samples_to_write;
	
	// configure i2s
	i2s_config_t i2s_config = {
        .mode = I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN,
        .sample_rate =  16000,
        .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
        .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
        .intr_alloc_flags = 0,
        .dma_buf_count = 2,
        .dma_buf_len = 512,
        .use_apll = 1,
        .tx_desc_auto_clear = 0,
	};
	
    // install and start i2s driver
    i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
    
    // init DAC pad
    i2s_set_dac_mode(I2S_DAC_CHANNEL_RIGHT_EN);  // RIGHT is DAC 1: GPIO25, LEFT is DAC 2: GPIO26
	
	// Load the sine wave into the buffer
	samples_to_write = 8 * NUM_SINE_SAMPLES;
	bytes_to_write = 2 * samples_to_write;
	
#ifdef SWAP_WORDS
	for (int i=0; i<samples_to_write; i+=2) {
		// Swap every 2 entries in the sine table when writing to the i2s buffer
		// WHY DOES THIS WORK???
		i2s_write_buf[i] = sine_data_frame[(i+1) % NUM_SINE_SAMPLES] + 0x8000;
		i2s_write_buf[i+1] = sine_data_frame[i % NUM_SINE_SAMPLES] + 0x8000;
	}
#else
	for (int i=0; i<samples_to_write; i++) {
		// Offset signed value by 0x8000 to make unsigned for single-ended DAC output
		i2s_write_buf[i] = sine_data_frame[i % NUM_SINE_SAMPLES] + 0x8000;
	}
#endif

	// Output the buffer forever
	while (1) {
		i2s_write(I2S_NUM_0, i2s_write_buf, bytes_to_write, &bytes_written, portMAX_DELAY);
		if (bytes_written != bytes_to_write) {
			printf("tried to write %d, actually wrote %d", bytes_to_write, bytes_written);
		}
	}
		
    return ESP_OK;
}
[/Codebox]

Re: Samples swapped for mono I2S driving built-in ESP32 DAC (v4.4.4)

Posted: Sun Mar 05, 2023 2:20 am
by ESP_Sprite
SWAP_WORDS is the right solution; the I2S hardware outputs the most significant word first while the rest of the ESP32 is little-endian. It's not ideal, and I think later chips (S2, S3 etc) have a way of flipping that, but the original ESP32 doesn't.

Re: Samples swapped for mono I2S driving built-in ESP32 DAC (v4.4.4)

Posted: Mon Mar 06, 2023 7:05 pm
by danjulio
@ESP_Sprite - thank you. Good to understand how the ESP32 works.

I have a related question. I also wrote a demo that attempts to echo an ADC input back to the DAC output using I2S. It configures I2S 0 for both input and output (ADC + DAC) @ 16 bits. Then it just spins in a loop reading a buffer from the ADC (i2s_read) and writing it to the DAC (i2s_write). It works, however on occasion it seems data output from the DAC is corrupted (I have checked the data being passed into i2s_write and it appears correct). I notice that you don't appear to support ADC/DAC as I2S sources in IDF v5.0. Are they supported in v4.4? Should I expect this to work or is there some problem and it won't really work?

Re: Samples swapped for mono I2S driving built-in ESP32 DAC (v4.4.4)

Posted: Wed Mar 08, 2023 1:00 am
by ESP_Sprite
I think that did not change because of hardware issues, but because we refactored the drivers; you're likely to find that functionality elsewhere. (E.g. for the ADCs, I think we have some 'periodic ADC' driver.)

Re: Samples swapped for mono I2S driving built-in ESP32 DAC (v4.4.4)

Posted: Sat Feb 10, 2024 9:39 am
by HarishKalla
HI, I am a beginner in developing the I2S protocol. I used espressif IDE V2.12.0 to implement a program, but I could not find the option to configure the channel format as I2S_CHANNEL_FMT_ONLY_LEFT. How can I set the I2S peripheral to use only the left channel data? And please tell the procedure to store I2S audio output data in SD card without lost.
Thank you.