use core::ptr;
use core::sync::atomic::{AtomicU32, AtomicU8, Ordering};
use embassy_stm32::gpio::Output as GpioOut;
use embassy_stm32::i2c::{I2c, Master};
use embassy_stm32::mode::Blocking;
use embassy_stm32::pac;
use embassy_time::Timer;
const CODEC_ADDR: u8 = 0x4C;
pub async fn init_codec(i2c: &mut I2c<'_, Blocking, Master>, reset: &mut GpioOut<'_>) -> bool {
reset.set_low(); Timer::after_millis(10).await;
reset.set_high();
Timer::after_millis(20).await;
let seq: [[u8; 2]; 6] = [
[0x02, 0x80], [0x03, 0x08], [0x04, 0x44], [0x05, 0x04], [0x06, 0x02], [0x0E, 0x00], ];
let mut ok = true;
for reg in seq {
ok &= i2c.blocking_write(CODEC_ADDR, ®).is_ok();
}
ok
}
const DMA2_STREAM1_IRQ: usize = 57;
const TX_WORDS: usize = 0x400;
const RX_WORDS: usize = 0x200;
const OLM_WORDS_PER_FRAME: usize = 4;
pub const FRAMES_PER_REFILL: usize = (TX_WORDS / 2) / OLM_WORDS_PER_FRAME;
const FULL_SCALE_20BIT: f32 = 0x0007_FFFF as f32;
const SAMPLE_RATE: u32 = 48_000;
const SAI1_A_DR: u32 = 0x4001_5820;
const SAI1_B_DR: u32 = 0x4001_5840;
const SAI2_A_DR: u32 = 0x4001_5C20;
const AUDIO_TX1: *mut u32 = 0x3000_0000 as *mut u32;
const AUDIO_RX: *mut u32 = 0x3000_1000 as *mut u32;
const AUDIO_TX2: *mut u32 = 0x3000_1800 as *mut u32;
const SAI_GCR: u32 = 0x0000_0010;
const SAI_BLOCK_RUN_MASK: u32 = 0x0003_0000;
const DMAMUX_REQ_SAI1_A: u32 = 0x57;
const DMAMUX_REQ_SAI1_B: u32 = 0x58;
const DMAMUX_REQ_SAI2_A: u32 = 0x59;
const DMA_TX_CR: u32 = 0x0003_555F;
const DMA_RX_CR: u32 = 0x0003_551F;
const DMA_TX_FCR: u32 = 0x0000_0088;
const DMA_RX_FCR: u32 = 0x0000_00A0;
#[derive(Clone, Copy)]
struct SaiBlockConfig {
cr1: u32,
cr2: u32,
frcr: u32,
slotr: u32,
im: u32,
}
impl SaiBlockConfig {
const fn disabled_cr1(self) -> u32 {
self.cr1 & !SAI_BLOCK_RUN_MASK
}
}
#[derive(Clone, Copy)]
struct DmaStreamConfig {
stream: usize,
words: u32,
peripheral: u32,
memory: *mut u32,
fcr: u32,
cr: u32,
}
const SAI1_A: SaiBlockConfig = SaiBlockConfig { cr1: 0x0013_22E0, cr2: 0x0000_0001, frcr: 0x0002_3F7F, slotr: 0xFFFF_0380, im: 0x0000_0005 };
const SAI1_B: SaiBlockConfig = SaiBlockConfig { cr1: 0x0013_06E3, cr2: 0x0000_0001, frcr: 0x0005_1F3F, slotr: 0xFFFF_0180, im: 0x0000_0061 };
const SAI2_A: SaiBlockConfig = SaiBlockConfig { cr1: 0x0013_2AE2, cr2: 0x0000_0001, frcr: 0x0002_3F7F, slotr: 0xFFFF_0380, im: 0x0000_0061 };
const DMA_SAI1_B_RX: DmaStreamConfig = DmaStreamConfig { stream: 5, words: RX_WORDS as u32, peripheral: SAI1_B_DR, memory: AUDIO_RX, fcr: DMA_RX_FCR, cr: DMA_RX_CR };
const DMA_SAI2_A_TX: DmaStreamConfig = DmaStreamConfig { stream: 4, words: TX_WORDS as u32, peripheral: SAI2_A_DR, memory: AUDIO_TX2, fcr: DMA_TX_FCR, cr: DMA_TX_CR };
const DMA_SAI1_A_TX: DmaStreamConfig = DmaStreamConfig { stream: 1, words: TX_WORDS as u32, peripheral: SAI1_A_DR, memory: AUDIO_TX1, fcr: DMA_TX_FCR, cr: DMA_TX_CR };
const DAC_CHANNELS: usize = 8;
#[derive(Clone, Copy, PartialEq, Eq, defmt::Format)]
pub enum Dac {
Dac1,
Dac2,
Dac3,
Dac4,
Dac5,
Dac6,
Dac7,
Dac8,
}
impl Dac {
pub const ALL: [Dac; DAC_CHANNELS] = [
Dac::Dac1, Dac::Dac2, Dac::Dac3, Dac::Dac4, Dac::Dac5, Dac::Dac6, Dac::Dac7, Dac::Dac8,
];
pub const fn index(self) -> usize {
self as usize
}
pub const fn label(self) -> &'static str {
match self {
Dac::Dac1 => "DAC1",
Dac::Dac2 => "DAC2",
Dac::Dac3 => "DAC3",
Dac::Dac4 => "DAC4",
Dac::Dac5 => "DAC5",
Dac::Dac6 => "DAC6",
Dac::Dac7 => "DAC7",
Dac::Dac8 => "DAC8",
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, defmt::Format)]
pub enum Output {
Phones,
Jack1,
Jack2,
Jack3,
}
impl Output {
pub const ALL: [Output; 4] = [Output::Phones, Output::Jack1, Output::Jack2, Output::Jack3];
pub const fn label(self) -> &'static str {
match self {
Output::Phones => "PHONES",
Output::Jack1 => "JACK1",
Output::Jack2 => "JACK2",
Output::Jack3 => "JACK3",
}
}
pub const fn left(self) -> Dac {
match self {
Output::Phones => Dac::Dac1,
Output::Jack1 => Dac::Dac3,
Output::Jack2 => Dac::Dac5,
Output::Jack3 => Dac::Dac7,
}
}
pub const fn right(self) -> Dac {
match self {
Output::Phones => Dac::Dac2,
Output::Jack1 => Dac::Dac4,
Output::Jack2 => Dac::Dac6,
Output::Jack3 => Dac::Dac8,
}
}
pub const fn channels(self) -> [Dac; 2] {
[self.left(), self.right()]
}
}
#[derive(Clone, Copy, PartialEq, Eq, defmt::Format)]
pub enum AudioIn {
Adc1,
Adc2,
}
static PHASE: AtomicU32 = AtomicU32::new(0);
static mut SINE_LUT: [i32; 256] = [0; 256];
static PHASE_INC: AtomicU32 = AtomicU32::new(0);
static OUTPUT_MASK: AtomicU8 = AtomicU8::new(0);
extern "C" fn audio_dma_isr() {
let lisr = pac::DMA2.isr(0).read();
pac::DMA2.ifcr(0).write(|w| {
w.set_tcif(1, true);
w.set_htif(1, true);
w.set_teif(1, true);
w.set_dmeif(1, true);
w.set_feif(1, true);
});
if lisr.htif(1) {
fill_half(0);
}
if lisr.tcif(1) {
fill_half(1);
}
}
fn fill_half(half: usize) {
let lut = unsafe { &*ptr::addr_of!(SINE_LUT) };
let inc = PHASE_INC.load(Ordering::Relaxed);
let mask = OUTPUT_MASK.load(Ordering::Relaxed);
let mut phase = PHASE.load(Ordering::Relaxed);
let base = half * (TX_WORDS / 2);
for f in 0..FRAMES_PER_REFILL {
let s = lut[(phase >> 24) as usize & 0xFF];
phase = phase.wrapping_add(inc);
let mut d = [0i32; DAC_CHANNELS];
for (ch, slot) in d.iter_mut().enumerate() {
if mask & (1 << ch) != 0 {
*slot = s;
}
}
let w1 = pack_dac6(&d);
let w2 = pack_dac2(&d);
let idx = base + f * OLM_WORDS_PER_FRAME;
for k in 0..OLM_WORDS_PER_FRAME {
unsafe {
ptr::write_volatile(AUDIO_TX1.add(idx + k), w1[k]);
ptr::write_volatile(AUDIO_TX2.add(idx + k), w2[k]);
}
}
}
PHASE.store(phase, Ordering::Relaxed);
cortex_m::asm::dsb(); }
pub fn play_sine(freq_hz: u32) {
play_sine_out(freq_hz, Output::Phones);
}
pub fn play_sine_out(freq_hz: u32, out: Output) {
play_sine_on(freq_hz, &out.channels());
}
pub fn play_sine_on(freq_hz: u32, dacs: &[Dac]) {
let mut mask = 0u8;
for d in dacs {
mask |= 1 << d.index();
}
OUTPUT_MASK.store(mask, Ordering::Relaxed);
bringup(freq_hz);
unmask_master_irq(); }
fn bringup(freq_hz: u32) {
mask_master_irq();
let lut = unsafe { &mut *ptr::addr_of_mut!(SINE_LUT) };
let mut i = 0;
while i < 256 {
let ph = i as f32 / 256.0 * core::f32::consts::TAU;
lut[i] = (libm::sinf(ph) * 0.5 * FULL_SCALE_20BIT) as i32;
i += 1;
}
PHASE_INC.store((((freq_hz as u64) << 32) / SAMPLE_RATE as u64) as u32, Ordering::Relaxed);
pac::RCC.ahb2enr().modify(|w| {
w.set_sram1en(true);
w.set_sram2en(true);
});
let _ = pac::RCC.ahb2enr().read();
pac::RCC.d2ccip1r().modify(|w| {
w.set_sai1sel(pac::rcc::vals::Saisel::PLL2_P);
w.set_sai23sel(pac::rcc::vals::Saisel::PLL2_P);
});
enable_and_reset(|v| pac::RCC.apb2enr().modify(|w| w.set_sai1en(v)), |v| pac::RCC.apb2rstr().modify(|w| w.set_sai1rst(v)));
enable_and_reset(|v| pac::RCC.apb2enr().modify(|w| w.set_sai2en(v)), |v| pac::RCC.apb2rstr().modify(|w| w.set_sai2rst(v)));
enable_and_reset(|v| pac::RCC.ahb1enr().modify(|w| w.set_dma2en(v)), |v| pac::RCC.ahb1rstr().modify(|w| w.set_dma2rst(v)));
config_sai_pin(pac::GPIOE, 2, 6);
config_sai_pin(pac::GPIOE, 5, 6);
config_sai_pin(pac::GPIOE, 4, 6);
config_sai_pin(pac::GPIOB, 2, 6);
config_sai_pin(pac::GPIOE, 3, 6);
config_sai_pin(pac::GPIOD, 11, 10);
config_sai_pin(pac::GPIOD, 12, 10);
config_sai_pin(pac::GPIOD, 13, 10);
init_audio_buffers();
use pac::{dma, dmamux, sai};
pac::SAI1.gcr().write_value(sai::regs::Gcr(SAI_GCR));
pac::SAI2.gcr().write_value(sai::regs::Gcr(SAI_GCR));
configure_sai_block(pac::SAI1.ch(0), SAI1_A, false);
configure_sai_block(pac::SAI1.ch(1), SAI1_B, false);
configure_sai_block(pac::SAI2.ch(0), SAI2_A, false);
pac::DMA2.st(1).cr().write_value(dma::regs::Cr(0));
pac::DMA2.st(4).cr().write_value(dma::regs::Cr(0));
pac::DMA2.st(5).cr().write_value(dma::regs::Cr(0));
while pac::DMA2.st(1).cr().read().en() || pac::DMA2.st(4).cr().read().en() || pac::DMA2.st(5).cr().read().en() {}
pac::DMA2.ifcr(0).write_value(dma::regs::Ixr(0xFFFF_FFFF));
pac::DMA2.ifcr(1).write_value(dma::regs::Ixr(0xFFFF_FFFF));
pac::DMAMUX1.ccr(9).write_value(dmamux::regs::Ccr(DMAMUX_REQ_SAI1_A));
pac::DMAMUX1.ccr(12).write_value(dmamux::regs::Ccr(DMAMUX_REQ_SAI2_A));
pac::DMAMUX1.ccr(13).write_value(dmamux::regs::Ccr(DMAMUX_REQ_SAI1_B));
configure_dma_stream(DMA_SAI1_B_RX);
configure_dma_stream(DMA_SAI2_A_TX);
configure_dma_stream(DMA_SAI1_A_TX);
configure_sai_block(pac::SAI1.ch(1), SAI1_B, true);
configure_sai_block(pac::SAI2.ch(0), SAI2_A, true);
configure_sai_block(pac::SAI1.ch(0), SAI1_A, true);
install_isr();
const NVIC_ICER1: *mut u32 = 0xE000_E184 as *mut u32;
const NVIC_ICER2: *mut u32 = 0xE000_E188 as *mut u32;
let icer1 = (1u32 << (56 - 32)) | (1 << (57 - 32)) | (1 << (58 - 32)) | (1 << (59 - 32)) | (1 << (60 - 32));
let icer2 = (1u32 << (68 - 64)) | (1 << (69 - 64)) | (1 << (70 - 64));
unsafe {
ptr::write_volatile(NVIC_ICER1, icer1);
ptr::write_volatile(NVIC_ICER2, icer2);
}
cortex_m::asm::dsb();
defmt::info!("sai: stock topology up — SAI1_A DAC1-6 + SAI2_A DAC7-8 + SAI1_B RX, {} Hz", SAMPLE_RATE);
}
pub fn unmask_master_irq() {
const NVIC_ISER1: *mut u32 = 0xE000_E104 as *mut u32;
unsafe { ptr::write_volatile(NVIC_ISER1, 1 << (DMA2_STREAM1_IRQ - 32)) };
}
fn mask_master_irq() {
const NVIC_ICER1: *mut u32 = 0xE000_E184 as *mut u32;
unsafe { ptr::write_volatile(NVIC_ICER1, 1 << (DMA2_STREAM1_IRQ - 32)) };
cortex_m::asm::dsb();
cortex_m::asm::isb();
}
#[repr(align(1024))]
struct VectorTable([u32; 256]);
static mut RAM_VTABLE: VectorTable = VectorTable([0; 256]);
fn install_isr() {
let scb = unsafe { &*cortex_m::peripheral::SCB::PTR };
let src = scb.vtor.read() as *const u32; let table = unsafe { &mut *ptr::addr_of_mut!(RAM_VTABLE) };
for i in 0..256 {
table.0[i] = unsafe { ptr::read_volatile(src.add(i)) };
}
table.0[16 + DMA2_STREAM1_IRQ] = audio_dma_isr as usize as u32;
cortex_m::asm::dsb();
unsafe { scb.vtor.write(table.0.as_ptr() as u32) };
cortex_m::asm::dsb();
cortex_m::asm::isb();
}
fn config_sai_pin(port: pac::gpio::Gpio, pin: usize, af: u8) {
use pac::gpio::vals;
port.afr(pin / 8).modify(|w| w.set_afr(pin % 8, af));
port.ospeedr().modify(|w| w.set_ospeedr(pin, vals::Ospeedr::MEDIUM_SPEED));
port.pupdr().modify(|w| w.set_pupdr(pin, vals::Pupdr::FLOATING));
port.otyper().modify(|w| w.set_ot(pin, vals::Ot::PUSH_PULL));
port.moder().modify(|w| w.set_moder(pin, vals::Moder::ALTERNATE));
}
fn enable_and_reset(enable: impl Fn(bool), reset: impl Fn(bool)) {
enable(true);
let _ = pac::RCC.ahb1enr().read(); reset(true);
reset(false);
}
fn init_audio_buffers() {
for i in 0..RX_WORDS {
unsafe { ptr::write_volatile(AUDIO_RX.add(i), 0) };
}
for i in 0..TX_WORDS {
unsafe {
ptr::write_volatile(AUDIO_TX1.add(i), 0);
ptr::write_volatile(AUDIO_TX2.add(i), 0);
}
}
}
fn pack_dac6(s: &[i32; DAC_CHANNELS]) -> [u32; 4] {
let mut w = [0u32; 4];
let m = |x: i32| (x as u32) & 0x000F_FFFF;
put_20(&mut w, 0, m(s[0]));
put_20(&mut w, 20, m(s[2]));
put_20(&mut w, 40, m(s[4]));
put_20(&mut w, 64, m(s[1]));
put_20(&mut w, 84, m(s[3]));
put_20(&mut w, 104, m(s[5]));
w
}
fn pack_dac2(s: &[i32; DAC_CHANNELS]) -> [u32; 4] {
let mut w = [0u32; 4];
let m = |x: i32| (x as u32) & 0x000F_FFFF;
put_20(&mut w, 0, m(s[6])); put_20(&mut w, 64, m(s[7])); w
}
fn put_20(words: &mut [u32; 4], start_bit: usize, sample: u32) {
let w = start_bit / 32;
let off = start_bit % 32;
if off <= 12 {
words[w] |= sample << (12 - off);
} else {
words[w] |= sample >> (off - 12);
words[w + 1] |= sample << (44 - off);
}
}
fn configure_sai_block(block: pac::sai::Ch, config: SaiBlockConfig, enable: bool) {
use pac::sai::regs;
let cr1 = if enable { config.cr1 } else { config.disabled_cr1() };
block.cr1().write_value(regs::Cr1(cr1));
block.cr2().write_value(regs::Cr2(config.cr2));
block.frcr().write_value(regs::Frcr(config.frcr));
block.slotr().write_value(regs::Slotr(config.slotr));
block.im().write_value(regs::Im(config.im));
}
fn configure_dma_stream(config: DmaStreamConfig) {
use pac::dma::regs;
let st = pac::DMA2.st(config.stream);
st.ndtr().write_value(regs::Ndtr(config.words));
st.par().write_value(config.peripheral);
st.m0ar().write_value(config.memory as u32);
st.m1ar().write_value(0);
st.fcr().write_value(regs::Fcr(config.fcr));
st.cr().write_value(regs::Cr(config.cr));
}