Problem of config I2S for both ADC and DAC

Hi all.
the sketch below used ESP32 + INMP441 + MAX98357 try to record audio and save on SD of wav file and play it out.
used two buttons to control the record and play.
record and save on SD OK,
but the play make out error of (some time nothing happened pressed button):

E (14960) I2S: i2s_write(2129): TX mode is not enabled
22:43:51.239 -> BUTTON_PLAY!
22:43:51.286 -> PlayWav()

how to fix please.
Thanks
Adam

#include <driver/i2s.h>
#include <SPIFFS.h>
#include <SPI.h>
#include <SD.h>

#include "xtronicalPLAY.h"

//I2S ADC SET mic
#define I2S_WS     25   //mic // INMP441 == PIN_I2S_LRC; 
#define I2S_SD     33   /// 
#define I2S_SCK    32   /// bck

// SD Card
#define SD_CS      5
#define SPI_MOSI   23
#define SPI_MISO   19
#define SPI_SCK    18

#define I2S_PORT I2S_NUM_0 // I2S_NUM_0
#define I2S_DAC_PORT I2S_NUM_1
#define I2S_SAMPLE_RATE   (16000)
#define I2S_SAMPLE_BITS   (16)
#define I2S_READ_LEN      (16 * 1024)
#define RECORD_TIME       (5) //Seconds
#define I2S_CHANNEL_NUM   (1)
#define FLASH_RECORD_SIZE (I2S_CHANNEL_NUM * I2S_SAMPLE_RATE * I2S_SAMPLE_BITS / 8 * RECORD_TIME)

//I2S DAC set speaker
#define I2S_BCLK 4   //
#define I2S_LRC  16
#define I2S_MCLK 0
#define I2S_DOUT 2

// I2C
//#define IIC_CLK 32
//#define IIC_DATA 33
#define GPIO_PA_EN 21

#define BUTTON_SAVE      34 // mic's Button pin number
#define BUTTON_PLAY      35 // Speaker's Button pin number

File file;
File file_sd;
File file_txt;
const char filename[] = "/Test.wav";
const int headerSize = 44;

int old_setupMark = 0, old_loopmark = 0, BUTTON_PLAYmark = 0;

uint8_t         m_f_channelEnabled = 3;

//.............................. for play
#define NUM_BYTES_TO_READ_FROM_FILE 1024    // How many bytes to read from wav file at a time
File WavFile;                                 // Object for root of SD card directory
static const i2s_port_t i2s_num = I2S_NUM_0;  // i2s port number

struct WavHeader_Struct
{
  //   RIFF Section
  char RIFFSectionID[4];      // Letters "RIFF"
  uint32_t Size;              // Size of entire file less 8
  char RiffFormat[4];         // Letters "WAVE"

  //   Format Section
  char FormatSectionID[4];    // letters "fmt"
  uint32_t FormatSize;        // Size of format section less 8
  uint16_t FormatID;          // 1=uncompressed PCM
  uint16_t NumChannels;       // 1=mono,2=stereo
  uint32_t SampleRate;        // 44100, 16000, 8000 etc.
  uint32_t ByteRate;          // =SampleRate * Channels * (BitsPerSample/8)
  uint16_t BlockAlign;        // =Channels * (BitsPerSample/8)
  uint16_t BitsPerSample;     // 8,16,24 or 32

  // Data Section
  char DataSectionID[4];      // The letters "data"
  uint32_t DataSize;          // Size of the data that follows
} WavHeader;

//............................... play

