|
| 1 | +# ES8311 mono audio codec driver |
| 2 | +# Initialises the ES8311 over I2C so that the ESP32 I2S peripheral can route |
| 3 | +# audio to/from the on-board speaker/microphone. |
| 4 | +# |
| 5 | +# Register layout and initialisation sequence are taken directly from the |
| 6 | +# Espressif reference driver shipped with the Freenove ESP32-S3 Display |
| 7 | +# tutorial sketches (Sketch_07.1_Music / Sketch_07.2_Echo, es8311.cpp). |
| 8 | +# |
| 9 | +# Clock configuration (MCLK_MULTIPLE = 256, 16-bit I2S, two slots per frame): |
| 10 | +# MCLK = sample_rate × 256 (driven by MCK PWM pin) |
| 11 | +# BCLK = MCLK / 4 (bclk_div = 4 → REG06 = bclk_div−1 = 3) |
| 12 | +# LRCK = MCLK / 256 = sample_rate (lrck_h=0x00, lrck_l=0xFF → REG07/08) |
| 13 | +# ADC/DAC oversampling rate = 0x10 (REG03 / REG04) |
| 14 | +# These divider values are identical for every standard sample rate when |
| 15 | +# MCLK = rate × 256 (verified against Espressif coeff_div[] table). |
| 16 | +# |
| 17 | +# The codec runs as I2S slave (ESP32-S3 drives BCLK and LRCK). |
| 18 | + |
| 19 | +import time |
| 20 | + |
| 21 | +try: |
| 22 | + from micropython import const |
| 23 | +except ImportError: |
| 24 | + def const(x): return x |
| 25 | + |
| 26 | +I2C_ADDR = const(0x18) |
| 27 | + |
| 28 | +# --------------------------------------------------------------------------- |
| 29 | +# Register addresses (from es8311_reg.h, Espressif reference driver) |
| 30 | +# --------------------------------------------------------------------------- |
| 31 | +_REG00_RESET = const(0x00) # reset + power control |
| 32 | +_REG01_CLK_SRC = const(0x01) # clock source select, all-clock enable |
| 33 | +_REG02_CLK_DIV = const(0x02) # pre-divider / pre-multiplier |
| 34 | +_REG03_ADC_OSR = const(0x03) # ADC fs-mode and oversampling rate |
| 35 | +_REG04_DAC_OSR = const(0x04) # DAC oversampling rate |
| 36 | +_REG05_CLKDIV = const(0x05) # ADC and DAC clock dividers |
| 37 | +_REG06_BCLKDIV = const(0x06) # BCLK (SCLK) inverter and divider |
| 38 | +_REG07_LRCK_H = const(0x07) # LRCK divider high byte |
| 39 | +_REG08_LRCK_L = const(0x08) # LRCK divider low byte |
| 40 | +_REG09_SDP_IN = const(0x09) # serial data port for DAC (playback input to codec) |
| 41 | +_REG0A_SDP_OUT = const(0x0A) # serial data port for ADC (recording output from codec) |
| 42 | +_REG0D_SYS = const(0x0D) # system: power-up analog circuitry |
| 43 | +_REG0E_SYS = const(0x0E) # system: enable analog PGA + ADC modulator |
| 44 | +_REG12_DAC_EN = const(0x12) # system: power-up DAC |
| 45 | +_REG13_SYS = const(0x13) # system: enable HP output driver |
| 46 | +_REG14_MIC = const(0x14) # microphone: DMIC select, analog PGA gain |
| 47 | +_REG16_ADC_GAIN = const(0x16) # ADC digital gain (separate from volume; default 4 = 24 dB) |
| 48 | +_REG17_ADC_VOL = const(0x17) # ADC volume / gain |
| 49 | +_REG1C_ADC_EQ = const(0x1C) # ADC equalizer bypass + DC-offset cancel |
| 50 | +_REG31_DAC_MUTE = const(0x31) # DAC soft-mute control (bits[6:5] = 11 → muted) |
| 51 | +_REG32_DAC_VOL = const(0x32) # DAC output volume (0x00=muted, 0xFF=max) |
| 52 | +_REG37_DAC_EQ = const(0x37) # DAC equalizer / ramp-rate control |
| 53 | + |
| 54 | +# SDP format word: slave mode (bit7=0), 16-bit resolution (bits[4:2]=011) |
| 55 | +_SDP_16BIT_SLAVE = const(0x0C) |
| 56 | + |
| 57 | +# Default DAC volume at init: 85% using Espressif formula (volume*256/100)−1 |
| 58 | +_DEFAULT_VOL_REG = const(0xD8) # = (85*256//100) - 1 ≈ 85% output volume |
| 59 | + |
| 60 | + |
| 61 | +class ES8311: |
| 62 | + """ |
| 63 | + ES8311 codec initialiser. |
| 64 | +
|
| 65 | + Usage:: |
| 66 | +
|
| 67 | + i2c = machine.I2C(0, sda=Pin(16), scl=Pin(15), freq=400_000) |
| 68 | + codec = ES8311(i2c) |
| 69 | + """ |
| 70 | + |
| 71 | + def __init__(self, i2c): |
| 72 | + self._i2c = i2c |
| 73 | + self._init() |
| 74 | + |
| 75 | + # ------------------------------------------------------------------ |
| 76 | + def _wr(self, reg, val): |
| 77 | + self._i2c.writeto_mem(I2C_ADDR, reg, bytes([val])) |
| 78 | + |
| 79 | + def _rd(self, reg): |
| 80 | + buf = bytearray(1) |
| 81 | + self._i2c.readfrom_mem_into(I2C_ADDR, reg, buf) |
| 82 | + return buf[0] |
| 83 | + |
| 84 | + # ------------------------------------------------------------------ |
| 85 | + def _init(self): |
| 86 | + # --- Reset sequence (matches Espressif es8311_init) --- |
| 87 | + self._wr(_REG00_RESET, 0x1F) # assert reset |
| 88 | + time.sleep_ms(20) |
| 89 | + self._wr(_REG00_RESET, 0x00) # release reset |
| 90 | + self._wr(_REG00_RESET, 0x80) # power-on command (required) |
| 91 | + |
| 92 | + # --- Clock configuration --- |
| 93 | + # REG01: enable all internal clocks; select MCLK from MCLK pin (bit7=0) |
| 94 | + self._wr(_REG01_CLK_SRC, 0x3F) |
| 95 | + # REG02: pre_div=1 (bits[7:5]=000), pre_multi=×1 (bits[4:3]=00) |
| 96 | + self._wr(_REG02_CLK_DIV, 0x00) |
| 97 | + # REG03: ADC fs_mode=single-speed (bit6=0), ADC OSR=0x10 |
| 98 | + self._wr(_REG03_ADC_OSR, 0x10) |
| 99 | + # REG04: DAC OSR=0x10 |
| 100 | + self._wr(_REG04_DAC_OSR, 0x10) |
| 101 | + # REG05: ADC clk_div=1 (bits[7:4]=0000), DAC clk_div=1 (bits[3:0]=0000) |
| 102 | + self._wr(_REG05_CLKDIV, 0x00) |
| 103 | + # REG06: BCLK divider = bclk_div−1 = 4−1 = 3 (MCLK/4 = BCLK for 16-bit stereo) |
| 104 | + self._wr(_REG06_BCLKDIV, 0x03) |
| 105 | + # REG07/08: LRCK divider = 0x00FF = 255+1 = 256 (MCLK/256 = sample_rate) |
| 106 | + self._wr(_REG07_LRCK_H, 0x00) |
| 107 | + self._wr(_REG08_LRCK_L, 0xFF) |
| 108 | + |
| 109 | + # --- I2S serial data format: 16-bit, standard I2S, slave mode --- |
| 110 | + self._wr(_REG09_SDP_IN, _SDP_16BIT_SLAVE) # DAC (playback) |
| 111 | + self._wr(_REG0A_SDP_OUT, _SDP_16BIT_SLAVE) # ADC (recording) |
| 112 | + |
| 113 | + # --- System / analog power-up --- |
| 114 | + self._wr(_REG0D_SYS, 0x01) # power up analog circuitry |
| 115 | + self._wr(_REG0E_SYS, 0x02) # enable analog PGA + ADC modulator |
| 116 | + self._wr(_REG12_DAC_EN, 0x00) # power up DAC |
| 117 | + self._wr(_REG13_SYS, 0x10) # enable output to HP driver |
| 118 | + self._wr(_REG14_MIC, 0x1A) # enable analog mic input, max PGA gain |
| 119 | + |
| 120 | + # --- ADC (microphone) --- |
| 121 | + self._wr(_REG16_ADC_GAIN, 0x04) # ADC digital gain = 24 dB (default) |
| 122 | + self._wr(_REG17_ADC_VOL, 0xC8) # ADC gain/volume (Espressif default) |
| 123 | + self._wr(_REG1C_ADC_EQ, 0x6A) # ADC equalizer bypass, cancel DC offset |
| 124 | + |
| 125 | + # --- DAC (speaker) --- |
| 126 | + self._wr(_REG32_DAC_VOL, _DEFAULT_VOL_REG) # set output volume (~85%) |
| 127 | + self._wr(_REG37_DAC_EQ, 0x08) # bypass DAC equalizer |
| 128 | + |
| 129 | + # Soft-mute the DAC at boot — unmuted by on_open callback when playback starts |
| 130 | + self.dac_mute(True) |
| 131 | + |
| 132 | + print("ES8311: codec initialised") |
| 133 | + |
| 134 | + def dac_mute(self, mute=True): |
| 135 | + """ |
| 136 | + Soft-mute or unmute the DAC output. |
| 137 | +
|
| 138 | + Uses the ES8311's built-in ramp so the transition is pop-free. |
| 139 | + Does not affect the DAC power state or volume register. |
| 140 | +
|
| 141 | + Args: |
| 142 | + mute: True to mute, False to unmute |
| 143 | + """ |
| 144 | + val = self._rd(_REG31_DAC_MUTE) |
| 145 | + if mute: |
| 146 | + val |= 0x60 # bits[6:5] = 11 → soft mute on |
| 147 | + else: |
| 148 | + val &= ~0x60 # bits[6:5] = 00 → soft mute off |
| 149 | + self._wr(_REG31_DAC_MUTE, val) |
| 150 | + |
| 151 | + def set_dac_volume(self, percent): |
| 152 | + """ |
| 153 | + Set DAC (speaker) volume. |
| 154 | +
|
| 155 | + Args: |
| 156 | + percent: 0 (mute) … 100 (maximum) |
| 157 | + """ |
| 158 | + percent = max(0, min(100, percent)) |
| 159 | + if percent == 0: |
| 160 | + val = 0 |
| 161 | + else: |
| 162 | + val = (percent * 256 // 100) - 1 |
| 163 | + self._wr(_REG32_DAC_VOL, val) |
| 164 | + |
| 165 | + def set_adc_volume(self, percent): |
| 166 | + """ |
| 167 | + Set ADC (microphone) gain. |
| 168 | +
|
| 169 | + Args: |
| 170 | + percent: 0 (minimum) … 100 (maximum, 0xC8 default) |
| 171 | + """ |
| 172 | + percent = max(0, min(100, percent)) |
| 173 | + val = percent * 0xC8 // 100 |
| 174 | + self._wr(_REG17_ADC_VOL, val) |
0 commit comments