Skip to main content

daisy_embassy/codec/
wm8731.rs

1use embassy_stm32::{
2    self as hal, Peri, peripherals,
3    sai::{
4        self, BitOrder, ClockStrobe, DataSize, FifoThreshold, FrameSyncOffset, FrameSyncPolarity,
5        Mode, StereoMono, SyncInput, TxRx,
6    },
7    time::Hertz,
8};
9use hal::peripherals::*;
10
11use defmt::{info, unwrap};
12use embassy_time::Timer;
13
14use crate::audio::{AudioConfig, AudioPeripherals, Fs};
15
16const I2C_FS: Hertz = Hertz(100_000);
17
18/// A simple HAL for the Cirrus Logic/ Wolfson WM8731 audio codec
19pub struct Codec<'a> {
20    i2c: hal::i2c::I2c<'a, hal::mode::Blocking, hal::i2c::Master>,
21    sai_tx: sai::Sai<'a, peripherals::SAI1, u32>,
22    sai_rx: sai::Sai<'a, peripherals::SAI1, u32>,
23    pub sai_tx_config: sai::Config,
24    pub sai_rx_config: sai::Config,
25}
26
27impl<'a> Codec<'a> {
28    pub async fn new(
29        p: AudioPeripherals<'a>,
30        audio_config: AudioConfig,
31        tx_buffer: &'a mut [u32],
32        rx_buffer: &'a mut [u32],
33    ) -> Self {
34        info!("set up i2c");
35        let mut i2c_config = hal::i2c::Config::default();
36        i2c_config.frequency = I2C_FS;
37        let i2c = embassy_stm32::i2c::I2c::new_blocking(
38            p.i2c2,
39            p.codec_pins.SCL,
40            p.codec_pins.SDA,
41            i2c_config,
42        );
43
44        info!("set up sai");
45        let (sub_block_rx, sub_block_tx) = hal::sai::split_subblocks(p.sai1);
46        let mut sai_rx_config = sai::Config::default();
47        sai_rx_config.mode = Mode::Master;
48        sai_rx_config.tx_rx = TxRx::Receiver;
49        sai_rx_config.sync_output = true;
50        sai_rx_config.clock_strobe = ClockStrobe::Falling;
51        sai_rx_config.master_clock_divider = audio_config.fs.into_clock_divider();
52        sai_rx_config.stereo_mono = StereoMono::Stereo;
53        sai_rx_config.data_size = DataSize::Data24;
54        sai_rx_config.bit_order = BitOrder::MsbFirst;
55        sai_rx_config.frame_sync_polarity = FrameSyncPolarity::ActiveHigh;
56        sai_rx_config.frame_sync_offset = FrameSyncOffset::OnFirstBit;
57        sai_rx_config.frame_length = 64;
58        sai_rx_config.frame_sync_active_level_length = embassy_stm32::sai::word::U7(32);
59        sai_rx_config.fifo_threshold = FifoThreshold::Quarter;
60
61        let mut sai_tx_config = sai_rx_config;
62        sai_tx_config.mode = Mode::Slave;
63        sai_tx_config.tx_rx = TxRx::Transmitter;
64        sai_tx_config.sync_input = SyncInput::Internal;
65        sai_tx_config.clock_strobe = ClockStrobe::Rising;
66        sai_tx_config.sync_output = false;
67
68        let sai_tx = hal::sai::Sai::new_synchronous(
69            sub_block_tx,
70            p.codec_pins.SD_B,
71            p.dma1_ch1,
72            tx_buffer,
73            sai_tx_config,
74        );
75
76        let sai_rx = hal::sai::Sai::new_asynchronous_with_mclk(
77            sub_block_rx,
78            p.codec_pins.SCK_A,
79            p.codec_pins.SD_A,
80            p.codec_pins.FS_A,
81            p.codec_pins.MCLK_A,
82            p.dma1_ch2,
83            rx_buffer,
84            sai_rx_config,
85        );
86
87        let mut codec = Self {
88            i2c,
89            sai_tx,
90            sai_rx,
91            sai_tx_config,
92            sai_rx_config,
93        };
94
95        codec.setup_wm8731(audio_config.fs).await;
96
97        codec
98    }
99
100    //====================wm8731 register set up functions============================
101    async fn setup_wm8731(&mut self, fs: Fs) {
102        use wm8731::WM8731;
103        info!("setup wm8731 from I2C");
104
105        Timer::after_micros(10).await;
106
107        // reset
108        self.write_wm8731_reg(WM8731::reset());
109        Timer::after_micros(10).await;
110
111        // wakeup
112        self.write_wm8731_reg(WM8731::power_down(|w| {
113            Self::final_power_settings(w);
114            //output off before start()
115            w.output().power_off();
116        }));
117        Timer::after_micros(10).await;
118
119        // disable input mute, set to 0dB gain
120        self.write_wm8731_reg(WM8731::left_line_in(|w| {
121            w.both().enable();
122            w.mute().disable();
123            w.volume().nearest_dB(0);
124        }));
125        Timer::after_micros(10).await;
126
127        // sidetone off; DAC selected; bypass off; line input selected; mic muted; mic boost off
128        self.write_wm8731_reg(WM8731::analog_audio_path(|w| {
129            w.sidetone().disable();
130            w.dac_select().select();
131            w.bypass().disable();
132            w.input_select().line_input();
133            w.mute_mic().enable();
134            w.mic_boost().disable();
135        }));
136        Timer::after_micros(10).await;
137
138        // disable DAC mute, deemphasis for 48k
139        self.write_wm8731_reg(WM8731::digital_audio_path(|w| {
140            w.dac_mut().disable();
141            w.deemphasis().frequency_48();
142        }));
143        Timer::after_micros(10).await;
144
145        // nothing inverted, slave, 24-bits, MSB format
146        self.write_wm8731_reg(WM8731::digital_audio_interface_format(|w| {
147            w.bit_clock_invert().no_invert();
148            w.master_slave().slave();
149            w.left_right_dac_clock_swap().right_channel_dac_data_right();
150            w.left_right_phase().data_when_daclrc_low();
151            w.bit_length().bits_24();
152            w.format().left_justified();
153        }));
154        Timer::after_micros(10).await;
155
156        // no clock division, normal mode
157        self.write_wm8731_reg(WM8731::sampling(|w| {
158            w.core_clock_divider_select().normal();
159            w.base_oversampling_rate().normal_256();
160            match fs {
161                Fs::Fs8000 => {
162                    w.sample_rate().adc_8();
163                }
164                Fs::Fs32000 => {
165                    w.sample_rate().adc_32();
166                }
167                Fs::Fs44100 => {
168                    w.sample_rate().adc_441();
169                }
170                Fs::Fs48000 => {
171                    w.sample_rate().adc_48();
172                }
173                Fs::Fs88200 => {
174                    w.sample_rate().adc_882();
175                }
176                Fs::Fs96000 => {
177                    w.sample_rate().adc_96();
178                }
179            }
180            w.usb_normal().normal();
181        }));
182        Timer::after_micros(10).await;
183
184        // set active
185        self.write_wm8731_reg(WM8731::active().active());
186        Timer::after_micros(10).await;
187
188        //Note: WM8731's output not yet enabled.
189    }
190
191    fn write_wm8731_reg(&mut self, r: wm8731::Register) {
192        const AD: u8 = 0x1a; // or 0x1b if CSB is high
193
194        // WM8731 has 16 bits registers.
195        // The first 7 bits are for the addresses, and the rest 9 bits are for the "value"s.
196        // Let's pack wm8731::Register into 16 bits.
197        let byte1: u8 = ((r.address << 1) & 0b1111_1110) | (((r.value >> 8) & 0b0000_0001) as u8);
198        let byte2: u8 = (r.value & 0b1111_1111) as u8;
199        unwrap!(self.i2c.blocking_write(AD, &[byte1, byte2]));
200    }
201
202    fn final_power_settings(w: &mut wm8731::power_down::PowerDown) {
203        w.power_off().power_on();
204        w.clock_output().power_off();
205        w.oscillator().power_off();
206        w.output().power_on();
207        w.dac().power_on();
208        w.adc().power_on();
209        w.mic().power_off();
210        w.line_input().power_on();
211    }
212
213    pub async fn start(&mut self) -> Result<(), sai::Error> {
214        info!("start WM8731");
215        self.write_wm8731_reg(wm8731::WM8731::power_down(Self::final_power_settings));
216        embassy_time::Timer::after_micros(10).await;
217
218        info!("start SAI");
219        self.sai_rx.start()
220    }
221
222    pub fn release(
223        self,
224    ) -> (
225        sai::Sai<'a, SAI1, u32>,
226        sai::Sai<'a, SAI1, u32>,
227        hal::i2c::I2c<'a, hal::mode::Blocking, hal::i2c::Master>,
228    ) {
229        (self.sai_tx, self.sai_rx, self.i2c)
230    }
231
232    pub async fn read(&mut self, read_buf: &mut [u32]) -> Result<(), sai::Error> {
233        self.sai_rx.read(read_buf).await
234    }
235
236    pub async fn write(&mut self, write_buf: &[u32]) -> Result<(), sai::Error> {
237        self.sai_tx.write(write_buf).await
238    }
239}
240
241#[allow(non_snake_case)]
242pub struct Pins<'a> {
243    pub SCL: Peri<'a, PH4>,    // I2C SCL
244    pub SDA: Peri<'a, PB11>,   // I2C SDA
245    pub MCLK_A: Peri<'a, PE2>, // SAI1 MCLK_A
246    pub SCK_A: Peri<'a, PE5>,  // SAI1 SCK_A
247    pub FS_A: Peri<'a, PE4>,   // SAI1 FS_A
248    pub SD_A: Peri<'a, PE6>,   // SAI1 SD_A
249    pub SD_B: Peri<'a, PE3>,   // SAI1 SD_B
250}