yahboom
HomeLearn
Solution
Blog
Online Store
About Us
EN/中文
ESP32 Core Board📖
1. ESP32 Introduction
  • 1. ESP32S3 Overview
  • 2. Environment Setup Introduction
  • 3. Dev Board Introduction
2. ESP-IDF Development Tutorial
1. Before Development
  • 1. Environment Setup (Windows)
  • 2. Environment Setup (Linux)
  • 3. Build and Flash
2. Basic Peripherals
  • 1. LED Control
  • 2. Button Detection
  • 3. Peripheral Interrupts
  • 4. ESP_TIMER
  • 5. GPTIMER
  • 6. Watchdog Timer
  • 7. DMA
  • 8. ADC
  • 9. PWM Breathing LED
  • 10. RGB LED Effects
3. Serial Communication and Storage System
  • 1. Serial UART Communication Experiment
  • 2. I2C_OLED Display Experiment
  • 3. SPI Bus Communication Experiment
  • 4. PSRAM External Memory Experiment
  • 5. Flash Read and Write Experiment
  • 6. USB Emulated Mass Storage Experiment
4. FreeRTOS operating system
  • 1.FreeRTOS Core Concepts Theory
  • 2.Task Creation and Management
  • 3.Queue
  • 4.Binary Semaphore
  • 5.Mutex
  • 6.Event Groups
  • 7.Software Timer
  • 8.Task Scheduler
  • 9.Low Power Mode
  • 10.Deadlock
  • 11.Priority Inversion
5. Network Protocols and Wireless Communication
  • 1. WIFI Network Basics
  • 2. WIFI STA Mode
  • 3. WIFI AP Mode
  • 4. UDP Client Experiment
  • 5. UDP Server Experiment
  • 6. TCP Client Experiment
  • 7. TCP Server Experiment
  • 8. Socket Programming Basics
  • 9. HTTP Client Experiment
  • 10. HTTP Server Experiment
  • 11. MQTT IoT Communication Protocol
  • 12. WIFI OTA Upgrade
  • 13. Bluetooth Fundamentals
  • 14. Bluetooth Slave Mode
  • 15. Bluetooth Master Mode
6. Audio Applications
  • 1.I2S Microphone Recording and U-Disk Playback
  • 2.I2S Audio Playback Experiment
  • 3.Repeater Experiment
7. Xiaozhi AI Voice
  • 1. Burn Xiaozhi Firmware
  • 2. Network and Configuration
  • 3. AI Agent Settings
  • 4. Program Analysis
  • 5. Free Chat
3. Arduino Development Course
1. Before Development
  • 1. Environment Setup
  • 2. Build and Flash
2. Basic Peripherals
  • 1. LED Control
  • 2. Button Detection
  • 3. Peripheral Interrupts
  • 4. ESP_TIMER
  • 5. GPTIMER
  • 6. Watchdog Timer
  • 7. ADC
  • 8. PWM Breathing LED
  • 9. RGB LED Effects
3. Serial Communication and Storage System
  • 1. Serial UART Communication
  • 2. I2C OLED Display
  • 3. SPI Bus Communication
  • 4. Flash Read and Write
  • 5. USB Emulated Mass Storage
4. Network Protocols and Wireless Communication
  • 1. WIFI Network Basics
  • 2. WIFI STA Mode
  • 3. WIFI AP Mode
  • 4. UDP Server Experiment
  • 5. UDP Client Experiment
  • 6. TCP Client Experiment
  • 7. TCP Server Experiment
  • 8. HTTP Client Experiment
  • 9. HTTP Server Experiment
  • 10. MQTT IoT Communication Protocol
  • 11. WIFI OTA Upgrade
  • 12. Bluetooth Fundamentals
  • 13. Bluetooth Slave Mode
  • 14. Bluetooth Master Mode
5. Audio Applications
  • 1.I2S Microphone Recording and U-Disk Playback
  • 2.I2S Audio Playback Experiment
  • 3.Repeater Experiment
4. MicroPython Development Course
1. Before Development
  • 1. Environment Setup
  • 2. Build and Flash
  • 3. Thonny IDE
2. Basic Peripherals
  • 1. LED Control
  • 2. Button Detection
  • 3. Peripheral Interrupts
  • 4. TIMER_IT
  • 5. Watchdog Timer
  • 6. ADC
  • 7. PWM Breathing LED
  • 8. RGB LED Effects
3. Serial Communication and Storage System
  • 1. Serial UART Communication Experiment
  • 2. I2C_OLED Display Experiment
  • 3. SPI Bus Communication Experiment
  • 4. Flash Read and Write Experiment
  • 5. Print Storage
