viniciusdias.tech

research

Hardware

One development board and one expansion board, used as a pair. No additional modules and no soldering are required.

What is on the board

Pin map

Taken from the firmware. The manufacturer’s schematic names the same signals from the codec’s point of view, so the data pins read reversed there; see the codec page.

Signal GPIO Function
SW1 12 button 1; hold to record
SW2 11 button 2
SW3 13 button 3
LED 21 blinks while a slot is chosen
I2C SDA 14 ES8388 control
I2C SCL 47 ES8388 control
I2S MCLK 8 codec master clock
I2S BCLK 3 bit clock
I2S LRCLK 9 word clock
I2S DOUT 46 ESP32 → ES8388 DSDIN (DAC)
I2S DIN 10 ES8388 ASDOUT → ESP32 (ADC)
play_tone/main/main.c
#define I2C_SDA_PIN         14
#define I2C_SCL_PIN         47

#define I2S_MCLK_PIN        8
#define I2S_BCLK_PIN        3
#define I2S_LRCLK_PIN       9
#define I2S_DOUT_PIN        46
#define I2S_DIN_PIN         10

Buttons and LED

Each button sits between its GPIO and ground, pulled up on the board through 100 kΩ, with a 100 nF capacitor across it. A pressed button therefore reads low. The firmware also enables the ESP32’s internal pull-up, so the input is defined on boards populated without the external resistor.

3V3 100 kΩ GPIO12 · GPIO11 · GPIO13 buttons 100 nF GND pressed = low
Button wiring, identical for SW1, SW2 and SW3. The capacitor filters mechanical bounce in hardware; the firmware adds a 50 ms delay and a second read.

All three buttons are configured at once from a bit mask:

play_tone/main/main.c — app_main()
    /* GPIOs */
    gpio_config_t out_cfg = {
        .pin_bit_mask = (1ULL << LED_PIN),
        .mode = GPIO_MODE_OUTPUT,
    };
    gpio_config(&out_cfg);
    gpio_set_level(LED_PIN, 0);

    uint64_t btn_mask = 0;
    for (int i = 0; i < NUM_SLOTS; i++)
        btn_mask |= (1ULL << BTN_PIN[i]);

    gpio_config_t btn_cfg = {
        .pin_bit_mask = btn_mask,
        .mode = GPIO_MODE_INPUT,
        .pull_up_en = GPIO_PULLUP_ENABLE,
    };
    gpio_config(&btn_cfg);

Why the amplifier output is bridged

The HT6872 amplifies the difference between the codec’s two outputs rather than one output against ground, which doubles the voltage swing available from a 5 V supply. If both channels carry the same signal the difference is zero and the output is silent. The firmware sets I2S to mono on the left slot, which leaves ROUT1 at zero.

ES8388 DAC LOUT1 ROUT1 HT6872 IN− IN+ OUT− OUT+ SPEAKER output = gain × (IN+ − IN−) both channels equal ⇒ output = 0
Bridge-tied output. Driving one channel gives half the maximum bridged power; full power would require inverted signals on both channels.

Audio parameters

16 kHz, 16-bit, mono. Three slots of up to 20 seconds each: 16000 × 2 bytes × 20 s = 640 000 B per slot. With the temporary recording buffer, peak allocation is 2 560 000 B against 8 MiB of PSRAM.

play_tone/main/main.c
#define NUM_SLOTS           3
static const int BTN_PIN[NUM_SLOTS] = { 12, 11, 13 };  /* SW1, SW2, SW3 */

#define LONG_PRESS_MS       1000    /* tempo para ativar gravação */

/* =====================================================================
 * Áudio
 * ===================================================================== */
#define SAMPLE_RATE         16000
#define RECORD_MAX_SEC      20
#define MAX_SLOT_BYTES      (SAMPLE_RATE * 2 * RECORD_MAX_SEC)  /* ~640 KB */
#define CHUNK_BYTES         4096

PSRAM is volatile: the three slots are lost when the board loses power. The firmware does not write audio to flash.