Problem getting I2S to work
Posted: 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
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
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);
}