Problem getting I2S to work

Michael.K
Posts: 2
Joined: Tue Jun 10, 2025 9:27 pm

Problem getting I2S to work

Postby Michael.K » Tue Jun 10, 2025 9:45 pm

I am trying to output a 1kHz sine wave to an attached I2S DAC (PCM5102A).

I generate one cycle in memory (sampled at 32 points) and output the buffer continually. The sample rate is set to 32 KHz / 32 bit per channel. I have presumed that I had to convert from little endian (as calculated by the ESP32) to big endian for the I2S transport.

I've tried various combinations and settings without success.

What I end up with is a complex waveform with higher frequencies than 1kHz (but the filters in the DAC are probably hiding what is really generated).

The bck clock looks like it is the correct phase (checking the PCM5102A data sheet).

A stripped down Espressif sample code was used (see below).
If anyone has seen this before and knows a solution or suggestion please let me know !

Michael
scope_1.png
scope_1.png (61.1 KiB) Viewed 558 times

Code: Select all

/*
 * Output a 1 KHz sinewave on a PCM5102A I2S audio board
 *
 * BCK       - IO 22
 * WR (LRCK) - IO  5  
 * DOUT      - IO 21
 * SPDX-License-Identifier: Unlicense OR CC0-1.0
 */

#include <stdint.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s_std.h"
#include "driver/gpio.h"
#include "esp_check.h"
#include "sdkconfig.h"
#include <math.h>
#include <string.h>

#define PCM5102A_BCK    22
#define PCM5102A_WR      5
#define PCM5102A_DOUT   21
#define PCM5102A_DIN    21

#define SAMPLES_PER_CYCLE 	32
#define CYCLES_PER_BUFFER	16
#define BUFF_SIZE			(sizeof( uint32_t) * 2 * SAMPLES_PER_CYCLE * CYCLES_PER_BUFFER)
uint32_t sine_table[ SAMPLES_PER_CYCLE ] = { 0 };

static i2s_chan_handle_t                tx_chan;        // I2S tx channel handler

#define FIXED_POINT_SCALE ((1u<<31)-1) // 2^31 for a 32:32 fixed-point format


static void initSineTable( void ) {
	union {
		int32_t    i32;
		uint32_t   u32;
		uint8_t  c8[4];
	} littleEndian32, bigEndian32;
	
	// Precalculate sinewave and convert from little endian to bigendian
	for( int i = 0; i < SAMPLES_PER_CYCLE; i++ ) {
		littleEndian32.i32 = (int32_t)( sin( i * 2*M_PI / SAMPLES_PER_CYCLE ) * (double)FIXED_POINT_SCALE);
		printf( "==> %10d\n", (int)littleEndian32.i32 );
		bigEndian32.c8[ 0 ] = littleEndian32.c8[ 3 ];
		bigEndian32.c8[ 1 ] = littleEndian32.c8[ 2 ];
		bigEndian32.c8[ 2 ] = littleEndian32.c8[ 1 ];
		bigEndian32.c8[ 3 ] = littleEndian32.c8[ 0 ];
		sine_table[ i ] = bigEndian32.u32;
	}
}


static void i2s_example_write_task(void *args)
{
    uint8_t *w_buf = (uint8_t *)calloc(1, BUFF_SIZE);
    assert(w_buf); // Check if w_buf allocation success

	static int phase = 0;
	
	size_t w_bytes = BUFF_SIZE;

    // Load the buffer with the 32 bit sine wave samples (bigendian)
	
	for (int i = 0; i < BUFF_SIZE; i += (2 * sizeof( uint32_t))) {
		memcpy( &w_buf[i],   &sine_table[ phase ], sizeof( uint32_t) );
		memcpy( &w_buf[i+sizeof( uint32_t)], &sine_table[ phase ], sizeof( uint32_t) );
		if( ++phase >= SAMPLES_PER_CYCLE)
		    phase = 0;
	}

	// Preload buffers
	while (w_bytes == BUFF_SIZE) {
        /* Here we load the target buffer repeatedly, until all the DMA buffers are preloaded */
        ESP_ERROR_CHECK(i2s_channel_preload_data(tx_chan, w_buf, BUFF_SIZE, &w_bytes));
    }
	
    // Enable the TX channel
    ESP_ERROR_CHECK(i2s_channel_enable(tx_chan));
    while (1) {
        /* Write i2s data */
        if (i2s_channel_write( tx_chan, w_buf, BUFF_SIZE, &w_bytes, 1000) == ESP_OK) {
            printf("Write Task: i2s write %d bytes\n", w_bytes);
        } else {
            printf("Write Task: i2s write failed\n");
        }
        vTaskDelay(pdMS_TO_TICKS(200));
    }
    free(w_buf);
    vTaskDelete(NULL);
}



