Skip to content

2.4_2.8inch_Lesson10_Microphone_Record_Playback

1. Course Introduction

This lesson is an audio input/output experiment: it uses the PDM microphone on the 2.4-inch / 2.8-inch V1.1 / V1.2 board to record 5 seconds of audio, then plays it back through the onboard I2S amplifier and speaker.

For the 2.4-inch / 2.8-inch board: the microphone uses GPIO9 / GPIO10, the speaker I2S interface uses GPIO13 / GPIO11 / GPIO12, amplifier mute is controlled by GPIO21, and GPIO45 is pulled high to select the MIC function.

2. Learning Objectives

  • Be able to open and flash the Lesson10 microphone record-and-playback project in the Arduino IDE.
  • Be able to explain the pin differences between the PDM microphone input and the I2S amplifier output.
  • Be able to understand the roles of recordWAV(), setPinsPdmRx(), and setPins() in ESP_I2S.h.
  • Be able to determine from the serial log whether recording, memory allocation, and I2S playback are working properly.
  • Be able to adjust PLAYBACK_GAIN based on the output volume or any distortion (clipping).

3. What You Need

  • A CrowPanel Advance 2.4-inch or 2.8-inch V1.1 / V1.2 development board.
  • A USB data cable that supports data transfer.
  • An external speaker or the audio output module included with the course.
  • The Arduino IDE.
  • esp32 by Espressif Systems version 3.3.8 installed via Boards Manager.
  • Project code: Mic_Record_5s_Playback.ino.

Code and Resource Download

Code download: - lesson-10/Mic_Record_5s_Playback

4. Software Operation Steps

  1. Open the project in the Arduino IDE:
Mic_Record_5s_Playback/Mic_Record_5s_Playback.ino

image-20260730144255632

image-20260730144429108

  1. Connect the external speaker, and use a USB cable that supports data transfer to connect the board to your computer. IMG_8128

  2. Complete the development environment configuration from the Tools menu in order: confirm that the esp32 by Espressif Systems version in Boards Manager is 3.3.8, then set Board to ESP32S3 Dev Module, choose the serial port corresponding to your board under Port, set Flash Size to 16MB (128Mb), set PSRAM to OPI PSRAM, and set Partition Scheme to Huge APP (3MB No OTA/1MB SPIFFS).

image-20260730144712204

  1. Click Upload to compile and wait for the upload to finish. If it gets stuck at Connecting..., check for port conflicts and use the BOOT/RESET download mode as required by the board.

2.4_2.8_Arduino-10_05

  1. Open the Serial Monitor in the top-right corner, set the baud rate to 115200, and observe the log. After the board resets, it automatically records for 5 seconds and plays back once. To record again, type r or R in the Serial Monitor and send it.

image-20260730145208627

image-20260730145248727

Note: This lesson depends on ESP_I2S.h, which is included with Arduino-ESP32 3.3.8. If you are using an older ESP32 Core, ESP_I2S.h or the recordWAV() interface may not be found.

5. Hardware Operation Steps

  1. Confirm that the board is a 2.4-inch / 2.8-inch V1.1 / V1.2, and connect the speaker or the audio output module included with the course.

IMG_8127

  1. Connect the board to your computer using a USB data cable.

IMG_8128

  1. After flashing, keep the surrounding environment quiet and wait for the serial output Recording for 5 seconds....

  2. Speak toward the microphone or play a sound, and wait for playback to start automatically once recording finishes.

  3. After the serial prompt Send r in Serial Monitor to run it again., you can send r again to re-record and play back.

image-20260730145248727

6. Key Code Explanation

6.1 Audio Pins for the Small-Sized Board

constexpr int MIC_CLK = 9;
constexpr int MIC_DATA = 10;

constexpr int SPK_BCLK = 13;
constexpr int SPK_LRCLK = 11;
constexpr int SPK_DATA = 12;

constexpr int AMP_MUTE_PIN = 21;
constexpr int AUDIO_SELECT_PIN = 45;
constexpr int AUDIO_SELECT_MIC_LEVEL = HIGH;

This set of definitions is the core of adapting the large-screen code to the 2.4-inch / 2.8-inch board. MIC_CLK and MIC_DATA are the PDM microphone inputs; SPK_BCLK, SPK_LRCLK, and SPK_DATA are the I2S playback outputs; AMP_MUTE_PIN controls amplifier mute; AUDIO_SELECT_PIN is used to select the MIC function, and the current code pulls GPIO45 high.

6.2 Selecting the Microphone Path and Muting the Amplifier

void selectMicrophonePath() {
  pinMode(AUDIO_SELECT_PIN, OUTPUT);
  digitalWrite(AUDIO_SELECT_PIN, AUDIO_SELECT_MIC_LEVEL);
}

void muteAmplifier(bool mute) {
  pinMode(AMP_MUTE_PIN, OUTPUT);
  digitalWrite(AMP_MUTE_PIN, mute ? HIGH : LOW);
}

The large-screen example sends audio control commands through the I2C address 0x30; the small-sized board does not use this control method, so the updated code switches to controlling the GPIO directly. Before recording, call selectMicrophonePath() to select the microphone function, and at the same time call muteAmplifier(true) to mute the amplifier, preventing feedback or noise during the recording stage; unmute again when playing back.

6.3 Recording Parameters