void wavHeader(byte* header, int wavSize)
{
  header[0] = 'R';
  header[1] = 'I';
  header[2] = 'F';
  header[3] = 'F';
  unsigned int fileSize = wavSize + headerSize - 8;
  header[4] = (byte)(fileSize & 0xFF);
  header[5] = (byte)((fileSize >> 8) & 0xFF);
  header[6] = (byte)((fileSize >> 16) & 0xFF);
  header[7] = (byte)((fileSize >> 24) & 0xFF);
  header[8] = 'W';
  header[9] = 'A';
  header[10] = 'V';
  header[11] = 'E';
  header[12] = 'f';
  header[13] = 'm';
  header[14] = 't';
  header[15] = ' ';
  header[16] = 0x10;
  header[17] = 0x00;
  header[18] = 0x00;
  header[19] = 0x00;
  header[20] = 0x01;
  header[21] = 0x00;
  header[22] = 0x01;
  header[23] = 0x00;
  header[24] = 0x80;
  header[25] = 0x3E;
  header[26] = 0x00;
  header[27] = 0x00;
  header[28] = 0x00;
  header[29] = 0x7D;
  header[30] = 0x00;
  header[31] = 0x00;
  header[32] = 0x02;
  header[33] = 0x00;
  header[34] = 0x10;
  header[35] = 0x00;
  header[36] = 'd';
  header[37] = 'a';
  header[38] = 't';
  header[39] = 'a';
  header[40] = (byte)(wavSize & 0xFF);
  header[41] = (byte)((wavSize >> 8) & 0xFF);
  header[42] = (byte)((wavSize >> 16) & 0xFF);
  header[43] = (byte)((wavSize >> 24) & 0xFF);

}

void listSPIFFS(void)
{
  Serial.println(F("\r\nListing SPIFFS files:"));
  static const char line[] PROGMEM = "=================================================";
  Serial.println(FPSTR(line));
  Serial.println(F("  File name                              Size"));
  Serial.println(FPSTR(line));
  fs::File root = SPIFFS.open("/");
  //fs::File root = SD.open("/");
  if (!root)
  {
    Serial.println(F("Failed to open directory"));
    return;
  }
  if (!root.isDirectory())
  {
    Serial.println(F("Not a directory"));
    return;
  }
  fs::File file = root.openNextFile();
  while (file)
  {
    if (file.isDirectory())
    {
      Serial.print("DIR : ");
      String fileName = file.name();
      Serial.print(fileName);
    }
    else
    {
      String fileName = file.name();
      Serial.print("  " + fileName);
      // File path can be 31 characters maximum in SPIFFS
      int spaces = 33 - fileName.length(); // Tabulate nicely
      if (spaces < 1) spaces = 1;
      while (spaces--) Serial.print(" ");
      String fileSize = (String) file.size();
      spaces = 10 - fileSize.length(); // Tabulate nicely
      if (spaces < 1) spaces = 1;
      while (spaces--) Serial.print(" ");
      Serial.println(fileSize + " bytes");
    }
    file = root.openNextFile();
  }
  Serial.println(FPSTR(line));
  Serial.println();
  root.close();
  delay(1000);
}
//Function to init sd-card

void SD_card_init()
{
  SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
  if (!SD.begin(SD_CS))
  {
    Serial.println("SD Initialization failed!");
    while (1); // stop
  }
  else
  {
    Serial.println("SD connect!");
  }
  SD.remove(filename);
  file_sd = SD.open(filename, FILE_WRITE);
  if (!file_sd)
  {
    Serial.println("File is not available!");
  }
  byte header[headerSize];
  wavHeader(header, FLASH_RECORD_SIZE);
  file_sd.write(header, headerSize);
  //file_sd.close();
  file_txt = SD.open("/Test_buf.txt", FILE_WRITE);
  //list_SD();
}
//Function to init SPIFFS

void SPIFFSInit()
{
  if (!SPIFFS.begin(true))
  {
    Serial.println("SPIFFS initialisation failed!");
    while (1) yield();
  }
  else
  {
    Serial.println("SPIFFS connect!");
  }
  //SPIFFS.remove(filename);
  SPIFFS.format();
  file = SPIFFS.open(filename, FILE_WRITE);
  if (!file)
  {
    Serial.println("File is not available!");
  }
  byte header[headerSize];
  wavHeader(header, FLASH_RECORD_SIZE);
  file.write(header, headerSize);
  listSPIFFS();
}