4. Network Protocols and Wireless Communication
  • 1. WIFI Network Basics
  • 2. WIFI STA Mode
  • 3. WIFI AP Mode
  • 4. UDP Client Experiment
  • 5. UDP Server Experiment
  • 6. TCP Client Experiment
  • 7. TCP Server Experiment
  • 8. HTTP Client Experiment
  • 9. HTTP Server Experiment
  • 10. MQTT
  • 11. Bluetooth Fundamentals
  • 12. Bluetooth Slave Mode
  • 13. Bluetooth Master Mode
5. Audio Applications
  • 1. I2S Audio Playback Experiment
  • 2. Repeater Experiment
Download
  • Code
  • Hardware_Info
  • 3D_Model_File
  • Software_Tools
  • VMware
  • VM_File
Download ZIP

Welcome to ESP32 Core Board repository

3.Repeater Experiment

Audio Recorder/Player Experiment


Audio Recorder/Player Experiment1. Audio Recording and Playback OverviewWhy Do You Need This Tutorial?Application ScenariosWhat This Project Does1.1 Comparison with the Previous Two Projects2. Core Concepts2.1 Full-Duplex I2S2.2 ES8311 BOTH Mode2.3 Pop Suppression Mechanism2.4 SPIFFS File Storage3. Project Architecture and Flow3.1 Recording Data Flow3.2 Playback Data Flow4. Hardware Dependencies5. Source Code Details5.1 app_speech.h — Macro Configuration and Interfaces5.2 app_speech.c — Full-Duplex Audio Chain5.3 wav_file_player.c — Player (with DAC Gain Control)5.4 main.c — Main Entry and Button State Machine5.5 wav_handler.c — WAV File Read/Write5.6 rgb_led.c — WS2812 RGB LED6. Operation Flow6.1 menuconfig Configuration6.2 Build and Flash6.3 Normal Operation6.4 Edge Cases7. Common Issues


1. Audio Recording and Playback Overview

Why Do You Need This Tutorial?

In embedded audio development, simultaneously implementing recording and playback is a key milestone for advancement. Standalone playback (Audio_Playing) or recording (Audio_Recording) only requires I2S half-duplex mode, whereas recorder/player combo demands full-duplex I2S bus driving both ADC and DAC simultaneously, with careful management of timing and resource sharing between the two. This tutorial is designed to solve this challenge. It not only teaches you how to establish a full-duplex audio chain, but also provides production-ready solutions for pop suppression, button interaction, file storage, and more.

Application Scenarios

The techniques demonstrated in this project can be widely applied to the following scenarios:

  • Smart Voice Assistants: Complete chain of audio sampling → speech recognition → speech synthesis playback

  • Intercom/Access Control Systems: Half-duplex communication where pressing the button records (talk) and releasing plays the other party's voice

  • Audio Memo/Answering Machine: Human interaction paradigm with long press to record, short press to playback

  • Educational Toys/Learning Devices: Learning feedback loop of echo recording → playback comparison

  • IoT Voice Alarm: Event-triggered pre-recorded voice, local instant playback

What This Project Does

This project implements a button-driven audio recorder/player that simultaneously drives the ES8311 ADC and DAC via full-duplex I2S bus, supporting dual-mode operation with long-press-to-record and short-press-to-play. Recording data is stored in WAV format in the SPIFFS flash filesystem, with built-in pop suppression mechanism to ensure playback quality.

Core capabilities:

FunctionDescription
RecordingES8311 ADC, 16kHz / 16-bit / mono, max 20 seconds, heap memory buffer 640KB
PlaybackES8311 DAC, 16kHz / 16-bit / stereo (mono duplicated), SPIFFS storage
Button LogicBOOT button (GPIO0): Long press+hold=record, release=stop; Short press=play/pause/resume
LED FeedbackWS2812 RGB LED: Recording=green, Recording done=red, Playing=rainbow color animation
Pop SuppressionDAC mute → min gain → mute flush → PA on → DAC stays muted, restore gain and unmute before playback

1.1 Comparison with the Previous Two Projects

FeatureAudio_PlayingAudio_RecordingAudio_Play_Record_v2
DAC Playback✓✗✓
ADC Recording✗✓✓
I2S ModeTX onlyRX onlyTX+RX Full-Duplex
StorageSPIFFSFAT + USB MSCSPIFFS
LED DriverManual RMTled_strip libraryManual RMT
Pop SuppressionPA last on + mute flushN/ADAC hardware mute + multi-stage register control
Button LogicShort press play/pause, long press stopShort press start/stop recordingShort press play/pause/resume, long press record

2. Core Concepts

2.1 Full-Duplex I2S

Full-duplex means I2S has both TX and RX channels open simultaneously, sharing MCLK / BCLK / WS clock signals:

          MCLK (GPIO15)
