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

2.I2S Audio Playback Experiment

I2S Audio Playback Experiment


I2S Audio Playback Experiment1. Audio Playback Overview2. Core Concepts2.1 I2S Audio Bus2.2 ES8311 Codec (DAC Mode)2.3 WAV File Format2.4 SPIFFS Flash Filesystem2.5 WS2812 RGB LED3. Hardware Features and Dependencies3.1 ESP32-S3 I2S Audio Interface Features3.2 ES8311 Audio Codec Specifications3.3 Pin Assignment Table3.4 Arduino IDE Configuration3.5 Library Dependencies4. Program Flow4.1 Overall Flowchart4.2 Playback Data Flow4.3 File Structure5. Source Code Details5.1 app_speech.h —— Macro Configuration and Interface5.2 app_speech.cpp —— Audio Subsystem InitializationI2C Bus Initialization and Device ScanI2S Driver InitializationES8311 I2C Read/Write WrappersES8311 Hardware ResetDAC Initialization (Anti-Pop Complete Sequence)Main Initialization Entry5.3 wav_file_player.cpp —— WAV Player5.3.1 Initialization and Corrupted File Detection5.3.2 Playback Task (wav_play_task)5.3.3 Serial Upload Listener5.3.4 Public Interface5.4 rgb_led.cpp —— WS2812 RGB LED5.5 Main Program —— Button State Machine and LED Effects6. Operation Flow6.1 IDE Configuration and Build6.2 Normal Playback6.3 Replace Playback Files6.4 Edge Cases7. FAQAppendix A: ES8311 Register Quick Reference (DAC Playback)Appendix B: Code Index


1. Audio Playback Overview

This experiment implements a ESP32-S3-based I2S audio player, driving an ES8311 audio codec (DAC mode) via the I2S bus, reading WAV files from SPIFFS flash filesystem, and driving a speaker through an NS4150 power amplifier. Playback control is via the BOOT button (short press: play/pause, long press: stop), and WS2812 RGB LED provides rainbow light effect feedback.

xxxxxxxxxx
[SPIFFS WAV file] → [ESP32-S3 I2S TX] → [ES8311 DAC] → [NS4150 Amp] → [Speaker]
FunctionDescription
File StorageSPIFFS flash filesystem, stores WAV audio files
Audio DecodingES8311 DAC, 16-bit / 16kHz / stereo output
Playback ControlBOOT button (GPIO0): short press play/pause, long press stop
LED FeedbackWS2812 RGB LED: rainbow hue animation during playback
AmplifierNS4150 Class D amp, GPIO7 enable
Serial UploadSupports uploading WAV files to SPIFFS via serial (no recompilation needed)

2. Core Concepts

2.1 I2S Audio Bus

I2S (Inter-IC Sound) is a standard digital audio transmission protocol, containing the following signal lines:

SignalFull NameFunction
MCLKMaster ClockMaster clock, = sample rate × 256
BCLKBit ClockBit clock, one pulse per bit of data
WS / LRCLKWord SelectFrame clock, low = left channel, high = right channel
DOUTData OutTransmit data (ESP32 → DAC)
DINData InReceive data (ADC → ESP32), this project only uses DOUT

I2S uses Philips standard format (left-justified, MSB first), WS toggles one BCLK before the data MSB.

In Arduino, I2S is operated via ESP_I2S.h, initialization requires only 2 lines:

xxxxxxxxxx
I2S.setPins(I2S_BCLK, I2S_WS, I2S_DOUT, -1, I2S_MCLK);
I2S.begin(I2S_MODE_STD, SAMPLE_RATE, I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO);

2.2 ES8311 Codec (DAC Mode)

ES8311 is a low-power audio codec from Shunli Semiconductor, configured via I2C register access (address 0x18), with I2S for audio data transmission. This project uses DAC mode, using the Wire object to manually write ES8311 register operations without additional library dependencies. See section 3.2 for complete specifications.

2.3 WAV File Format

WAV is based on the RIFF specification, with PCM encoding consisting of 44-byte header + raw PCM data:

xxxxxxxxxx
Offset    Size    Field              Description
0x00    4       "RIFF"            Magic number
0x04    4       file_len          File size - 8
0x08    4       "WAVE"            Format identifier
0x0C    4       "fmt "            Format chunk identifier
0x10    4       16                PCM format chunk size
0x14    2       1                 Audio format (1=PCM)
0x16    2       channels          Number of channels (1=mono, 2=stereo)
0x18    4       sample_rate       Sample rate (e.g. 16000 Hz)
0x1C    4       byte_rate         Byte rate
0x20    2       block_align       Frame alignment
0x22    2       bit_depth         Bit depth (16)
0x24    4       "data"            Data chunk identifier
0x28    4       data_len          PCM data byte count

This project's WAV files are 16kHz / 16-bit / mono, software copies to stereo during playback.

2.4 SPIFFS Flash Filesystem

