Membuat Loopback Test Mic-ke-Speaker dengan ES8311+NS4150B dan ESP32-S3
Kalau kamu sedang eksplorasi modul audio codec ES8311+NS4150B untuk proyek berbasis ESP32-S3, misalnya untuk voice assistant, perekam suara, atau proyek AI yang butuh input mic dan output speaker sekaligus. Tulisan ini sekedar berbagi pengalaman saya saat menyalakan modul ini dari nol sampai bisa loopback (suara dari mic langsung diputar ke speaker) menggunakan PlatformIO.
Saya tulis juga beberapa jebakan yang saya temui di tengah jalan, siapa tahu bisa menghemat waktu kamu.
Kenalan dulu dengan modulnya

Modul ini punya dua komponen:
- ES8311, codec audio mono (ADC + DAC dalam satu chip), berkomunikasi lewat I2S untuk data audio dan I2C untuk konfigurasi.
- NS4150B, amplifier Class-D mono untuk menggerakkan speaker.
Pin yang tersedia di modul: Mic+Speaker, VCC, GND, SD, SDA, SCL, MC, BCK, DO, WS, DI.
Wiring ke ESP32-S3
Berikut pemetaan pin yang saya pakai (ESP32-S3-DevKitC-1):
| Pin Modul | Fungsi | GPIO ESP32-S3 |
|---|---|---|
| VCC | Power | 3V3 |
| GND | Ground | GND |
| SDA | I2C data (konfigurasi codec) | GPIO1 |
| SCL | I2C clock (konfigurasi codec) | GPIO2 |
| MC (MCLK) | Master clock ke codec | GPIO4 |
| BCK | Bit clock I2S | GPIO5 |
| WS | Word select I2S | GPIO6 |
| DI | Data masuk ke codec (dari ESP32, jalur speaker) | GPIO7 |
| DO | Data keluar dari codec (ke ESP32, jalur mic) | GPIO8 |
| SD | Enable amplifier NS4150B | GPIO9 |
Beberapa catatan penting soal wiring:
- ESP32-S3 berperan sebagai I2S master, dialah yang menghasilkan sinyal MCLK, BCLK, dan WS. ES8311 berperan sebagai slave.
- ES8311 dan NS4150B berbagi jalur BCK dan WS yang sama, karena keduanya bagian dari satu chip codec, beda dengan setup yang pakai chip ADC dan DAC terpisah.
- Alamat I2C default ES8311 biasanya
0x18, tapi tergantung posisi pin CE di board bisa juga0x19. Kalau codec tidak terdeteksi, ini yang pertama dicek.