static void i2s_example_init_std_simplex(void)
{
    /* Setp 1: Determine the I2S channel configuration and allocate two channels one by one
     * The default configuration can be generated by the helper macro,
     * it only requires the I2S controller id and I2S role
     * The tx and rx channels here are registered on different I2S controller,
     * Except ESP32 and ESP32-S2, others allow to register two separate tx & rx channels on a same controller */
    i2s_chan_config_t tx_chan_cfg = {
	 	.id                   = I2S_NUM_AUTO, 
		.role                 = I2S_ROLE_MASTER, 
		.dma_desc_num         = 6,
	 	.dma_frame_num        = 240, 
		.auto_clear_after_cb  = 0, 
		.auto_clear_before_cb = 0,
	 	.allow_pd             = 0, 
		.intr_priority        = 0,
	 }; 	// I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);

    ESP_ERROR_CHECK(i2s_new_channel(&tx_chan_cfg, &tx_chan, NULL));

    /* Step 2: Setting the configurations of standard mode and initialize each channels one by one
     * The slot configuration and clock configuration can be generated by the macros
     * These two helper macros is defined in 'i2s_std.h' which can only be used in STD mode.
     * They can help to specify the slot and clock configurations for initialization or re-configuring */
    i2s_std_config_t tx_std_cfg = {
        .clk_cfg  = { // I2S_STD_CLK_DEFAULT_CONFIG(32000),
				.sample_rate_hz = 32000, 
				.clk_src = I2S_CLK_SRC_DEFAULT,
				.mclk_multiple = I2S_MCLK_MULTIPLE_256,
		}, 
#undef PHILIPS
#ifdef PHILIPS
		I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO),
#else
		.slot_cfg = { // I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO),
			.data_bit_width = I2S_DATA_BIT_WIDTH_32BIT,
			.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
			.slot_mode      = I2S_SLOT_MODE_STEREO,
			.slot_mask      = I2S_STD_SLOT_BOTH,
			.ws_width       = I2S_DATA_BIT_WIDTH_32BIT, 
			.ws_pol 		= 0, 
			.bit_shift 		= 0,
			.msb_right      = 0,
		},  
#endif
        .gpio_cfg = {
            .mclk = I2S_GPIO_UNUSED,    // some codecs may require mclk signal, this example doesn't need it
			.bclk = PCM5102A_BCK,		// IO 22
			.ws   = PCM5102A_WR,		// IO 5
			.dout = PCM5102A_DOUT,		// IO 21
            .din  = PCM5102A_DIN,	//
            .invert_flags = {
                .mclk_inv = false,
                .bclk_inv = false,
                .ws_inv   = false,
            },
        },
    };
    ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_chan, &tx_std_cfg));
}


void app_main(void)
{
    i2s_example_init_std_simplex();

	initSineTable( );
    xTaskCreate(i2s_example_write_task, "i2s_example_write_task", 4096, NULL, 5, NULL);
}

Michael.K
Posts: 2
Joined: Tue Jun 10, 2025 9:27 pm

Re: Problem getting I2S to work

Postby Michael.K » Wed Jun 11, 2025 2:11 pm

Apparently, although I2S is big endian and the ESP32 little endian, no conversion is needed.
I guess

Code: Select all

i2s_channel_write()
assumes that a conversion is needed.

Michael

Code: Select all

/*
 * Output a 1 KHz sinewave on a PCM5102A I2S audio board
 *
 * BCK       - IO 22
 * WR (LRCK) - IO  5  
 * DOUT      - IO 21
 * SPDX-License-Identifier: Unlicense OR CC0-1.0
 */

