Page 1 of 1

Distortion and noise in I2S Slave Mode Audio Playback with ESP32 and APLL

Posted: Thu Dec 07, 2023 2:14 pm
by uoosef
Hi everyone,

I'm working on a project where I need to interface an ESP32 with an M66 module using I2S for audio playback. I've encountered a problem with audio quality – specifically, distortion – when the ESP32 is set in I2S slave mode using APLL(because quectel m66 can only operate in master mode).

Here’s a brief overview of my setup:

ESP32 Configuration: I2S Slave Mode
Target Device: M66 module (acting as the I2S master)
Application: Audio playback from the ESP32 to the Quectel M66
The i2s driver part of my configuration for the ESP32 is as follows:

Code: Select all

i2s_config_t i2s_config = {
        .mode = (i2s_mode_t)(I2S_MODE_SLAVE | I2S_MODE_TX | I2S_MODE_RX),  //only I2S_MODE_MASTER was changed to I2S_MODE_SLAVE
        .sample_rate = 8000,
        .bits_per_sample = (i2s_bits_per_sample_t)16,
        .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
        .communication_format = (i2s_comm_format_t) (I2S_COMM_FORMAT_STAND_I2S),
        .intr_alloc_flags = 0, // default interrupt priority
        .dma_buf_count = 3,
        .dma_buf_len = 300,//64,
        .use_apll = true
    };
Issue Description:
When audio is played back, there's noticeable distortion. The same audio plays fine on a different device that can operate in I2S slave mode, indicating that the issue is likely with the ESP32's configuration or capability in slave mode with APLL.

Questions for the Community:

Has anyone experienced similar issues with I2S slave mode and APLL on the ESP32?
Are there known limitations or configurations with the APLL that might lead to audio distortion?
Any suggestions on buffer configurations or alternative approaches that might resolve this distortion?
I appreciate any insights or advice you can offer. Thanks in advance for your help!

My actual code:

Code: Select all

#include "driver/i2s.h"
#include "driver/uart.h"
#include "audio_data.h"

#define I2S_NUM         I2S_NUM_0 // I2S port number
#define I2S_BCK_IO      (27)      // BCK pin
#define I2S_WS_IO       (25)      // WS pin
#define I2S_DO_IO       (26)      // SD_OUT pin
#define I2S_DI_IO       (32)      // SD_IN pin

#define UART_NUM        UART_NUM_1 // UART port number
#define TXD_PIN         (17)       // UART TX pin
#define RXD_PIN         (33)       // UART RX pin
#define BUF_SIZE        (1024)     // UART buffer size
#define UART_BUF_SIZE   1024

#define SAMPLE_RATE 8000
#define TONE_FREQ 5000   // Frequency in Hz (A4 note)
#define DURATION 2      // Duration in seconds

void setup() {
    Serial.begin(115200);
    //generateTone(); 
    Serial.print("Initializing...");
    // Initialize I2S
    i2s_config_t i2s_config = {
        .mode = (i2s_mode_t)(I2S_MODE_SLAVE | I2S_MODE_TX | I2S_MODE_RX),  //only I2S_MODE_MASTER was changed to I2S_MODE_SLAVE
        .sample_rate = 8000,
        .bits_per_sample = (i2s_bits_per_sample_t)16,
        .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
        .communication_format = (i2s_comm_format_t) (I2S_COMM_FORMAT_STAND_I2S),
        .intr_alloc_flags = 0, // default interrupt priority
        .dma_buf_count = 3,
        .dma_buf_len = 300,//64,
        .use_apll = true
    };
    i2s_pin_config_t pin_config = {
        .bck_io_num = I2S_BCK_IO,
        .ws_io_num = I2S_WS_IO,
        .data_out_num = I2S_DO_IO,
        .data_in_num = I2S_DI_IO,
    };
    i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL);
    i2s_set_pin(I2S_NUM, &pin_config);

    // Initialize UART
    uart_config_t uart_config = {
        .baud_rate = 115200,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE
    };
    uart_driver_install(UART_NUM, BUF_SIZE * 2, 0, 0, NULL, 0);
    uart_param_config(UART_NUM, &uart_config);
    uart_set_pin(UART_NUM, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);

    //sendSMS(phoneNumber, messageText);
    //playAudio(output_raw, output_raw_len);
    xTaskCreate(&audio_play_task, "audio_play_task", 4096, NULL, 8, NULL);
}

