311 lines
9.5 KiB
Rust
311 lines
9.5 KiB
Rust
|
use embassy_hal_common::into_ref;
|
||
|
|
||
|
use crate::gpio::sealed::{AFType, Pin as _};
|
||
|
use crate::gpio::AnyPin;
|
||
|
use crate::pac::spi::vals;
|
||
|
use crate::rcc::get_freqs;
|
||
|
use crate::spi::{Config as SpiConfig, *};
|
||
|
use crate::time::Hertz;
|
||
|
use crate::{Peripheral, PeripheralRef};
|
||
|
|
||
|
#[derive(Copy, Clone)]
|
||
|
pub enum Mode {
|
||
|
Master,
|
||
|
Slave,
|
||
|
}
|
||
|
|
||
|
#[derive(Copy, Clone)]
|
||
|
pub enum Function {
|
||
|
Transmit,
|
||
|
Receive,
|
||
|
}
|
||
|
|
||
|
#[derive(Copy, Clone)]
|
||
|
pub enum Standard {
|
||
|
Philips,
|
||
|
MsbFirst,
|
||
|
LsbFirst,
|
||
|
PcmLongSync,
|
||
|
PcmShortSync,
|
||
|
}
|
||
|
|
||
|
impl Standard {
|
||
|
#[cfg(any(spi_v1, spi_f1))]
|
||
|
pub const fn i2sstd(&self) -> vals::I2sstd {
|
||
|
match self {
|
||
|
Standard::Philips => vals::I2sstd::PHILIPS,
|
||
|
Standard::MsbFirst => vals::I2sstd::MSB,
|
||
|
Standard::LsbFirst => vals::I2sstd::LSB,
|
||
|
Standard::PcmLongSync => vals::I2sstd::PCM,
|
||
|
Standard::PcmShortSync => vals::I2sstd::PCM,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[cfg(any(spi_v1, spi_f1))]
|
||
|
pub const fn pcmsync(&self) -> vals::Pcmsync {
|
||
|
match self {
|
||
|
Standard::PcmLongSync => vals::Pcmsync::LONG,
|
||
|
_ => vals::Pcmsync::SHORT,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[derive(Copy, Clone)]
|
||
|
pub enum Format {
|
||
|
/// 16 bit data length on 16 bit wide channel
|
||
|
Data16Channel16,
|
||
|
/// 16 bit data length on 32 bit wide channel
|
||
|
Data16Channel32,
|
||
|
/// 24 bit data length on 32 bit wide channel
|
||
|
Data24Channel32,
|
||
|
/// 32 bit data length on 32 bit wide channel
|
||
|
Data32Channel32,
|
||
|
}
|
||
|
|
||
|
impl Format {
|
||
|
#[cfg(any(spi_v1, spi_f1))]
|
||
|
pub const fn datlen(&self) -> vals::Datlen {
|
||
|
match self {
|
||
|
Format::Data16Channel16 => vals::Datlen::SIXTEENBIT,
|
||
|
Format::Data16Channel32 => vals::Datlen::SIXTEENBIT,
|
||
|
Format::Data24Channel32 => vals::Datlen::TWENTYFOURBIT,
|
||
|
Format::Data32Channel32 => vals::Datlen::THIRTYTWOBIT,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[cfg(any(spi_v1, spi_f1))]
|
||
|
pub const fn chlen(&self) -> vals::Chlen {
|
||
|
match self {
|
||
|
Format::Data16Channel16 => vals::Chlen::SIXTEENBIT,
|
||
|
Format::Data16Channel32 => vals::Chlen::THIRTYTWOBIT,
|
||
|
Format::Data24Channel32 => vals::Chlen::THIRTYTWOBIT,
|
||
|
Format::Data32Channel32 => vals::Chlen::THIRTYTWOBIT,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[derive(Copy, Clone)]
|
||
|
pub enum ClockPolarity {
|
||
|
IdleLow,
|
||
|
IdleHigh,
|
||
|
}
|
||
|
|
||
|
impl ClockPolarity {
|
||
|
#[cfg(any(spi_v1, spi_f1))]
|
||
|
pub const fn ckpol(&self) -> vals::Ckpol {
|
||
|
match self {
|
||
|
ClockPolarity::IdleHigh => vals::Ckpol::IDLEHIGH,
|
||
|
ClockPolarity::IdleLow => vals::Ckpol::IDLELOW,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// [`I2S`] configuration.
|
||
|
///
|
||
|
/// - `MS`: `Master` or `Slave`
|
||
|
/// - `TR`: `Transmit` or `Receive`
|
||
|
/// - `STD`: I2S standard, eg `Philips`
|
||
|
/// - `FMT`: Frame Format marker, eg `Data16Channel16`
|
||
|
#[non_exhaustive]
|
||
|
#[derive(Copy, Clone)]
|
||
|
pub struct Config {
|
||
|
pub mode: Mode,
|
||
|
pub function: Function,
|
||
|
pub standard: Standard,
|
||
|
pub format: Format,
|
||
|
pub clock_polarity: ClockPolarity,
|
||
|
pub master_clock: bool,
|
||
|
}
|
||
|
|
||
|
impl Default for Config {
|
||
|
fn default() -> Self {
|
||
|
Self {
|
||
|
mode: Mode::Master,
|
||
|
function: Function::Transmit,
|
||
|
standard: Standard::Philips,
|
||
|
format: Format::Data16Channel16,
|
||
|
clock_polarity: ClockPolarity::IdleLow,
|
||
|
master_clock: true,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub struct I2S<'d, T: Instance, Tx, Rx> {
|
||
|
_peri: Spi<'d, T, Tx, Rx>,
|
||
|
sd: Option<PeripheralRef<'d, AnyPin>>,
|
||
|
ws: Option<PeripheralRef<'d, AnyPin>>,
|
||
|
ck: Option<PeripheralRef<'d, AnyPin>>,
|
||
|
mck: Option<PeripheralRef<'d, AnyPin>>,
|
||
|
}
|
||
|
|
||
|
impl<'d, T: Instance, Tx, Rx> I2S<'d, T, Tx, Rx> {
|
||
|
/// Note: Full-Duplex modes are not supported at this time
|
||
|
pub fn new(
|
||
|
peri: impl Peripheral<P = T> + 'd,
|
||
|
sd: impl Peripheral<P = impl MosiPin<T>> + 'd,
|
||
|
ws: impl Peripheral<P = impl WsPin<T>> + 'd,
|
||
|
ck: impl Peripheral<P = impl CkPin<T>> + 'd,
|
||
|
mck: impl Peripheral<P = impl MckPin<T>> + 'd,
|
||
|
txdma: impl Peripheral<P = Tx> + 'd,
|
||
|
rxdma: impl Peripheral<P = Rx> + 'd,
|
||
|
freq: Hertz,
|
||
|
config: Config,
|
||
|
) -> Self {
|
||
|
into_ref!(sd, ws, ck, mck);
|
||
|
|
||
|
unsafe {
|
||
|
sd.set_as_af(sd.af_num(), AFType::OutputPushPull);
|
||
|
sd.set_speed(crate::gpio::Speed::VeryHigh);
|
||
|
|
||
|
ws.set_as_af(ws.af_num(), AFType::OutputPushPull);
|
||
|
ws.set_speed(crate::gpio::Speed::VeryHigh);
|
||
|
|
||
|
ck.set_as_af(ck.af_num(), AFType::OutputPushPull);
|
||
|
ck.set_speed(crate::gpio::Speed::VeryHigh);
|
||
|
|
||
|
mck.set_as_af(mck.af_num(), AFType::OutputPushPull);
|
||
|
mck.set_speed(crate::gpio::Speed::VeryHigh);
|
||
|
}
|
||
|
|
||
|
let spi = Spi::new_internal(peri, txdma, rxdma, freq, SpiConfig::default());
|
||
|
|
||
|
#[cfg(all(rcc_f4, not(stm32f410)))]
|
||
|
let pclk = unsafe { get_freqs() }.plli2s.unwrap();
|
||
|
|
||
|
#[cfg(stm32f410)]
|
||
|
let pclk = T::frequency();
|
||
|
|
||
|
let (odd, div) = compute_baud_rate(pclk, freq, config.master_clock, config.format);
|
||
|
|
||
|
#[cfg(any(spi_v1, spi_f1))]
|
||
|
unsafe {
|
||
|
use stm32_metapac::spi::vals::{I2scfg, Odd};
|
||
|
|
||
|
// 1. Select the I2SDIV[7:0] bits in the SPI_I2SPR register to define the serial clock baud
|
||
|
// rate to reach the proper audio sample frequency. The ODD bit in the SPI_I2SPR
|
||
|
// register also has to be defined.
|
||
|
|
||
|
T::REGS.i2spr().modify(|w| {
|
||
|
w.set_i2sdiv(div);
|
||
|
w.set_odd(match odd {
|
||
|
true => Odd::ODD,
|
||
|
false => Odd::EVEN,
|
||
|
});
|
||
|
|
||
|
w.set_mckoe(config.master_clock);
|
||
|
});
|
||
|
|
||
|
// 2. Select the CKPOL bit to define the steady level for the communication clock. Set the
|
||
|
// MCKOE bit in the SPI_I2SPR register if the master clock MCK needs to be provided to
|
||
|
// the external DAC/ADC audio component (the I2SDIV and ODD values should be
|
||
|
// computed depending on the state of the MCK output, for more details refer to
|
||
|
// Section 28.4.4: Clock generator).
|
||
|
|
||
|
// 3. Set the I2SMOD bit in SPI_I2SCFGR to activate the I2S functionalities and choose the
|
||
|
// I2S standard through the I2SSTD[1:0] and PCMSYNC bits, the data length through the
|
||
|
// DATLEN[1:0] bits and the number of bits per channel by configuring the CHLEN bit.
|
||
|
// Select also the I2S master mode and direction (Transmitter or Receiver) through the
|
||
|
// I2SCFG[1:0] bits in the SPI_I2SCFGR register.
|
||
|
|
||
|
// 4. If needed, select all the potential interruption sources and the DMA capabilities by
|
||
|
// writing the SPI_CR2 register.
|
||
|
|
||
|
// 5. The I2SE bit in SPI_I2SCFGR register must be set.
|
||
|
|
||
|
T::REGS.i2scfgr().modify(|w| {
|
||
|
w.set_ckpol(config.clock_polarity.ckpol());
|
||
|
|
||
|
w.set_i2smod(true);
|
||
|
w.set_i2sstd(config.standard.i2sstd());
|
||
|
w.set_pcmsync(config.standard.pcmsync());
|
||
|
|
||
|
w.set_datlen(config.format.datlen());
|
||
|
w.set_chlen(config.format.chlen());
|
||
|
|
||
|
w.set_i2scfg(match (config.mode, config.function) {
|
||
|
(Mode::Master, Function::Transmit) => I2scfg::MASTERTX,
|
||
|
(Mode::Master, Function::Receive) => I2scfg::MASTERRX,
|
||
|
(Mode::Slave, Function::Transmit) => I2scfg::SLAVETX,
|
||
|
(Mode::Slave, Function::Receive) => I2scfg::SLAVERX,
|
||
|
});
|
||
|
|
||
|
w.set_i2se(true)
|
||
|
});
|
||
|
}
|
||
|
#[cfg(spi_v2)]
|
||
|
unsafe {}
|
||
|
#[cfg(any(spi_v3, spi_v4))]
|
||
|
unsafe {}
|
||
|
|
||
|
Self {
|
||
|
_peri: spi,
|
||
|
sd: Some(sd.map_into()),
|
||
|
ws: Some(ws.map_into()),
|
||
|
ck: Some(ck.map_into()),
|
||
|
mck: Some(mck.map_into()),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub async fn write<W: Word>(&mut self, data: &[W]) -> Result<(), Error>
|
||
|
where
|
||
|
Tx: TxDma<T>,
|
||
|
{
|
||
|
self._peri.write(data).await
|
||
|
}
|
||
|
|
||
|
pub async fn read<W: Word>(&mut self, data: &mut [W]) -> Result<(), Error>
|
||
|
where
|
||
|
Tx: TxDma<T>,
|
||
|
Rx: RxDma<T>,
|
||
|
{
|
||
|
self._peri.read(data).await
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl<'d, T: Instance, Tx, Rx> Drop for I2S<'d, T, Tx, Rx> {
|
||
|
fn drop(&mut self) {
|
||
|
unsafe {
|
||
|
self.sd.as_ref().map(|x| x.set_as_disconnected());
|
||
|
self.ws.as_ref().map(|x| x.set_as_disconnected());
|
||
|
self.ck.as_ref().map(|x| x.set_as_disconnected());
|
||
|
self.mck.as_ref().map(|x| x.set_as_disconnected());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Note, calculation details:
|
||
|
// Fs = i2s_clock / [256 * ((2 * div) + odd)] when master clock is enabled
|
||
|
// Fs = i2s_clock / [(channel_length * 2) * ((2 * div) + odd)]` when master clock is disabled
|
||
|
// channel_length is 16 or 32
|
||
|
//
|
||
|
// can be rewritten as
|
||
|
// Fs = i2s_clock / (coef * division)
|
||
|
// where coef is a constant equal to 256, 64 or 32 depending channel length and master clock
|
||
|
// and where division = (2 * div) + odd
|
||
|
//
|
||
|
// Equation can be rewritten as
|
||
|
// division = i2s_clock/ (coef * Fs)
|
||
|
//
|
||
|
// note: division = (2 * div) + odd = (div << 1) + odd
|
||
|
// in other word, from bits point of view, division[8:1] = div[7:0] and division[0] = odd
|
||
|
fn compute_baud_rate(i2s_clock: Hertz, request_freq: Hertz, mclk: bool, data_format: Format) -> (bool, u8) {
|
||
|
let coef = if mclk {
|
||
|
256
|
||
|
} else if let Format::Data16Channel16 = data_format {
|
||
|
32
|
||
|
} else {
|
||
|
64
|
||
|
};
|
||
|
|
||
|
let (n, d) = (i2s_clock.0, coef * request_freq.0);
|
||
|
let division = (n + (d >> 1)) / d;
|
||
|
|
||
|
if division < 4 {
|
||
|
(false, 2)
|
||
|
} else if division > 511 {
|
||
|
(true, 255)
|
||
|
} else {
|
||
|
((division & 1) == 1, (division >> 1) as u8)
|
||
|
}
|
||
|
}
|