#include <stdint.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s_std.h"
#include "driver/gpio.h"
#include "esp_check.h"
#include "sdkconfig.h"
#include <math.h>
#include <string.h>
#include <endian.h>

#define PCM5102A_BCK    GPIO_NUM_22
#define PCM5102A_WR     GPIO_NUM_5
#define PCM5102A_DOUT   GPIO_NUM_21
#define PCM5102A_DIN    GPIO_NUM_16
#define PCM5102A_MCLK   GPIO_NUM_0

#define SAMPLES_PER_CYCLE 	32
#define CYCLES_PER_BUFFER	16
#define BUFF_SIZE			(sizeof( uint32_t) * 2 * SAMPLES_PER_CYCLE * CYCLES_PER_BUFFER)
uint32_t sine_table[ SAMPLES_PER_CYCLE ] = { 0 };

static i2s_chan_handle_t                tx_chan;        // I2S tx channel handler

#define FIXED_POINT_SCALE ((1u<<31)-1) // 2^31 for a 32:32 fixed-point format


static void initSineTable( void ) {
	int32_t    littleEndian32;
	
	// Precalculate sinewave and convert from little endian to bigendian
	for( int i = 0; i < SAMPLES_PER_CYCLE; i++ ) {
		littleEndian32 = (int32_t)( sin( i * 2*M_PI / SAMPLES_PER_CYCLE ) * (double)FIXED_POINT_SCALE);
		sine_table[ i ] = littleEndian32; // htobe32( littleEndian32 );
	}
}


static void i2s_example_write_task(void *args)
{
    uint8_t *w_buf = (uint8_t *)calloc(1, BUFF_SIZE);
    assert(w_buf); // Check if w_buf allocation success

	static int phase = 0;
	
	size_t w_bytes = BUFF_SIZE;

    // Load the buffer with the 32 bit sine wave samples (bigendian)
	
	for (int i = 0; i < BUFF_SIZE; i += (2 * sizeof( uint32_t))) {
		memcpy( &w_buf[i],   &sine_table[ phase ], sizeof( uint32_t) );
		memcpy( &w_buf[i+sizeof( uint32_t)], &sine_table[ phase ], sizeof( uint32_t) );
		if( ++phase >= SAMPLES_PER_CYCLE)
		    phase = 0;
	}

	// Preload buffers
	while (w_bytes == BUFF_SIZE) {
        /* Here we load the target buffer repeatedly, until all the DMA buffers are preloaded */
        ESP_ERROR_CHECK(i2s_channel_preload_data(tx_chan, w_buf, BUFF_SIZE, &w_bytes));
    }
	
    // Enable the TX channel
    ESP_ERROR_CHECK(i2s_channel_enable(tx_chan));
    while (1) {
        /* Write i2s data */
        if (i2s_channel_write( tx_chan, w_buf, BUFF_SIZE, &w_bytes, 1000) == ESP_OK) {
            printf("Write Task: i2s write %d bytes\n", w_bytes);
        } else {
            printf("Write Task: i2s write failed\n");
        }
        vTaskDelay(pdMS_TO_TICKS(200));
    }
    free(w_buf);
    vTaskDelete(NULL);
}



static void i2s_example_init_std_simplex(void)
{
    /* Setp 1: Determine the I2S channel configuration and allocate two channels one by one
     * The default configuration can be generated by the helper macro,
     * it only requires the I2S controller id and I2S role
     * The tx and rx channels here are registered on different I2S controller,
     * Except ESP32 and ESP32-S2, others allow to register two separate tx & rx channels on a same controller */
    i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
/*
	i2s_chan_config_t tx_chan_cfg =	{   
		.id                   = I2S_NUM_AUTO, 
		.role                 = I2S_ROLE_MASTER, 
		.dma_desc_num         = 6,
	 	.dma_frame_num        = 240, 
		.auto_clear_after_cb  = 0, 
		.auto_clear_before_cb = 0,
	 	.allow_pd             = 0, 
		.intr_priority        = 0,
	}; 	
*/
    ESP_ERROR_CHECK(i2s_new_channel(&tx_chan_cfg, &tx_chan, NULL));

    /* Step 2: Setting the configurations of standard mode and initialize each channels one by one
     * The slot configuration and clock configuration can be generated by the macros
     * These two helper macros is defined in 'i2s_std.h' which can only be used in STD mode.
     * They can help to specify the slot and clock configurations for initialization or re-configuring */
    i2s_std_config_t tx_std_cfg = {
        .clk_cfg  = I2S_STD_CLK_DEFAULT_CONFIG(32000),
/*		.clk_cfg  = { 	
			.sample_rate_hz = 32000, 
			.clk_src        = I2S_CLK_SRC_DEFAULT,
			.mclk_multiple  = I2S_MCLK_MULTIPLE_256,
		}, 
*/
#define PHILIPS
#ifdef PHILIPS
		.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO),
#else
		.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO),