SPIFFS is a SPI Flash filesystem. In Arduino, mount with SPIFFS.begin(true) (one line, auto-formats on failure), file operations use the File object: .open() / .read() / .close().

2.5 WS2812 RGB LED

WS2812 is a single-wire cascaded RGB LED, GRB byte order, 24-bit color. This project uses the Adafruit_NeoPixel library: led.setPixelColor() + led.show(), library handles RMT timing internally.


3. Hardware Features and Dependencies

3.1 ESP32-S3 I2S Audio Interface Features

FeatureParameter
I2S Peripherals2 (I2S0 / I2S1)
I2C Peripherals2 (I2C0 / I2C1)
SPI FlashUp to 16MB
GPIO MatrixAny GPIO mapped to any peripheral
Arduino Core VersionESP32 v3.3.8 (based on IDF v5.x)

ESP32-S3 GPIO matrix allows any GPIO to be configured as I2S/I2C function pins, no fixed mapping needed. This project uses I2S0 TX channel for audio output, I2C0 to configure ES8311 registers.

3.2 ES8311 Audio Codec Specifications

FeatureParameter
I2C Address0x18 (7-bit, AD0=0, AD1=0)
DAC SNR95dBA
DAC Sample Rate8kHz ~ 96kHz
Supported FormatsI2S, Left-Justified, Right-Justified, DSP/PCM
Output Volume0~100% (Arduino version caps at 75%, corresponding to register value 0~255)
Operating Voltage1.8V ~ 3.3V (analog), 1.8V (digital)
Operating ModeDAC / ADC / BOTH (this project uses DAC mode)
PackageQFN-32 (4x4mm)

ES8311 operates in slave mode — BCLK and LRCLK are provided by ESP32-S3 I2S master, MCLK output from ESP32-S3 at 4.096MHz.

Clock Tree: MCLK = 16000 × 256 = 4.096MHz, BCLK = MCLK / 4 = 1.024MHz, WS = 16kHz. I2S Philips standard format, 16-bit width, stereo slots (mono WAV playback copies to left/right slots in software).

3.3 Pin Assignment Table

PinFunctionConnection TargetDirection
GPIO42I2C SCLES8311 SCLOutput
GPIO41I2C SDAES8311 SDABidirectional
GPIO15I2S MCLKES8311 MCLKOutput
GPIO16I2S BCLKES8311 BCLKOutput
GPIO18I2S WSES8311 LRCKOutput
GPIO8I2S DOUTES8311 SDIN (DAC)Output
GPIO7PA_ENNS4150 amp enableOutput
GPIO48WS2812 DataRGB LED data inputOutput
GPIO0BOOT buttonActive-low buttonInput with pullup

3.4 Arduino IDE Configuration

ConfigurationValueDescription
BoardESP32S3 Dev ModuleESP32-S3 general dev board
USB CDC On BootEnabledEnable USB serial
Flash Size16MB (128Mb)Match onboard 16MB SPI Flash
Partition SchemeCustom (partitions.csv)Custom partition with SPIFFS
PSRAMOPI PSRAMEnable 8MB PSRAM

Custom partition allocates ~3.44MB to SPIFFS: spiffs, data, spiffs, 0x90000, 0x370000.

3.5 Library Dependencies

LibraryPurposeReference Tutorial
ESP32 (by Espressif)Arduino core (includes ESP_I2S, SPIFFS, Wire, FreeRTOS)Arduino → 1. Before Development → 1.Environment Setup
Adafruit_NeoPixelWS2812 RGB LED driver2.Basic Peripherals → 9.RGB LED Effects

ES8311 is fully controlled via I2C register writes, no additional library needed.


4. Program Flow

4.1 Overall Flowchart

xxxxxxxxxx
setup():
  1. digitalWrite(VOICE_EN_PIN, 0)     ← Pull PA low ASAP to suppress power-on pop
  2. Serial.begin(115200)
  3. Codec_I2S_init()                  ← Init audio playback chain
     ├─ power_on_codec()               ← PA pin config (keep low)
     ├─ i2c_init()                     ← Wire.setPins + Wire.begin + I2C scan
     ├─ i2s_driver_init()              ← I2S.setPins + I2S.begin
     ├─ es8311_hard_reset()            ← ES8311 hardware reset
     └─ es8311_init_dac()              ← Mute→Clock→DAC→Vol→Flush→PA→Unmute
  4. wav_player_init()                 ← SPIFFS mount + file scan + corruption check
  5. btn_play_init()                   ← pinMode(BTN_PLAY_PIN, INPUT_PULLUP)
  6. rgb_led_init(); rgb_led_off()

loop():
  ├─ Button detection (1kHz): debounce → timing → short/long judgment
  ├─ Rainbow LED (~10ms step while playing)
  ├─ wav_player_check_upload()        ← Background serial upload listener
  └─ vTaskDelay(1)

4.2 Playback Data Flow

