I2S 24bit Slave

StijnVM
Posts: 4
Joined: Mon Jul 30, 2018 7:31 pm

I2S 24bit Slave

Postby StijnVM » Sun Aug 05, 2018 4:18 pm

Hello!

I already reported the problem I'm having in the ESP-IDF subforum but this may be a better place.
See https://esp32.com/viewtopic.php?f=13&t=6582 for more detail but in a nutshell:

Trying to interface the ESP32 with a USB to I2S bridge which can only be configured as an I2S master and instead of using a continuous clock, outputs bursts of 24 clockpulses for each sample. ESP32 should only output data right now.

So I'm trying to use the ESP32 I2S in 24 bit slave mode but was seeing strange shifting behaviour in the audio data and now I've reduced the setup to 2 ESP32's to check if the problem could be the bursty clock. However, I'm still seeing some odd behaviour.

The code I'm using on the slave device:

Code: Select all

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s.h"
#include "esp_system.h"
#include <math.h>

#define I2S_NUM         (0)

static void setup_testblock()
{
   unsigned int n_blocks = 40;
    int *samples_data = malloc(8*n_blocks);
       size_t i2s_bytes_write = 0;

    unsigned int i;
    for(i = 0; i < n_blocks; i++)
    {
            samples_data[i*2] = -1431655681;          //0b101010101010101010101010(11111111) last byte should be dropped
            samples_data[i*2 + 1] = -286331137;         //0b111011101110111011101110(11111111) last byte should be dropped
    }

    i2s_write(I2S_NUM, samples_data, n_blocks*4, &i2s_bytes_write, 100);
    //i2s_write_expand(I2S_NUM, samples_data, ((bits+8)/16)*SAMPLE_PER_CYCLE*4, 16, 16, &i2s_bytes_write, 10);

    free(samples_data);
}
void app_main()
{
    i2s_config_t i2s_config = {
        .mode = I2S_MODE_SLAVE | I2S_MODE_TX,                                  // Only TX
        .sample_rate = 48000,
        .bits_per_sample = 24,
        .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,                           //2-channels
        .communication_format = I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB,
        .dma_buf_count = 2,
        .dma_buf_len = 30,
        .use_apll = true,
        .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1                               //Interrupt level 1
    };
    i2s_pin_config_t pin_config = {
        .bck_io_num = 26,
        .ws_io_num = 22,
        .data_out_num = 25,
        .data_in_num = -1                                                       //Not used
    };
    i2s_driver_install(0, &i2s_config, 0, NULL);
    i2s_set_pin(0, &pin_config);

    setup_testblock();
    while (1)
    {
        vTaskDelay(5000/portTICK_RATE_MS);
    }

}
If I understand correctly, it basically always writes the same pattern for left and right to the DMA buffers. The I2S peripheral then loops this data as output.
This works fine if I set both the master and slave to 32 bit. 0b10101010101010101010101011111111 and 0b11101110111011101110111011111111 is repeated. However, when I set the ESP's to 24 bit, the slave should drop the final byte (all the 1's). This happens most of the time.. but quite often, 2 bytes of zeroes are inserted, the data is shifted but then recovers on the falling LRClock.
See https://drive.google.com/file/d/1l0-Akl ... gBeJC/view and https://drive.google.com/file/d/1UeaQfL ... my2rn/view for scope screenshots.
Top is LRclock and bottom is Data.
I believe this problem is related to the problem I'm having with the USB-I2S bridge.

Since the testprogram is just sitting in the while(1) loop, I think the problem lies deeper than the software and may not be easy to solve..
Can anyone please confirm this behaviour? Any suggestions?

Thanks!

crespum
Posts: 3
Joined: Thu Nov 29, 2018 6:21 pm

Re: I2S 24bit Slave

Postby crespum » Fri Mar 22, 2019 8:19 am

Hi @StijnVM. I'm running into a similar issue. Have you ever found a solution for it?

SebastiaoRocha
Posts: 1
Joined: Tue Jul 19, 2022 5:11 pm

Re: I2S 24bit Slave

Postby SebastiaoRocha » Wed Jul 20, 2022 12:22 am

Hi, i can confirm that there is a bug at ESP32 I2S 24Bit.

