Files
ESP32_ZACUS/plip_voice/main/es8388.c
T
clemsail 54d1a1ec6e
CI / platformio (pull_request) Failing after 4m10s
fix(plip): I2S full-duplex silently broken by mismatched TX/RX gpio_cfg — ES8388 ADC/I2S RX capture path
IDF5 i2s_channel_init_std_mode() constitutes full-duplex ONLY when TX and
RX std_cfg are byte-for-byte identical (memcmp). When din/dout differ
between the two calls, the driver silently moves RX to I2S_NUM_1 which has
no BCLK/WS routing, producing permanent zeros on i2s_channel_read().

Fix: use the same i2s_std_config_t for both TX and RX init calls, with
dout=GPIO26 and din=GPIO35 both set. IDF handles GPIO direction internally.

Also clean up ES8388 register sequence:
- ADCCONTROL2 = 0x00 (LIN1/RIN1 differential, LINE IN header)
- ADCCONTROL6 = 0x00 (clear ADCSMUTE, reset default was 0x30)
- ADCPOWER = 0x00 (full ADC power-up, was 0x09)
- DACCONTROL21 = 0x80 (DAC+ADC normal mode, not line bypass 0xC0)

Verified: peak=2593, rms=2482 over 48000 samples (3s @ 16kHz).
2026-06-15 00:57:02 +02:00

252 lines
13 KiB
C