xxxxxxxxxx
SPIFFS file → file.read(mono_buf) → Mono→Stereo copy
    → I2S.write(stereo_buf) → I2S DOUT → ES8311 DAC → NS4150 amp → Speaker

4.3 File Structure

xxxxxxxxxx
Arduino_Player_V2/
├── Arduino_Player_V2.ino       ← Main program: setup() + loop() + button FSM
├── app_speech.h / .cpp         ← ES8311 driver + I2S init
├── wav_file_player.h / .cpp    ← SPIFFS mount + playback task + upload
├── rgb_led.h / .cpp            ← WS2812 driver (Adafruit_NeoPixel)
├── partitions.csv              ← Partition table (512KB app + 3.44MB SPIFFS)
├── data/*.wav                  ← WAV files to flash
└── flash_spiffs.bat / .ps1     ← SPIFFS flash scripts

5. Source Code Details

Source code: Source code → Arduino → 4.Audio → Arduino_Player_V2 → Arduino_Player_V2.ino

5.1 app_speech.h —— Macro Configuration and Interface

app_speech.h is the header file for the entire audio playback subsystem, uniformly defining I2C/I2S pin mappings, audio sampling parameters, and external interface function declarations. All macros are centrally managed for easy hardware pin changes.

xxxxxxxxxx
/* ===== Pins ===== */
#define I2C_SCL_PIN     42          // I2C clock
#define I2C_SDA_PIN     41          // I2C data
#define I2S_MCLK        15          // I2S master clock
#define I2S_BCLK        16          // I2S bit clock
#define I2S_WS          18          // I2S word select
#define I2S_DOUT        8           // I2S data out (TX)
#define I2S_DIN         -1          // Unused
#define VOICE_EN_PIN    7           // PA enable

/* ===== Audio ===== */
#define SAMPLE_RATE     16000       // 16kHz sample rate
#define MCLK_MULTIPLE   256         // MCLK = 16000 x 256 = 4.096MHz
#define VOICE_VOLUME    75          // Volume 0-100 (ES8311 max 75)
#define BUF_SIZE        1024        // Playback buffer size (bytes)

/* ===== Button ===== */
#define BTN_PLAY_PIN    0           // BOOT button (active-low)

/* ===== API | Public API ===== */
void Codec_I2S_init(void);                      // Full audio system init
void app_speech_play(uint8_t *buf, uint32_t len); // Write PCM to I2S
void es8311SetMute(bool mute);                  // Mute control
void paEnable(bool enable);                     // PA enable

5.2 app_speech.cpp —— Audio Subsystem Initialization

Codec_I2S_init() initializes the entire playback chain in sequence, with the power amplifier enabled last to avoid power-on pop noise.

I2C Bus Initialization and Device Scan

I2C uses Arduino standard Wire library, after Wire.setPins() specifies pins, Wire.begin() starts the bus. After initialization, scan addresses 1~126 to confirm ES8311 responds at 0x18.

xxxxxxxxxx
#include <Wire.h>                             // Arduino standard I2C library
#include <ESP_I2S.h>                          // ESP32 Arduino I2S wrapper

I2SClass I2S;                                   // Global I2S object

static void i2c_init(void) {
    Wire.setPins(I2C_SDA_PIN, I2C_SCL_PIN);     // Assign I2C pins
    Wire.begin();                                // Start I2C (default 100kHz)

    // Scan 1~126, confirm ES8311 at 0x18
    for (uint8_t addr = 1; addr < 127; addr++) {
        Wire.beginTransmission(addr);
        if (Wire.endTransmission() == 0)      // Returns 0 = ACK = device present
            Serial.printf("  [OK] 0x%02X\n", addr);
    }
}

I2S Driver Initialization

I2S.setPins() configures BCLK/WS/DOUT/MCLK pins (DIN filled with -1 means unused), I2S.begin() starts standard Philips mode, 16kHz sample rate, 16-bit width, stereo slot I2S TX channel. Two lines of code encapsulate underlying channel creation, clock calculation, and GPIO binding.

xxxxxxxxxx
static void i2s_driver_init(void) {
    // BCLK, WS, DOUT, DIN(-1=unused), MCLK
    I2S.setPins(I2S_BCLK, I2S_WS, I2S_DOUT, -1, I2S_MCLK);
    if (!I2S.begin(I2S_MODE_STD, SAMPLE_RATE, I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO)) {
        Serial.println("[I2S] ERROR: Init failed");
        return;
    }
    delay(100);
}

ES8311 I2C Read/Write Wrappers

All ES8311 register operations are built on these two functions. es_write() sends register address and value then generates STOP to end transmission; es_read() first writes register address (no STOP), then initiates read request to get 1 byte of data.

xxxxxxxxxx
/* Write one byte to register */
static void es_write(uint8_t reg, uint8_t val) {
    Wire.beginTransmission(ES8311_ADDR);
    Wire.write(reg); Wire.write(val);          // Send register address + value
    Wire.endTransmission();                     // Generate STOP
}