I had done all tests, see bellow.

Filled buffer ( left/right channels ) with the folowing pattern:
"spaces to better undertand"
16bit: 0x01 02 03 04
24bit: 0x01 02 03 04 05 06
32bit: 0x01 02 03 04 05 06 07 08

All cases,
LRCK/WS is ok, toggle at specified bit count
BCK is ok, toggle many times as specified bit count
DATA is ok, have all bits as specified bit count

Data Byte value:
1) 44.1k/48k - 8bit ERROR! All values for both channels outputs value 0x02 ( just for test, bad quality )

2) 44.1k/48k - 16bit Correct! output pattern is like expected.

3) 44.1k/48k - 24bit ERROR! pattern is ( please note, i2s sent bytes inverted order from buffer ):
Output: 01 02 03 - 03 04 05 - 05 06 01 - 01 02 03 - 03 04 05 - 05 06 01 - 01 02 03 - 03 04 05 - 05 06 01 - 01 02 03
Expected: 01 02 03 - 04 05 06 - 01 02 03 - 04 05 06 - 01 02 03 - 04 05 06 - 01 02 03 - 04 05 06 - 01 02 03 - 04 05 06

Looks like last byte is replicated to next one, ignoring buffer sequence, so its adding one byte before each sample channel.

4) 44.1k/48k - /32bit Correct! output pattern is like expected.

I cant find source code that write to i2s driver, just find headers, not implementation, i will try more, if i found maybe i can fix it, but it may takes longer.

So, anyone with knowledge can fix it faster, as it looks like a mistake at byte index.

Please note, chacked with logic analizer.

Code: Untitled.cpp Select all


#include <WiFi.h>
#include "driver/i2s.h"

#define I2S_DATA 25 //
#define I2S_BLCK 26 //
#define I2S_LRCK 27 //
#define I2S_MCLK 3 //

QueueHandle_t i2sQueueHandle;
TaskHandle_t i2sOutputTaskHandle = NULL;

i2s_config_t i2s_config;
i2s_pin_config_t i2s_pin_config;

// Just change that to test
const unsigned char bitsPerSample = I2S_BITS_PER_SAMPLE_24BIT;
const unsigned long sampleRate = 48000;

// hardcoded only for this test, max stereo samples for single DMA write is:
// 32 bits audio is 128: 1024 total bytes
// 24 bits audio si 170: 1020 total bytes
// 16 bits audio is 256: 1024 total bytes
unsigned long samples = int( 1024 / ((bitsPerSample/8)*2) );

unsigned char bytes = (bitsPerSample/8)*2;

// Buffer like struct array is not dynamic for hold bitspersample data
// So used bytes array instead, this prevents headcache.
unsigned char buffer[2048];