constexpr uint32_t SAMPLE_RATE = 16000;
constexpr uint32_t RECORD_SECONDS = 5;
constexpr float PLAYBACK_GAIN = 8.0f;
constexpr size_t WAV_HEADER_SIZE = 44;

SAMPLE_RATE is 16 kHz, which is suitable for voice recording; RECORD_SECONDS means each recording lasts 5 seconds; PLAYBACK_GAIN is the playback gain used to amplify the sound captured by the microphone; WAV_HEADER_SIZE is the length of a standard WAV file header, which must be skipped when extracting the PCM data later.

6.4 PDM Microphone Recording

audio.setPinsPdmRx(MIC_CLK, MIC_DATA);
if (!audio.begin(I2S_MODE_PDM_RX, SAMPLE_RATE,
                 I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO)) {
  Serial.println("ERROR: PDM microphone initialization failed.");
  return false;
}

Serial.println("Recording for 5 seconds...");
uint8_t *wav = audio.recordWAV(RECORD_SECONDS, &wavSize);
audio.end();

setPinsPdmRx() sets the clock and data pins for the PDM microphone. audio.begin() starts the I2S peripheral in PDM RX mode, with a sample rate of 16 kHz, a bit width of 16 bits, and mono channels. recordWAV() records for the specified number of seconds and returns the in-memory data with a WAV file header. After recording finishes, call audio.end() to release the current I2S channel, making it easier to switch to the standard I2S playback mode afterward.

6.5 Recording Data Check

if (wav == nullptr || wavSize <= WAV_HEADER_SIZE) {
  Serial.println("ERROR: Recording failed (check PSRAM/memory). ");
  free(wav);
  return false;
}
Serial.printf("Recording complete: %u bytes\n", static_cast<unsigned>(wavSize));

recordWAV() needs to allocate memory for the recorded data. If PSRAM is not enabled or memory is insufficient, wav may be null; if the data length is less than or equal to 44 bytes, it means only the file header was obtained and there is no valid PCM audio data. With a normal 5-second, 16 kHz, 16-bit mono recording, the data volume is approximately 160 KB plus the WAV file header.

6.6 Mono to Right-Channel Stereo Conversion

uint8_t *monoBytes = wav + WAV_HEADER_SIZE;
size_t monoSize = wavSize - WAV_HEADER_SIZE;
size_t sampleCount = monoSize / sizeof(int16_t);
size_t stereoSize = sampleCount * 2 * sizeof(int16_t);

int16_t *stereo = static_cast<int16_t *>(
    heap_caps_malloc(stereoSize, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
bool stereoInPsram = stereo != nullptr;
if (stereo == nullptr) {
  stereo = static_cast<int16_t *>(malloc(stereoSize));
}

recordWAV() returns mono data with a WAV header, so the code first skips the 44-byte header, then calculates the number of samples. The onboard playback path uses standard I2S stereo data, so the code allocates a new stereo buffer and expands the mono data into stereo.

const int16_t *mono = reinterpret_cast<const int16_t *>(monoBytes);
for (size_t i = 0; i < sampleCount; ++i) {
  stereo[i * 2] = 0;
  stereo[i * 2 + 1] = amplify(mono[i]);
}

The current audio output is mainly connected to the right channel, so the left channel is filled with 0 and the right channel is filled with the amplified recorded data. amplify() clamps the maximum and minimum values to avoid numeric overflow.

6.7 Playback to the I2S Amplifier

audio.setPins(SPK_BCLK, SPK_LRCLK, SPK_DATA);
if (!audio.begin(I2S_MODE_STD, SAMPLE_RATE,
                 I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO)) {
  Serial.println("ERROR: I2S speaker initialization failed.");
  return false;
}

Serial.println("Playing the recording...");
muteAmplifier(false);
size_t bytesWritten = audio.write(reinterpret_cast<uint8_t *>(stereo), stereoSize);
delay(20);
muteAmplifier(true);
audio.end();

After recording finishes, the I2S peripheral switches to standard output mode. setPins() sets the BCLK, LRCLK, and DATA pins used by the amplifier; audio.write() writes the stereo PCM data to I2S. Unmute before playback, then after playback finishes add a 20 ms delay to let the last DMA data be sent out, and mute again to reduce background noise or popping.

6.8 Serial-Triggered Re-Recording

void loop() {
  if (Serial.available()) {
    char command = Serial.read();
    if (command == 'r' || command == 'R') {
      while (Serial.available()) Serial.read();
      recordAndPlay();
    }
  }
  delay(10);
}

After power-on, setup() automatically runs recordAndPlay() once. The program then waits for serial input in loop(), and upon receiving r or R it clears the serial buffer and runs the record-and-playback flow again. This lets you repeatedly test the microphone and speaker without re-flashing the program.

7. Experimental Observations

After flashing completes and the board resets, the serial output will be, in order:

5-second microphone record/playback demo
Target board: CrowPanel Advance 2.4/2.8inch V1.1/V1.2
Send r in Serial Monitor to run it again.
Recording for 5 seconds...
Recording complete: ...
Playing the recording...
Playback complete: ... bytes written.

Under normal conditions, the board first records 5 seconds of ambient sound, then plays it back through the speaker. After sending r, it records and plays back again.

image-20260730145509555

8. Code Download

Code download: lesson-10/Mic_Record_5s_Playback