/* Read one byte from register */
static uint8_t es_read(uint8_t reg) {
    Wire.beginTransmission(ES8311_ADDR);
    Wire.write(reg);
    Wire.endTransmission(false);               // No STOP, keep bus
    Wire.requestFrom((uint8_t)ES8311_ADDR, (uint8_t)1);
    return Wire.available() ? Wire.read() : 0;
}

ES8311 Hardware Reset

After ES8311 powers on, a complete hardware reset sequence must be executed, including analog power enable, clock tree initialization, slave mode setting, external MCLK selection, and ADC/DAC filter configuration. Register write order strictly follows chip manual requirements.

xxxxxxxxxx
static void es8311_hard_reset(void) {
    es_write(REG0D, 0xFA);  es_write(REG44, 0x08);  es_write(REG44, 0x08);
    es_write(REG01, 0x30);  es_write(REG02, 0x00);  es_write(REG03, 0x10);
    es_write(REG04, 0x10);  es_write(REG05, 0x00);  es_write(REG16, 0x24);
    es_write(REG0B, 0x00);  es_write(REG0C, 0x00);  es_write(REG10, 0x1F);
    es_write(REG11, 0x7F);  es_write(REG00, 0x80);

    es_write(REG00, es_read(REG00) & 0xBF);          // Slave mode
    es_write(REG01, 0x3F & 0x7F);                    // External MCLK
    es_write(REG06, es_read(REG06) & ~0x20);
    es_write(REG13, 0x10);  es_write(REG1B, 0x0A);  es_write(REG1C, 0x6A);
    es_write(REG44, 0x58);

    delay(10);
}

DAC Initialization (Anti-Pop Complete Sequence)

This is the most critical function for eliminating power-on/startup pop noise. The core idea is to ensure that when the power amplifier turns on, the speaker receives a mute signal instead of random noise. The 7-step sequence is interlocked with strict order:

xxxxxxxxxx
static void es8311_init_dac(void) {
    es8311SetMute(true);                                 // 1. Mute first
    es8311_set_format_clock();                           // 2. I2S clock config
    es8311_start_dac();                                  // 3. Start DAC (still muted)
    es8311SetVolume(VOICE_VOLUME);                       // 4. Set volume
    // 5. Flush I2S with silence
    uint8_t silence[320] = {0};
    I2S.write(silence, sizeof(silence));                 // Write 320 bytes of 0 (40ms silence)
    // 6. PA on (no noise, still muted)
    paEnable(true);
    delay(50);
    // 7. Unmute — pop already suppressed
    es8311SetMute(false);
}

Mute → configure clock → start DAC → flush silence → enable PA → unmute. When the power amplifier turns on, the speaker receives a silent signal, so the pop is naturally eliminated.

Main Initialization Entry

Codec_I2S_init() chains all above steps into a complete audio chain initialization sequence. Order is critical: I2S must start before ES8311 (ES8311 is slave, depends on ESP32 for MCLK/BCLK/WS); PA is enabled last, ensuring the entire chain outputs stable silence when the amplifier turns on.

xxxxxxxxxx
void Codec_I2S_init(void) {
    Serial.println("===== Audio System Init (Playback Only) =====");

    power_on_codec();        // 1. PA power preparation
    i2c_init();              // 2. I2C bus + scan
    i2s_driver_init();       // 3. I2S start (MCLK must precede ES8311)
    es8311_hard_reset();     // 4. ES8311 hardware reset
    delay(50);
    es8311_init_dac();       // 5. DAC init

    Serial.println("===== Audio Player Ready =====");
}

5.3 wav_file_player.cpp —— WAV Player

Responsible for SPIFFS mounting, file scanning and verification, playback task management, and serial upload monitoring.

5.3.1 Initialization and Corrupted File Detection

wav_player_init() mounts SPIFFS (auto-formats on failure), then scans all files and lists them. For .wav files, additionally reads the data_len field from the header and compares it against the actual file size — incomplete files are automatically marked and deleted to prevent corrupted files from serial upload interruptions.

xxxxxxxxxx
#include <SPIFFS.h>                              
#include <freertos/FreeRTOS.h>                    
#include <freertos/task.h>

static volatile bool s_is_playing = false;        
static volatile bool s_active     = false;        
static volatile bool s_paused     = false;        
static volatile bool s_stop       = false;        

static uint8_t mono_buf[BUF_SIZE];                // Mono read buffer
static uint8_t stereo_buf[BUF_SIZE * 2];          // Stereo expansion buffer