Percobaan pertama: pakai library, ternyata ribet
Awalnya saya coba pakai library siap pakai (arduino-audio-driver + arduino-audio-tools dari Phil Schatzmann) supaya tidak perlu urus register codec secara manual. Tapi ternyata muncul error linker yang cukup membingungkan:
undefined reference to `audio_driver::TLV320AIC3110::HPL_ANA_VOL_CTRL_ADDR'
Anehnya, saya sama sekali tidak pakai codec TLV320AIC3110, karena saya pakai ES8311. Setelah ditelusuri, “nampaknya” penyebabnya adalah cara library ini mendeklarasikan alamat register sebagai static constexpr di dalam class. Ini baru otomatis “inline” (tidak perlu didefinisikan ulang) kalau di-compile dengan C++17 ke atas, sementara compiler default PlatformIO untuk ESP32-S3 masih pakai C++11.
Solusinya sebenarnya cukup sederhana, tambahkan flag compiler di platformio.ini:
build_unflags = -std=gnu++11
build_flags = -std=gnu++17
Tapi setelah masalah ini beres, saya memutuskan pindah ke pendekatan yang lebih ringan dan lebih mudah dikontrol: menulis driver I2C untuk ES8311 sendiri, langsung pakai driver/i2s.h bawaan ESP-IDF.
Pendekatan kedua: driver manual (yang akhirnya jalan)
Ini pendekatan yang saya pakai sampai akhir, dan hasilnya jauh lebih stabil.
Yang perlu dipahami: ES8311 itu codec asli, bukan sekadar mic atau DAC pasif. Artinya dia perlu diinisialisasi lewat I2C dulu power-up, konfigurasi clock, unmute sebelum data audio di jalur I2S benar-benar terdengar. Kalau langkah ini dilewatkan, wiring bisa 100% benar tapi speaker tetap bisu.
Berikut struktur kode yang saya pakai:
- Fungsi
es8311_write()danes8311_read(), komunikasi I2C dasar ke codec. - Fungsi
es8311_init_16k(), urutan inisialisasi register codec untuk sample rate 16kHz, format I2S normal, 16-bit, dengan codec dikonfigurasi sebagai slave (karena ESP32-S3 jadi master). - Setup I2S di
driver/i2s.h, satu port I2S saja (I2S_NUM_0) dalam mode full-duplex (TX + RX bersamaan), karena ES8311 memang cuma satu chip yang menangani mic dan speaker sekaligus. - Loop utama: baca dari mic (
i2s_read), langsung tulis ke speaker (i2s_write), inilah loopback-nya.
#include <Arduino.h>
#include <Wire.h>
#include <driver/i2s.h>
// ================== PINOUT ==================
// I2C (kontrol codec ES8311)
#define I2C_SDA 1
#define I2C_SCL 2
// I2S (audio, satu bus dipakai bareng utk speaker+mic karena satu chip codec)
#define I2S_MCLK 4
#define I2S_BCK 5
#define I2S_WS 6
#define I2S_DO_TO_CODEC 7 // ESP32 TX -> pin “DI” di modul (masuk ke speaker/DAC)
#define I2S_DI_FROM_CODEC 8 // pin “DO” di modul -> ESP32 RX (keluar dari mic/ADC)
#define PA_ENABLE_PIN 9 // pin “SD” di modul, enable ampli NS4150B
// ================== ES8311 I2C ==================
#define ES8311_ADDR 0x18 // 7-bit. Kalau gak ke-detect, coba 0x19
// Register map (dari driver resmi Espressif esp-adf)
#define REG00_RESET 0x00
#define REG01_CLK1 0x01
#define REG02_CLK2 0x02
#define REG03_CLK3 0x03
#define REG04_CLK4 0x04
#define REG05_CLK5 0x05
#define REG06_CLK6 0x06
#define REG07_CLK7 0x07
#define REG08_CLK8 0x08
#define REG09_SDPIN 0x09 // DAC (speaker) I2S format
#define REG0A_SDPOUT 0x0A // ADC (mic) I2S format
#define REG0B_SYS 0x0B
#define REG0C_SYS 0x0C
#define REG0D_SYS_PWR 0x0D
#define REG0E_SYS_PWR 0x0E
#define REG10_SYS 0x10
#define REG11_SYS 0x11
#define REG12_SYS_DACEN 0x12
#define REG13_SYS 0x13
#define REG14_SYS_MIC 0x14
#define REG15_ADC 0x15
#define REG16_ADC_GAIN 0x16
#define REG17_ADC_VOL 0x17
#define REG1B_ADC 0x1B
#define REG1C_ADC 0x1C
#define REG32_DAC_VOL 0x32
#define REG37_DAC 0x37
#define REG44_GPIO 0x44
#define REG45_GP 0x45
static bool es8311_write(uint8_t reg, uint8_t val) {
Wire.beginTransmission(ES8311_ADDR);
Wire.write(reg);
Wire.write(val);
return Wire.endTransmission() == 0;
}
// Volume speaker, skala 0-100 (0=paling pelan, 100=paling kencang)
static void setSpeakerVolume(uint8_t percent) {
if (percent > 100) percent = 100;
uint8_t regval = map(percent, 0, 100, 0x00, 0xFF);
es8311_write(REG32_DAC_VOL, regval);
}
// Gain mic, skala 0-100 (0=paling kecil, 100=paling besar/sensitif)
static void setMicGain(uint8_t percent) {
if (percent > 100) percent = 100;
uint8_t level = map(percent, 0, 100, 0, 10); // ES8311 punya ~10 step gain (tiap step ±3dB)
es8311_write(REG16_ADC_GAIN, level);
}
static uint8_t es8311_read(uint8_t reg) {
Wire.beginTransmission(ES8311_ADDR);
Wire.write(reg);
Wire.endTransmission(false);
Wire.requestFrom((int)ES8311_ADDR, 1);
if (Wire.available()) return Wire.read();
return 0;
}
// Init utk MCLK = 256 * 16000Hz = 4.096MHz, format I2S normal, 16-bit, codec = SLAVE
// (ESP32-S3 sbg I2S master yg generate BCK/WS/MCLK)
static bool es8311_init_16k() {
bool ok = true;
ok &= es8311_write(REG01_CLK1, 0x30);
ok &= es8311_write(REG02_CLK2, 0x00);
ok &= es8311_write(REG03_CLK3, 0x10);
ok &= es8311_write(REG16_ADC_GAIN, 0x24);
ok &= es8311_write(REG04_CLK4, 0x10);
ok &= es8311_write(REG05_CLK5, 0x00);
ok &= es8311_write(REG0B_SYS, 0x00);
ok &= es8311_write(REG0C_SYS, 0x00);
ok &= es8311_write(REG10_SYS, 0x1F);
ok &= es8311_write(REG11_SYS, 0x7F);
ok &= es8311_write(REG00_RESET, 0x80);
// Set codec sbg SLAVE (bit6 = 0), karena ESP32-S3 yg jadi I2S master
uint8_t regv = es8311_read(REG00_RESET);
regv &= 0xBF;
ok &= es8311_write(REG00_RESET, regv);
ok &= es8311_write(REG01_CLK1, 0x3F);
// MCLK diambil dari pin MCLK eksternal (bukan dari BCK)
regv = es8311_read(REG01_CLK1);
regv &= 0x7F;
ok &= es8311_write(REG01_CLK1, regv);
// Koefisien clock utk MCLK 4.096MHz / Fs 16kHz
// pre_div=1 pre_multi=1 adc_div=1 dac_div=1 fs_mode=0 lrck_h=0x00 lrck_l=0xFF bclk_div=0x04 osr=0x10
ok &= es8311_write(REG02_CLK2, 0x00);
ok &= es8311_write(REG05_CLK5, 0x00);
ok &= es8311_write(REG03_CLK3, 0x10); // fs_mode=0, adc_osr=0x10
ok &= es8311_write(REG04_CLK4, 0x10); // dac_osr=0x10
ok &= es8311_write(REG07_CLK7, 0x00); // lrck_h
ok &= es8311_write(REG08_CLK8, 0xFF); // lrck_l
ok &= es8311_write(REG06_CLK6, 0x03); // bclk_div=4 -> reg=3
// MCLK & SCLK tidak diinvert
regv = es8311_read(REG01_CLK1);
regv &= ~0x40;
ok &= es8311_write(REG01_CLK1, regv);
regv = es8311_read(REG06_CLK6);
regv &= ~0x20;
ok &= es8311_write(REG06_CLK6, regv);
ok &= es8311_write(REG13_SYS, 0x10);
ok &= es8311_write(REG1B_ADC, 0x0A);
ok &= es8311_write(REG1C_ADC, 0x6A);
ok &= es8311_write(REG44_GPIO, 0x08);
// Format I2S normal, 16-bit, utk jalur DAC (speaker) & ADC (mic)
ok &= es8311_write(REG09_SDPIN, 0x0C);
ok &= es8311_write(REG0A_SDPOUT, 0x0C);
// Start ADC + DAC
ok &= es8311_write(REG32_DAC_VOL, 0x80); // volume speaker ~-32dB, turunkan lagi kalau masih feedback
ok &= es8311_write(REG17_ADC_VOL, 0xBF); // volume mic max
ok &= es8311_write(REG0E_SYS_PWR, 0x02);
ok &= es8311_write(REG12_SYS_DACEN, 0x00); // un-mute DAC
ok &= es8311_write(REG14_SYS_MIC, 0x1A); // analog mic (bukan PDM)
ok &= es8311_write(REG0D_SYS_PWR, 0x01); // power up utama
ok &= es8311_write(REG15_ADC, 0x40);
ok &= es8311_write(REG37_DAC, 0x48);
ok &= es8311_write(REG45_GP, 0x00);
return ok;
}
static void setupI2S() {
i2s_config_t cfg = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_RX),
.sample_rate = 16000,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 8,
.dma_buf_len = 64,
.use_apll = true,
.tx_desc_auto_clear = true,
.mclk_multiple = I2S_MCLK_MULTIPLE_256
};
i2s_pin_config_t pins = {
.mck_io_num = I2S_MCLK,
.bck_io_num = I2S_BCK,
.ws_io_num = I2S_WS,
.data_out_num = I2S_DO_TO_CODEC,
.data_in_num = I2S_DI_FROM_CODEC
};
i2s_driver_install(I2S_NUM_0, &cfg, 0, NULL);
i2s_set_pin(I2S_NUM_0, &pins);
}
void setup() {
Serial.begin(115200);
delay(300);
Serial.println(“=== ES8311 Loopback (raw driver) ===”);
pinMode(PA_ENABLE_PIN, OUTPUT);
digitalWrite(PA_ENABLE_PIN, HIGH); // enable ampli NS4150B
Wire.begin(I2C_SDA, I2C_SCL);
Wire.setClock(100000);
// cek codec kedetek
Wire.beginTransmission(ES8311_ADDR);
if (Wire.endTransmission() != 0) {
Serial.println(“ES8311 TIDAK terdeteksi di 0x18! Coba 0x19, atau cek wiring SDA/SCL.”);
} else {
Serial.println(“ES8311 terdeteksi di I2C.”);
}
if (!es8311_init_16k()) {
Serial.println(“Ada write I2C yang gagal saat init ES8311 (cek wiring).”);
} else {
Serial.println(“ES8311 init OK.”);
}
setSpeakerVolume(70); // 0-100, atur sesuai kebutuhan
setMicGain(50); // 0-100, atur sesuai kebutuhan
setupI2S();
Serial.println(“I2S siap. Bicara dekat mic…”);
}
void loop() {
static int16_t buffer[64];
size_t bytes_read = 0, bytes_written = 0;
i2s_read(I2S_NUM_0, buffer, sizeof(buffer), &bytes_read, portMAX_DELAY);
i2s_write(I2S_NUM_0, buffer, bytes_read, &bytes_written, portMAX_DELAY);
}
Dan platformio.ini-nya jauh lebih sederhana, tidak butuh library eksternal sama sekali:
[env:esp32-s3-devkitc-1]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
monitor_speed = 115200
build_flags = -DCORE_DEBUG_LEVEL=3
Masalah feedback (dan cara mengatasinya)
Begitu loopback pertama kali menyala, muncul masalah klasik: feedback/squeal, sama seperti mic sound system yang terlalu dekat dengan speaker. Ini wajar terjadi karena mic dan speaker saling “mendengar” satu sama lain secara fisik. Bukan bug software.
Dua hal yang membantu:
- Turunkan volume speaker. Register
REG32di ES8311 mengontrol volume DAC, dengan resolusi 0.5dB per step (0x00 = paling pelan, 0xFF = paling kencang, 0xBF ≈ 0dB netral). - Jauhkan posisi mic dari speaker secara fisik, atau beri peredam di antaranya.
Supaya lebih gampang diatur, saya buat dua fungsi bantu yang menerima skala 0–100 (bukan hex) untuk volume speaker dan gain mic:
Contoh pemakaian:
setSpeakerVolume(40); // 0-100
setMicGain(50); // 0-100
Setelah volume speaker diturunkan dan mic dijauhkan sedikit, feedback hilang dan loopback berjalan bersih.
Penutup
Beberapa pelajaran yang saya bawa dari eksperimen ini:
- Untuk codec audio “beneran” seperti ES8311, jangan lupa langkah inisialisasi I2C — ini yang paling sering terlewat.
- Kalau pakai library pihak ketiga dan muncul error linker aneh yang menyebut komponen yang tidak kamu pakai, curigai masalah standar C++ compiler, bukan cuma soal wiring.
- Feedback pada loopback mic-speaker itu gejala akustik, bukan bug . Solusinya kombinasi software (volume) dan fisik (jarak).
Semoga tulisan ini membantu kalau kamu sedang mengutak-atik modul yang sama. Kalau ada pertanyaan, silakan tinggalkan komentar di bawah.
Ditulis berdasarkan pengalaman langsung menyambungkan ES8311+NS4150B ke ESP32-S3-DevKitC-1 menggunakan PlatformIO/Arduino framework.