//Function to init i2s microfone
void i2sInit()
{
  i2s_config_t i2s_configR = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),  // ADD:| I2S_MODE_TX
    .sample_rate = I2S_SAMPLE_RATE,
    .bits_per_sample = i2s_bits_per_sample_t(I2S_SAMPLE_BITS),
    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
    .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
    .intr_alloc_flags = 0,
    .dma_buf_count = 64,
    .dma_buf_len = 1024,
    .use_apll = 1
  };
  i2s_driver_install(I2S_PORT, &i2s_configR, 0, NULL);
  //i2s_driver_install(I2S_DAC_PORT, &i2s_dac_config, 0, NULL);
  const i2s_pin_config_t pin_config = {
    .bck_io_num =  I2S_SCK,
    .ws_io_num =   I2S_WS,
    .data_out_num = -1,
    .data_in_num = I2S_SD
  };
  i2s_set_pin(I2S_PORT, &pin_config);
  //i2s_set_pin(I2S_DAC_PORT,&pin_dac_config);
}


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

  pinMode(BUTTON_SAVE, INPUT);
  pinMode(BUTTON_PLAY, INPUT);
  SPIFFSInit();
  SD_card_init();

  i2sInit();
//  i2s_DAC_Init();
  // i2s_adc(); // this start record put into button start
  //xTaskCreate(i2s_adc, "i2s_adc", 1024 * 4 , NULL, 1, NULL);
  //xTaskCreate(app_main, "app_main", 1024 * 2, NULL, 1, NULL);
  //xTaskCreate(play_file, "play_file", 1024 * 4, NULL, 1, NULL);

  xtronical_setup();

}
void loop() {

  if (digitalRead(BUTTON_SAVE)) // 34; Record button pushed = Start recording
  {
    Serial.println("BUTTON_SAVE!");
    save_audio();
  }

  if (digitalRead(BUTTON_PLAY)) // 35; Record button pushed = Start recording
  {
    Serial.println("BUTTON_PLAY!");
    PlayWav();
  }

}


void example_disp_buf(uint8_t* buf, int length)
{
  printf("======\n");
  for (int i = 0; i < length; i++) {
    printf("%02x ", buf[i]);
    if ((i + 1) % 8 == 0) {
      printf("\n");
    }
  }
  printf("======\n");
}

void i2s_adc_data_scale(uint8_t * d_buff, uint8_t* s_buff, uint32_t len)
{
  uint32_t j = 0;
  uint32_t dac_value = 0;
  for (int i = 0; i < len; i += 2) {
    dac_value = ((((uint16_t) (s_buff[i + 1] & 0xf) << 8) | ((s_buff[i + 0]))));
    d_buff[j++] = 0;
    d_buff[j++] = dac_value * 256 / 2048;
  }
}