void wav_player_init(void) {
    if (!SPIFFS.begin(true)) {                     // auto-format on mount failure
        Serial.println("[SPIFFS] ERROR: Mount failed");
        return;
    }
    Serial.printf("[SPIFFS] %uKB total / %uKB used\n",
                  SPIFFS.totalBytes() / 1024, SPIFFS.usedBytes() / 1024);

    // Scan all files + check WAV integrity
    File root = SPIFFS.open("/");
    File f    = root.openNextFile();
    while (f) {
        // For .wav files: compare header data_len against actual file size
        // Incomplete files auto-detected and deleted
        ...
    }
}

Initialization auto-detects WAV file integrity — compares header data_len with actual file size, auto-deletes incomplete files. Especially useful for files that may be truncated after serial uploads.

5.3.2 Playback Task (wav_play_task)

Playback task runs as a FreeRTOS Task (8KB stack, priority 5), core flow in three phases: (1) Open file and parse WAV header, verify RIFF magic, PCM format, 16-bit depth; (2) Loop read mono PCM data, copy to stereo, push via I2S.write() to DMA; (3) On playback end or stop signal, send mute frames to flush I2S pipeline, close file and self-delete task.

xxxxxxxxxx
#pragma pack(push, 1)                              // 1-byte alignment
typedef struct {
    char     riff[4];      uint32_t file_len;      // "RIFF" + file length
    char     wave[4];      char     fmt[4];        // "WAVE" + "fmt "
    uint32_t fmt_len;      uint16_t audio_fmt;     // 16 + 1(PCM)
    uint16_t channels;     uint32_t sample_rate;   // channels + sample rate
    uint32_t byte_rate;    uint16_t block_align;   // byte rate + block align
    uint16_t bits_per_sample;                       // bit depth
    char     data[4];      uint32_t data_len;       // "data" + data length
} wav_header_t;
#pragma pack(pop)

static void wav_play_task(void *arg) {
    const char *path = (const char *)arg;

    do {
        // --- File open & WAV parsing ---
        File f = SPIFFS.open(path, "r");
        if (!f || f.size() == 0) break;

        wav_header_t hdr;
        f.read((uint8_t *)&hdr, sizeof(hdr));            // Read 44-byte WAV header

        // Validate: "RIFF" + "WAVE" + PCM + 16-bit
        if (memcmp(hdr.riff, "RIFF", 4) ||
            memcmp(hdr.wave, "WAVE", 4)) break;
        if (hdr.audio_fmt != 1 || hdr.bits_per_sample != 16) break;

        bool expand = (hdr.channels == 1);               // Mono needs stereo expansion
        s_active = true;

        // --- Core Playback Loop ---
        size_t   rd;
        uint32_t total = 0;
        while (!s_stop && (rd = f.read(mono_buf, BUF_SIZE)) > 0) {
            while (s_paused && !s_stop)                   // Pause wait loop
                vTaskDelay(pdMS_TO_TICKS(50));
            if (s_stop) break;

            total += rd;
            if (expand) {
                // Mono → Stereo: {s0,s0, s1,s1, ...}
                int16_t *s = (int16_t *)mono_buf;
                int16_t *d = (int16_t *)stereo_buf;
                size_t   cnt = rd / 2;                   // Sample count (16-bit)
                for (size_t i = 0; i < cnt; i++)
                    d[i * 2] = d[i * 2 + 1] = s[i];      // Each sample to both slots
                app_speech_play(stereo_buf, cnt * 4);
            } else {
                app_speech_play(mono_buf, rd);            // Already stereo, write directly
            }
            vTaskDelay(pdMS_TO_TICKS(3));                 // 3ms CPU yield
        }
        f.close();
    } while (false);

    // Cleanup: silence → reset → delete task
    uint8_t silence[320] = {0};
    app_speech_play(silence, sizeof(silence));            // Flush I2S pipeline
    s_is_playing = false; s_active = false;
    vTaskDelete(NULL);                                     // Task self-delete
}

Playback loop key points:

  • do { ... } while(false) + break for early error exit, avoids nested if/else

  • Mono {s0} → stereo {s0,s0}: each 16-bit sample copied to left/right channel slots

  • vTaskDelay(3) yields CPU each round, ensures loop() button polling is not blocked

  • After playback ends, send 320 bytes of silence to prevent trailing noise from residual data

5.3.3 Serial Upload Listener

Each loop() frame call, continuously monitors serial. First waits for ###UPLOAD###<size>:<filename>\n command, after receiving, checks remaining space, creates file, replies READY; then receives byte-by-byte, writes to SPIFFS in 256-byte batches; after completion, replies DONE, after 500ms ESP.restart() to rescan files. When SPIFFS is empty, also controls WS2812 to blink white light at 300ms period, prompting user to upload files.

xxxxxxxxxx
void wav_player_check_upload(void) {
    // Empty SPIFFS → white LED slow blink (300ms)
    ...

    while (Serial.available()) {
        char c = Serial.read();

        // --- Command phase: parse ###UPLOAD###size:filename ---
        if (s_up_rem == 0) {
            // Reply READY after receiving command
            ...
        }

        // --- Data phase: buffered write (flush every 256 bytes) ---
        s_up_file.write(s_up_buf, s_up_buf_pos);

        if (s_up_rem == 0) {                             // Transfer complete
            s_up_file.close();
            ESP.restart();                               // Restart to rescan SPIFFS
        }
    }
}