void playAudio(const unsigned char* data, size_t size) {
    i2s_set_clk(I2S_NUM, 8000, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_MONO);

    size_t bytes_written;
    for (size_t i = 0; i < size; i++) {
        uint16_t sample = data[i];
        //sample = sample << 8;  // Convert 8-bit to 16-bit if necessary
        i2s_write(I2S_NUM, &sample, sizeof(sample), &bytes_written, portMAX_DELAY);
    }
}

void loop() {
    // Relay data from Serial Monitor to M66
    while (Serial.available()) {
        size_t len = Serial.available();
        uint8_t data[len];
        Serial.readBytes(data, len);
        uart_write_bytes(UART_NUM, (const char *)data, len);

        // Echo the sent data back to Serial Monitor for confirmation
        Serial.print("> ");
        Serial.write(data, len);
    }

    // Relay data from M66 to Serial Monitor
    uint8_t data[UART_BUF_SIZE];
    int length = uart_read_bytes(UART_NUM, data, UART_BUF_SIZE, 20 / portTICK_RATE_MS);
    if (length > 0) {
        Serial.print("< ");
        Serial.write(data, length);
    }
    // Play beep every second
}

void sendATCommand(const char* cmd) {
  uart_write_bytes(UART_NUM, cmd, strlen(cmd));
  uart_write_bytes(UART_NUM, "\r\n", 2); // Send newline character
  delay(1000); // Wait for command execution
}

void audio_play_task (void *pvParameter)
{
    while(1) {
      const char *ptr = (const char *)output_raw;
      unsigned int offset = 0;
      signed short samples[256], i;
      size_t written;

    // Play array from signed, 16-bit stereo file
      size_t bytes_written;
      for (size_t i = 0; i < output_raw_len; i++) {
          uint16_t sample = output_raw[i];
          sample = sample << 8;  // Convert 8-bit to 16-bit if necessary
          i2s_write(I2S_NUM, &sample, sizeof(sample), &bytes_written, portMAX_DELAY);
      }
      size_t i2=0;
      // Silence here
      while (i2<5)
      {
          for (i=0; i<256; i++)
              samples[i] = 0;
          i2s_write (I2S_NUM, (const char*) samples, 512, &written, portMAX_DELAY);
          i2++;
      }
    }
}

Re: Distortion and noise in I2S Slave Mode Audio Playback with ESP32 and APLL

Posted: Fri Dec 08, 2023 2:03 pm
by uoosef
Ok i did find out that my communication format was wrong and it should use "I2S_COMM_FORMAT_PCM". it makes sense because with I2S_COMM_FORMAT_STAND_I2S if i change the dma_buf_count to anything except 3 it wont work.
i also configured my m66 through the at command to AT+QPCMON=1,0,8,0,0.

also this is how i connected the pcm pins of my m66 devkit to i2s peripheral pins of my esp32:

I2S_BCK ---> PCM_CLK
I2S_WS ---> PCM_SYNC
I2S_SDIN ---> PCM_OUT
I2S_SDOUT ---> PCM_IN
GND ---> GND

my new i2s driver configuration:

Code: Select all

i2s_config_t i2s_config = {
        .mode = (i2s_mode_t)(I2S_MODE_SLAVE | I2S_MODE_TX | I2S_MODE_RX),  //only I2S_MODE_MASTER was changed to I2S_MODE_SLAVE
        .sample_rate = 256*1000/16,
        .bits_per_sample = (i2s_bits_per_sample_t)16,
        .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
        .communication_format = (i2s_comm_format_t) (I2S_COMM_FORMAT_PCM),
        .intr_alloc_flags = 0, // default interrupt priority
        .dma_buf_count = 8,
        .dma_buf_len = 512,//64,
        .use_apll = true,
        .tx_desc_auto_clear = true,
        .fixed_mclk = 0
    };
but quality of the sound is still very poor and i can hear lots of distortion. besides that there isnt much of documentation about I2S_COMM_FORMAT_PCM mode and i dont know how it interprets the "word signal" in this mode? does it expect that the ws be high for left part of each sample or it can work with pcm sync momentery up for each sample?

also attached my code and a sample of recorded audio during an actual call for anyone who that is interested, so please if anybody can help, it would be much appreciated
code_and_sample_audio.zip
(606 KiB) Downloaded 201 times