void save_audio() {

  int i2s_read_len = I2S_READ_LEN;
  int flash_wr_size = 0;
  size_t bytes_read;

  char* i2s_read_buff = (char*) calloc(i2s_read_len, sizeof(char));
  uint8_t* flash_write_buff = (uint8_t*) calloc(i2s_read_len, sizeof(uint8_t)); //sizeof(char)
  i2s_read(I2S_PORT, (void*) i2s_read_buff, i2s_read_len, &bytes_read, portMAX_DELAY);
  i2s_read(I2S_PORT, (void*) i2s_read_buff, i2s_read_len, &bytes_read, portMAX_DELAY);

  Serial.println(" *** Recording Start *** ");
  while (flash_wr_size < FLASH_RECORD_SIZE) {
    //read data from I2S bus, in this case, from ADC.
    i2s_read(I2S_PORT, (void*) i2s_read_buff, i2s_read_len, &bytes_read, portMAX_DELAY);
    //example_disp_buf((uint8_t*) i2s_read_buff, 64);
    //save original data from I2S(ADC) into flash.
    i2s_adc_data_scale(flash_write_buff, (uint8_t*)i2s_read_buff, i2s_read_len);
    //example_disp_buf((uint8_t*) flash_write_buff, 64);
    file.write((const byte*) flash_write_buff, i2s_read_len);
    file_sd.write((const byte*) flash_write_buff, i2s_read_len);
    file_txt.write((const byte*) flash_write_buff, i2s_read_len);
    flash_wr_size += i2s_read_len;
    ets_printf("Sound recording %u%%\n", flash_wr_size * 100 / FLASH_RECORD_SIZE);
    //ets_printf("Never Used Stack Size: %u\n", uxTaskGetStackHighWaterMark(NULL));
  }
  file.close();
  file_sd.close();
  file_txt.close();
  i2s_driver_uninstall(I2S_PORT);
  //  i2s_DAC_Init();   /// was comment out
  //i2s_driver_uninstall(I2S_DAC_PORT);

  free(i2s_read_buff);
  i2s_read_buff = NULL;
  free(flash_write_buff);
  flash_write_buff = NULL;

  listSPIFFS();
  //  play_file();
  // play_wav();
  //list_SD();
  vTaskDelete(NULL);
  //new
 i2s_driver_uninstall(I2S_NUM_0); //i2s_num;; stop & destroy i2s driver  I2S_NUM_0
}

//........................

 static const i2s_config_t i2s_configT =
  {
  .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
  .sample_rate = 44100,                                 // Note, this will be changed later
  .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
  .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
  .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
  .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,             // high interrupt priority
  .dma_buf_count = 8,                                   // 8 buffers
  .dma_buf_len = 64,                                    // 64 bytes per buffer, so 8K of buffer space
  .use_apll = 0,
  .tx_desc_auto_clear = true,
  .fixed_mclk = -1
  };   

// These are the physical wiring connections to our I2S decoder board/chip from the esp32, there are other connections
// required for the chips mentioned at the top (but not to the ESP32), please visit the page mentioned at the top for
// further information regarding these other connections.



  static const i2s_pin_config_t pin_config =
  {
  .bck_io_num = I2S_BCLK,                           // The bit clock connectiom, goes to pin 27 of ESP32
  .ws_io_num = I2S_LRC,                             // Word select, also known as word select or left right clock
  .data_out_num = I2S_DOUT,                         // Data out from the ESP32, connect to DIN on 38357A
  .data_in_num = I2S_PIN_NO_CHANGE                  // we are not interested in I2S data into the ESP32
  };   

 void xtronical_setup() {
  //  Serial.begin(115200);                               // Used for info/debug
  //  SDCardInit();
    i2s_driver_install(i2s_num, &i2s_configT, 0, NULL);
    i2s_set_pin(i2s_num, &pin_config);
  // get the wav file from the SD card
  WavFile = SD.open("/wavfile.wav");                   // Open the wav file
  if (WavFile == false)
    Serial.println("Could not open 'wavfile.wav'");
  else
  {
    WavFile.read((byte *) &WavHeader, 44);              // Read in the WAV header, which is first 44 bytes of the file.
    // We have to typecast to bytes for the "read" function
    DumpWAVHeader(&WavHeader);                          // Dump the header data to serial, optional!
    if (ValidWavData(&WavHeader))                       // optional if your sure the WAV file will be valid.
      i2s_set_sample_rates(i2s_num, WavHeader.SampleRate);      //set sample rate
  }
}


