From e14c912dac115337137d6eb32ba90dadc9b166eb Mon Sep 17 00:00:00 2001 From: clement Date: Fri, 19 Jun 2026 08:52:22 +0200 Subject: [PATCH] feat(audio): driver ES8388 I2C nouvelle API --- components/bsp/CMakeLists.txt | 3 + components/bsp/include/board_config.h | 49 +++++ components/hal_es8388/CMakeLists.txt | 5 + components/hal_es8388/es8388.c | 293 +++++++++++++++++++++++++ components/hal_es8388/include/es8388.h | 29 +++ main/CMakeLists.txt | 2 +- main/app_main.c | 4 +- 7 files changed, 383 insertions(+), 2 deletions(-) create mode 100644 components/bsp/CMakeLists.txt create mode 100644 components/bsp/include/board_config.h create mode 100644 components/hal_es8388/CMakeLists.txt create mode 100644 components/hal_es8388/es8388.c create mode 100644 components/hal_es8388/include/es8388.h diff --git a/components/bsp/CMakeLists.txt b/components/bsp/CMakeLists.txt new file mode 100644 index 0000000..b53853f --- /dev/null +++ b/components/bsp/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register( + INCLUDE_DIRS "include" +) diff --git a/components/bsp/include/board_config.h b/components/bsp/include/board_config.h new file mode 100644 index 0000000..4a4a012 --- /dev/null +++ b/components/bsp/include/board_config.h @@ -0,0 +1,49 @@ +#pragma once + +/* + * AI-Thinker ESP32-A1S Audio Kit V2.2 — Hardware Pin Configuration + * ES8388 codec (I2C control + I2S data), SD card (SPI), power amp. + * DIP switch: 1=OFF, 2=ON, 3=ON, 4=OFF, 5=OFF (SD active, KEY2 busy) + */ + +/* ---------- I2C Bus (ES8388 control) ---------- */ +#define PLIP_I2C_PORT I2C_NUM_0 +#define PLIP_I2C_SCL 32 +#define PLIP_I2C_SDA 33 +#define PLIP_I2C_FREQ_HZ 400000 + +/* ---------- ES8388 Audio Codec (I2C address) ---------- */ +#define PLIP_ES8388_ADDR 0x10 /* 7-bit: 0x10 with ADDR pin low */ + +/* ---------- I2S Audio Data ---------- */ +#define PLIP_I2S_NUM I2S_NUM_0 +#define PLIP_I2S_MCLK 0 /* GPIO0 — must be 0/1/3 on original ESP32 */ +#define PLIP_I2S_BCLK 27 +#define PLIP_I2S_WS 25 /* LRCK */ +#define PLIP_I2S_DOUT 26 /* DAC → speaker */ +#define PLIP_I2S_DIN 35 /* ADC ← mic (input-only, ES8388 ASDOUT) */ + +/* ---------- Power Amplifier ---------- */ +#define PLIP_PA_ENABLE 21 /* Active HIGH, drives NS4150 amp */ + +/* ---------- SD Card (SPI / HSPI) ---------- */ +#define PLIP_SD_CS 13 +#define PLIP_SD_MOSI 15 +#define PLIP_SD_MISO 2 +#define PLIP_SD_SCK 14 +#define PLIP_SD_MOUNT "/sdcard" + +/* ---------- Audio Parameters ---------- */ +#define PLIP_SAMPLE_RATE 16000 +#define PLIP_BITS_PER_SAMPLE 16 +#define PLIP_CHANNELS 2 /* ES8388 I2S always stereo; output averaged */ + +/* ---------- Off-hook GPIO (dev kit uses BOOT/KEY1 GPIO4 as stand-in) ---------- */ +/* Actual value comes from CONFIG_PLIP_HOOK_GPIO (Kconfig) */ + +/* ---------- SLIC K50835F / AG1171-class front-end (A1S board wiring) ---------- */ +/* KEY3=GPIO19, KEY4=GPIO23, KEY5=GPIO18, KEY6=GPIO5 share these pins — reassigned to SLIC */ +#define PLIP_SLIC_RM 18 /* Ring Mode output — HIGH = ring burst active */ +#define PLIP_SLIC_FR 5 /* Forward/Reverse output — toggled at 25 Hz for bell */ +#define PLIP_SLIC_SHK 23 /* Switch Hook input — active-LOW (K50835F open-collector) */ +#define PLIP_SLIC_PD 19 /* Power Down (open-drain) — HIGH = SLIC active */ diff --git a/components/hal_es8388/CMakeLists.txt b/components/hal_es8388/CMakeLists.txt new file mode 100644 index 0000000..d059546 --- /dev/null +++ b/components/hal_es8388/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "es8388.c" + INCLUDE_DIRS "include" + REQUIRES esp_driver_i2c esp_driver_gpio bsp +) diff --git a/components/hal_es8388/es8388.c b/components/hal_es8388/es8388.c new file mode 100644 index 0000000..acc4dc4 --- /dev/null +++ b/components/hal_es8388/es8388.c @@ -0,0 +1,293 @@ +/* + * es8388.c — ES8388 codec driver (IDF 5.x, nouvelle API i2c_master). + * + * 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) + */ + +/* Transformation 4a : remplacement du bloc d'includes + déclarations handles I2C. */ +#include "es8388.h" +#include "board_config.h" + +#include + +#include "driver/i2c_master.h" +#include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" + +#define TAG "es8388" + +/* I2C master bus + device handles (new driver/i2c_master.h API). */ +static i2c_master_bus_handle_t s_i2c_bus = NULL; +static i2c_master_dev_handle_t s_es_dev = NULL; + +/* ── 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 (Transformation 4b : nouvelle API i2c_master) ───────────── */ + +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_transmit(s_es_dev, buf, sizeof(buf), 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_transmit_receive(s_es_dev, ®, 1, value, 1, 100); +} + +esp_err_t es8388_write_reg(uint8_t reg, uint8_t value) +{ + return i2c_write_reg(reg, value); +} + +/* ── Public API ──────────────────────────────────────────────────────────── */ + +esp_err_t es8388_init(void) +{ + /* Transformation 4c : init I2C bus + device avec la nouvelle API i2c_master. */ + i2c_master_bus_config_t bus_cfg = { + .i2c_port = PLIP_I2C_PORT, + .sda_io_num = PLIP_I2C_SDA, + .scl_io_num = PLIP_I2C_SCL, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .flags.enable_internal_pullup = true, + }; + esp_err_t ret = i2c_new_master_bus(&bus_cfg, &s_i2c_bus); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "i2c_new_master_bus: %s", esp_err_to_name(ret)); + return ret; + } + i2c_device_config_t dev_cfg = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = PLIP_ES8388_ADDR, + .scl_speed_hz = PLIP_I2C_FREQ_HZ, + }; + ret = i2c_master_bus_add_device(s_i2c_bus, &dev_cfg, &s_es_dev); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "i2c_master_bus_add_device: %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; + + /* 3c. Internal DLL stabilisation for LOW sample rates (16 kHz). UNDOCUMENTED + * registers 0x35/0x37/0x39 — the proven A252 driver (hardware/projects/ + * slic-phone Es8388Driver.cpp) sets these to "disable internal DLL for low + * sample-rate stability". WITHOUT them the ADC clock domain is unstable at + * 16 kHz and the ADC outputs a frozen DC value (capture = flat, no AC) even + * though analog signal is present on the LIN pin — exactly our symptom. The + * DAC tolerates it, which is why playback worked but capture didn't. They + * must be set here in the boot sequence (a live poke can't re-lock the DLL). */ + if (i2c_write_reg(0x35, 0xA0) != ESP_OK) return ESP_FAIL; + if (i2c_write_reg(0x37, 0xD0) != ESP_OK) return ESP_FAIL; + if (i2c_write_reg(0x39, 0xD0) != 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, 0x88) != ESP_OK) return ESP_FAIL; /* MIC PGA +24dB L+R — the SLIC handset mic is QUIET (~2-5% FS at +12dB); +24dB lifts speech above the capture VAD onset. The old "+24dB too hot" note was an artifact of the broken ADC (pre-DLL-fix); measured clean at ~5% peak now. */ + /* ADCCONTROL2 (0x0A): input select. The K50835F SLIC handset transmit audio is + * wired to LIN2/RIN2 on this bench — PROVEN: speech captured (ACrms 196, crest 10.3) + * on 0x50, vs DC-only floating offset on 0x00 (LIN1). */ + if (i2c_write_reg(ES8388_ADC_CTL2, 0x50) != ESP_OK) return ESP_FAIL; /* LIN2/RIN2 — SLIC handset mic */ + 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 +12dB, input=LIN2/RIN2 (SLIC handset), DACCTL21=0x80"); + return ESP_OK; +} + +esp_err_t es8388_set_volume(uint8_t vol) +{ + /* ES8388 OUTx volume registers are GAIN, not attenuation: 0x00 = -45 dB + * (min) .. 0x21 = 0 dB (max); higher value = louder (>0x21 = mute/reserved). + * Map 0..100 → 0x00..0x21. (The previous code inverted this, so vol=100 + * produced 0x00 = quietest — confirmed at the bench.) + * DACCONTROL24 (0x2E)=OUT1L, 25 (0x2F)=OUT1R, 26 (0x30)=OUT2L, 27 (0x31)=OUT2R. + * DACCONTROL21 (0x2B) is the ADC/DAC LRCK sync register — DO NOT touch here. */ + if (vol > 100) vol = 100; + uint8_t reg_val = (uint8_t)((int)vol * 0x21 / 100); + if (reg_val > 0x21) reg_val = 0x21; + ESP_LOGI(TAG, "set_volume: %d%% -> reg=0x%02X (0x21=max,0dB)", 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 */ + r |= i2c_write_reg(0x31, reg_val); /* OUT2 R volume (DACCONTROL27) */ + return r; +} + +esp_err_t es8388_set_dac_volume(uint8_t atten) +{ + /* DACCONTROL4 (0x04) = LDACVOL, DACCONTROL5 (0x05) = RDACVOL: DIGITAL DAC + * volume, applied BEFORE the analog output stages. 0x00 = 0 dB, each step + * = -0.5 dB, up to 0xC0 = -96 dB (mute). Lowering this gives analog + * headroom while keeping the output-stage volume (es8388_set_volume) high. */ + if (atten > 0xC0) atten = 0xC0; + ESP_LOGI(TAG, "set_dac_volume: atten=0x%02X (-%.1f dB)", atten, atten * 0.5f); + esp_err_t r = i2c_write_reg(ES8388_DAC_CTL4, atten); + r |= i2c_write_reg(ES8388_DAC_CTL5, atten); + 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); +} diff --git a/components/hal_es8388/include/es8388.h b/components/hal_es8388/include/es8388.h new file mode 100644 index 0000000..536d3c6 --- /dev/null +++ b/components/hal_es8388/include/es8388.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Init I2C master bus (driver/i2c_master.h) + ES8388 register sequence + PA on. + * Returns ESP_OK if the codec answers on I2C and is configured. */ +esp_err_t es8388_init(void); + +/* Output volume 0..100 %. */ +esp_err_t es8388_set_volume(uint8_t vol); + +/* Digital DAC attenuation: 0x00 = 0 dB .. 0xC0 = -96 dB. */ +esp_err_t es8388_set_dac_volume(uint8_t atten); + +/* Mute / unmute the DAC. */ +esp_err_t es8388_mute(bool mute); + +/* Diagnostic register access. */ +esp_err_t es8388_read_reg(uint8_t reg, uint8_t *value); +esp_err_t es8388_write_reg(uint8_t reg, uint8_t value); + +#ifdef __cplusplus +} +#endif diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 54d3a10..b5d10d5 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,5 +1,5 @@ idf_component_register( SRCS "app_main.c" INCLUDE_DIRS "." - REQUIRES nvs_flash config_store + REQUIRES nvs_flash config_store hal_es8388 ) diff --git a/main/app_main.c b/main/app_main.c index 21ba534..b2012e2 100644 --- a/main/app_main.c +++ b/main/app_main.c @@ -2,6 +2,7 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "config_store.h" +#include "es8388.h" static const char *TAG = "rtc_phone"; @@ -9,7 +10,8 @@ void app_main(void) { ESP_LOGI(TAG, "RTC BL PHONE — socle ESP-IDF v5.4 (esp32 classique)"); ESP_ERROR_CHECK(config_store_init()); - ESP_LOGI(TAG, "boot OK — aucun driver chargé (Phase 1)"); + ESP_ERROR_CHECK(es8388_init()); + ESP_LOGI(TAG, "boot OK — Phase 2 HAL audio ES8388"); while (true) { ESP_LOGI(TAG, "heartbeat"); vTaskDelay(pdMS_TO_TICKS(5000));