Skip to main content

oxstack_drivers/
es8311.rs

1//! Everest ES8311 mono audio codec (one ADC, one DAC) over I²C control.
2//!
3//! The driver configures the codec as an I²S **slave** with 16-bit samples
4//! and its internal clock derived from the bit clock (`MCLK = BCLK × 8`).
5//! The host needs no MCLK line, and with 16-bit stereo frames
6//! (BCLK = 32 × fs) the codec's clock registers are the same at every
7//! supported rate. Only the host's I²S clock sets the sample rate.
8//!
9//! Power is managed by [`Mode`]. [`Es8311::init`] leaves the codec in
10//! [`Mode::Off`] with the DAC muted at its minimum volume, so a fresh codec
11//! can never make a sound until the caller explicitly asks for playback.
12//!
13//! Register values follow M5Stack's M5Unified (MIT) and Espressif's
14//! `esp-bsp` `es8311` component (Apache-2.0).
15
16use embedded_hal_async::delay::DelayNs;
17use embedded_hal_async::i2c::I2c;
18
19/// I²C address with CE tied low.
20pub const ADDRESS_CE_LOW: u8 = 0x18;
21/// I²C address with CE tied high.
22pub const ADDRESS_CE_HIGH: u8 = 0x19;
23/// Values of the `CHD1`/`CHD2` chip-ID registers (`0xFD`, `0xFE`).
24pub const CHIP_ID: [u8; 2] = [0x83, 0x11];
25
26/// Sample rates supported with BCLK-derived clocking and 16-bit stereo
27/// frames. These are the rates for which Espressif's coefficient table has an
28/// `MCLK = 32 × fs` entry; lower rates are left out until verified.
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30#[cfg_attr(feature = "defmt", derive(defmt::Format))]
31#[allow(missing_docs)]
32pub enum SampleRate {
33    Hz22050,
34    Hz32000,
35    Hz44100,
36    Hz48000,
37    Hz64000,
38    Hz88200,
39    Hz96000,
40}
41
42impl SampleRate {
43    /// The rate in hertz.
44    pub const fn hz(self) -> u32 {
45        match self {
46            SampleRate::Hz22050 => 22_050,
47            SampleRate::Hz32000 => 32_000,
48            SampleRate::Hz44100 => 44_100,
49            SampleRate::Hz48000 => 48_000,
50            SampleRate::Hz64000 => 64_000,
51            SampleRate::Hz88200 => 88_200,
52            SampleRate::Hz96000 => 96_000,
53        }
54    }
55}
56
57/// Which signal paths are powered.
58#[derive(Clone, Copy, Debug, PartialEq, Eq)]
59#[cfg_attr(feature = "defmt", derive(defmt::Format))]
60pub enum Mode {
61    /// Everything powered down. Silent.
62    Off,
63    /// DAC on; ADC off.
64    Playback,
65    /// Microphone PGA and ADC on; DAC powered down, muted and unclocked.
66    /// Silent.
67    Record,
68    /// Both paths on, as in Espressif's reference init. Not yet verified
69    /// on hardware.
70    Duplex,
71}
72
73impl Mode {
74    const fn plays(self) -> bool {
75        matches!(self, Mode::Playback | Mode::Duplex)
76    }
77    const fn records(self) -> bool {
78        matches!(self, Mode::Record | Mode::Duplex)
79    }
80}
81
82/// Analog microphone pre-amplifier gain, 0–30 dB in 3 dB steps. The codec
83/// adds a further +24 dB digital `ADC_SCALE` (register `0x16` reset value).
84#[derive(Clone, Copy, Debug, PartialEq, Eq)]
85#[cfg_attr(feature = "defmt", derive(defmt::Format))]
86pub struct MicGain(u8);
87
88impl MicGain {
89    /// 0 dB (M5Unified's default).
90    pub const DB0: Self = Self(0);
91    /// Maximum, 30 dB.
92    pub const DB30: Self = Self(10);
93
94    /// The nearest step at or below `db`, clamped to 0–30 dB.
95    pub const fn from_db(db: u8) -> Self {
96        let step = db / 3;
97        Self(if step > 10 { 10 } else { step })
98    }
99
100    /// The gain in dB.
101    pub const fn db(self) -> u8 {
102        self.0 * 3
103    }
104}
105
106/// Digital volume: −95.5 dB to +32 dB in 0.5 dB steps (`0xBF` = 0 dB).
107#[derive(Clone, Copy, Debug, PartialEq, Eq)]
108#[cfg_attr(feature = "defmt", derive(defmt::Format))]
109pub struct Volume(pub u8);
110
111impl Volume {
112    /// The quietest setting (−95.5 dB), effectively silent.
113    pub const MIN: Self = Self(0x00);
114    /// Unity gain.
115    pub const UNITY: Self = Self(0xBF);
116
117    /// From a level in 0.5 dB steps relative to unity, saturating.
118    pub const fn from_half_db(half_db: i16) -> Self {
119        let raw = 0xBF + half_db;
120        Self(if raw < 0 {
121            0
122        } else if raw > 0xFF {
123            0xFF
124        } else {
125            raw as u8
126        })
127    }
128}
129
130mod reg {
131    pub const RESET: u8 = 0x00;
132    pub const CLK_MANAGER_01: u8 = 0x01;
133    pub const CLK_MANAGER_02: u8 = 0x02;
134    pub const CLK_MANAGER_03: u8 = 0x03;
135    pub const CLK_MANAGER_04: u8 = 0x04;
136    pub const CLK_MANAGER_05: u8 = 0x05;
137    pub const CLK_MANAGER_07: u8 = 0x07;
138    pub const CLK_MANAGER_08: u8 = 0x08;
139    pub const SDP_IN: u8 = 0x09;
140    pub const SDP_OUT: u8 = 0x0A;
141    pub const SYSTEM_0D: u8 = 0x0D;
142    pub const SYSTEM_0E: u8 = 0x0E;
143    pub const SYSTEM_12: u8 = 0x12;
144    pub const SYSTEM_13: u8 = 0x13;
145    pub const SYSTEM_14: u8 = 0x14;
146    pub const ADC_17: u8 = 0x17;
147    pub const ADC_1C: u8 = 0x1C;
148    pub const DAC_31: u8 = 0x31;
149    pub const DAC_32: u8 = 0x32;
150    pub const DAC_37: u8 = 0x37;
151    pub const CHD1: u8 = 0xFD;
152    pub const CHD2: u8 = 0xFE;
153}
154
155mod val {
156    /// `RESET`: reset everything.
157    pub const RESET_ALL: u8 = 0x1F;
158    /// `RESET`: state machine on, I²S slave.
159    pub const CSM_ON_SLAVE: u8 = 0x80;
160    /// `CLK_MANAGER_01`: MCLK from BCLK (bit 7) plus clock-enable bits:
161    /// MCLK (5), BCLK (4), ADC (3), DAC (2), analog ADC (1), analog DAC (0).
162    pub const CLK_FROM_BCLK: u8 = 0x80;
163    pub const CLK_COMMON: u8 = 0x30;
164    pub const CLK_DAC: u8 = 0x05;
165    pub const CLK_ADC: u8 = 0x0A;
166    /// `CLK_MANAGER_02`: pre-divide ÷1, pre-multiply ×8 (BCLK 32·fs → 256·fs).
167    pub const PRE_DIV1_MUL8: u8 = 0x18;
168    /// `CLK_MANAGER_03/04`: single-speed, OSR 0x10 for ADC and DAC.
169    pub const OSR: u8 = 0x10;
170    /// `SDP_IN`/`SDP_OUT`: I²S format, 16-bit words.
171    pub const I2S_16BIT: u8 = 0x0C;
172    /// `SYSTEM_0D`: analog circuitry on / off.
173    pub const ANALOG_ON: u8 = 0x01;
174    pub const ANALOG_OFF: u8 = 0xFC;
175    /// `SYSTEM_0E`: PGA + ADC modulator on / off.
176    pub const ADC_ON: u8 = 0x02;
177    pub const ADC_OFF: u8 = 0x6A;
178    /// `SYSTEM_12`: DAC on / off.
179    pub const DAC_ON: u8 = 0x00;
180    pub const DAC_OFF: u8 = 0x02;
181    /// `SYSTEM_13`: bit 4 (HPSW) selects headphone drive (Espressif's
182    /// playback value); `0x40` is the reset value. There is **no** output
183    /// enable bit: silence comes from DAC power-down, mute, minimum volume
184    /// and gated DAC clocks together.
185    pub const HP_DRIVE: u8 = 0x10;
186    pub const OUTPUT_RESET: u8 = 0x40;
187    /// `SYSTEM_14`: analog mic on MIC1P/N; low nibble is PGA gain.
188    pub const MIC1_SELECT: u8 = 0x10;
189    /// `ADC_1C`: ADC equaliser bypass, DC-offset cancel.
190    pub const ADC_EQ_BYPASS_DC_CANCEL: u8 = 0x6A;
191    /// `DAC_31`: both DAC mute bits.
192    pub const DAC_MUTE: u8 = 0x60;
193    /// `DAC_37`: DAC equaliser bypass.
194    pub const DAC_EQ_BYPASS: u8 = 0x08;
195}
196
197/// ES8311 driver.
198pub struct Es8311<I2C> {
199    i2c: I2C,
200    addr: u8,
201    mode: Mode,
202    mic_gain: MicGain,
203}
204
205impl<I2C: I2c> Es8311<I2C> {
206    /// Wraps a bus handle. Call [`init`](Self::init) before anything else.
207    pub const fn new(i2c: I2C, addr: u8) -> Self {
208        Self {
209            i2c,
210            addr,
211            mode: Mode::Off,
212            mic_gain: MicGain::DB0,
213        }
214    }
215
216    /// Reads `CHD1`/`CHD2`. Expect [`CHIP_ID`]. The chip doesn't
217    /// auto-increment on reads, so this reads one register at a time.
218    pub async fn chip_id(&mut self) -> Result<[u8; 2], I2C::Error> {
219        Ok([self.read(reg::CHD1).await?, self.read(reg::CHD2).await?])
220    }
221
222    /// Resets the codec and programs clocking and the 16-bit I²S format.
223    /// Leaves it in [`Mode::Off`], DAC muted at [`Volume::MIN`].
224    pub async fn init(&mut self, delay: &mut impl DelayNs) -> Result<(), I2C::Error> {
225        self.write(reg::RESET, val::RESET_ALL).await?;
226        delay.delay_ms(20).await;
227        self.write(reg::RESET, 0x00).await?;
228        // Mute digitally before anything is powered.
229        self.write(reg::DAC_31, val::DAC_MUTE).await?;
230        self.write(reg::DAC_32, Volume::MIN.0).await?;
231        self.write(reg::RESET, val::CSM_ON_SLAVE).await?;
232
233        self.write(reg::CLK_MANAGER_01, val::CLK_FROM_BCLK).await?;
234        self.write(reg::CLK_MANAGER_02, val::PRE_DIV1_MUL8).await?;
235        self.write(reg::CLK_MANAGER_03, val::OSR).await?;
236        self.write(reg::CLK_MANAGER_04, val::OSR).await?;
237        self.write(reg::CLK_MANAGER_05, 0x00).await?;
238        self.write(reg::CLK_MANAGER_07, 0x00).await?;
239        self.write(reg::CLK_MANAGER_08, 0xFF).await?;
240        self.write(reg::SDP_IN, val::I2S_16BIT).await?;
241        self.write(reg::SDP_OUT, val::I2S_16BIT).await?;
242        self.write(reg::ADC_1C, val::ADC_EQ_BYPASS_DC_CANCEL)
243            .await?;
244        self.write(reg::DAC_37, val::DAC_EQ_BYPASS).await?;
245
246        self.mode = Mode::Duplex; // force every register to be written
247        self.set_mode(Mode::Off).await
248    }
249
250    /// Current power mode.
251    pub const fn mode(&self) -> Mode {
252        self.mode
253    }
254
255    /// Powers the requested paths. Silence outside playback modes rests on
256    /// four independent controls: DAC power-down (`0x12`), DAC mute (`0x31`),
257    /// minimum volume until set (`0x32`), and gated DAC clocks (`0x01`).
258    ///
259    /// The I²S clock must be running before
260    /// entering any mode other than [`Mode::Off`].
261    ///
262    /// Paths are powered down before others are powered up, and the DAC
263    /// mute is left as it is. Call [`set_dac_mute`](Self::set_dac_mute)
264    /// after the first samples are flowing to avoid a pop.
265    pub async fn set_mode(&mut self, mode: Mode) -> Result<(), I2C::Error> {
266        if mode == self.mode {
267            return Ok(());
268        }
269        if !mode.plays() {
270            self.write(reg::SYSTEM_13, val::OUTPUT_RESET).await?;
271            self.write(reg::SYSTEM_12, val::DAC_OFF).await?;
272        }
273        if !mode.records() {
274            self.write(reg::SYSTEM_0E, val::ADC_OFF).await?;
275        }
276
277        let mut clocks = val::CLK_FROM_BCLK;
278        if mode != Mode::Off {
279            clocks |= val::CLK_COMMON;
280        }
281        if mode.plays() {
282            clocks |= val::CLK_DAC;
283        }
284        if mode.records() {
285            clocks |= val::CLK_ADC;
286        }
287        self.write(reg::CLK_MANAGER_01, clocks).await?;
288
289        if mode == Mode::Off {
290            self.write(reg::SYSTEM_0D, val::ANALOG_OFF).await?;
291        } else {
292            self.write(reg::SYSTEM_0D, val::ANALOG_ON).await?;
293        }
294        if mode.records() {
295            self.write(reg::SYSTEM_14, val::MIC1_SELECT | self.mic_gain.0)
296                .await?;
297            self.write(reg::ADC_17, Volume::UNITY.0).await?;
298            self.write(reg::SYSTEM_0E, val::ADC_ON).await?;
299        }
300        if mode.plays() {
301            self.write(reg::SYSTEM_12, val::DAC_ON).await?;
302            self.write(reg::SYSTEM_13, val::HP_DRIVE).await?;
303        }
304        self.mode = mode;
305        Ok(())
306    }
307
308    /// Microphone pre-amplifier gain (applied now if recording).
309    pub async fn set_mic_gain(&mut self, gain: MicGain) -> Result<(), I2C::Error> {
310        self.mic_gain = gain;
311        if self.mode.records() {
312            self.write(reg::SYSTEM_14, val::MIC1_SELECT | gain.0)
313                .await?;
314        }
315        Ok(())
316    }
317
318    /// Digital ADC volume.
319    pub async fn set_adc_volume(&mut self, volume: Volume) -> Result<(), I2C::Error> {
320        self.write(reg::ADC_17, volume.0).await
321    }
322
323    /// Digital DAC volume.
324    pub async fn set_dac_volume(&mut self, volume: Volume) -> Result<(), I2C::Error> {
325        self.write(reg::DAC_32, volume.0).await
326    }
327
328    /// Soft-mutes the DAC (both mute bits).
329    pub async fn set_dac_mute(&mut self, mute: bool) -> Result<(), I2C::Error> {
330        self.write(reg::DAC_31, if mute { val::DAC_MUTE } else { 0x00 })
331            .await
332    }
333
334    /// Returns the bus handle.
335    pub fn release(self) -> I2C {
336        self.i2c
337    }
338
339    async fn write(&mut self, reg: u8, value: u8) -> Result<(), I2C::Error> {
340        self.i2c.write(self.addr, &[reg, value]).await
341    }
342
343    async fn read(&mut self, reg: u8) -> Result<u8, I2C::Error> {
344        let mut b = [0];
345        self.i2c.write_read(self.addr, &[reg], &mut b).await?;
346        Ok(b[0])
347    }
348}
349
350#[cfg(test)]
351mod tests {
352    extern crate std;
353    use super::*;
354    use crate::test_util::block_on;
355    use core::convert::Infallible;
356    use embedded_hal_async::i2c::Operation;
357    use std::vec::Vec;
358
359    /// Records every register write in order.
360    #[derive(Default)]
361    struct Log(Vec<(u8, u8)>);
362
363    impl Log {
364        fn last(&self, reg: u8) -> Option<u8> {
365            self.0
366                .iter()
367                .rev()
368                .find(|(r, _)| *r == reg)
369                .map(|(_, v)| *v)
370        }
371        fn position(&self, reg: u8, value: u8) -> usize {
372            self.0.iter().position(|w| *w == (reg, value)).unwrap()
373        }
374    }
375
376    impl embedded_hal_async::i2c::ErrorType for Log {
377        type Error = Infallible;
378    }
379
380    impl I2c for Log {
381        async fn transaction(
382            &mut self,
383            addr: u8,
384            ops: &mut [Operation<'_>],
385        ) -> Result<(), Infallible> {
386            assert_eq!(addr, ADDRESS_CE_LOW);
387            for op in ops {
388                if let Operation::Write([r, v]) = op {
389                    self.0.push((*r, *v));
390                }
391            }
392            Ok(())
393        }
394    }
395
396    struct NoDelay;
397    impl DelayNs for NoDelay {
398        async fn delay_ns(&mut self, _: u32) {}
399    }
400
401    fn init() -> Es8311<Log> {
402        let mut c = Es8311::new(Log::default(), ADDRESS_CE_LOW);
403        block_on(c.init(&mut NoDelay)).unwrap();
404        c
405    }
406
407    #[test]
408    fn init_ends_silent_and_powered_down() {
409        let log = init().release();
410        assert_eq!(log.last(reg::DAC_31), Some(val::DAC_MUTE));
411        assert_eq!(log.last(reg::DAC_32), Some(0x00));
412        assert_eq!(log.last(reg::SYSTEM_12), Some(val::DAC_OFF));
413        assert_eq!(log.last(reg::SYSTEM_13), Some(val::OUTPUT_RESET));
414        assert_eq!(log.last(reg::SYSTEM_0D), Some(val::ANALOG_OFF));
415        // Muting happens before the state machine is switched on.
416        assert!(log.position(reg::DAC_32, 0x00) < log.position(reg::RESET, val::CSM_ON_SLAVE));
417    }
418
419    #[test]
420    fn record_never_touches_the_output_path() {
421        let mut c = init();
422        let before = c.i2c.0.len();
423        block_on(c.set_mode(Mode::Record)).unwrap();
424        let log = c.release();
425        let writes = &log.0[before..];
426        assert!(!writes.contains(&(reg::SYSTEM_12, val::DAC_ON)));
427        assert!(!writes.contains(&(reg::SYSTEM_13, val::HP_DRIVE)));
428        assert_eq!(log.last(reg::SYSTEM_0E), Some(val::ADC_ON));
429    }
430
431    #[test]
432    fn clock_bits_match_m5unified_per_mode() {
433        for (mode, clocks) in [
434            (Mode::Playback, 0xB5),
435            (Mode::Record, 0xBA),
436            (Mode::Duplex, 0xBF),
437        ] {
438            let mut c = init();
439            block_on(c.set_mode(mode)).unwrap();
440            assert_eq!(
441                c.release().last(reg::CLK_MANAGER_01),
442                Some(clocks),
443                "{mode:?}"
444            );
445        }
446    }
447
448    #[test]
449    fn gains_and_volumes() {
450        assert_eq!(MicGain::from_db(31), MicGain::DB30);
451        assert_eq!(MicGain::from_db(7).db(), 6);
452        assert_eq!(Volume::from_half_db(0), Volume::UNITY);
453        assert_eq!(Volume::from_half_db(-1000), Volume::MIN);
454        assert_eq!(Volume::from_half_db(-12).0, 0xB3); // −6 dB
455    }
456}