diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 24d0015c..710b8f79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,4 +46,4 @@ jobs: - uses: actions-rs/cargo@v1 with: command: check - args: --features=${{ matrix.mcu }},rt,usb_fs,sdio,can --examples + args: --features=${{ matrix.mcu }},rt,usb_fs,sdio,can,i2s --examples diff --git a/CHANGELOG.md b/CHANGELOG.md index 553dca79..0d85747b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Update the sdio driver to match the changes in the PAC - Update README.md with current information +### Added + +- Added support for I2S communication using SPI peripherals, and two examples [#265] + +[#265]: https://github.com/stm32-rs/stm32f4xx-hal/pull/265 + ## [v0.9.0] - 2021-04-04 ### Changed diff --git a/Cargo.toml b/Cargo.toml index 6e447aeb..b0c4f20e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ repository = "https://github.com/stm32-rs/stm32f4xx-hal" version = "0.9.0" [package.metadata.docs.rs] -features = ["stm32f429", "rt", "usb_fs", "can"] +features = ["stm32f429", "rt", "usb_fs", "can", "i2s"] targets = ["thumbv7em-none-eabihf"] [dependencies] @@ -41,6 +41,10 @@ cast = { default-features = false, version = "0.2.2" } void = { default-features = false, version = "1.0.2" } embedded-hal = { features = ["unproven"], version = "0.2.3" } +[dependencies.stm32_i2s_v12x] +version = "0.2.0" +optional = true + [dev-dependencies] panic-semihosting = "0.5.3" cortex-m-semihosting = "0.3.3" @@ -83,6 +87,8 @@ can = ["bxcan"] sdio = ["sdio-host"] +i2s = ["stm32_i2s_v12x"] + [profile.dev] debug = true lto = true @@ -136,6 +142,14 @@ required-features = ["rt", "stm32f411"] name = "can-send" required-features = ["can", "stm32f405"] +[[example]] +name = "i2s-audio-out" +required-features = ["stm32f411", "rt", "i2s"] + +[[example]] +name = "i2s-audio-out-dma" +required-features = ["stm32f411", "rt", "i2s"] + [[example]] name = "rtic" required-features = ["rt", "stm32f407"] diff --git a/examples/cs43l22/mod.rs b/examples/cs43l22/mod.rs new file mode 100644 index 00000000..75ee726a --- /dev/null +++ b/examples/cs43l22/mod.rs @@ -0,0 +1,119 @@ +//! Bare-bones driver for configuring a CS43L22 digital-analog converter + +use stm32f4xx_hal::hal::blocking::i2c::{Read, Write}; + +/// Interface to the I2C control port of a Cirrus Logic CS43L22 DAC +pub struct Cs43L22 { + /// I2C interface + i2c: I, + /// Address of DAC + address: u8, +} + +impl Cs43L22 +where + I: Write + Read, +{ + pub fn new(i2c: I, address: u8) -> Self { + Cs43L22 { i2c, address } + } + + /// Does basic configuration as specified in the datasheet + pub fn basic_setup(&mut self) -> Result<(), ::Error> { + // Settings from section 4.11 of the datasheet + self.write(Register::Magic00, 0x99)?; + self.write(Register::Magic47, 0x80)?; + self.write(Register::Magic32, 0x80)?; + self.write(Register::Magic32, 0x00)?; + self.write(Register::Magic00, 0x00) + } + + /// Writes the value of one register + pub fn write(&mut self, register: Register, value: u8) -> Result<(), ::Error> { + // Set auto-increment bit + let map = (register as u8) | 0x80; + self.i2c.write(self.address, &[map, value]) + } + + /// Reads the value of one register + #[allow(dead_code)] + pub fn read( + &mut self, + register: Register, + ) -> Result::Error, ::Error>> { + let mut values = [0u8]; + self.read_multiple(register, &mut values)?; + Ok(values[0]) + } + /// Reads the values of zero or more consecutive registers + #[allow(dead_code)] + pub fn read_multiple( + &mut self, + register: Register, + values: &mut [u8], + ) -> Result<(), CombinedI2cError<::Error, ::Error>> { + // Two transactions: set the memory address pointer, then read + // An empty write sets the address + // Set auto-increment bit + let map = (register as u8) | 0x80; + self.i2c + .write(self.address, &[map]) + .map_err(CombinedI2cError::Write)?; + self.i2c + .read(self.address, values) + .map_err(CombinedI2cError::Read) + } +} + +#[derive(Debug)] +pub enum CombinedI2cError { + Read(R), + Write(W), +} + +/// CS43L22 registers +#[allow(dead_code)] +pub enum Register { + /// This is used in the specified startup sequence, but its actual content is not documented. + Magic00 = 0x00, + Id = 0x01, + PowerCtl1 = 0x02, + PowerCtl2 = 0x04, + ClockingCtl = 0x05, + InterfaceCtl1 = 0x06, + InterfaceCtl2 = 0x07, + PassthroughASelect = 0x08, + PassthroughBSelect = 0x09, + AnalogZcSr = 0x0a, + PassthroughGangCtl = 0x0c, + PlaybackCtl1 = 0x0d, + MiscCtl = 0x0e, + PlaybackCtl2 = 0x0f, + PassthroughAVol = 0x14, + PassthroughBVol = 0x15, + PcmAVol = 0x1a, + PcmBVol = 0x1b, + BeepFreqOnTime = 0x1c, + BeepVolOffTime = 0x1d, + BeepToneCfg = 0x1e, + ToneCtl = 0x1f, + MasterAVol = 0x20, + MasterBVol = 0x21, + HeadphoneAVol = 0x22, + HeadphoneBVol = 0x23, + SpeakerAVol = 0x24, + SpeakerBVol = 0x25, + ChannelMixer = 0x26, + LimitCtl1 = 0x27, + LimitClt2 = 0x28, + LimitAttack = 0x29, + Status = 0x2e, + BatteryComp = 0x2f, + VpBatteryLevel = 0x30, + SpeakerStatus = 0x31, + /// This is used in the specified startup sequence, but its actual content is not documented. + Magic32 = 0x32, + ChargePumpFreq = 0x34, + /// This is used in the specified startup sequence, but its actual content is not documented. + Magic47 = 0x47, +} diff --git a/examples/i2s-audio-out-dma.rs b/examples/i2s-audio-out-dma.rs new file mode 100644 index 00000000..e5a52316 --- /dev/null +++ b/examples/i2s-audio-out-dma.rs @@ -0,0 +1,252 @@ +//! +//! # I2S example for STM32F411 +//! +//! This application demonstrates I2S communication with the DAC on an STM32F411E-DISCO board. +//! Unlike `i2s-audio-out`, this example uses DMA instead of writing one sample at a time. +//! +//! # Hardware required +//! +//! * STM32F407G-DISC1 or STM32F411E-DISCO evaluation board +//! * Headphones or speakers with a headphone plug +//! +//! # Procedure +//! +//! 1. Connect the headphones or speakers to the headphone jack on the evaluation board +//! (warning: the DAC may produce a powerful signal that becomes a very loud sound. +//! Set the speaker volume to minimum, or do not put on the headphones.) +//! 2. Load this compiled application on the microcontroller and run it +//! +//! Expected behavior: the speakers/headphones emit a continuous 750 Hz tone +//! +//! # Pins and addresses +//! +//! * PD4 -> DAC ~RESET (pulled low) +//! +//! * PB9 -> SDA (pulled high) +//! * PB6 -> SCL (pulled high) +//! +//! * PC7 -> MCLK +//! * PC10 -> SCK (bit clock) +//! * PC12 -> SD +//! * PA4 -> WS +//! +//! DAC I2C address 0x94 +//! + +#![no_std] +#![no_main] + +mod cs43l22; + +use core::cell::RefCell; + +use panic_halt as _; + +use cortex_m::interrupt::Mutex; +use cortex_m_rt::entry; + +use stm32_i2s_v12x::format::{Data16Frame16, FrameFormat}; +use stm32_i2s_v12x::{MasterClock, MasterConfig, Polarity, TransmitMode}; + +use stm32f4xx_hal::delay::Delay; +use stm32f4xx_hal::dma::config::DmaConfig; +use stm32f4xx_hal::dma::MemoryToPeripheral; +use stm32f4xx_hal::dma::{Channel0, Stream5, StreamsTuple, Transfer}; +use stm32f4xx_hal::gpio::gpioa::PA4; +use stm32f4xx_hal::gpio::gpioc::{PC10, PC12, PC7}; +use stm32f4xx_hal::gpio::Alternate; +use stm32f4xx_hal::i2c::I2c; +use stm32f4xx_hal::i2s::I2s; +use stm32f4xx_hal::pac::{interrupt, Interrupt}; +use stm32f4xx_hal::pac::{CorePeripherals, Peripherals}; +use stm32f4xx_hal::prelude::*; +use stm32f4xx_hal::stm32::{DMA1, SPI3}; + +use cs43l22::{Cs43L22, Register}; + +/// Volume in decibels +/// +/// Depending on your speakers, you may need to adjust this value. +const VOLUME: i8 = -100; + +const SINE_SAMPLES: usize = 64; + +/// A sine wave spanning 64 samples +/// +/// With a sample rate of 48 kHz, this produces a 750 Hz tone. +const SINE_750: [i16; SINE_SAMPLES] = [ + 0, 3211, 6392, 9511, 12539, 15446, 18204, 20787, 23169, 25329, 27244, 28897, 30272, 31356, + 32137, 32609, 32767, 32609, 32137, 31356, 30272, 28897, 27244, 25329, 23169, 20787, 18204, + 15446, 12539, 9511, 6392, 3211, 0, -3211, -6392, -9511, -12539, -15446, -18204, -20787, -23169, + -25329, -27244, -28897, -30272, -31356, -32137, -32609, -32767, -32609, -32137, -31356, -30272, + -28897, -27244, -25329, -23169, -20787, -18204, -15446, -12539, -9511, -6392, -3211, +]; + +#[entry] +fn main() -> ! { + // Make a copy of SINE_750 with three differences: + // 1. It's a way to get a &'static mut + // 2. Its type is u16 instead of i16 + // 3. Each sample is repeated (for the left and right channels) + static mut SINE_750_MUT: [u16; SINE_SAMPLES * 2] = [0; SINE_SAMPLES * 2]; + + let cp = CorePeripherals::take().unwrap(); + let dp = Peripherals::take().unwrap(); + + let rcc = dp.RCC.constrain(); + // The 86 MHz frequency can be divided to get a sample rate very close to 48 kHz. + let clocks = rcc.cfgr.use_hse(8.mhz()).i2s_clk(86.mhz()).freeze(); + + let gpioa = dp.GPIOA.split(); + let gpiob = dp.GPIOB.split(); + let gpioc = dp.GPIOC.split(); + let gpiod = dp.GPIOD.split(); + + let mut delay = Delay::new(cp.SYST, clocks); + + let i2c = I2c::new( + dp.I2C1, + ( + gpiob.pb6.into_alternate_af4_open_drain(), + gpiob.pb9.into_alternate_af4_open_drain(), + ), + 100.khz(), + clocks, + ); + // Shift the address to deal with different ways of representing I2C addresses + let mut dac = Cs43L22::new(i2c, 0x94 >> 1); + + let mut dac_reset = gpiod.pd4.into_push_pull_output(); + + // I2S pins: (WS, CK, MCLK, SD) for I2S3 + let i2s_pins = ( + gpioa.pa4.into_alternate_af6(), + gpioc.pc10.into_alternate_af6(), + gpioc.pc7.into_alternate_af6(), + gpioc.pc12.into_alternate_af6(), + ); + let hal_i2s = I2s::i2s3(dp.SPI3, i2s_pins, clocks); + let i2s_clock = hal_i2s.input_clock(); + + // Audio timing configuration: + // Sample rate 48 kHz + // 16 bits per sample -> SCK rate 1.536 MHz + // MCK frequency = 256 * sample rate -> MCK rate 12.228 MHz (also equal to 8 * SCK rate) + let sample_rate = 48000; + + let i2s = stm32_i2s_v12x::I2s::new(hal_i2s); + let mut i2s = i2s.configure_master_transmit(MasterConfig::with_sample_rate( + i2s_clock.0, + sample_rate, + Data16Frame16, + FrameFormat::PhilipsI2s, + Polarity::IdleHigh, + MasterClock::Enable, + )); + i2s.set_dma_enabled(true); + + // Keep DAC reset low for at least one millisecond + delay.delay_ms(1u8); + // Release the DAC from reset + dac_reset.set_high().unwrap(); + // Wait at least 550 ns before starting I2C communication + delay.delay_us(1u8); + + dac.basic_setup().unwrap(); + // Clocking control from the table in section 4.6 of the datasheet: + // Auto mode: disabled + // Speed mode: 01 (single-speed) + // 8 kHz, 16 kHz, or 32 kHz sample rate: no + // 27 MHz video clock: no + // Internal MCLK/LRCLCK ratio: 00 + // MCLK divide by 2: no + dac.write(Register::ClockingCtl, 0b0_01_0_0_00_0).unwrap(); + // Interface control: + // Slave mode + // SCLK not inverted + // DSP mode disabled + // Interface format I2S + // Word length 16 bits + dac.write(Register::InterfaceCtl1, 0b0_0_0_0_01_11).unwrap(); + + // Reduce the headphone volume something more comfortable + dac.write(Register::HeadphoneAVol, VOLUME as u8).unwrap(); + dac.write(Register::HeadphoneBVol, VOLUME as u8).unwrap(); + + // Power up DAC + dac.write(Register::PowerCtl1, 0b1001_1110).unwrap(); + + { + // Copy samples from flash into the buffer that DMA will use + let mut dest_iter = SINE_750_MUT.iter_mut(); + for sample in SINE_750.iter() { + // Duplicate sample for the left and right channels + let left = dest_iter.next().unwrap(); + let right = dest_iter.next().unwrap(); + *left = *sample as u16; + *right = *sample as u16; + } + } + + // Set up DMA: DMA 1 stream 5 channel 0 memory -> peripheral + let dma1_streams = StreamsTuple::new(dp.DMA1); + let dma_config = DmaConfig::default() + .memory_increment(true) + .transfer_complete_interrupt(true); + let mut dma_transfer: I2sDmaTransfer = + Transfer::init(dma1_streams.5, i2s, SINE_750_MUT, None, dma_config); + + dma_transfer.start(|i2s| i2s.enable()); + // Hand off transfer to interrupt handler + cortex_m::interrupt::free(|cs| *G_TRANSFER.borrow(cs).borrow_mut() = Some(dma_transfer)); + // Enable interrupt + unsafe { + cortex_m::peripheral::NVIC::unmask(Interrupt::DMA1_STREAM5); + } + + loop { + // Don't WFI. That can cause problems attaching the debugger. + } +} + +type I2sDmaTransfer = Transfer< + Stream5, + Channel0, + stm32_i2s_v12x::I2s< + I2s< + SPI3, + ( + PA4>, + PC10>, + PC7>, + PC12>, + ), + >, + TransmitMode, + >, + MemoryToPeripheral, + &'static mut [u16; SINE_SAMPLES * 2], +>; + +/// DMA transfer handoff from main() to interrupt handler +static G_TRANSFER: Mutex>> = Mutex::new(RefCell::new(None)); + +/// This interrupt handler runs when DMA 1 finishes a transfer to the I2S peripheral +#[interrupt] +fn DMA1_STREAM5() { + static mut TRANSFER: Option = None; + + let transfer = TRANSFER.get_or_insert_with(|| { + cortex_m::interrupt::free(|cs| G_TRANSFER.borrow(cs).replace(None).unwrap()) + }); + + transfer.clear_transfer_complete_interrupt(); + unsafe { + transfer + .next_transfer_with(|buffer, _active_buffer| { + // Transfer again with the same buffer + (buffer, ()) + }) + .unwrap(); + } +} diff --git a/examples/i2s-audio-out.rs b/examples/i2s-audio-out.rs new file mode 100644 index 00000000..6d00ec0a --- /dev/null +++ b/examples/i2s-audio-out.rs @@ -0,0 +1,194 @@ +//! +//! # I2S example for STM32F411 +//! +//! This application demonstrates I2S communication with the DAC on an STM32F411E-DISCO board +//! +//! # Hardware required +//! +//! * STM32F407G-DISC1 or STM32F411E-DISCO evaluation board +//! * Headphones or speakers with a headphone plug +//! +//! # Procedure +//! +//! 1. Connect the headphones or speakers to the headphone jack on the evaluation board +//! (warning: the DAC may produce a powerful signal that becomes a very loud sound. +//! Set the speaker volume to minimum, or do not put on the headphones.) +//! 2. Load this compiled application on the microcontroller and run it +//! +//! Expected behavior: the speakers/headphones emit 1 second of 375 Hz tone followed by 1 second of +//! 750 Hz tone, repeating indefinitely. +//! +//! # Pins and addresses +//! +//! * PD4 -> DAC ~RESET (pulled low) +//! +//! * PB9 -> SDA (pulled high) +//! * PB6 -> SCL (pulled high) +//! +//! * PC7 -> MCLK +//! * PC10 -> SCK (bit clock) +//! * PC12 -> SD +//! * PA4 -> WS +//! +//! DAC I2C address 0x94 +//! + +#![no_std] +#![no_main] + +mod cs43l22; + +use panic_halt as _; + +use cortex_m_rt::entry; + +use stm32_i2s_v12x::format::{Data16Frame16, FrameFormat}; +use stm32_i2s_v12x::{MasterClock, MasterConfig, Polarity}; + +use stm32f4xx_hal::delay::Delay; +use stm32f4xx_hal::i2c::I2c; +use stm32f4xx_hal::i2s::I2s; +use stm32f4xx_hal::nb::block; +use stm32f4xx_hal::pac::{CorePeripherals, Peripherals}; +use stm32f4xx_hal::prelude::*; + +use cs43l22::{Cs43L22, Register}; + +/// Volume in decibels +/// +/// Depending on your speakers, you may need to adjust this value. +const VOLUME: i8 = -100; + +/// A sine wave spanning 64 samples +/// +/// With a sample rate of 48 kHz, this produces a 750 Hz tone. +const SINE_750: [i16; 64] = [ + 0, 3211, 6392, 9511, 12539, 15446, 18204, 20787, 23169, 25329, 27244, 28897, 30272, 31356, + 32137, 32609, 32767, 32609, 32137, 31356, 30272, 28897, 27244, 25329, 23169, 20787, 18204, + 15446, 12539, 9511, 6392, 3211, 0, -3211, -6392, -9511, -12539, -15446, -18204, -20787, -23169, + -25329, -27244, -28897, -30272, -31356, -32137, -32609, -32767, -32609, -32137, -31356, -30272, + -28897, -27244, -25329, -23169, -20787, -18204, -15446, -12539, -9511, -6392, -3211, +]; + +/// A sine wave spanning 128 samples +/// +/// With a sample rate of 48 kHz, this produces a 375 Hz tone. +const SINE_375: [i16; 128] = [ + 0, 1607, 3211, 4807, 6392, 7961, 9511, 11038, 12539, 14009, 15446, 16845, 18204, 19519, 20787, + 22004, 23169, 24278, 25329, 26318, 27244, 28105, 28897, 29621, 30272, 30851, 31356, 31785, + 32137, 32412, 32609, 32727, 32767, 32727, 32609, 32412, 32137, 31785, 31356, 30851, 30272, + 29621, 28897, 28105, 27244, 26318, 25329, 24278, 23169, 22004, 20787, 19519, 18204, 16845, + 15446, 14009, 12539, 11038, 9511, 7961, 6392, 4807, 3211, 1607, 0, -1607, -3211, -4807, -6392, + -7961, -9511, -11038, -12539, -14009, -15446, -16845, -18204, -19519, -20787, -22004, -23169, + -24278, -25329, -26318, -27244, -28105, -28897, -29621, -30272, -30851, -31356, -31785, -32137, + -32412, -32609, -32727, -32767, -32727, -32609, -32412, -32137, -31785, -31356, -30851, -30272, + -29621, -28897, -28105, -27244, -26318, -25329, -24278, -23169, -22004, -20787, -19519, -18204, + -16845, -15446, -14009, -12539, -11038, -9511, -7961, -6392, -4807, -3211, -1607, +]; + +#[entry] +fn main() -> ! { + let cp = CorePeripherals::take().unwrap(); + let dp = Peripherals::take().unwrap(); + + let gpioa = dp.GPIOA.split(); + let gpiob = dp.GPIOB.split(); + let gpioc = dp.GPIOC.split(); + let gpiod = dp.GPIOD.split(); + + let rcc = dp.RCC.constrain(); + // The 86 MHz frequency can be divided to get a sample rate very close to 48 kHz. + let clocks = rcc.cfgr.use_hse(8.mhz()).i2s_clk(86.mhz()).freeze(); + + let mut delay = Delay::new(cp.SYST, clocks); + + let i2c = I2c::new( + dp.I2C1, + ( + gpiob.pb6.into_alternate_af4_open_drain(), + gpiob.pb9.into_alternate_af4_open_drain(), + ), + 100.khz(), + clocks, + ); + // Shift the address to deal with different ways of representing I2C addresses + let mut dac = Cs43L22::new(i2c, 0x94 >> 1); + + let mut dac_reset = gpiod.pd4.into_push_pull_output(); + + // I2S pins: (WS, CK, MCLK, SD) for I2S3 + let i2s_pins = ( + gpioa.pa4.into_alternate_af6(), + gpioc.pc10.into_alternate_af6(), + gpioc.pc7.into_alternate_af6(), + gpioc.pc12.into_alternate_af6(), + ); + let hal_i2s = I2s::i2s3(dp.SPI3, i2s_pins, clocks); + let i2s_clock = hal_i2s.input_clock(); + + // Audio timing configuration: + // Sample rate 48 kHz + // 16 bits per sample -> SCK rate 1.536 MHz + // MCK frequency = 256 * sample rate -> MCK rate 12.228 MHz (also equal to 8 * SCK rate) + let sample_rate = 48000; + + let i2s = stm32_i2s_v12x::I2s::new(hal_i2s); + let mut i2s = i2s.configure_master_transmit(MasterConfig::with_sample_rate( + i2s_clock.0, + sample_rate, + Data16Frame16, + FrameFormat::PhilipsI2s, + Polarity::IdleHigh, + MasterClock::Enable, + )); + + // Keep DAC reset low for at least one millisecond + delay.delay_ms(1u8); + // Release the DAC from reset + dac_reset.set_high().unwrap(); + // Wait at least 550 ns before starting I2C communication + delay.delay_us(1u8); + + dac.basic_setup().unwrap(); + // Clocking control from the table in section 4.6 of the datasheet: + // Auto mode: disabled + // Speed mode: 01 (single-speed) + // 8 kHz, 16 kHz, or 32 kHz sample rate: no + // 27 MHz video clock: no + // Internal MCLK/LRCLCK ratio: 00 + // MCLK divide by 2: no + dac.write(Register::ClockingCtl, 0b0_01_0_0_00_0).unwrap(); + // Interface control: + // Slave mode + // SCLK not inverted + // DSP mode disabled + // Interface format I2S + // Word length 16 bits + dac.write(Register::InterfaceCtl1, 0b0_0_0_0_01_11).unwrap(); + + // Reduce the headphone volume something more comfortable + dac.write(Register::HeadphoneAVol, VOLUME as u8).unwrap(); + dac.write(Register::HeadphoneBVol, VOLUME as u8).unwrap(); + + // Power up DAC + dac.write(Register::PowerCtl1, 0b1001_1110).unwrap(); + + // Start sending samples + i2s.enable(); + let sine_375_1sec = SINE_375.iter().cloned().cycle().take(sample_rate as usize); + let sine_750_1sec = SINE_750.iter().cloned().cycle().take(sample_rate as usize); + + loop { + // Play one second of each tone + for sample in sine_375_1sec.clone() { + // Transmit the same sample on the left and right channels + block!(i2s.transmit(sample)).unwrap(); + block!(i2s.transmit(sample)).unwrap(); + } + for sample in sine_750_1sec.clone() { + // Transmit the same sample on the left and right channels + block!(i2s.transmit(sample)).unwrap(); + block!(i2s.transmit(sample)).unwrap(); + } + } +} diff --git a/src/i2s.rs b/src/i2s.rs new file mode 100644 index 00000000..5db0ef06 --- /dev/null +++ b/src/i2s.rs @@ -0,0 +1,468 @@ +//! I2S (inter-IC Sound) communication using SPI peripherals +//! +//! This module is only available if the `i2s` feature is enabled. + +use stm32_i2s_v12x::{Instance, RegisterBlock}; + +use crate::pac::RCC; +use crate::time::Hertz; +use crate::{bb, rcc::Clocks, spi}; + +// I2S pins are mostly the same as the corresponding SPI pins: +// MOSI -> SD +// NSS -> WS (the current SPI code doesn't define NSS pins) +// SCK -> CK +// The master clock output is separate. + +/// A pin that can be used as SD (serial data) +pub trait PinSd {} +/// A pin that can be used as WS (word select, left/right clock) +pub trait PinWs {} +/// A pin that can be used as CK (bit clock) +pub trait PinCk {} +/// A pin that can be used as MCK (master clock output) +pub trait PinMck {} + +/// Each MOSI pin can also be used as SD +impl PinSd for P where P: spi::PinMosi {} +/// Each SCK pin can also be used as CK +impl PinCk for P where P: spi::PinSck {} + +/// A placeholder for when the MCLK pin is not needed +pub struct NoMasterClock; + +mod sealed { + pub trait Sealed {} +} + +/// A set of pins configured for I2S communication: (WS, CK, MCLK, SD) +/// +/// NoMasterClock can be used instead of the master clock pin. +pub trait Pins {} + +impl Pins for (PWS, PCK, PMCLK, PSD) +where + PWS: PinWs, + PCK: PinCk, + PMCLK: PinMck, + PSD: PinSd, +{ +} + +/// Master clock (MCK) pins +mod mck_pins { + macro_rules! pin_mck { + ($($PER:ident => $pin:ident<$af:ident>,)+) => { + $( + impl crate::i2s::sealed::Sealed for $pin> {} + impl crate::i2s::PinMck<$PER> for $pin> {} + )+ + }; + } + + mod common { + use crate::gpio::{gpioc::PC6, AF5}; + use crate::pac::SPI2; + // All STM32F4 models support PC6 for SPI2/I2S2 + pin_mck! { SPI2 => PC6, } + } + + #[cfg(any( + feature = "stm32f411", + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + ))] + mod pa3_pa6_pb10 { + use crate::gpio::{ + gpioa::{PA3, PA6}, + gpiob::PB10, + AF5, AF6, + }; + use crate::pac::{SPI2, SPI3}; + pin_mck! { + SPI2 => PA3, + SPI2 => PA6, + SPI3 => PB10, + } + } + + #[cfg(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + ))] + mod pc4_af5 { + use crate::gpio::{gpioc::PC4, AF5}; + use crate::pac::SPI1; + pin_mck! { SPI1 => PC4, } + } + + // On all models except the STM32F410, PC7 is the master clock output from I2S3. + #[cfg(any( + feature = "stm32f401", + feature = "stm32f405", + feature = "stm32f407", + feature = "stm32f411", + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f415", + feature = "stm32f417", + feature = "stm32f423", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479", + ))] + mod i2s3_pc7_af6 { + use crate::gpio::{gpioc::PC7, AF6}; + use crate::pac::SPI3; + pin_mck! { SPI3 => PC7, } + } + + // On the STM32F410, PC7 is the master clock output from I2S1 instead of I2S3. + // Also, PB10 is the master clock output from I2S1 instead of I2S3. + #[cfg(feature = "stm32f410")] + mod i2s1_pc7_af6 { + use crate::gpio::{gpiob::PB10, gpioc::PC7, AF6}; + use crate::pac::SPI1; + pin_mck! { + SPI1 => PC7, + SPI1 => PB10, + } + } +} + +/// Word select (WS) pins +mod ws_pins { + macro_rules! pin_ws { + ($($PER:ident => $pin:ident<$af:ident>,)+) => { + $( + impl crate::i2s::sealed::Sealed for $pin> {} + impl crate::i2s::PinWs<$PER> for $pin> {} + )+ + }; + } + + mod common { + use crate::gpio::{ + gpiob::{PB12, PB9}, + AF5, + }; + use crate::pac::SPI2; + // All STM32F4 models support these pins + pin_ws! { + SPI2 => PB9, + SPI2 => PB12, + } + } + + /// Pins available on all models except the STM32F410 + #[cfg(any( + feature = "stm32f401", + feature = "stm32f405", + feature = "stm32f407", + feature = "stm32f411", + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f415", + feature = "stm32f417", + feature = "stm32f423", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479", + ))] + mod not_f410 { + use crate::gpio::{ + gpioa::{PA15, PA4}, + AF6, + }; + use crate::pac::SPI3; + pin_ws! { + SPI3 => PA4, + SPI3 => PA15, + } + } + #[cfg(any( + feature = "stm32f410", + feature = "stm32f411", + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + ))] + mod pa4_af5_pa15_af5 { + use crate::gpio::{ + gpioa::{PA15, PA4}, + AF5, + }; + use crate::pac::SPI1; + pin_ws! { + SPI1 => PA4, + SPI1 => PA15, + } + } + #[cfg(any( + feature = "stm32f411", + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + ))] + mod pb12_pe4_pe11 { + use crate::gpio::{ + gpiob::PB12, + gpioe::{PE11, PE4}, + AF5, AF6, + }; + use crate::pac::{SPI4, SPI5}; + pin_ws! { + SPI4 => PB12, + SPI4 => PE4, + SPI4 => PE11, + SPI5 => PE4, + SPI5 => PE11, + } + } + + #[cfg(any(feature = "stm32f413", feature = "stm32f423"))] + mod pa11 { + use crate::gpio::{gpioa::PA11, AF5}; + use crate::pac::SPI2; + pin_ws! { SPI2 => PA11, } + } + + #[cfg(any( + feature = "stm32f410", + feature = "stm32f411", + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + ))] + mod pb1 { + use crate::gpio::{gpiob::PB1, AF6}; + use crate::pac::SPI5; + pin_ws! { SPI5 => PB1, } + } + + #[cfg(feature = "stm32f446")] + mod pb4_pd1 { + use crate::gpio::{gpiob::PB4, gpiod::PD1, AF7}; + use crate::pac::SPI2; + pin_ws! { + SPI2 => PB4, + SPI2 => PD1, + } + } + + #[cfg(any( + feature = "stm32f405", + feature = "stm32f407", + feature = "stm32f415", + feature = "stm32f417", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f469", + feature = "stm32f479", + ))] + mod pi0 { + use crate::gpio::{gpioi::PI0, AF5}; + use crate::pac::SPI2; + pin_ws! { SPI2 => PI0, } + } +} + +// All STM32F4 models use the same bits in APB1ENR, APB2ENR, APB1RSTR, and APB2RSTR to enable +// and reset the SPI peripherals. +// SPI1: APB2 bit 12 +// SPI2: APB1 bit 14 +// SPI3: APB1 bit 15 +// SPI4: APB2 bit 13 +// SPI5: APB2 bit 20 + +/// Implements Instance for I2s<$SPIX, _> and creates an I2s::$spix function to create and enable +/// the peripheral +/// +/// $SPIX: The fully-capitalized name of the SPI peripheral (example: SPI1) +/// $i2sx: The lowercase I2S name of the peripheral (example: i2s1). This is the name of the +/// function that creates an I2s and enables the peripheral clock. +/// $clock: The name of the Clocks function that returns the frequency of the I2S clock input +/// to this SPI peripheral (i2s_cl, i2s_apb1_clk, or i2s2_apb_clk) +/// $apbxenr: The lowercase name of the RCC peripheral enable register (apb1enr or apb2enr) +/// $apbxrstr: The lowercase name of the RCC peripheral reset register (apb1rstr or apb2rstr) +/// $rcc_bit: The index (starting at 0) in $apbxenr and $apbxrstr of the enable and reset bits +/// for this SPI peripheral +macro_rules! i2s { + ($SPIX:ty, $i2sx:ident, $clock:ident, $apbxenr:ident, $apbxrstr:ident, $rcc_bit:expr) => { + impl I2s<$SPIX, PINS> + where + PINS: Pins<$SPIX>, + { + /// Creates an I2s object around an SPI peripheral and pins + /// + /// This function enables and resets the SPI peripheral, but does not configure it. + /// + /// The returned I2s object implements [stm32_i2s_v12x::Instance], so it can be used + /// to configure the peripheral and communicate. + /// + /// # Panics + /// + /// This function panics if the I2S clock input (from the I2S PLL or similar) + /// is not configured. + pub fn $i2sx(spi: $SPIX, pins: PINS, clocks: Clocks) -> Self { + let input_clock = clocks + .$clock() + .expect("I2S clock input for SPI not enabled"); + unsafe { + // NOTE(unsafe) this reference will only be used for atomic writes with no side effects. + let rcc = &(*RCC::ptr()); + // Enable clock, enable reset, clear, reset + bb::set(&rcc.$apbxenr, $rcc_bit); + + // Stall the pipeline to work around erratum 2.1.13 (DM00037591) + cortex_m::asm::dsb(); + + bb::set(&rcc.$apbxrstr, $rcc_bit); + bb::clear(&rcc.$apbxrstr, $rcc_bit); + } + I2s { + _spi: spi, + _pins: pins, + input_clock, + } + } + } + impl PinMck<$SPIX> for NoMasterClock {} + unsafe impl Instance for I2s<$SPIX, PINS> + where + PINS: Pins<$SPIX>, + { + const REGISTERS: *mut RegisterBlock = <$SPIX>::ptr() as *mut _; + } + }; +} + +// Actually define the SPI instances that can be used for I2S +// Each one has to be split into two declarations because the F412, F413, F423, and F446 +// have two different I2S clocks while other models have only one. + +#[cfg(any(feature = "stm32f410", feature = "stm32f411"))] +i2s!(crate::pac::SPI1, i2s1, i2s_clk, apb2enr, apb2rstr, 12); +#[cfg(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", +))] +i2s!(crate::pac::SPI1, i2s1, i2s_apb2_clk, apb2enr, apb2rstr, 12); + +// All STM32F4 models support SPI2/I2S2 +#[cfg(not(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", +)))] +i2s!(crate::pac::SPI2, i2s2, i2s_clk, apb1enr, apb1rstr, 14); +#[cfg(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", +))] +i2s!(crate::pac::SPI2, i2s2, i2s_apb1_clk, apb1enr, apb1rstr, 14); + +// All STM32F4 models except STM32F410 support SPI3/I2S3 +#[cfg(any( + feature = "stm32f401", + feature = "stm32f405", + feature = "stm32f407", + feature = "stm32f411", + feature = "stm32f415", + feature = "stm32f417", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f469", + feature = "stm32f479", +))] +i2s!(crate::pac::SPI3, i2s3, i2s_clk, apb1enr, apb1rstr, 15); +#[cfg(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", +))] +i2s!(crate::pac::SPI3, i2s3, i2s_apb1_clk, apb1enr, apb1rstr, 15); + +#[cfg(feature = "stm32f411")] +i2s!(crate::pac::SPI4, i2s4, i2s_clk, apb2enr, apb2rstr, 13); +#[cfg(any(feature = "stm32f412", feature = "stm32f413", feature = "stm32f423"))] +i2s!(crate::pac::SPI4, i2s4, i2s_apb2_clk, apb2enr, apb2rstr, 13); + +#[cfg(any(feature = "stm32f410", feature = "stm32f411"))] +i2s!(crate::pac::SPI5, i2s5, i2s_clk, apb2enr, apb2rstr, 20); +#[cfg(any(feature = "stm32f412", feature = "stm32f413", feature = "stm32f423"))] +i2s!(crate::pac::SPI5, i2s5, i2s_apb2_clk, apb2enr, apb2rstr, 20); + +/// An I2s wrapper around an SPI object and pins +pub struct I2s { + _spi: I, + _pins: PINS, + /// Frequency of clock input to this peripheral from the I2S PLL or related source + input_clock: Hertz, +} + +impl I2s +where + PINS: Pins, +{ + /// Returns the frequency of the clock signal that the SPI peripheral is receiving from the + /// I2S PLL or similar source + pub fn input_clock(&self) -> Hertz { + self.input_clock + } +} + +// DMA support: reuse existing mappings for SPI +mod dma { + use super::*; + use crate::dma::traits::{DMASet, PeriAddress}; + use core::ops::Deref; + + /// I2S DMA reads from and writes to the data register + unsafe impl PeriAddress for stm32_i2s_v12x::I2s, MODE> + where + I2s: Instance, + PINS: Pins, + SPI: Deref, + { + /// SPI_DR is only 16 bits. Multiple transfers are needed for a 24-bit or 32-bit sample, + /// as explained in the reference manual. + type MemSize = u16; + + fn address(&self) -> u32 { + let registers = &*self.instance()._spi; + ®isters.dr as *const _ as u32 + } + } + + /// DMA is available for I2S based on the underlying implementations for SPI + unsafe impl DMASet + for stm32_i2s_v12x::I2s, MODE> + where + SPI: DMASet, + PINS: Pins, + { + } +} diff --git a/src/lib.rs b/src/lib.rs index 6a9888e4..f488f63f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -120,6 +120,8 @@ pub mod delay; pub mod gpio; #[cfg(feature = "device-selected")] pub mod i2c; +#[cfg(all(feature = "device-selected", feature = "i2s"))] +pub mod i2s; #[cfg(all( feature = "usb_fs", any( diff --git a/tools/check.py b/tools/check.py index 49184f2a..a5ab1a81 100755 --- a/tools/check.py +++ b/tools/check.py @@ -28,7 +28,7 @@ def main(): crate_info = cargo_meta["packages"][0] - features = ["{},rt,usb_fs".format(x) + features = ["{},rt,usb_fs,can,i2s".format(x) for x in crate_info["features"].keys() if x.startswith("stm32f4")]