diff options
| author | xoviat <[email protected]> | 2023-05-03 18:17:57 -0500 |
|---|---|---|
| committer | xoviat <[email protected]> | 2023-05-03 18:17:57 -0500 |
| commit | 02d6e0d14dec98c01a2b327b0050e80845922510 (patch) | |
| tree | 002ec296b3e5a15dcf39ed9005b0d666b1c268cf | |
| parent | 374c92a4f0fda2932a0a86e5dcc3dc33651a48c7 (diff) | |
stm32/i2s: add module and example for f4
| -rw-r--r-- | embassy-stm32/build.rs | 4 | ||||
| -rw-r--r-- | embassy-stm32/src/i2s.rs | 310 | ||||
| -rw-r--r-- | embassy-stm32/src/lib.rs | 2 | ||||
| -rw-r--r-- | embassy-stm32/src/spi/mod.rs | 15 | ||||
| -rw-r--r-- | examples/stm32f4/src/bin/i2s_dma.rs | 36 |
5 files changed, 367 insertions, 0 deletions
diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index a00c6c416..dcee535b5 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs | |||
| @@ -420,6 +420,10 @@ fn main() { | |||
| 420 | (("spi", "SCK"), quote!(crate::spi::SckPin)), | 420 | (("spi", "SCK"), quote!(crate::spi::SckPin)), |
| 421 | (("spi", "MOSI"), quote!(crate::spi::MosiPin)), | 421 | (("spi", "MOSI"), quote!(crate::spi::MosiPin)), |
| 422 | (("spi", "MISO"), quote!(crate::spi::MisoPin)), | 422 | (("spi", "MISO"), quote!(crate::spi::MisoPin)), |
| 423 | (("spi", "NSS"), quote!(crate::spi::CsPin)), | ||
| 424 | (("spi", "I2S_MCK"), quote!(crate::spi::MckPin)), | ||
| 425 | (("spi", "I2S_CK"), quote!(crate::spi::CkPin)), | ||
| 426 | (("spi", "I2S_WS"), quote!(crate::spi::WsPin)), | ||
| 423 | (("i2c", "SDA"), quote!(crate::i2c::SdaPin)), | 427 | (("i2c", "SDA"), quote!(crate::i2c::SdaPin)), |
| 424 | (("i2c", "SCL"), quote!(crate::i2c::SclPin)), | 428 | (("i2c", "SCL"), quote!(crate::i2c::SclPin)), |
| 425 | (("rcc", "MCO_1"), quote!(crate::rcc::McoPin)), | 429 | (("rcc", "MCO_1"), quote!(crate::rcc::McoPin)), |
diff --git a/embassy-stm32/src/i2s.rs b/embassy-stm32/src/i2s.rs new file mode 100644 index 000000000..2bb199f68 --- /dev/null +++ b/embassy-stm32/src/i2s.rs | |||
| @@ -0,0 +1,310 @@ | |||
| 1 | use embassy_hal_common::into_ref; | ||
| 2 | |||
| 3 | use crate::gpio::sealed::{AFType, Pin as _}; | ||
| 4 | use crate::gpio::AnyPin; | ||
| 5 | use crate::pac::spi::vals; | ||
| 6 | use crate::rcc::get_freqs; | ||
| 7 | use crate::spi::{Config as SpiConfig, *}; | ||
| 8 | use crate::time::Hertz; | ||
| 9 | use crate::{Peripheral, PeripheralRef}; | ||
| 10 | |||
| 11 | #[derive(Copy, Clone)] | ||
| 12 | pub enum Mode { | ||
| 13 | Master, | ||
| 14 | Slave, | ||
| 15 | } | ||
| 16 | |||
| 17 | #[derive(Copy, Clone)] | ||
| 18 | pub enum Function { | ||
| 19 | Transmit, | ||
| 20 | Receive, | ||
| 21 | } | ||
| 22 | |||
| 23 | #[derive(Copy, Clone)] | ||
| 24 | pub enum Standard { | ||
| 25 | Philips, | ||
| 26 | MsbFirst, | ||
| 27 | LsbFirst, | ||
| 28 | PcmLongSync, | ||
| 29 | PcmShortSync, | ||
| 30 | } | ||
| 31 | |||
| 32 | impl Standard { | ||
| 33 | #[cfg(any(spi_v1, spi_f1))] | ||
| 34 | pub const fn i2sstd(&self) -> vals::I2sstd { | ||
| 35 | match self { | ||
| 36 | Standard::Philips => vals::I2sstd::PHILIPS, | ||
| 37 | Standard::MsbFirst => vals::I2sstd::MSB, | ||
| 38 | Standard::LsbFirst => vals::I2sstd::LSB, | ||
| 39 | Standard::PcmLongSync => vals::I2sstd::PCM, | ||
| 40 | Standard::PcmShortSync => vals::I2sstd::PCM, | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | #[cfg(any(spi_v1, spi_f1))] | ||
| 45 | pub const fn pcmsync(&self) -> vals::Pcmsync { | ||
| 46 | match self { | ||
| 47 | Standard::PcmLongSync => vals::Pcmsync::LONG, | ||
| 48 | _ => vals::Pcmsync::SHORT, | ||
| 49 | } | ||
| 50 | } | ||
| 51 | } | ||
| 52 | |||
| 53 | #[derive(Copy, Clone)] | ||
| 54 | pub enum Format { | ||
| 55 | /// 16 bit data length on 16 bit wide channel | ||
| 56 | Data16Channel16, | ||
| 57 | /// 16 bit data length on 32 bit wide channel | ||
| 58 | Data16Channel32, | ||
| 59 | /// 24 bit data length on 32 bit wide channel | ||
| 60 | Data24Channel32, | ||
| 61 | /// 32 bit data length on 32 bit wide channel | ||
| 62 | Data32Channel32, | ||
| 63 | } | ||
| 64 | |||
| 65 | impl Format { | ||
| 66 | #[cfg(any(spi_v1, spi_f1))] | ||
| 67 | pub const fn datlen(&self) -> vals::Datlen { | ||
| 68 | match self { | ||
| 69 | Format::Data16Channel16 => vals::Datlen::SIXTEENBIT, | ||
| 70 | Format::Data16Channel32 => vals::Datlen::SIXTEENBIT, | ||
| 71 | Format::Data24Channel32 => vals::Datlen::TWENTYFOURBIT, | ||
| 72 | Format::Data32Channel32 => vals::Datlen::THIRTYTWOBIT, | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | #[cfg(any(spi_v1, spi_f1))] | ||
| 77 | pub const fn chlen(&self) -> vals::Chlen { | ||
| 78 | match self { | ||
| 79 | Format::Data16Channel16 => vals::Chlen::SIXTEENBIT, | ||
| 80 | Format::Data16Channel32 => vals::Chlen::THIRTYTWOBIT, | ||
| 81 | Format::Data24Channel32 => vals::Chlen::THIRTYTWOBIT, | ||
| 82 | Format::Data32Channel32 => vals::Chlen::THIRTYTWOBIT, | ||
| 83 | } | ||
| 84 | } | ||
| 85 | } | ||
| 86 | |||
| 87 | #[derive(Copy, Clone)] | ||
| 88 | pub enum ClockPolarity { | ||
| 89 | IdleLow, | ||
| 90 | IdleHigh, | ||
| 91 | } | ||
| 92 | |||
| 93 | impl ClockPolarity { | ||
| 94 | #[cfg(any(spi_v1, spi_f1))] | ||
| 95 | pub const fn ckpol(&self) -> vals::Ckpol { | ||
| 96 | match self { | ||
| 97 | ClockPolarity::IdleHigh => vals::Ckpol::IDLEHIGH, | ||
| 98 | ClockPolarity::IdleLow => vals::Ckpol::IDLELOW, | ||
| 99 | } | ||
| 100 | } | ||
| 101 | } | ||
| 102 | |||
| 103 | /// [`I2S`] configuration. | ||
| 104 | /// | ||
| 105 | /// - `MS`: `Master` or `Slave` | ||
| 106 | /// - `TR`: `Transmit` or `Receive` | ||
| 107 | /// - `STD`: I2S standard, eg `Philips` | ||
| 108 | /// - `FMT`: Frame Format marker, eg `Data16Channel16` | ||
| 109 | #[non_exhaustive] | ||
| 110 | #[derive(Copy, Clone)] | ||
| 111 | pub struct Config { | ||
| 112 | pub mode: Mode, | ||
| 113 | pub function: Function, | ||
| 114 | pub standard: Standard, | ||
| 115 | pub format: Format, | ||
| 116 | pub clock_polarity: ClockPolarity, | ||
| 117 | pub master_clock: bool, | ||
| 118 | } | ||
| 119 | |||
| 120 | impl Default for Config { | ||
| 121 | fn default() -> Self { | ||
| 122 | Self { | ||
| 123 | mode: Mode::Master, | ||
| 124 | function: Function::Transmit, | ||
| 125 | standard: Standard::Philips, | ||
| 126 | format: Format::Data16Channel16, | ||
| 127 | clock_polarity: ClockPolarity::IdleLow, | ||
| 128 | master_clock: true, | ||
| 129 | } | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 133 | pub struct I2S<'d, T: Instance, Tx, Rx> { | ||
| 134 | _peri: Spi<'d, T, Tx, Rx>, | ||
| 135 | sd: Option<PeripheralRef<'d, AnyPin>>, | ||
| 136 | ws: Option<PeripheralRef<'d, AnyPin>>, | ||
| 137 | ck: Option<PeripheralRef<'d, AnyPin>>, | ||
| 138 | mck: Option<PeripheralRef<'d, AnyPin>>, | ||
| 139 | } | ||
| 140 | |||
| 141 | impl<'d, T: Instance, Tx, Rx> I2S<'d, T, Tx, Rx> { | ||
| 142 | /// Note: Full-Duplex modes are not supported at this time | ||
| 143 | pub fn new( | ||
| 144 | peri: impl Peripheral<P = T> + 'd, | ||
| 145 | sd: impl Peripheral<P = impl MosiPin<T>> + 'd, | ||
| 146 | ws: impl Peripheral<P = impl WsPin<T>> + 'd, | ||
| 147 | ck: impl Peripheral<P = impl CkPin<T>> + 'd, | ||
| 148 | mck: impl Peripheral<P = impl MckPin<T>> + 'd, | ||
| 149 | txdma: impl Peripheral<P = Tx> + 'd, | ||
| 150 | rxdma: impl Peripheral<P = Rx> + 'd, | ||
| 151 | freq: Hertz, | ||
| 152 | config: Config, | ||
| 153 | ) -> Self { | ||
| 154 | into_ref!(sd, ws, ck, mck); | ||
| 155 | |||
| 156 | unsafe { | ||
| 157 | sd.set_as_af(sd.af_num(), AFType::OutputPushPull); | ||
| 158 | sd.set_speed(crate::gpio::Speed::VeryHigh); | ||
| 159 | |||
| 160 | ws.set_as_af(ws.af_num(), AFType::OutputPushPull); | ||
| 161 | ws.set_speed(crate::gpio::Speed::VeryHigh); | ||
| 162 | |||
| 163 | ck.set_as_af(ck.af_num(), AFType::OutputPushPull); | ||
| 164 | ck.set_speed(crate::gpio::Speed::VeryHigh); | ||
| 165 | |||
| 166 | mck.set_as_af(mck.af_num(), AFType::OutputPushPull); | ||
| 167 | mck.set_speed(crate::gpio::Speed::VeryHigh); | ||
| 168 | } | ||
| 169 | |||
| 170 | let spi = Spi::new_internal(peri, txdma, rxdma, freq, SpiConfig::default()); | ||
| 171 | |||
| 172 | #[cfg(all(rcc_f4, not(stm32f410)))] | ||
| 173 | let pclk = unsafe { get_freqs() }.plli2s.unwrap(); | ||
| 174 | |||
| 175 | #[cfg(stm32f410)] | ||
| 176 | let pclk = T::frequency(); | ||
| 177 | |||
| 178 | let (odd, div) = compute_baud_rate(pclk, freq, config.master_clock, config.format); | ||
| 179 | |||
| 180 | #[cfg(any(spi_v1, spi_f1))] | ||
| 181 | unsafe { | ||
| 182 | use stm32_metapac::spi::vals::{I2scfg, Odd}; | ||
| 183 | |||
| 184 | // 1. Select the I2SDIV[7:0] bits in the SPI_I2SPR register to define the serial clock baud | ||
| 185 | // rate to reach the proper audio sample frequency. The ODD bit in the SPI_I2SPR | ||
| 186 | // register also has to be defined. | ||
| 187 | |||
| 188 | T::REGS.i2spr().modify(|w| { | ||
| 189 | w.set_i2sdiv(div); | ||
| 190 | w.set_odd(match odd { | ||
| 191 | true => Odd::ODD, | ||
| 192 | false => Odd::EVEN, | ||
| 193 | }); | ||
| 194 | |||
| 195 | w.set_mckoe(config.master_clock); | ||
| 196 | }); | ||
| 197 | |||
| 198 | // 2. Select the CKPOL bit to define the steady level for the communication clock. Set the | ||
| 199 | // MCKOE bit in the SPI_I2SPR register if the master clock MCK needs to be provided to | ||
| 200 | // the external DAC/ADC audio component (the I2SDIV and ODD values should be | ||
| 201 | // computed depending on the state of the MCK output, for more details refer to | ||
| 202 | // Section 28.4.4: Clock generator). | ||
| 203 | |||
| 204 | // 3. Set the I2SMOD bit in SPI_I2SCFGR to activate the I2S functionalities and choose the | ||
| 205 | // I2S standard through the I2SSTD[1:0] and PCMSYNC bits, the data length through the | ||
| 206 | // DATLEN[1:0] bits and the number of bits per channel by configuring the CHLEN bit. | ||
| 207 | // Select also the I2S master mode and direction (Transmitter or Receiver) through the | ||
| 208 | // I2SCFG[1:0] bits in the SPI_I2SCFGR register. | ||
| 209 | |||
| 210 | // 4. If needed, select all the potential interruption sources and the DMA capabilities by | ||
| 211 | // writing the SPI_CR2 register. | ||
| 212 | |||
| 213 | // 5. The I2SE bit in SPI_I2SCFGR register must be set. | ||
| 214 | |||
| 215 | T::REGS.i2scfgr().modify(|w| { | ||
| 216 | w.set_ckpol(config.clock_polarity.ckpol()); | ||
| 217 | |||
| 218 | w.set_i2smod(true); | ||
| 219 | w.set_i2sstd(config.standard.i2sstd()); | ||
| 220 | w.set_pcmsync(config.standard.pcmsync()); | ||
| 221 | |||
| 222 | w.set_datlen(config.format.datlen()); | ||
| 223 | w.set_chlen(config.format.chlen()); | ||
| 224 | |||
| 225 | w.set_i2scfg(match (config.mode, config.function) { | ||
| 226 | (Mode::Master, Function::Transmit) => I2scfg::MASTERTX, | ||
| 227 | (Mode::Master, Function::Receive) => I2scfg::MASTERRX, | ||
| 228 | (Mode::Slave, Function::Transmit) => I2scfg::SLAVETX, | ||
| 229 | (Mode::Slave, Function::Receive) => I2scfg::SLAVERX, | ||
| 230 | }); | ||
| 231 | |||
| 232 | w.set_i2se(true) | ||
| 233 | }); | ||
| 234 | } | ||
| 235 | #[cfg(spi_v2)] | ||
| 236 | unsafe {} | ||
| 237 | #[cfg(any(spi_v3, spi_v4))] | ||
| 238 | unsafe {} | ||
| 239 | |||
| 240 | Self { | ||
| 241 | _peri: spi, | ||
| 242 | sd: Some(sd.map_into()), | ||
| 243 | ws: Some(ws.map_into()), | ||
| 244 | ck: Some(ck.map_into()), | ||
| 245 | mck: Some(mck.map_into()), | ||
| 246 | } | ||
| 247 | } | ||
| 248 | |||
| 249 | pub async fn write<W: Word>(&mut self, data: &[W]) -> Result<(), Error> | ||
| 250 | where | ||
| 251 | Tx: TxDma<T>, | ||
| 252 | { | ||
| 253 | self._peri.write(data).await | ||
| 254 | } | ||
| 255 | |||
| 256 | pub async fn read<W: Word>(&mut self, data: &mut [W]) -> Result<(), Error> | ||
| 257 | where | ||
| 258 | Tx: TxDma<T>, | ||
| 259 | Rx: RxDma<T>, | ||
| 260 | { | ||
| 261 | self._peri.read(data).await | ||
| 262 | } | ||
| 263 | } | ||
| 264 | |||
| 265 | impl<'d, T: Instance, Tx, Rx> Drop for I2S<'d, T, Tx, Rx> { | ||
| 266 | fn drop(&mut self) { | ||
| 267 | unsafe { | ||
| 268 | self.sd.as_ref().map(|x| x.set_as_disconnected()); | ||
| 269 | self.ws.as_ref().map(|x| x.set_as_disconnected()); | ||
| 270 | self.ck.as_ref().map(|x| x.set_as_disconnected()); | ||
| 271 | self.mck.as_ref().map(|x| x.set_as_disconnected()); | ||
| 272 | } | ||
| 273 | } | ||
| 274 | } | ||
| 275 | |||
| 276 | // Note, calculation details: | ||
| 277 | // Fs = i2s_clock / [256 * ((2 * div) + odd)] when master clock is enabled | ||
| 278 | // Fs = i2s_clock / [(channel_length * 2) * ((2 * div) + odd)]` when master clock is disabled | ||
| 279 | // channel_length is 16 or 32 | ||
| 280 | // | ||
| 281 | // can be rewritten as | ||
| 282 | // Fs = i2s_clock / (coef * division) | ||
| 283 | // where coef is a constant equal to 256, 64 or 32 depending channel length and master clock | ||
| 284 | // and where division = (2 * div) + odd | ||
| 285 | // | ||
| 286 | // Equation can be rewritten as | ||
| 287 | // division = i2s_clock/ (coef * Fs) | ||
| 288 | // | ||
| 289 | // note: division = (2 * div) + odd = (div << 1) + odd | ||
| 290 | // in other word, from bits point of view, division[8:1] = div[7:0] and division[0] = odd | ||
| 291 | fn compute_baud_rate(i2s_clock: Hertz, request_freq: Hertz, mclk: bool, data_format: Format) -> (bool, u8) { | ||
| 292 | let coef = if mclk { | ||
| 293 | 256 | ||
| 294 | } else if let Format::Data16Channel16 = data_format { | ||
| 295 | 32 | ||
| 296 | } else { | ||
| 297 | 64 | ||
| 298 | }; | ||
| 299 | |||
| 300 | let (n, d) = (i2s_clock.0, coef * request_freq.0); | ||
| 301 | let division = (n + (d >> 1)) / d; | ||
| 302 | |||
| 303 | if division < 4 { | ||
| 304 | (false, 2) | ||
| 305 | } else if division > 511 { | ||
| 306 | (true, 255) | ||
| 307 | } else { | ||
| 308 | ((division & 1) == 1, (division >> 1) as u8) | ||
| 309 | } | ||
| 310 | } | ||
diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 11820b7a0..7c83a6984 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs | |||
| @@ -44,6 +44,8 @@ pub mod i2c; | |||
| 44 | #[cfg(crc)] | 44 | #[cfg(crc)] |
| 45 | pub mod crc; | 45 | pub mod crc; |
| 46 | pub mod flash; | 46 | pub mod flash; |
| 47 | #[cfg(all(spi_v1, rcc_f4))] | ||
| 48 | pub mod i2s; | ||
| 47 | #[cfg(stm32wb)] | 49 | #[cfg(stm32wb)] |
| 48 | pub mod ipcc; | 50 | pub mod ipcc; |
| 49 | pub mod pwm; | 51 | pub mod pwm; |
diff --git a/embassy-stm32/src/spi/mod.rs b/embassy-stm32/src/spi/mod.rs index aefa42435..22ab423b6 100644 --- a/embassy-stm32/src/spi/mod.rs +++ b/embassy-stm32/src/spi/mod.rs | |||
| @@ -207,6 +207,17 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { | |||
| 207 | Self::new_inner(peri, None, None, None, txdma, rxdma, freq, config) | 207 | Self::new_inner(peri, None, None, None, txdma, rxdma, freq, config) |
| 208 | } | 208 | } |
| 209 | 209 | ||
| 210 | #[allow(dead_code)] | ||
| 211 | pub(crate) fn new_internal( | ||
| 212 | peri: impl Peripheral<P = T> + 'd, | ||
| 213 | txdma: impl Peripheral<P = Tx> + 'd, | ||
| 214 | rxdma: impl Peripheral<P = Rx> + 'd, | ||
| 215 | freq: Hertz, | ||
| 216 | config: Config, | ||
| 217 | ) -> Self { | ||
| 218 | Self::new_inner(peri, None, None, None, txdma, rxdma, freq, config) | ||
| 219 | } | ||
| 220 | |||
| 210 | fn new_inner( | 221 | fn new_inner( |
| 211 | peri: impl Peripheral<P = T> + 'd, | 222 | peri: impl Peripheral<P = T> + 'd, |
| 212 | sck: Option<PeripheralRef<'d, AnyPin>>, | 223 | sck: Option<PeripheralRef<'d, AnyPin>>, |
| @@ -1039,6 +1050,10 @@ pub trait Instance: Peripheral<P = Self> + sealed::Instance + RccPeripheral {} | |||
| 1039 | pin_trait!(SckPin, Instance); | 1050 | pin_trait!(SckPin, Instance); |
| 1040 | pin_trait!(MosiPin, Instance); | 1051 | pin_trait!(MosiPin, Instance); |
| 1041 | pin_trait!(MisoPin, Instance); | 1052 | pin_trait!(MisoPin, Instance); |
| 1053 | pin_trait!(CsPin, Instance); | ||
| 1054 | pin_trait!(MckPin, Instance); | ||
| 1055 | pin_trait!(CkPin, Instance); | ||
| 1056 | pin_trait!(WsPin, Instance); | ||
| 1042 | dma_trait!(RxDma, Instance); | 1057 | dma_trait!(RxDma, Instance); |
| 1043 | dma_trait!(TxDma, Instance); | 1058 | dma_trait!(TxDma, Instance); |
| 1044 | 1059 | ||
diff --git a/examples/stm32f4/src/bin/i2s_dma.rs b/examples/stm32f4/src/bin/i2s_dma.rs new file mode 100644 index 000000000..e8d7b5f77 --- /dev/null +++ b/examples/stm32f4/src/bin/i2s_dma.rs | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use core::fmt::Write; | ||
| 6 | |||
| 7 | use defmt::*; | ||
| 8 | use embassy_executor::Spawner; | ||
| 9 | use embassy_stm32::i2s::{Config, I2S}; | ||
| 10 | use embassy_stm32::time::Hertz; | ||
| 11 | use heapless::String; | ||
| 12 | use {defmt_rtt as _, panic_probe as _}; | ||
| 13 | |||
| 14 | #[embassy_executor::main] | ||
| 15 | async fn main(_spawner: Spawner) { | ||
| 16 | let p = embassy_stm32::init(Default::default()); | ||
| 17 | info!("Hello World!"); | ||
| 18 | |||
| 19 | let mut i2s = I2S::new( | ||
| 20 | p.SPI2, | ||
| 21 | p.PC3, // sd | ||
| 22 | p.PB12, // ws | ||
| 23 | p.PB10, // ck | ||
| 24 | p.PC6, // mck | ||
| 25 | p.DMA1_CH4, | ||
| 26 | p.DMA1_CH3, | ||
| 27 | Hertz(1_000_000), | ||
| 28 | Config::default(), | ||
| 29 | ); | ||
| 30 | |||
| 31 | for n in 0u32.. { | ||
| 32 | let mut write: String<128> = String::new(); | ||
| 33 | core::write!(&mut write, "Hello DMA World {}!\r\n", n).unwrap(); | ||
| 34 | i2s.write(&mut write.as_bytes()).await.ok(); | ||
| 35 | } | ||
| 36 | } | ||