void PlayWav()
{

 Serial.println("PlayWav()");
 
  //new add to remove the i2s driver installed for record
  ///  i2s_driver_uninstall(i2s_num); //stop & destroy i2s driver  /// I2S_NUM_0
  //  i2s_driver_uninstall(I2S_NUM_0);  // got: E (13461) I2S: i2s_driver_uninstall(2048): I2S port 0 has not installed

  static bool ReadingFile = true;                     // True if reading file from SD. false if filling I2S buffer
  static byte Samples[NUM_BYTES_TO_READ_FROM_FILE];   // Memory allocated to store the data read in from the wav file
  static uint16_t BytesRead;                          // Num bytes actually read from the wav file which will either be
  // NUM_BYTES_TO_READ_FROM_FILE or less than this if we are very
  // near the end of the file. i.e. we can't read beyond the file.

  if (ReadingFile)                                    // Read next chunk of data in from file if needed
  {
    BytesRead = ReadFile(Samples);                    // Read data into our memory buffer, return num bytes read in
    ReadingFile = false;                              // Switch to sending the buffer to the I2S
  }
  else
    ReadingFile = FillI2SBuffer(Samples, BytesRead);     // We keep calling this routine until it returns true, at which point
  // this will swap us back to Reading the next block of data from the file.
  // Reading true means it has managed to push all the data to the I2S
  // Handler, false means there still more to do and you should call this
  // routine again and again until it returns true.
}

uint16_t ReadFile(byte* Samples)
{
  static uint32_t BytesReadSoFar = 0;                 // Number of bytes read from file so far
  uint16_t BytesToRead;                               // Number of bytes to read from the file

  if (BytesReadSoFar + NUM_BYTES_TO_READ_FROM_FILE > WavHeader.DataSize) // If next read will go past the end then adjust the
    BytesToRead = WavHeader.DataSize - BytesReadSoFar;                // amount to read to whatever is remaining to read
  else
    BytesToRead = NUM_BYTES_TO_READ_FROM_FILE;                        // Default to max to read

  WavFile.read(Samples, BytesToRead);                 // Read in the bytes from the file
  BytesReadSoFar += BytesToRead;                      // Update the total bytes red in so far

  if (BytesReadSoFar >= WavHeader.DataSize)           // Have we read in all the data?
  {
    WavFile.seek(44);                                 // Reset to start of wav data
    BytesReadSoFar = 0;                               // Clear to no bytes read in so far
  }
  return BytesToRead;                                 // return the number of bytes read into buffer
}

bool FillI2SBuffer(byte* Samples, uint16_t BytesInBuffer)
{
  // Writes bytes to buffer, returns true if all bytes sent else false, keeps track itself of how many left
  // to write, so just keep calling this routine until returns true to know they've all been written, then
  // you can re-fill the buffer

  size_t BytesWritten;                        // Returned by the I2S write routine,
  static uint16_t BufferIdx = 0;              // Current pos of buffer to output next
  uint8_t* DataPtr;                           // Point to next data to send to I2S
  uint16_t BytesToSend;                       // Number of bytes to send to I2S

  // To make the code eaier to understand I'm using to variables to some calculations, normally I'd write this calcs
  // directly into the line of code where they belong, but this make it easier to understand what's happening

  DataPtr = Samples + BufferIdx;                           // Set address to next byte in buffer to send out
  BytesToSend = BytesInBuffer - BufferIdx;                 // This is amount to send (total less what we've already sent)
  i2s_write(i2s_num, DataPtr, BytesToSend, &BytesWritten, 1); // Send the bytes, wait 1 RTOS tick to complete
  BufferIdx += BytesWritten;                               // increasue by number of bytes actually written

  if (BufferIdx >= BytesInBuffer)
  {
    // sent out all bytes in buffer, reset and return true to indicate this
    BufferIdx = 0;
    return true;
  }
  else
    return false;       // Still more data to send to I2S so return false to indicate this
}

void SDCardInit()
{
  pinMode(SD_CS, OUTPUT);
  digitalWrite(SD_CS, HIGH); // SD card chips select, must use GPIO 5 (ESP32 SS)
  if (!SD.begin(SD_CS))
  {
    Serial.println("Error talking to SD card!");
    while (true);                 // end program
  }
}