Protocol format: ###UPLOAD###<filesize>:<filename>\n. After receiving, check SPIFFS remaining space, if insufficient reply ERR:SPIFFS full. Write in 256-byte batches, after completion reply DONE and auto-restart.

5.3.4 Public Interface

FunctionDescription
wav_player_play_file(path)Start FreeRTOS playback task (8KB stack, priority 5)
wav_player_pause_resume()Pause/resume toggle
wav_player_stop()Set s_stop=true to signal task exit
wav_player_is_busy()Task alive (including paused)
wav_player_is_playing()Actively streaming (excluding pause)

5.4 rgb_led.cpp —— WS2812 RGB LED

WS2812 LED is controlled via Adafruit_NeoPixel library: led.begin() initializes, led.setBrightness(60) prevents glare, led.setPixelColor() + led.show() sets color and refreshes. Rainbow effect uses hue ring algorithm, splitting 16-bit hue (0-65535) into 6 sectors, linear interpolation within sectors for smooth color transitions.

xxxxxxxxxx
#include <Adafruit_NeoPixel.h>

static Adafruit_NeoPixel led(1, RGB_LED_PIN, NEO_GRB + NEO_KHZ800);  // 1 LED, GPIO48, GRB byte order, 800kHz
static uint16_t g_hue = 0;                                             // Hue 0-65535

void rgb_led_init(void) {
    led.begin();              // Start NeoPixel library
    led.setBrightness(60);    // Brightness 60/255 (anti-glare)
    led.clear();              // All black
    led.show();               // Send data to strip
}

void rgb_led_rainbow_step(void) {
    uint8_t sector = (g_hue >> 13) & 0x7;            // Sector 0-5 (top 3 bits)
    uint8_t x      = (g_hue >> 5)  & 0xFF;           // Blend factor 0-255
    uint8_t r, gr, b;

    switch (sector) {
    case 0: r = 255;       gr = x;        b = 0;        break;  // Red→Yellow
    case 1: r = 255 - x;   gr = 255;      b = 0;        break;  // Yellow→Green
    case 2: r = 0;         gr = 255;      b = x;        break;  // Green→Cyan
    case 3: r = 0;         gr = 255 - x;  b = 255;      break;  // Cyan→Blue
    case 4: r = x;         gr = 0;        b = 255;      break;  // Blue→Magenta
    default: r = 255;      gr = 0;        b = 255 - x;  break;  // Magenta→Red
    }
    rgb_led_set(r, gr, b);
    g_hue += 400;                                          // Hue step ~2.2°
}

Hue 0-65535 divided into 6 sectors of 60° each (R→Y→G→C→B→M), linear interpolation within sectors for smooth transitions. Each step g_hue += 400, about 164 steps to complete a cycle.

5.5 Main Program —— Button State Machine and LED Effects

Arduino_Player_V2.ino is the entry point of the entire project, setup() pulls PA low in sequence, initializes serial, audio chain, SPIFFS, button, and LED. loop() implements 1kHz button state machine and rainbow LED control, calling upload listener every frame.

xxxxxxxxxx
#include "app_speech.h"
#include "wav_file_player.h"
#include "rgb_led.h"

#define LONG_PRESS_MS  800              // Long-press threshold 800ms

void setup() {
    // Pull PA LOW ASAP to suppress reset pop
    pinMode(VOICE_EN_PIN, OUTPUT);
    digitalWrite(VOICE_EN_PIN, 0);

    Serial.begin(115200);                // Serial baud rate 115200
    Serial.setRxBufferSize(8192);        // Enlarge RX buffer (for uploads)
    delay(500);                          // Wait for serial stability

    Codec_I2S_init();                    // Audio system init
    wav_player_init();                   // SPIFFS mount + file scan
    btn_play_init();                     // Button GPIO init
    rgb_led_init(); rgb_led_off();       // LED init + off

    const char *f = wav_player_first_wav();
    if (f) Serial.printf("Play file: %s\n", f);
    else   Serial.println("No .wav files in SPIFFS!");

    Serial.println("Short=Play/Stop | Long=End");
    Serial.println("====================================\n");
}

loop() runs at 1kHz (vTaskDelay(1)), implements three-layer button logic and rainbow LED: falling edge debounce and record timestamp → rising edge distinguishes short press (<800ms) and long press (≥800ms); if held beyond threshold without release, triggers long press immediately. During playback, rainbow steps every 5 frames (~10ms), auto turns off LED when not playing.