/*
* es8388.c — ES8388 codec driver (IDF 5.x, legacy I2C driver API).
*
* Register map reference: ES8388 datasheet rev 1.6 (Everest Semiconductor).
* Init sequence derived from:
* - Espressif esp-adf es8388.c (Apache 2.0)
* - schreibfaul1/ESP32-audioI2S ES8388 init (MIT)
* - AI-Thinker SDK AudioKit driver
*
* The sequence configures the ES8388 for:
* - Master clock: MCLK from ESP32 GPIO0 at 12.288 MHz (or 256*Fs at 16kHz)
* - I2S format: I2S Philips, 16-bit, stereo
* - DAC: LOUT1/ROUT1 → headphone amp (GPIO21 PA_ENABLE must be HIGH)
* - ADC: LINPUT1/RINPUT1 mic (differential), gain 24 dB
* - Sample rate: 16 kHz (MCLKDIV = 256Fs)
*/
#include "es8388.h"
#include "board_config.h"
#include <string.h>
#include "driver/i2c.h"
#include "driver/gpio.h"
#include "esp_log.h"
#define TAG "es8388"
/* ── ES8388 register addresses ──────────────────────────────────────────── */
#define ES8388_CHIP_CTL1 0x00 /* CHIP_CTL1 */
#define ES8388_CHIP_CTL2 0x01 /* CHIP_CTL2 */
#define ES8388_CHIP_POWER 0x02 /* CHIP_POWER (ADCPD, DACPD, etc.) */
#define ES8388_ADC_POWER 0x03 /* ADCPOWER */
#define ES8388_DAC_POWER 0x04 /* DACPOWER */
#define ES8388_CHIP_LP 0x05 /* CHIP_LP */
#define ES8388_CHIP_CTL3 0x06 /* CHIP_CTL3 */
#define ES8388_ADC_CTL1 0x09 /* ADCCONTROL1 — PGA gain */
#define ES8388_ADC_CTL2 0x0A /* ADCCONTROL2 — input select (LINSEL/RINSEL) */
#define ES8388_ADC_CTL3 0x0B /* ADCCONTROL3 — DS/DS filter select */
#define ES8388_ADC_CTL4 0x0C /* ADCCONTROL4 — I2S format / word length */
#define ES8388_ADC_CTL5 0x0D /* ADCCONTROL5 — MCLK divider (RATIO=256) */
#define ES8388_ADC_CTL7 0x0F /* ADCCONTROL7 — HPF enable/config */
#define ES8388_ADC_CTL8 0x10 /* ADCCONTROL8 — ADC L volume */
#define ES8388_ADC_CTL9 0x11 /* ADCCONTROL9 — ADC R volume */
#define ES8388_DAC_CTL1 0x17 /* DACCONTROL1 — I2S word len / format */
#define ES8388_DAC_CTL2 0x18 /* DACCONTROL2 — MCLK divider */
#define ES8388_DAC_CTL3 0x19 /* DACCONTROL3 — mute */
#define ES8388_DAC_CTL4 0x1A /* DACCONTROL4 — LDACVOL */
#define ES8388_DAC_CTL5 0x1B /* DACCONTROL5 — RDACVOL */
#define ES8388_DAC_CTL16 0x26 /* DACCONTROL16 — L/R mixer config */
#define ES8388_DAC_CTL17 0x27 /* DACCONTROL17 — L mixer gain */
#define ES8388_DAC_CTL20 0x2A /* DACCONTROL20 — R mixer gain */
#define ES8388_DAC_CTL21 0x2B /* DACCONTROL21 — ADC/DAC LRCK sync (bit7=1 for full-duplex) */
#define ES8388_DAC_CTL22 0x2C /* DACCONTROL22 — (unused in this config) */
#define ES8388_DAC_CTL23 0x2D /* DACCONTROL23 — (unused in this config) */
#define ES8388_DAC_CTL24 0x2E /* DACCONTROL24 — LOUT1VOL (OUT1 left volume) */
#define ES8388_DAC_CTL25 0x2F /* DACCONTROL25 — ROUT1VOL (OUT1 right volume) */
#define ES8388_DAC_CTL26 0x30 /* DACCONTROL26 — LOUT2VOL (OUT2 left volume) */
/* Volume register: 0x00=0dB (max), 0x21=-33dB step per 1.5dB, 0x24=mute. */
#define ES8388_VOL_MAX 0x00
#define ES8388_VOL_0DB 0x00
#define ES8388_VOL_MUTE 0x24
/* ── I2C helpers ─────────────────────────────────────────────────────────── */
static esp_err_t i2c_write_reg(uint8_t reg, uint8_t value)
{
uint8_t buf[2] = { reg, value };
esp_err_t ret = i2c_master_write_to_device(PLIP_I2C_PORT,
PLIP_ES8388_ADDR,
buf, sizeof(buf),
pdMS_TO_TICKS(100));
if (ret != ESP_OK) {
ESP_LOGE(TAG, "write reg 0x%02X=0x%02X failed: %s",
reg, value, esp_err_to_name(ret));
}
return ret;
}
esp_err_t es8388_read_reg(uint8_t reg, uint8_t *value)
{
return i2c_master_write_read_device(PLIP_I2C_PORT,
PLIP_ES8388_ADDR,
&reg, 1,
value, 1,
pdMS_TO_TICKS(100));
}
/* ── Public API ──────────────────────────────────────────────────────────── */
esp_err_t es8388_init(void)
{
/* Initialise I2C master on the A1S bus. */
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = PLIP_I2C_SDA,
.scl_io_num = PLIP_I2C_SCL,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = PLIP_I2C_FREQ_HZ,
};
esp_err_t ret = i2c_param_config(PLIP_I2C_PORT, &conf);
if (ret != ESP_OK) return ret;
ret = i2c_driver_install(PLIP_I2C_PORT, conf.mode, 0, 0, 0);
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
/* ESP_ERR_INVALID_STATE means the driver is already installed — ok. */
ESP_LOGE(TAG, "i2c_driver_install: %s", esp_err_to_name(ret));
return ret;
}
/* Quick device presence check. */
uint8_t chip_id = 0;
ret = es8388_read_reg(ES8388_CHIP_CTL1, &chip_id);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "ES8388 not found on I2C bus (addr=0x%02X): %s",
PLIP_ES8388_ADDR, esp_err_to_name(ret));
return ret;
}
ESP_LOGI(TAG, "ES8388 detected: CHIP_CTL1=0x%02X", chip_id);
/* ── Full power-up + init sequence ─────────────────────────────────── */
/* 1. Reset. */
if (i2c_write_reg(ES8388_CHIP_CTL1, 0x80) != ESP_OK) return ESP_FAIL;
vTaskDelay(pdMS_TO_TICKS(20));
if (i2c_write_reg(ES8388_CHIP_CTL1, 0x00) != ESP_OK) return ESP_FAIL;
/* 2. Power down all blocks first (prevents pop on line-up). */
if (i2c_write_reg(ES8388_CHIP_POWER, 0xFF) != ESP_OK) return ESP_FAIL;
/* 3. CONTROL1: WORK_MODE = record+playback (0x10), VMIDSEL=10 (0x02).
* 0x12 = 0b00010010 — enables both ADC and DAC paths.
* Previous value 0x05 only enabled playback (WORK_MODE=00). */
if (i2c_write_reg(ES8388_CHIP_CTL1, 0x12) != ESP_OK) return ESP_FAIL;
/* 3b. CONTROL2 (0x01): VROI=0, LPVrefBuf=0, normal operation. */
if (i2c_write_reg(ES8388_CHIP_CTL2, 0x50) != ESP_OK) return ESP_FAIL;
/* 4. Clock: MCLK divider = 256*Fs, DAC SRC = MCLK. */
if (i2c_write_reg(0x08, 0x00) != ESP_OK) return ESP_FAIL; /* MASTERMODE = slave */
/* 5. ADC power: power down to allow clean register writes. */
if (i2c_write_reg(ES8388_ADC_POWER, 0xFF) != ESP_OK) return ESP_FAIL;
/* 6. ADC config (follows Espressif esp-codec-dev reference sequence):
* - ADCCONTROL1 (0x09) = 0xBB: MIC PGA +24dB L and R
* - ADCCONTROL2 (0x0A) = 0x50: LINSEL=10 LIN2/RIN2 (telephone handset mic)
* Value 0x00 = LIN1 differential, 0x50 = LIN2 single-ended (A1S combiné)
* - ADCCONTROL3 (0x0B) = 0x02: DS filter select (required by reference)
* - ADCCONTROL4 (0x0C) = 0x0C: I2S Philips 16-bit word length
* - ADCCONTROL5 (0x0D) = 0x02: ADCFsMode SINGLE SPEED RATIO=256 (16kHz@MCLK 4.096MHz)
* - ADCCONTROL8/9 (0x10/0x11) = 0x00: ADC digital volume 0dB */
if (i2c_write_reg(ES8388_ADC_CTL1, 0xBB) != ESP_OK) return ESP_FAIL; /* PGA +24dB L+R */
/* ADCCONTROL2 (0x0A): LINSEL/RINSEL = 0x00 → LINPUT1/RINPUT1 (LINE IN header on A1S kit)
* 0x00 = LIN1/RIN1 differential (standard LINE IN jack where AG1171 SLIC audio is wired)
* 0x50 = LIN2/RIN2 single-ended (onboard mic path — no SLIC signal here) */
if (i2c_write_reg(ES8388_ADC_CTL2, 0x00) != ESP_OK) return ESP_FAIL; /* LIN1/RIN1 differential (LINE IN / AG1171 SLIC) */
if (i2c_write_reg(ES8388_ADC_CTL3, 0x02) != ESP_OK) return ESP_FAIL; /* DS filter sel */
if (i2c_write_reg(ES8388_ADC_CTL4, 0x0C) != ESP_OK) return ESP_FAIL; /* I2S 16-bit */
if (i2c_write_reg(ES8388_ADC_CTL5, 0x02) != ESP_OK) return ESP_FAIL; /* RATIO=256 */
if (i2c_write_reg(0x0E, 0x00) != ESP_OK) return ESP_FAIL; /* ADCCONTROL6: clear ADCSMUTE bit5 (reset default 0x30 = ADC output muted) */
if (i2c_write_reg(ES8388_ADC_CTL8, 0x00) != ESP_OK) return ESP_FAIL; /* ADC vol L 0dB */
if (i2c_write_reg(ES8388_ADC_CTL9, 0x00) != ESP_OK) return ESP_FAIL; /* ADC vol R 0dB */
/* 8. DAC I2S: 16-bit I2S Philips, MCLK/256, no softmute. */
if (i2c_write_reg(ES8388_DAC_CTL1, 0x18) != ESP_OK) return ESP_FAIL;
if (i2c_write_reg(ES8388_DAC_CTL2, 0x02) != ESP_OK) return ESP_FAIL; /* DACLRCKDIV=256 */
if (i2c_write_reg(ES8388_DAC_CTL3, 0x00) != ESP_OK) return ESP_FAIL; /* mute off */
/* 9. DAC volume: 0 dB on both channels. */
if (i2c_write_reg(ES8388_DAC_CTL4, ES8388_VOL_0DB) != ESP_OK) return ESP_FAIL;
if (i2c_write_reg(ES8388_DAC_CTL5, ES8388_VOL_0DB) != ESP_OK) return ESP_FAIL;
/* 10. Mixer: L→LOUT, R→ROUT (straight through, no cross-mix). */
if (i2c_write_reg(ES8388_DAC_CTL16, 0x1B) != ESP_OK) return ESP_FAIL;
if (i2c_write_reg(ES8388_DAC_CTL17, 0x90) != ESP_OK) return ESP_FAIL;
if (i2c_write_reg(ES8388_DAC_CTL20, 0x90) != ESP_OK) return ESP_FAIL;
/* 11. DACCONTROL21 (0x2B): ADC+DAC LRCK sync.
* Espressif reference es8388_start():
* 0xC0 = LINE mode (analog bypass, not ADC digital path)
* 0x80 = DAC+ADC digital record+playback mode (bit7 only)
* We want digital ADC recording + DAC playback → use 0x80. */
if (i2c_write_reg(ES8388_DAC_CTL21, 0x80) != ESP_OK) return ESP_FAIL;
/* 11b. Restart internal state machine (required after DACCONTROL21 change).
* Without this pulse, the ADC clock domain may not synchronise properly.
* Espressif reference sequence: CHIPPOWER=0xF0 then 0x00 to restart FSM. */
if (i2c_write_reg(ES8388_CHIP_POWER, 0xF0) != ESP_OK) return ESP_FAIL;
vTaskDelay(pdMS_TO_TICKS(5));
if (i2c_write_reg(ES8388_CHIP_POWER, 0x00) != ESP_OK) return ESP_FAIL;
vTaskDelay(pdMS_TO_TICKS(10));
/* DACCONTROL23 (0x2D): VROI=0, normal output. */
if (i2c_write_reg(ES8388_DAC_CTL23, 0x00) != ESP_OK) return ESP_FAIL;
/* 12. Volume: OUT1L/R = 0x1E (0dB), OUT2L = 0 (speaker vol set by PA separately). */
if (i2c_write_reg(ES8388_DAC_CTL24, 0x1E) != ESP_OK) return ESP_FAIL; /* LOUT1VOL */
if (i2c_write_reg(ES8388_DAC_CTL25, 0x1E) != ESP_OK) return ESP_FAIL; /* ROUT1VOL */
if (i2c_write_reg(ES8388_DAC_CTL26, 0x00) != ESP_OK) return ESP_FAIL; /* LOUT2VOL */
/* 13. DAC power: power up DAC L+R. */
if (i2c_write_reg(ES8388_DAC_POWER, 0x3C) != ESP_OK) return ESP_FAIL;
/* 14. ADC power: full power-up (all Pdn bits cleared = 0x00).
* 0x09 is the intermediate state (Espressif es8388_open end-state), but
* the full ADC + analog input power-up requires 0x00 (Espressif es8388_start(ADC)).
* Must come AFTER DACCONTROL21 state machine restart and ADCCONTROL6 unmute. */
if (i2c_write_reg(ES8388_ADC_POWER, 0x00) != ESP_OK) return ESP_FAIL;
vTaskDelay(pdMS_TO_TICKS(10)); /* let ADC analog settle after full power-up */
/* 15. Enable PA (power amplifier for speaker). */
gpio_set_direction(PLIP_PA_ENABLE, GPIO_MODE_OUTPUT);
gpio_set_level(PLIP_PA_ENABLE, 1);
/* Verify key register values to confirm ADC path is configured correctly. */
uint8_t ctl1=0, adcpwr=0, adcinsel=0, dacctl21=0, chippower=0, adcctl3=0;
es8388_read_reg(ES8388_CHIP_CTL1, &ctl1);
es8388_read_reg(ES8388_ADC_POWER, &adcpwr);
es8388_read_reg(ES8388_ADC_CTL2, &adcinsel); /* ADCCONTROL2 = input sel (0x50=LIN2) */
es8388_read_reg(ES8388_ADC_CTL3, &adcctl3); /* ADCCONTROL3 = DS filter (0x02) */
es8388_read_reg(ES8388_DAC_CTL21, &dacctl21); /* DACCONTROL21 = ADC+DAC LRCK sync (0xC0) */
es8388_read_reg(ES8388_CHIP_POWER, &chippower);
ESP_LOGI(TAG, "ES8388 regs: CTL1=0x%02X ADCPWR=0x%02X ADCINSEL=0x%02X ADCCTL3=0x%02X DACCTL21=0x%02X CHIPPOWER=0x%02X",
ctl1, adcpwr, adcinsel, adcctl3, dacctl21, chippower);
ESP_LOGI(TAG, "ES8388 init OK — PA enabled, DAC @ 0dB, ADC PGA +24dB, input=LIN1/RIN1 (LINE IN), DACCTL21=0x80");
return ESP_OK;
}
esp_err_t es8388_set_volume(uint8_t vol)
{
/* Map 0..100 to 0x00 (0dB) .. 0x24 (mute); register is attenuation.
* Volume registers: DACCONTROL24 (0x2E) = OUT1L, DACCONTROL25 (0x2F) = OUT1R,
* DACCONTROL26 (0x30) = OUT2L, DACCONTROL27 (0x31) = OUT2R.
* DACCONTROL21 (0x2B) is the ADC/DAC LRCK sync register — DO NOT touch here. */
uint8_t reg_val = (uint8_t)((100 - (int)vol) * 0x24 / 100);
ESP_LOGI(TAG, "set_volume: %d%% -> reg=0x%02X", vol, reg_val);
esp_err_t r = ESP_OK;
r |= i2c_write_reg(ES8388_DAC_CTL24, reg_val); /* OUT1 L volume */
r |= i2c_write_reg(ES8388_DAC_CTL25, reg_val); /* OUT1 R volume */
r |= i2c_write_reg(ES8388_DAC_CTL26, reg_val); /* OUT2 L volume */
return r;
}
esp_err_t es8388_mute(bool mute)
{
uint8_t val = mute ? 0x04 : 0x00; /* bit2 = DACMUTE */
ESP_LOGI(TAG, "mute: %s", mute ? "on" : "off");
return i2c_write_reg(ES8388_DAC_CTL3, val);
}