bool ValidWavData(WavHeader_Struct* Wav)
{

  if (memcmp(Wav->RIFFSectionID, "RIFF", 4) != 0)
  {
    Serial.print("Invalid data - Not RIFF format");
    return false;
  }
  if (memcmp(Wav->RiffFormat, "WAVE", 4) != 0)
  {
    Serial.print("Invalid data - Not Wave file");
    return false;
  }
  if (memcmp(Wav->FormatSectionID, "fmt", 3) != 0)
  {
    Serial.print("Invalid data - No format section found");
    return false;
  }
  if (memcmp(Wav->DataSectionID, "data", 4) != 0)
  {
    Serial.print("Invalid data - data section not found");
    return false;
  }
  if (Wav->FormatID != 1)
  {
    Serial.print("Invalid data - format Id must be 1");
    return false;
  }
  if (Wav->FormatSize != 16)
  {
    Serial.print("Invalid data - format section size must be 16.");
    return false;
  }
  if ((Wav->NumChannels != 1) & (Wav->NumChannels != 2))
  {
    Serial.print("Invalid data - only mono or stereo permitted.");
    return false;
  }
  if (Wav->SampleRate > 48000)
  {
    Serial.print("Invalid data - Sample rate cannot be greater than 48000");
    return false;
  }
  if ((Wav->BitsPerSample != 8) & (Wav->BitsPerSample != 16))
  {
    Serial.print("Invalid data - Only 8 or 16 bits per sample permitted.");
    return false;
  }
  return true;
}


void DumpWAVHeader(WavHeader_Struct* Wav)
{
  if (memcmp(Wav->RIFFSectionID, "RIFF", 4) != 0)
  {
    Serial.print("Not a RIFF format file - ");
    PrintData(Wav->RIFFSectionID, 4);
    return;
  }
  if (memcmp(Wav->RiffFormat, "WAVE", 4) != 0)
  {
    Serial.print("Not a WAVE file - ");
    PrintData(Wav->RiffFormat, 4);
    return;
  }
  if (memcmp(Wav->FormatSectionID, "fmt", 3) != 0)
  {
    Serial.print("fmt ID not present - ");
    PrintData(Wav->FormatSectionID, 3);
    return;
  }
  if (memcmp(Wav->DataSectionID, "data", 4) != 0)
  {
    Serial.print("data ID not present - ");
    PrintData(Wav->DataSectionID, 4);
    return;
  }
  // All looks good, dump the data
  Serial.print("Total size :"); Serial.println(Wav->Size);
  Serial.print("Format section size :"); Serial.println(Wav->FormatSize);
  Serial.print("Wave format :"); Serial.println(Wav->FormatID);
  Serial.print("Channels :"); Serial.println(Wav->NumChannels);
  Serial.print("Sample Rate :"); Serial.println(Wav->SampleRate);
  Serial.print("Byte Rate :"); Serial.println(Wav->ByteRate);
  Serial.print("Block Align :"); Serial.println(Wav->BlockAlign);
  Serial.print("Bits Per Sample :"); Serial.println(Wav->BitsPerSample);
  Serial.print("Data Size :"); Serial.println(Wav->DataSize);
}

void PrintData(const char* Data, uint8_t NumBytes)
{
  for (uint8_t i = 0; i < NumBytes; i++)
    Serial.print(Data[i]);
  Serial.println();
}

OUTPUT:

22:43:51.239 -> E (14960) I2S: i2s_write(2129): TX mode is not enabled
22:43:51.239 -> BUTTON_PLAY!
22:43:51.286 -> PlayWav()

I will take a SWAG and say when you are writing the SD card your code is blocked at that point until the write is finished.

2 Likes

Thank you.
do you have a example where and how ?
sorry I don't know that and PLAY happened after the file wrote and listed.

Sorry I do not but there are some on line, spend some time with your favorite search engine and you should find something.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.