aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--embassy-stm32/build.rs4
-rw-r--r--embassy-stm32/src/i2s.rs310
-rw-r--r--embassy-stm32/src/lib.rs2
-rw-r--r--embassy-stm32/src/spi/mod.rs15
-rw-r--r--embassy-stm32/src/wdg/mod.rs40
-rw-r--r--examples/stm32f4/src/bin/i2s_dma.rs36
6 files changed, 403 insertions, 4 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 @@
1use embassy_hal_common::into_ref;
2
3use crate::gpio::sealed::{AFType, Pin as _};
4use crate::gpio::AnyPin;
5use crate::pac::spi::vals;
6use crate::rcc::get_freqs;
7use crate::spi::{Config as SpiConfig, *};
8use crate::time::Hertz;
9use crate::{Peripheral, PeripheralRef};
10
11#[derive(Copy, Clone)]
12pub enum Mode {
13 Master,
14 Slave,
15}
16
17#[derive(Copy, Clone)]
18pub enum Function {
19 Transmit,
20 Receive,
21}
22
23#[derive(Copy, Clone)]
24pub enum Standard {
25 Philips,
26 MsbFirst,
27 LsbFirst,
28 PcmLongSync,
29 PcmShortSync,
30}
31
32impl 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)]
54pub 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
65impl 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)]
88pub enum ClockPolarity {
89 IdleLow,
90 IdleHigh,
91}
92
93impl 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)]
111pub 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
120impl 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
133pub 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
141impl<'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
265impl<'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
291fn 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 6722658c9..c9df5c1b2 100644
--- a/embassy-stm32/src/lib.rs
+++ b/embassy-stm32/src/lib.rs
@@ -39,6 +39,8 @@ pub mod i2c;
39#[cfg(crc)] 39#[cfg(crc)]
40pub mod crc; 40pub mod crc;
41pub mod flash; 41pub mod flash;
42#[cfg(all(spi_v1, rcc_f4))]
43pub mod i2s;
42#[cfg(stm32wb)] 44#[cfg(stm32wb)]
43pub mod ipcc; 45pub mod ipcc;
44pub mod pwm; 46pub mod pwm;
diff --git a/embassy-stm32/src/spi/mod.rs b/embassy-stm32/src/spi/mod.rs
index 7390830dc..580971e45 100644
--- a/embassy-stm32/src/spi/mod.rs
+++ b/embassy-stm32/src/spi/mod.rs
@@ -212,6 +212,17 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
212 Self::new_inner(peri, None, None, None, txdma, rxdma, freq, config) 212 Self::new_inner(peri, None, None, None, txdma, rxdma, freq, config)
213 } 213 }
214 214
215 #[allow(dead_code)]
216 pub(crate) fn new_internal(
217 peri: impl Peripheral<P = T> + 'd,
218 txdma: impl Peripheral<P = Tx> + 'd,
219 rxdma: impl Peripheral<P = Rx> + 'd,
220 freq: Hertz,
221 config: Config,
222 ) -> Self {
223 Self::new_inner(peri, None, None, None, txdma, rxdma, freq, config)
224 }
225
215 fn new_inner( 226 fn new_inner(
216 peri: impl Peripheral<P = T> + 'd, 227 peri: impl Peripheral<P = T> + 'd,
217 sck: Option<PeripheralRef<'d, AnyPin>>, 228 sck: Option<PeripheralRef<'d, AnyPin>>,
@@ -1044,6 +1055,10 @@ pub trait Instance: Peripheral<P = Self> + sealed::Instance + RccPeripheral {}
1044pin_trait!(SckPin, Instance); 1055pin_trait!(SckPin, Instance);
1045pin_trait!(MosiPin, Instance); 1056pin_trait!(MosiPin, Instance);
1046pin_trait!(MisoPin, Instance); 1057pin_trait!(MisoPin, Instance);
1058pin_trait!(CsPin, Instance);
1059pin_trait!(MckPin, Instance);
1060pin_trait!(CkPin, Instance);
1061pin_trait!(WsPin, Instance);
1047dma_trait!(RxDma, Instance); 1062dma_trait!(RxDma, Instance);
1048dma_trait!(TxDma, Instance); 1063dma_trait!(TxDma, Instance);
1049 1064
diff --git a/embassy-stm32/src/wdg/mod.rs b/embassy-stm32/src/wdg/mod.rs
index 92b9a5ca8..18ebf97d8 100644
--- a/embassy-stm32/src/wdg/mod.rs
+++ b/embassy-stm32/src/wdg/mod.rs
@@ -13,13 +13,13 @@ pub struct IndependentWatchdog<'d, T: Instance> {
13const MAX_RL: u16 = 0xFFF; 13const MAX_RL: u16 = 0xFFF;
14 14
15/// Calculates maximum watchdog timeout in us (RL = 0xFFF) for a given prescaler 15/// Calculates maximum watchdog timeout in us (RL = 0xFFF) for a given prescaler
16const fn max_timeout(prescaler: u16) -> u32 { 16const fn get_timeout_us(prescaler: u16, reload_value: u16) -> u32 {
17 1_000_000 * MAX_RL as u32 / (LSI_FREQ.0 / prescaler as u32) 17 1_000_000 * (reload_value + 1) as u32 / (LSI_FREQ.0 / prescaler as u32)
18} 18}
19 19
20/// Calculates watchdog reload value for the given prescaler and desired timeout 20/// Calculates watchdog reload value for the given prescaler and desired timeout
21const fn reload_value(prescaler: u16, timeout_us: u32) -> u16 { 21const fn reload_value(prescaler: u16, timeout_us: u32) -> u16 {
22 (timeout_us / prescaler as u32 * LSI_FREQ.0 / 1_000_000) as u16 22 (timeout_us / prescaler as u32 * LSI_FREQ.0 / 1_000_000) as u16 - 1
23} 23}
24 24
25impl<'d, T: Instance> IndependentWatchdog<'d, T> { 25impl<'d, T: Instance> IndependentWatchdog<'d, T> {
@@ -34,7 +34,7 @@ impl<'d, T: Instance> IndependentWatchdog<'d, T> {
34 // This iterates from 4 (2^2) to 256 (2^8). 34 // This iterates from 4 (2^2) to 256 (2^8).
35 let psc_power = unwrap!((2..=8).find(|psc_power| { 35 let psc_power = unwrap!((2..=8).find(|psc_power| {
36 let psc = 2u16.pow(*psc_power); 36 let psc = 2u16.pow(*psc_power);
37 timeout_us <= max_timeout(psc) 37 timeout_us <= get_timeout_us(psc, MAX_RL)
38 })); 38 }));
39 39
40 // Prescaler value 40 // Prescaler value
@@ -54,6 +54,14 @@ impl<'d, T: Instance> IndependentWatchdog<'d, T> {
54 wdg.rlr().write(|w| w.set_rl(rl)); 54 wdg.rlr().write(|w| w.set_rl(rl));
55 } 55 }
56 56
57 trace!(
58 "Watchdog configured with {}us timeout, desired was {}us (PR={}, RL={})",
59 get_timeout_us(psc, rl),
60 timeout_us,
61 pr,
62 rl
63 );
64
57 IndependentWatchdog { 65 IndependentWatchdog {
58 wdg: PhantomData::default(), 66 wdg: PhantomData::default(),
59 } 67 }
@@ -87,3 +95,27 @@ foreach_peripheral!(
87 impl Instance for crate::peripherals::$inst {} 95 impl Instance for crate::peripherals::$inst {}
88 }; 96 };
89); 97);
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn can_compute_timeout_us() {
105 assert_eq!(125, get_timeout_us(4, 0));
106 assert_eq!(512_000, get_timeout_us(4, MAX_RL));
107
108 assert_eq!(8_000, get_timeout_us(256, 0));
109 assert_eq!(32768_000, get_timeout_us(256, MAX_RL));
110
111 assert_eq!(8000_000, get_timeout_us(64, 3999));
112 }
113
114 #[test]
115 fn can_compute_reload_value() {
116 assert_eq!(0xFFF, reload_value(4, 512_000));
117 assert_eq!(0xFFF, reload_value(256, 32768_000));
118
119 assert_eq!(3999, reload_value(64, 8000_000));
120 }
121}
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
5use core::fmt::Write;
6
7use defmt::*;
8use embassy_executor::Spawner;
9use embassy_stm32::i2s::{Config, I2S};
10use embassy_stm32::time::Hertz;
11use heapless::String;
12use {defmt_rtt as _, panic_probe as _};
13
14#[embassy_executor::main]
15async 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}