/*
		.slot_cfg = {	
			.data_bit_width = I2S_DATA_BIT_WIDTH_32BIT,
			.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
			.slot_mode      = I2S_SLOT_MODE_STEREO,
			.slot_mask      = I2S_STD_SLOT_BOTH,
			.ws_width       = I2S_DATA_BIT_WIDTH_32BIT, 
			.ws_pol 		= 0, 
			.bit_shift 		= 0,
			.msb_right      = 0,
		}, 
*/
#endif
        .gpio_cfg = {
            .mclk = PCM5102A_MCLK,      // some codecs may require mclk signal, this example doesn't need it
			.bclk = PCM5102A_BCK,		// IO 22
			.ws   = PCM5102A_WR,		// IO 5
			.dout = PCM5102A_DOUT,		// IO 21
            .din  = PCM5102A_DIN,	    //
            .invert_flags = {
                .mclk_inv = false,
                .bclk_inv = false,
                .ws_inv   = false,
            },
        },
    };
    ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_chan, &tx_std_cfg));
}


void app_main(void)
{
    i2s_example_init_std_simplex();

	initSineTable( );
    xTaskCreate(i2s_example_write_task, "i2s_example_write_task", 4096, NULL, 5, NULL);
}


MicroController
Posts: 2694
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Problem getting I2S to work

Postby MicroController » Thu Jun 12, 2025 9:16 am

I have presumed that I had to convert from little endian (as calculated by the ESP32) to big endian for the I2S transport.
What if you don't reverse the byte order?

(Btw, gcc has byte-reversing functions built-in, like __builtin_bswap32(uint32_t).)

tpbedford
Posts: 42
Joined: Mon Feb 14, 2022 4:16 am

Re: Problem getting I2S to work

Postby tpbedford » Tue Jun 17, 2025 12:54 am

CYCLES_PER_BUFFER is 16 (so, 16ms worth) yet you have a delay of 200millis in your loop?

If the last param to i2c_write_channel is TickTypes_t then you don't even need the delay, surely that'll block till there's space to write that chunk?

Your scope capture -- at least where it looks like a sine wave -- looks like f=2.0-2.2kHz so there's possibly a mismatch in the datarate configured in ESP-IDF vs datarate configured in your DAC.

It looks like you're loading L+R channels into the buffer, I'd suggest loading L channel as something like 0x55555555 and R channel as 0xFFFF0000 and you should very clearly see on the i2s DOUT line that you have the correct timing and phase: 0x55555555 should align with LRCLK high and 0xFFFF0000 should align with LRCLK low, and L channel should be half BCLK.

You can also check endianness by sending 0x55550F0F or similar, and should see MSB toggling (0x5555) higher rate than LSB (x0F0F) within the LRCLK edges for that channel. Double-check against "9.3.2.2 PCM Audio Data Formats" of the datasheet, and you configuration of the DAC's "FMT" pin, to determine if the generated I2S is correct.

MicroController
Posts: 2694
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Problem getting I2S to work

Postby MicroController » Tue Jun 17, 2025 8:41 am

Your scope capture -- at least where it looks like a sine wave -- looks like f=2.0-2.2kHz
Notice that the screen is at 200us/div, so more like ~10kHz.

The wave form actually resembles what you get when the byte order is wrong. Should be trivial to test...

Who is online

Users browsing this forum: Bing [Bot], ChatGPT-User, Google [Bot], PetalBot and 1 guest