xxxxxxxxxx
void loop() {
    static bool     btn_prev       = true;    // Previous GPIO level
    static bool     btn_done       = false;   // Press already handled
    static uint32_t btn_down_ticks = 0;       // Press timestamp (ticks)
    static uint32_t rainbow_tick   = 0;       // Rainbow step counter

    bool btn_raw = digitalRead(BTN_PLAY_PIN); // HIGH=released, LOW=pressed

    // ===== Falling edge: debounce + record =====
    if (!btn_raw && btn_prev) {
        vTaskDelay(pdMS_TO_TICKS(10));                    // 10ms coarse debounce
        if (digitalRead(BTN_PLAY_PIN) == 0) {             // Confirm still pressed
            btn_prev       = false;
            btn_done       = false;
            btn_down_ticks = xTaskGetTickCount();         // Record tick count
        }
    }
    // ===== Rising edge: short/long press =====
    else if (btn_raw && !btn_prev) {
        btn_prev = true;
        if (btn_done) continue;                  // Long-press handled, skip

        uint32_t held = (xTaskGetTickCount() - btn_down_ticks) * portTICK_PERIOD_MS;
        if (held < LONG_PRESS_MS) {              // Short: Play/Pause
            if (wav_player_is_busy()) wav_player_pause_resume();
            else wav_player_play_file(wav_player_first_wav());
        } else {                                 // Long: Stop
            wav_player_stop(); rgb_led_off();
        }
    }
    // ===== Immediate long-press (don't wait for release) =====
    if (!btn_prev && !btn_done) {
        if ((xTaskGetTickCount() - btn_down_ticks) * portTICK_PERIOD_MS >= LONG_PRESS_MS) {
            btn_done = true;                     // Mark handled
            wav_player_stop(); rgb_led_off();
        }
    }
    // ===== Rainbow LED effect =====
    if (wav_player_is_playing()) {
        if (++rainbow_tick >= 5) {               // Step every 5 loops (~10ms)
            rainbow_tick = 0;
            rgb_led_rainbow_step();
        }
    } else {
        rainbow_tick = 0;                        // Reset when not playing
        rgb_led_off();                           // Auto-off when not playing
    }

    wav_player_check_upload();                   // Background upload listener
    vTaskDelay(pdMS_TO_TICKS(1));                // 1ms tick
}

6. Operation Flow

6.1 IDE Configuration and Build

Step 1: Install Dependencies

  • Board Manager: install "esp32 by Espressif Systems" (v3.3.8+)

  • Library Manager: search "Adafruit NeoPixel", install "Adafruit NeoPixel" (by Adafruit)

Import Library:

Import Library

Download Library

Step 2: Select Configuration

SettingValue
BoardESP32S3 Dev Module
Flash Size16MB (128Mb)
Partition SchemeCustom(partitions.csv)
PSRAMOPI PSRAM

Board Selection

Board Serial

Board Selection

Enable Flash Size: 16MB (128Mb)

FLASH

Custom Partition:

Custom Partition

Enable PSRAM:

Enable PSRAM

Step 3: Build and Flash

  1. Open Arduino_Player_V2.ino → Click "Upload"

    Files Not Downloaded

  2. First use requires separate SPIFFS image flash: run flash_spiffs.bat

    Must close serial monitor!!!

    Download Success

WAV file requirements: 16kHz / 16-bit / mono, place in data/ directory before flashing.

  1. If you need to change serial port, please change it in flash_spiffs.ps1 file (besides this, you must also confirm that the file paths of mkspiffs.exe and esptool.exe in the code all correspond).

When changing serial port, go to flash_spiffs.ps1 to change

6.2 Normal Playback

Power-On Serial Output:

xxxxxxxxxx
========== Audio Player ==========
===== Audio System Init (Playback Only) =====
[PWR] PA pin configured (off) on GPIO7
[I2C] Initialized (SDA:41 SCL:42)
===== I2C Bus Scan =====
  [OK] 0x18
  [OK] 0x3C
===== Found 2 device(s) =====
[I2S] Ready (MCLK:15 BCLK:16 WS:18 DOUT:8)
[ES8311] Hardware init done (slave, ext MCLK)
[ES8311] DAC enabled (FS:16000Hz Vol:75%)
===== Audio Player Ready =====
[SPIFFS] 3228KB total / 2520KB used
=== SPIFFS files ===
  Ideal_Thirties.wav (2560044 bytes)
[LED] init GPIO48
Play file: /Ideal_Thirties.wav
Short=Play/Stop | Long=End
===================================

Files Downloaded

Expected Behavior:

ActionExpectedLED Status
Short press BOOT (<0.8s)Start playbackRainbow flow
Short during playPauseOff
Short after pauseResumeRainbow flow
Long press BOOT (≥0.8s)Stop playbackOff
Play to file endAuto-stopOff
No files in SPIFFSWhite blink promptWhite 300ms blink

Playback:

Playback

Playback1Playback3Playback2

Pause:

Pause

Resume:

Resume

Stop:

Stop

Audio Files Not Downloaded:

No Files

Volume Adjustment:

Playback volume is determined by the VOICE_VOLUME macro in app_speech.h, default value is 75. Higher number means louder speaker, recompile and reflash to hear the difference.

Volume Adjustment

Why is the upper limit 75, not 100? ES8311 chip has an internal "digital volume knob" called DACV register (address 0x32), the es8311SetVolume() function converts your percentage to 0~255 (e.g. 75% maps to 191 = 0xBF). But this chip has a flaw — above 75%, the sound will "break" and become harsh and distorted. So the code simply caps it: regardless of what you pass, above 75 is calculated as 75. This is not a bug, it's intentional sound quality protection.

If you really feel the sound is not loud enough, don't hard-change this limit — that's the chip's ceiling, forcing it will just make the sound worse. The correct approach is to adjust hardware: there's a gain resistor on the NS4150 power amplifier chip, changing it can raise the overall volume by a level.

Where to change? Open app_speech.h, find:

xxxxxxxxxx
#define VOICE_VOLUME    75          // Volume 0-100 (ES8311 max 75)

Change 75 to your desired value (any integer between 0 and 75), save, recompile and upload.

6.3 Replace Playback Files

  • Re-flash SPIFFS (recommended): Put new WAV in data/ → run flash_spiffs.bat → press RST to restart (code always plays first file)

  • Change Music

  • Serial upload: Host sends ###UPLOAD###<size>:<filename>\n → wait READY → send binary data → wait DONE auto-restart

6.4 Edge Cases

ScenarioExpected Behavior
Long press BOOT during playback (≥0.8s)Stop immediately (don't wait for release), send silence frame then close file
Play to file endAuto-stop, LED off
No WAV files in SPIFFSWhite LED slow blink, serial prints warning
WAV format mismatchPrint error, refuse playback
Incomplete WAV fileAuto-detected and deleted during startup scan
Hold BOOT during power-onGPIO0 is strapping pin, may enter download mode

7. FAQ

SymptomCauseSolution
No sound outputPA not enabled / ES8311 not initializedMeasure GPIO7 should be ~3.3V during playback; confirm serial shows DAC enabled
No I2C deviceES8311 wiring errorCheck SCL=GPIO42, SDA=GPIO41
Choppy playbackTask stack insufficient / fragmentationIncrease task stack to 16384; reformat SPIFFS
Wrong playback speedWAV sample rate mismatchConfirm file is 16kHz, or modify SAMPLE_RATE macro
Noise/popPA enable timing wrongConfirm setup() pulls PA low first; confirm 7-step anti-pop sequence executed
No files in SPIFFSImage not flashedRun flash_spiffs.bat (close serial monitor first)
WS2812 not litLibrary not installed / GPIO48 wrongInstall Adafruit_NeoPixel; check wiring
Button no responseGPIO0 strapping conflictEnsure GPIO0 not pulled low during power-on
ESP_I2S.h not foundArduino core version too oldUpdate esp32 core ≥ v3.0.0
SPIFFS flash failedTool path incorrectModify esptool/mkspiffs paths in flash_spiffs.ps1

Appendix A: ES8311 Register Quick Reference (DAC Playback)

RegisterAddressWrite ValueFunction
RESET0x000x80 → 0x3FGlobal reset release → clear I2S reset
CLK_MGR10x010x30 → 0x3FMaster clock → external MCLK
CLK_MGR20x020x00pre_div=1, multi=x1
CLK_MGR30x030x10fs_mode=0, adc_osr=16
CLK_MGR40x040x20dac_osr=32
CLK_MGR50x050x00adc_div=1, dac_div=1
CLK_MGR60x06bclk_div=4BCLK = MCLK / 4 = 1.024MHz
SDP_IN0x090x0CDAC I2S standard mode, 16bit
SDP_OUT0x0A0x0CADC I2S standard mode, 16bit
SYSTEM30x0D0xFA → 0x01Analog power config
SYSTEM40x0E0x02Digital power enable
SYSTEM100x140x1APGA gain + DAC output enable
DAC70x370x08DAC ramp start
GPIO10x440x08 → 0x58GPIO ref → enable internal DAC ref
DAC_MUTE0x31bit6/bit5Mute control (1=mute, 0=normal)
DAC_VOL0x320xBF (75%)DAC digital volume

Appendix B: Code Index

FileLinesDescription
Arduino_Player_V2.ino113Main: setup + loop + button FSM
app_speech.h40Pin macros + API declarations
app_speech.cpp300+I2C/I2S init + ES8311 register ops
wav_file_player.h25WAV player API declarations
wav_file_player.cpp250+SPIFFS mount + playback task + upload
rgb_led.h15RGB LED API declarations
rgb_led.cpp80+WS2812 driver + rainbow algorithm
partitions.csv5Partition table

 

I2S Microphone Recording and U-Disk Playback
Repeater Experiment

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

Copyright © 2017-2026 Yahboom. All Rights Reserved.

EN/中文