constexpr char hexmap[] = {'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

void i2sOutputTask(void* pvParameters) {
size_t bytesWritten;

// Install driver
i2s_driver_install(I2S_NUM_0, &i2s_config, 4, &i2sQueueHandle );
i2s_set_pin(I2S_NUM_0, &i2s_pin_config);

//if(ESP_OK != i2s_set_clk(I2S_NUM_0, sampleRate, I2S_BITS_PER_SAMPLE_24BIT, I2S_CHANNEL_STEREO)){}

//i2s_check_set_mclk(I2S_NUM_0, I2S_MCLK );
i2s_zero_dma_buffer(I2S_NUM_0);

for(;;) {
// wait for some data to be requested
i2s_event_t evt;
if (xQueueReceive(i2sQueueHandle, &evt, portMAX_DELAY) == pdPASS) {

if (evt.type == I2S_EVENT_TX_DONE) {
i2s_write( I2S_NUM_0, (uint8_t *)buffer,
(samples*bytes), &bytesWritten, portMAX_DELAY
);

if ( bytesWritten != (samples*bytes) ){
Serial.println( "Not all bytes were written to I2S");
}
//i2s_write_expand( I2S_NUM_0, (uint8_t *)buffer, (samples*bytes), 24, 24, &bytesWritten, portMAX_DELAY );
}
}
}
}

void setup() {
Serial.begin( 115200 );

// Set pins
i2s_pin_config.bck_io_num = I2S_BLCK;
i2s_pin_config.ws_io_num = I2S_LRCK;
i2s_pin_config.data_out_num = I2S_DATA;
i2s_pin_config.data_in_num = I2S_PIN_NO_CHANGE;

// set config
i2s_config.mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_TX);
i2s_config.sample_rate = (i2s_bits_per_sample_t)sampleRate;
i2s_config.bits_per_sample = (i2s_bits_per_sample_t)bitsPerSample;
i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
i2s_config.communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_STAND_I2S;
i2s_config.intr_alloc_flags = 0;
i2s_config.dma_buf_count = 6;
i2s_config.dma_buf_len = int( 1024/ ((bitsPerSample/8)*2 ) );
i2s_config.use_apll = false;
i2s_config.fixed_mclk = 12288;
i2s_config.tx_desc_auto_clear = false;

// fill frames bytes with same byte value, for use with logic analizer only
// so dont expect audio, all bytes on right to 0x01 and bytes on left to 0x02
for ( int i = 0; i < samples; i++ ) {
// Left
switch ( bitsPerSample ) {
case I2S_BITS_PER_SAMPLE_8BIT:
// rigth
buffer[(i*bytes)] = 1;

// left
buffer[(i*bytes)+1] = 2;
break;

case I2S_BITS_PER_SAMPLE_16BIT:
// rigth
buffer[(i*bytes)] = 4;
buffer[(i*bytes)+1] = 3;

// left
buffer[(i*bytes)+2] = 2;
buffer[(i*bytes)+3] = 1;
break;

case I2S_BITS_PER_SAMPLE_24BIT:
// rigth
buffer[(i*bytes)] = 6;
buffer[(i*bytes)+1] = 5;
buffer[(i*bytes)+2] = 4;

// left
buffer[(i*bytes)+3] = 3;
buffer[(i*bytes)+4] = 2;
buffer[(i*bytes)+5] = 1;
break;

case I2S_BITS_PER_SAMPLE_32BIT:
// right
buffer[(i*bytes)] = 8;
buffer[(i*bytes)+1] = 7;
buffer[(i*bytes)+2] = 6;
buffer[(i*bytes)+3] = 5;

// left
buffer[(i*bytes)+4] = 4;
buffer[(i*bytes)+5] = 3;
buffer[(i*bytes)+6] = 2;
buffer[(i*bytes)+7] = 1;
break;

default:
Serial.printf("Unknown bits per sample: %d\n", bitsPerSample );
}
}

Serial.println( "" );
Serial.printf( "Bits per Sample: %d, Samples: %d, Bytes: %d, Total: %d\n",
bitsPerSample, samples, bytes, (samples*bytes)
);

// will print only first 4 samples ( stereo )
for ( int i = 0; i < (4*(bytes)); ++i) {
Serial.print( hexmap[(buffer[i] & 0xF0) >> 4] );
Serial.print( hexmap[buffer[i] & 0x0F] );
Serial.print( " " );
if ( ((i+1) % ( bytes/2) == 0 ) && ( i+1 != (4*(bytes)) )) { Serial.print( "- " ); }
}

xTaskCreatePinnedToCore(
i2sOutputTask, //Function to implement the task
"i2sOutputTask", //Name of the task
5000, //Stack size in words
NULL, //Task input parameter
0, //Priority of the task
&i2sOutputTaskHandle, //Task handle.
1 //Core where the task should run
);

}

void loop() {
delay(5);
}

philippe_44
Posts: 26
Joined: Thu May 23, 2019 3:05 pm

Re: I2S 24bit Slave

Postby philippe_44 » Sat Aug 27, 2022 7:15 pm

Have a look
------------------
It’s similar when the data is 32-bit width, but take care when using 8-bit and 24-bit data width. For 8-bit width, the written buffer should still using uint16_t (i.e. align with 2 bytes), and only the high 8 bits will be valid, the low 8 bits are dropped, and for 24-bit width, the buffer is supposed to use uint32_t (i.e. align with 4 bytes), and only the high 24 bits valid, the low 8 bits are dropped.

Another point is that, for the 8-bit and 16-bit mono mode, the real data on the line are swapped. To get the correct sequence, the writting buffer need to swap the data every two bytes.
-------------

Who is online

Users browsing this forum: No registered users and 0 guests