ESP32 ─────────────────────→ ES8311
          BCLK (GPIO16)
ESP32 ─────────────────────→ ES8311
          WS   (GPIO18)
ESP32 ─────────────────────→ ES8311
          DOUT (GPIO8)
ESP32 ─────────────────────→ ES8311 SDIN (DAC)    ← Playback path
          DIN  (GPIO17)
ESP32 ←───────────────────── ES8311 SDOUT (ADC)   ← Recording path
          I2C  (GPIO41/42)
ESP32 ←────────────────────→ ES8311 Control Interface

In full-duplex mode, TX and RX sampling parameters must match (same sample rate, same bit width), because in the standard Philips protocol they share the WS clock.

2.2 ES8311 BOTH Mode

ES8311 is configured in ADC+DAC dual mode (ESP_CODEC_DEV_WORK_MODE_BOTH):

xxxxxxxxxx
es8311_codec_cfg_t ecfg = {
    .codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH,
};
esp_codec_dev_cfg_t dc = {
    .dev_type = ESP_CODEC_DEV_TYPE_IN_OUT,       // Input + Output
};
ParameterValue
Working ModeBOTH (ADC + DAC)
I2S Clock SourceXTAL (external crystal)
Sample Rate16000 Hz
Bit Depth16-bit
ChannelsStereo (mono data duplicated to L+R)
MIC Gain40 dB

2.3 Pop Suppression Mechanism

Improper startup timing between ES8311 DAC and NS4150 amplifier at power-on can produce a "pop" sound. This project uses a multi-stage suppression strategy based on DAC hardware registers, fundamentally different from the Audio_Playing project's "PA last on + mute flush" strategy:

Comparison DimensionAudio_Playing (Player)Audio_Play_Record_v2 (Recorder/Player)
Suppression MethodMute flush + timing controlDAC hardware mute + gain register control + mute flush
PA Enable TimingPA turns on as the final step after I2S+Codec are readyPA turns on inside init_codec_official(), waiting 100ms for stabilization after
DAC StateSet to 90% volume after init, no muteDAC stays muted (mute=true) after init, only unmutes when playing
Before PlaybackOnly write ~25ms silence preambleFirst gain_max() → unmute → wait 10ms → then write silence preamble
When PausedDo not touch DACImmediately mute DAC, no noise
At End of PlaybackDo not touch DAC (only stop task)Silence trailer → re-mute DAC

This project's six-stage suppression flow:

xxxxxxxxxx
Stage 1: DAC mute (mute=true)
Stage 2: Enable analog path (REG0E=0x02)
Stage 3: Set minimum gain (REG32=0x00)
Stage 4: Write silence data to I2S TX pipe (~200ms zero-value PCM), flush residual noise
Stage 5: Enable amplifier power (PA=on), wait 100ms for stabilization
Stage 6: Keep DAC muted, wait for playback command

Recovery flow when playback starts:

​x
Before playback:
  ① app_speech_dac_gain_max()     → Set max gain (REG32=0xFF)
  ② app_speech_dac_mute(false)    → Unmute
  ③ vTaskDelay(10)                → Wait for gain and unmute to take effect
  ④ Write silence buffer as preamble → Flush once more to ensure cleanliness (256×4 bytes)

After playback ends:
  ① Write silence buffer as trailer (256×4 bytes)
  ② app_speech_dac_mute(true)     → Re-mute

Core Difference: The player relies on "PA last on, I2S already outputting silence" to avoid pops, suitable for pure playback scenarios. The recorder/player introduces DAC hardware mute and gain register control, enabling finer management of mute switching between play/pause/stop, adapting to the complex workflow of combined recording and playback.

2.4 SPIFFS File Storage

This project uses SPIFFS as the storage medium for WAV files (same as Audio_Playing, different from Audio_Recording's FAT + USB MSC).

ComparisonSPIFFSFAT + USB MSC
Direct PC ReadNot supportedSupported (USB drive mode)
Wear LevelingBuilt-inDepends on WL layer
File OperationsPOSIX fopen/freadPOSIX fopen/fread
Use CaseFirmware internal read-only resourcesData requiring PC interaction

3. Project Architecture and Flow

xxxxxxxxxx
app_main()
  ├─ remove(REC_FILE)         ← Delete old recording file
  ├─ Codec_I2S_init()         ← Full-duplex audio chain initialization
  │   ├─ power_on_codec()            PA pin initialized as output (off)
  │   ├─ i2c_init()                  I2C bus + scan
  │   ├─ i2s_driver_init()          I2S TX + RX full-duplex channels
  │   └─ init_codec_official()       ES8311 BOTH mode + pop suppression + PA on + wait stable
  │
  ├─ wav_player_init()        ← SPIFFS mount
  ├─ rgb_led_init()           ← WS2812 init and turn off lights
  ├─ malloc(REC_BUF_LEN)      ← Pre-allocate 640KB recording buffer
  │
  └─ xTaskCreate(v_button_task)  ← Button polling task

v_button_task (FreeRTOS Task, 50Hz):
  while(1):
    Detect BOOT button (active low)
    Debounce 30ms
    |
    ├─ Long press (≥800ms) → Recording flow:
    │   1. Set g_recording = true
    │   2. xTaskCreate(v_record_task)  Start recording task
    │   3. Wait for release (while button not released)
    │   4. Set g_recording = false
    │   5. Wait for recording task to exit
    │
    └─ Short press (<800ms) → Playback flow:
        1. Playing and paused → wav_player_resume()
        2. Playing and not paused → wav_player_pause()
        3. Idle         → wav_player_play_file(REC_FILE)

v_record_task (FreeRTOS Task):
  while(g_recording):
    i2s_channel_read(rx_handle_) → Extract left channel from stereo pair → g_rec_buf
  g_rec_len > 0 → wav_save_file("/spiffs/record.wav")

v_wav_play_task (wav_file_player.c, FreeRTOS Task):
  fopen → read WAV header → app_speech_dac_gain_max() → unmute → loop:
    fread(mono) → mono→stereo copy → i2s_channel_write(tx)
    When paused: while(s_is_paused) vTaskDelay
  → Silence flush → mute → fclose

3.1 Recording Data Flow

xxxxxxxxxx
ES8311 ADC → I2S DIN(GPIO17) → i2s_channel_read(rx_handle_)
  → Stereo pair {L0,R0,L1,R1,...} → Extract left channel {L0,L1,...}
  → g_rec_buf[heap]
  → wav_save_file() → SPIFFS /spiffs/record.wav

3.2 Playback Data Flow

xxxxxxxxxx
SPIFFS /spiffs/record.wav → fopen/fread → mono PCM
  → Mono→Stereo copy → i2s_channel_write(tx_handle_)
  → I2S DOUT(GPIO8) → ES8311 DAC → NS4150 PA → Speaker

4. Hardware Dependencies

Hardware Connections:

PinFunctionConnected To
GPIO42I2C SCLES8311 SCL
GPIO41I2C SDAES8311 SDA
GPIO15I2S MCLKES8311 MCLK
GPIO16I2S BCLKES8311 BCLK
GPIO18I2S WSES8311 LRCK
GPIO8I2S DOUTES8311 SDIN (DAC)
GPIO17I2S DINES8311 SDOUT (ADC)
GPIO7PA_ENNS4150 amplifier enable
GPIO48WS2812 DataRGB LED
GPIO0BOOT buttonActive low

Required Components: driver/i2s_std / driver/i2c_master / driver/rmt_tx / esp_spiffs / FreeRTOS

This project uses third-party libraries again: For details, refer back to (Detailed third-party library installation process: 2.Basic Peripherals→10.RGB LED Effects)

Note: esp_codec_dev and its dependency es8311 are third-party components provided by the ESP-IDF Component Registry, not built into IDF. Before using, declare the dependency in main/idf_component.yml, otherwise compilation will fail to find the header file:

xxxxxxxxxx
## IDF Component Manager Manifest File
dependencies:
idf:
 version: '>=4.1.0'
espressif/esp_codec_dev: ^1.5.10

After declaration, executing idf.py build will cause the Component Manager to automatically download the component to the managed_components/ directory. es8311 is a transitive dependency of esp_codec_dev and does not need to be declared separately.


5. Source Code Details

Complete source code: Source Code → ESP-IDF → 6.Audio → Audio_Play_Record_v2

5.1 app_speech.h — Macro Configuration and Interfaces

The header file centrally manages full-duplex I2S pin definitions, audio parameters, and channel handles, serving as the "configuration hub" for the entire audio chain. The following macro definitions and global declarations determine the hardware pins and audio quality parameters for recording/playback:

xxxxxxxxxx
/* I2S Full-Duplex Pins */
#define I2S_MCLK     GPIO_NUM_15        // Master Clock
#define I2S_BCLK     GPIO_NUM_16        // Bit Clock
#define I2S_WS       GPIO_NUM_18        // Word Select (LRCK)
#define I2S_DIN      GPIO_NUM_17        // RX: ES8311 ADC → ESP32
#define I2S_DOUT     GPIO_NUM_8         // TX: ESP32 → ES8311 DAC

/* Audio Parameters */
#define EXAMPLE_MIC_GAIN        40       // Mic Gain 40dB
#define EXAMPLE_VOICE_VOLUME    90       // Max Output Volume
#define EXAMPLE_SAMPLE_RATE     16000    // Sample Rate 16kHz
#define EXAMPLE_MCLK_MULTIPLE   256      // MCLK Multiple

/* Global I2S Channel Handles */
extern i2s_chan_handle_t tx_handle_;     // I2S TX Channel
extern i2s_chan_handle_t rx_handle_;     // I2S RX Channel

Interface list:

FunctionDescription
Codec_I2S_init()Initialize full-duplex audio chain
app_speech_record(buf, len, &read)Read PCM from I2S RX
app_speech_play(buf, len, &written)Write PCM to I2S TX
app_speech_pa_control(on)Control amplifier on/off
app_speech_dac_mute(mute)Control DAC mute
app_speech_dac_gain_set(gain)Set DAC volume (0-90)
app_speech_dac_gain_max()Set DAC max gain (REG32=0xFF)

5.2 app_speech.c — Full-Duplex Audio Chain

i2s_driver_init() — Full-duplex channel creation

This function is the core of what distinguishes this project from the previous two half-duplex projects: a single i2s_new_channel() call obtains both TX and RX channel handles simultaneously, both sharing the same clock parameters (std_cfg), ensuring strict consistency of sample rate and bit width in full-duplex mode.

xxxxxxxxxx
/* Channel Config: Master Mode */
i2s_chan_config_t cc = {
    .id   = I2S_NUM,
    .role = I2S_ROLE_MASTER,          // ESP32 provides clocks
};
// Create TX & RX both
i2s_new_channel(&cc, &tx_handle_, &rx_handle_);

/* Initialize TX Channel */
i2s_channel_init_std_mode(tx_handle_, &std_cfg);
i2s_channel_enable(tx_handle_);

/* Initialize RX Channel (shares same std_cfg parameters as TX) */
if (rx_handle_ != NULL) {
    i2s_channel_init_std_mode(rx_handle_, &std_cfg);
    i2s_channel_enable(rx_handle_);
}

TX and RX use the same std_cfg (sample rate, bit width, channel mode all match), because they share the WS clock.

init_codec_official() — BOTH mode + Pop Suppression

This function is the final step in audio chain initialization: configure ES8311 in ADC+DAC dual working mode, then execute the six-stage pop suppression sequence in strict order, ensuring the amplifier produces no "pop" sound at power-on.

xxxxxxxxxx
/* ES8311 Codec → BOTH mode (ADC + DAC) */
es8311_codec_cfg_t ecfg = {
    .codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH,
};

/* Device Type: Input + Output */
esp_codec_dev_cfg_t dc = {
    .dev_type = ESP_CODEC_DEV_TYPE_IN_OUT,
};

/* ====== Pop Suppression Sequence ====== */

// 1. DAC mute
esp_codec_dev_set_out_mute(codec_dev_, true);

// 2. Enable analog path
esp_codec_dev_write_reg(codec_dev_, 0x0E, 0x02);

// 3. Set minimum gain
esp_codec_dev_write_reg(codec_dev_, 0x32, 0x00);

// 4. Flush I2S pipe with silence data (~200ms zero-value PCM)
int16_t *z = calloc(1, EXAMPLE_SAMPLE_RATE * 4 / 5);
i2s_channel_write(tx_handle_, z, EXAMPLE_SAMPLE_RATE * 4 / 5,
                  &w, pdMS_TO_TICKS(300));
free(z);

// 5. Power on amplifier
app_speech_pa_control(true);
vTaskDelay(pdMS_TO_TICKS(100));       // Wait for PA stable

// 6. Keep DAC muted, wait for playback command

Core idea of pop suppression: Flush the entire I2S pipe clean before the amplifier powers on, ensuring no noise gets amplified when the amplifier turns on.

5.3 wav_file_player.c — Player (with DAC Gain Control)

Compared with the Audio_Playing project, v2's player integrates DAC gain and mute control:

wav_player_init() — SPIFFS mount, auto-format on first use. Suppress W-level warnings triggered by format_if_mount_failed by temporarily elevating the log level:

xxxxxxxxxx
void wav_player_init(void)
{
    esp_vfs_spiffs_conf_t conf = {
        .base_path              = "/spiffs",
        .partition_label        = "storage",
        .max_files              = 5,
        // Auto-format on first boot
        .format_if_mount_failed = true,
    };
    // Suppress auto-format W-level log
    esp_log_level_set("SPIFFS", ESP_LOG_ERROR);
    esp_vfs_spiffs_register(&conf);
    esp_log_level_set("SPIFFS", ESP_LOG_WARN);
    s_is_playing = false;
    ESP_LOGI(TAG, "SPIFFS mounted");
}

"Voice onset" flow before playback starts — First restore gain, then unmute, with a silence data buffer as preamble to completely eliminate playback start pops:

xxxxxxxxxx
/* Pop-Free Start: Max Gain → Unmute → Silence Cushion → Audio  */
app_speech_dac_gain_max();      // REG32 = 0xFF Max Gain
app_speech_dac_mute(false);     // Unmute
vTaskDelay(10);                 // Wait for registers to take effect

/* Pre-audio silence cushion to ensure DAC output is stable */
memset(stereo_buf, 0, 256 * 4);
i2s_channel_write(tx_handle_, stereo_buf, 256 * 4, &wd, pdMS_TO_TICKS(50));

"Voice offset" flow after playback ends — First flush silence data to clear residual I2S pipe content, then re-mute DAC, ensuring zero output from speaker during non-playback periods:

xxxxxxxxxx
/* Pop-Free End: Silence Flush → Re-Mute */
memset(stereo_buf, 0, 256 * 4);
i2s_channel_write(tx_handle_, stereo_buf, 256 * 4, &wd, pdMS_TO_TICKS(50));
app_speech_dac_mute(true);

Comparison with Audio_Playing: v1 only writes silence frames, v2 adds DAC mute/gain codec-level control on top.

Pause/Resume with DAC linkage — Mute immediately when pausing to avoid noise, unmute first when resuming before setting the flag, ensuring DAC is ready before audio data arrives:

xxxxxxxxxx
void wav_player_pause(void) {
    s_is_paused = true;                  // Mark as paused
    app_speech_dac_mute(true);           // Mute immediately
}

void wav_player_resume(void) {
    app_speech_dac_mute(false);          // Unmute first
    s_is_paused = false;                 // Then clear flag
}

5.4 main.c — Main Entry and Button State Machine

Button logic — A button state machine based on duration discrimination: first debounce 30ms, then poll to measure hold duration. ≥800ms is long press (record), <800ms is short press (play/pause/resume):

xxxxxxxxxx
#define DEBOUNCE_MS     30        // Debounce Time (ms)
#define LONG_PRESS_MS   800       // Long-Press Threshold (ms)

/* Detect press: Debounce then measure duration */
if (gpio_get_level(BUTTON_PIN) == 0) {
    vTaskDelay(DEBOUNCE_MS / portTICK_PERIOD_MS);
    // Skip if debounce failed
    if (gpio_get_level(BUTTON_PIN) != 0) continue;

    int hold_ms = 0;
    /* Measure hold duration */
    while (gpio_get_level(BUTTON_PIN) == 0) {
        vTaskDelay(50);
        hold_ms += 50;
        if (hold_ms >= LONG_PRESS_MS) break;
    }

    if (hold_ms >= LONG_PRESS_MS) {
        /* === Long Press: Start Recording === */
        if (wav_player_is_busy()) {
            // Cannot record during playback
            ESP_LOGW(TAG, "Cannot record during playback");
        } else if (g_recording) {
            // Already recording
            ESP_LOGW(TAG, "Already recording");
        } else {
            g_recording = true;
            xTaskCreate(v_record_task, "record", 8192, NULL, 5, NULL);

            /* Wait for release to stop recording */
            while (gpio_get_level(BUTTON_PIN) == 0) vTaskDelay(50);
            g_recording = false;
            vTaskDelay(pdMS_TO_TICKS(300));

            /* Ensure record task has exited */
            while (g_recording) vTaskDelay(pdMS_TO_TICKS(10));
        }
    } else {
        /* === Short Press: Play/Pause/Resume === */
        if (wav_player_is_busy()) {
            if (wav_player_is_paused()) wav_player_resume();
            else wav_player_pause();
        } else {
            wav_player_play_file(REC_FILE);
        }
        /* Wait for release */
        while (gpio_get_level(BUTTON_PIN) == 0) vTaskDelay(pdMS_TO_TICKS(10));
    }
}

Recording task — Extract left channel from stereo — ES8311 I2S RX returns interleaved stereo data (L0,R0,L1,R1,...), but the actual MIC signal only exists in the left channel slot. Right channel data needs to be discarded to extract pure mono PCM. LED is green during recording, briefly red then off after completion:

xxxxxxxxxx
/* FRAME_SIZE = 1920 bytes, DMA-aligned frame size */
int16_t *rx_buf = malloc(FRAME_SIZE);
g_rec_len = 0;
// Recording: Green LED
rgb_led_set(0, 255, 0);

while (g_recording) {
    /* Buffer overflow guard */
    uint32_t remaining = REC_BUF_LEN - g_rec_len;
    if (remaining < FRAME_SIZE / 2) break;

    i2s_channel_read(rx_handle_, (uint8_t *)rx_buf,
                     FRAME_SIZE, &bytes_read, pdMS_TO_TICKS(200));

    /* Stereo Interleaved → Mono Extract
     * {L0,R0, L1,R1, L2,R2, ...} → {L0, L1, L2, ...}  */
    // 4 bytes per (L+R) pair
    int npairs = bytes_read / 4;
    for (int i = 0; i < npairs; i++) {
        // Left channel only
        g_rec_buf[g_rec_len / 2 + i] = rx_buf[i * 2];
    }
    g_rec_len += npairs * 2;
}

// Done: Red LED → Save → Off
rgb_led_off();
rgb_led_set(255, 0, 0);
free(rx_buf);

if (g_rec_len > 0) {
    wav_save_file(REC_FILE, (uint8_t *)g_rec_buf, g_rec_len);
    rgb_led_off();
}

Why extract left channel? ES8311 is configured for stereo I2S in BOTH mode, but recording only needs mono. MIC signal appears in the left channel slot, right channel is silence or noise, which can be discarded directly.

LED effect logic — Main loop only manages rainbow LED effect during playback. Recording LED effects (green/red) are controlled internally by the recording task itself, achieving separation of concerns:

xxxxxxxxxx
/* Recording task controls its own LED (Green=recording, Red=done) */
/* Main loop only controls playback LED */
if (wav_player_is_playing()) {
    // Playing: Rainbow animation
    rgb_led_rainbow_step();
} else {
    // Not playing: Off
    rgb_led_off();
}

5.5 wav_handler.c — WAV File Read/Write

Same WAV processing module as Audio_Recording project, providing:

  • wav_create_header() — Fill WAV header magic number and PCM format parameters

  • wav_save_file() — PCM buffer → WAV file (auto-generate 44-byte header)

  • wav_load_file() — WAV file → PCM buffer (parse and skip header, verify RIFF magic)

5.6 rgb_led.c — WS2812 RGB LED

Same RMT driver implementation as Audio_Playing project:

  • 80MHz RMT clock → 12.5ns/tick resolution

  • GRB byte order encoding

  • Rainbow hue HSV sector algorithm (6 sectors, 1.4° per step)


6. Operation Flow

6.1 menuconfig Configuration

This project depends on a custom partition table (including SPIFFS storage partition) and PSRAM. The following configuration options must be enabled in menuconfig before compilation. For detailed instructions on general settings such as the partition table and Flash size (including menuconfig screenshots), please refer to Section 4.2.1 in 3.Serial Communication and Storage System->6. USB Emulated Mass Storage Experiment.

Config PathSettingValueDescription
Serial flasher config → Flash sizeFlash size16 MBMatch onboard 16MB SPI Flash
Partition Table → Partition TableCustom partition table CSVpartitions.csvCustom partition (with 4M SPIFFS storage)
Component config → ESP PSRAMSupport for external PSRAMEnableEnable 8MB PSRAM
Component config → ESP PSRAMAllow malloc() in external memoryEnableAllow malloc to allocate from PSRAM (640KB recording buffer)
Component config → ESP PSRAMAllow .bss segment in external memoryEnableAllow task stack to use PSRAM
Component config → SPIFFS ConfigurationMaximum Number of Partitions3SPIFFS max partition count
Component config → SPIFFS ConfigurationUse magicEnableEnable magic number check, prevent filesystem corruption
Component config → SPIFFS ConfigurationUse magic lengthEnableEnable length magic check
Component config → SPIFFS ConfigurationUse modification timeEnableEnable file modification timestamp

The above configuration is written to the project's sdkconfig.defaults. After executing idf.py set-target esp32s3, IDF will automatically apply it. If compilation errors indicate a feature is not enabled, enter idf.py menuconfig to check each item.

xxxxxxxxxx
# Flash size
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y

# Custom partition table
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"

# PSRAM / External RAM
CONFIG_SPIRAM=y
CONFIG_SPIRAM_USE_MALLOC=y
CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y
CONFIG_SPIRAM_MODE_OCT=y
CONFIG_SPIRAM_SPEED_80M=y

# SPIFFS
CONFIG_SPIFFS_MAX_PARTITIONS=3
CONFIG_SPIFFS_USE_MAGIC=y
CONFIG_SPIFFS_USE_MAGIC_LENGTH=y
CONFIG_SPIFFS_USE_MTIME=y

6.2 Build and Flash

ESP-IDF flashing process: See ESP-IDF → 1. Before Development → 3. Build and Flash.

6.3 Normal Operation

Step 1: Confirm hardware connections

This development board has ES8311 audio codec chip, NS4150 amplifier, and WS2812 RGB LED onboard. No external modules needed, ready to use after power-on. Ensure speaker is properly connected to amplifier output.

Step 2: Power on and reset, observe serial output:

xxxxxxxxxx
I (1083) MAIN: Audio Player/Recorder v2
I (1193) app_speech: I2C ok SDA:41 SCL:42
I (1363) app_speech: I2S ready MCLK:15 BCLK:16 WS:18 DOUT:8 DIN:17
I (1603) app_speech: ES8311 ok REG32=0x00 REG0E=0x00 PA=on MUTED
I (17633) WAV_PLAYER: SPIFFS mounted
I (17633) MAIN: Long press+hold: record | Short press: play/pause/resume

上电打印

Startup log outputs in sequence: I2C bus ready → I2S full-duplex channel established → ES8311 initialization and pop suppression complete (PA on but DAC muted) → SPIFFS mounted → Button hint. Old recording file is automatically deleted after power-on.

Step 3: Long press and hold BOOT button (>0.8s) to start recording:

xxxxxxxxxx
I (213453) MAIN: Long press -> start recording (hold to keep)
I (213453) MAIN: Recording started

LED turns green, indicating recording in progress. Speak toward the onboard MIC.

录音

Step 4: Release BOOT button to stop recording:

xxxxxxxxxx
I (219053) MAIN: Button released -> stop recording
I (220883) wav: Save OK: /spiffs/record.wav
I (220883) MAIN: Rec: 186240 bytes 5600ms -> 33257 B/s (expect 32000)

LED turns red (briefly), then goes off. Recording data is saved to SPIFFS flash in WAV format.

保存

录音和保存

Step 5: Short press BOOT button (<0.8s) to play the just-recorded audio:

xxxxxxxxxx
I (8200) main: Short press -> play
I (8200) WAV_PLAYER: Start: /spiffs/record.wav (1ch 16bit 16000Hz 48000bytes)
I (11200) WAV_PLAYER: Play: sent=48000 dur=3000ms -> 16000 B/s (expect 32000)

LED shows rainbow flowing effect, speaker outputs the just-recorded sound. DAC gain automatically restores to maximum during playback, and re-mutes after playback ends.

播放打印

播放1播放2播放3

 

Step 6: Short press BOOT button during playback:

  • First short press → Pause playback, DAC immediately mutes, LED goes off

  • Short press again → Resume playback, DAC unmutes, LED rainbow resumes flowing

  • Playback reaches end of file → Auto stop, DAC re-mutes, LED goes off

6.4 Edge Cases

ScenarioExpected Behavior
Short press with no recording filePlayback fails, error log printed
Long press during playbackWarning printed "Cannot record during playback"
Long press while already recordingWarning printed "Already recording"
Recording buffer full at 20 secondsAuto stop recording
No operation after power-onIdle wait, amplifier on but DAC muted (no noise)

7. Common Issues

SymptomCauseSolution
No sound in playback/recordingI2S clock config errorCheck MCLK/BCLK/WS pin connections
Pop sound during playbackPop suppression sequence not executedVerify mute→gain→amplifier flow in init_codec_official()
Recording file empty or all zerosI2S DIN (GPIO17) not connected / ES8311 ADC not enabledCheck hardware connections and ADC initialization
Recording volume too lowMIC gain insufficientIncrease EXAMPLE_MIC_GAIN (max 42dB)
Abnormal playback speedWAV file sample rate doesn't match playback paramsEnsure both recording and playback use 16kHz
Noise during recordingMIC bias power unstableCheck if amplifier GPIO7 is enabled (provides MIC bias)
Button unresponsiveDebounce time insufficient / polling frequency too lowIncrease DEBOUNCE_MS or reduce polling interval
LED doesn't change during playbackRMT init failed / pin conflictCheck if GPIO48 is used by other functions
Heap memory allocation failedInsufficient 640KB contiguous memoryCheck memory usage, consider reducing REC_MAX_SEC
Recording buffer fullReached 20-second recording limitIncrease REC_MAX_SEC (note memory constraints)
I2S Audio Playback Experiment
Burn Xiaozhi Firmware

COMPANY

  • About us
  • Team Style
  • Blog

LEARNING & EXPLORATION

  • Tutorials
  • Edge AI hardware
  • Educational robots
  • Embedded intelligent hardware

WHERE TO BUY

  • Yahboom Online Store
  • Amazon
  • Alibaba
  • AliExpress
  • Mercado

TECHNOLOGY SUPPORT

  • Email: support@yahboom.com

BE A DISTRIBUTOR

  • Email: sales@yahboom.com
QR Code
QR Code

Copyright © 2017-2026 Yahboom. All Rights Reserved.

EN/中文