Merge pull request #2992 from liarokapisv/i2s-spi_v3
Add spi v3 & Full-duplex support to the I2S driver.
This commit is contained in:
commit
694ac3a515
3 changed files with 268 additions and 74 deletions
|
@ -1,7 +1,9 @@
|
|||
//! Inter-IC Sound (I2S)
|
||||
|
||||
use embassy_hal_internal::into_ref;
|
||||
|
||||
use crate::gpio::{AFType, AnyPin, SealedPin};
|
||||
use crate::dma::ChannelAndRequest;
|
||||
use crate::gpio::{AFType, AnyPin, SealedPin, Speed};
|
||||
use crate::mode::Async;
|
||||
use crate::pac::spi::vals;
|
||||
use crate::spi::{Config as SpiConfig, *};
|
||||
|
@ -17,15 +19,6 @@ pub enum Mode {
|
|||
Slave,
|
||||
}
|
||||
|
||||
/// I2S function
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum Function {
|
||||
/// Transmit audio data
|
||||
Transmit,
|
||||
/// Receive audio data
|
||||
Receive,
|
||||
}
|
||||
|
||||
/// I2C standard
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum Standard {
|
||||
|
@ -42,7 +35,7 @@ pub enum Standard {
|
|||
}
|
||||
|
||||
impl Standard {
|
||||
#[cfg(any(spi_v1, spi_f1))]
|
||||
#[cfg(any(spi_v1, spi_v3, spi_f1))]
|
||||
const fn i2sstd(&self) -> vals::I2sstd {
|
||||
match self {
|
||||
Standard::Philips => vals::I2sstd::PHILIPS,
|
||||
|
@ -53,7 +46,7 @@ impl Standard {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(any(spi_v1, spi_f1))]
|
||||
#[cfg(any(spi_v1, spi_v3, spi_f1))]
|
||||
const fn pcmsync(&self) -> vals::Pcmsync {
|
||||
match self {
|
||||
Standard::PcmLongSync => vals::Pcmsync::LONG,
|
||||
|
@ -76,7 +69,7 @@ pub enum Format {
|
|||
}
|
||||
|
||||
impl Format {
|
||||
#[cfg(any(spi_v1, spi_f1))]
|
||||
#[cfg(any(spi_v1, spi_v3, spi_f1))]
|
||||
const fn datlen(&self) -> vals::Datlen {
|
||||
match self {
|
||||
Format::Data16Channel16 => vals::Datlen::BITS16,
|
||||
|
@ -86,7 +79,7 @@ impl Format {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(any(spi_v1, spi_f1))]
|
||||
#[cfg(any(spi_v1, spi_v3, spi_f1))]
|
||||
const fn chlen(&self) -> vals::Chlen {
|
||||
match self {
|
||||
Format::Data16Channel16 => vals::Chlen::BITS16,
|
||||
|
@ -107,7 +100,7 @@ pub enum ClockPolarity {
|
|||
}
|
||||
|
||||
impl ClockPolarity {
|
||||
#[cfg(any(spi_v1, spi_f1))]
|
||||
#[cfg(any(spi_v1, spi_v3, spi_f1))]
|
||||
const fn ckpol(&self) -> vals::Ckpol {
|
||||
match self {
|
||||
ClockPolarity::IdleHigh => vals::Ckpol::IDLEHIGH,
|
||||
|
@ -127,15 +120,13 @@ impl ClockPolarity {
|
|||
pub struct Config {
|
||||
/// Mode
|
||||
pub mode: Mode,
|
||||
/// Function (transmit, receive)
|
||||
pub function: Function,
|
||||
/// Which I2S standard to use.
|
||||
pub standard: Standard,
|
||||
/// Data format.
|
||||
pub format: Format,
|
||||
/// Clock polarity.
|
||||
pub clock_polarity: ClockPolarity,
|
||||
/// True to eanble master clock output from this instance.
|
||||
/// True to enable master clock output from this instance.
|
||||
pub master_clock: bool,
|
||||
}
|
||||
|
||||
|
@ -143,7 +134,6 @@ impl Default for Config {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
mode: Mode::Master,
|
||||
function: Function::Transmit,
|
||||
standard: Standard::Philips,
|
||||
format: Format::Data16Channel16,
|
||||
clock_polarity: ClockPolarity::IdleLow,
|
||||
|
@ -155,50 +145,168 @@ impl Default for Config {
|
|||
/// I2S driver.
|
||||
pub struct I2S<'d> {
|
||||
_peri: Spi<'d, Async>,
|
||||
sd: Option<PeripheralRef<'d, AnyPin>>,
|
||||
txsd: Option<PeripheralRef<'d, AnyPin>>,
|
||||
rxsd: Option<PeripheralRef<'d, AnyPin>>,
|
||||
ws: Option<PeripheralRef<'d, AnyPin>>,
|
||||
ck: Option<PeripheralRef<'d, AnyPin>>,
|
||||
mck: Option<PeripheralRef<'d, AnyPin>>,
|
||||
}
|
||||
|
||||
/// I2S function
|
||||
#[derive(Copy, Clone)]
|
||||
#[allow(dead_code)]
|
||||
enum Function {
|
||||
/// Transmit audio data
|
||||
Transmit,
|
||||
/// Receive audio data
|
||||
Receive,
|
||||
#[cfg(spi_v3)]
|
||||
/// Transmit and Receive audio data
|
||||
FullDuplex,
|
||||
}
|
||||
|
||||
impl<'d> I2S<'d> {
|
||||
/// Note: Full-Duplex modes are not supported at this time
|
||||
pub fn new<T: Instance>(
|
||||
/// Create a transmitter driver
|
||||
pub fn new_txonly<T: Instance>(
|
||||
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 = impl TxDma<T>> + 'd,
|
||||
freq: Hertz,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
into_ref!(sd);
|
||||
Self::new_inner(
|
||||
peri,
|
||||
new_pin!(sd, AFType::OutputPushPull, Speed::VeryHigh),
|
||||
None,
|
||||
ws,
|
||||
ck,
|
||||
mck,
|
||||
new_dma!(txdma),
|
||||
None,
|
||||
freq,
|
||||
config,
|
||||
Function::Transmit,
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a receiver driver
|
||||
pub fn new_rxonly<T: Instance>(
|
||||
peri: impl Peripheral<P = T> + 'd,
|
||||
sd: impl Peripheral<P = impl MisoPin<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,
|
||||
#[cfg(not(spi_v3))] txdma: impl Peripheral<P = impl TxDma<T>> + 'd,
|
||||
rxdma: impl Peripheral<P = impl RxDma<T>> + 'd,
|
||||
freq: Hertz,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
into_ref!(sd, ws, ck, mck);
|
||||
|
||||
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 mut spi_cfg = SpiConfig::default();
|
||||
spi_cfg.frequency = freq;
|
||||
let spi = Spi::new_internal(
|
||||
into_ref!(sd);
|
||||
Self::new_inner(
|
||||
peri,
|
||||
None,
|
||||
new_pin!(sd, AFType::OutputPushPull, Speed::VeryHigh),
|
||||
ws,
|
||||
ck,
|
||||
mck,
|
||||
#[cfg(not(spi_v3))]
|
||||
new_dma!(txdma),
|
||||
#[cfg(spi_v3)]
|
||||
None,
|
||||
new_dma!(rxdma),
|
||||
spi_cfg,
|
||||
);
|
||||
freq,
|
||||
config,
|
||||
#[cfg(not(spi_v3))]
|
||||
Function::Transmit,
|
||||
#[cfg(spi_v3)]
|
||||
Function::Receive,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(spi_v3)]
|
||||
/// Create a full duplex transmitter driver
|
||||
pub fn new_full_duplex<T: Instance>(
|
||||
peri: impl Peripheral<P = T> + 'd,
|
||||
txsd: impl Peripheral<P = impl MosiPin<T>> + 'd,
|
||||
rxsd: impl Peripheral<P = impl MisoPin<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 = impl TxDma<T>> + 'd,
|
||||
rxdma: impl Peripheral<P = impl RxDma<T>> + 'd,
|
||||
freq: Hertz,
|
||||
config: Config,
|
||||
) -> Self {
|
||||
into_ref!(txsd, rxsd);
|
||||
Self::new_inner(
|
||||
peri,
|
||||
new_pin!(txsd, AFType::OutputPushPull, Speed::VeryHigh),
|
||||
new_pin!(rxsd, AFType::OutputPushPull, Speed::VeryHigh),
|
||||
ws,
|
||||
ck,
|
||||
mck,
|
||||
new_dma!(txdma),
|
||||
new_dma!(rxdma),
|
||||
freq,
|
||||
config,
|
||||
Function::FullDuplex,
|
||||
)
|
||||
}
|
||||
|
||||
/// Write audio data.
|
||||
pub async fn read<W: Word>(&mut self, data: &mut [W]) -> Result<(), Error> {
|
||||
self._peri.read(data).await
|
||||
}
|
||||
|
||||
/// Write audio data.
|
||||
pub async fn write<W: Word>(&mut self, data: &[W]) -> Result<(), Error> {
|
||||
self._peri.write(data).await
|
||||
}
|
||||
|
||||
/// Transfer audio data.
|
||||
pub async fn transfer<W: Word>(&mut self, read: &mut [W], write: &[W]) -> Result<(), Error> {
|
||||
self._peri.transfer(read, write).await
|
||||
}
|
||||
|
||||
/// Transfer audio data in place.
|
||||
pub async fn transfer_in_place<W: Word>(&mut self, data: &mut [W]) -> Result<(), Error> {
|
||||
self._peri.transfer_in_place(data).await
|
||||
}
|
||||
|
||||
fn new_inner<T: Instance>(
|
||||
peri: impl Peripheral<P = T> + 'd,
|
||||
txsd: Option<PeripheralRef<'d, AnyPin>>,
|
||||
rxsd: Option<PeripheralRef<'d, AnyPin>>,
|
||||
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: Option<ChannelAndRequest<'d>>,
|
||||
rxdma: Option<ChannelAndRequest<'d>>,
|
||||
freq: Hertz,
|
||||
config: Config,
|
||||
function: Function,
|
||||
) -> Self {
|
||||
into_ref!(ws, ck, mck);
|
||||
|
||||
ws.set_as_af(ws.af_num(), AFType::OutputPushPull);
|
||||
ws.set_speed(Speed::VeryHigh);
|
||||
|
||||
ck.set_as_af(ck.af_num(), AFType::OutputPushPull);
|
||||
ck.set_speed(Speed::VeryHigh);
|
||||
|
||||
mck.set_as_af(mck.af_num(), AFType::OutputPushPull);
|
||||
mck.set_speed(Speed::VeryHigh);
|
||||
|
||||
let mut spi_cfg = SpiConfig::default();
|
||||
spi_cfg.frequency = freq;
|
||||
|
||||
let spi = Spi::new_internal(peri, txdma, rxdma, spi_cfg);
|
||||
|
||||
let regs = T::info().regs;
|
||||
|
||||
// TODO move i2s to the new mux infra.
|
||||
//#[cfg(all(rcc_f4, not(stm32f410)))]
|
||||
|
@ -208,26 +316,23 @@ impl<'d> I2S<'d> {
|
|||
|
||||
let (odd, div) = compute_baud_rate(pclk, freq, config.master_clock, config.format);
|
||||
|
||||
#[cfg(any(spi_v1, spi_f1))]
|
||||
#[cfg(any(spi_v1, spi_v3, spi_f1))]
|
||||
{
|
||||
#[cfg(spi_v3)]
|
||||
{
|
||||
regs.cr1().modify(|w| w.set_spe(false));
|
||||
|
||||
reset_incompatible_bitfields::<T>();
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
spi.info.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);
|
||||
});
|
||||
// 1. Select the I2SDIV[7:0] bits in the SPI_I2SPR/SPI_I2SCFGR register to define the serial clock baud
|
||||
// rate to reach the proper audio sample frequency. The ODD bit in the
|
||||
// SPI_I2SPR/SPI_I2SCFGR register also has to be defined.
|
||||
|
||||
// 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
|
||||
// MCKOE bit in the SPI_I2SPR/SPI_I2SCFGR 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).
|
||||
|
@ -243,50 +348,72 @@ impl<'d> I2S<'d> {
|
|||
|
||||
// 5. The I2SE bit in SPI_I2SCFGR register must be set.
|
||||
|
||||
spi.info.regs.i2scfgr().modify(|w| {
|
||||
let clk_reg = {
|
||||
#[cfg(any(spi_v1, spi_f1))]
|
||||
{
|
||||
regs.i2spr()
|
||||
}
|
||||
#[cfg(spi_v3)]
|
||||
{
|
||||
regs.i2scfgr()
|
||||
}
|
||||
};
|
||||
|
||||
clk_reg.modify(|w| {
|
||||
w.set_i2sdiv(div);
|
||||
w.set_odd(match odd {
|
||||
true => Odd::ODD,
|
||||
false => Odd::EVEN,
|
||||
});
|
||||
|
||||
w.set_mckoe(config.master_clock);
|
||||
});
|
||||
|
||||
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) {
|
||||
w.set_i2scfg(match (config.mode, function) {
|
||||
(Mode::Master, Function::Transmit) => I2scfg::MASTERTX,
|
||||
(Mode::Master, Function::Receive) => I2scfg::MASTERRX,
|
||||
#[cfg(spi_v3)]
|
||||
(Mode::Master, Function::FullDuplex) => I2scfg::MASTERFULLDUPLEX,
|
||||
(Mode::Slave, Function::Transmit) => I2scfg::SLAVETX,
|
||||
(Mode::Slave, Function::Receive) => I2scfg::SLAVERX,
|
||||
#[cfg(spi_v3)]
|
||||
(Mode::Slave, Function::FullDuplex) => I2scfg::SLAVEFULLDUPLEX,
|
||||
});
|
||||
|
||||
w.set_i2se(true)
|
||||
#[cfg(any(spi_v1, spi_f1))]
|
||||
w.set_i2se(true);
|
||||
});
|
||||
|
||||
#[cfg(spi_v3)]
|
||||
regs.cr1().modify(|w| w.set_spe(true));
|
||||
}
|
||||
|
||||
Self {
|
||||
_peri: spi,
|
||||
sd: Some(sd.map_into()),
|
||||
txsd: txsd.map(|w| w.map_into()),
|
||||
rxsd: rxsd.map(|w| w.map_into()),
|
||||
ws: Some(ws.map_into()),
|
||||
ck: Some(ck.map_into()),
|
||||
mck: Some(mck.map_into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Write audio data.
|
||||
pub async fn write<W: Word>(&mut self, data: &[W]) -> Result<(), Error> {
|
||||
self._peri.write(data).await
|
||||
}
|
||||
|
||||
/// Read audio data.
|
||||
pub async fn read<W: Word>(&mut self, data: &mut [W]) -> Result<(), Error> {
|
||||
self._peri.read(data).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> Drop for I2S<'d> {
|
||||
fn drop(&mut self) {
|
||||
self.sd.as_ref().map(|x| x.set_as_disconnected());
|
||||
self.txsd.as_ref().map(|x| x.set_as_disconnected());
|
||||
self.rxsd.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());
|
||||
|
@ -328,3 +455,71 @@ fn compute_baud_rate(i2s_clock: Hertz, request_freq: Hertz, mclk: bool, data_for
|
|||
((division & 1) == 1, (division >> 1) as u8)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(spi_v3)]
|
||||
// The STM32H7 reference manual specifies that any incompatible bitfields should be reset
|
||||
// to their reset values while operating in I2S mode.
|
||||
fn reset_incompatible_bitfields<T: Instance>() {
|
||||
let regs = T::info().regs;
|
||||
regs.cr1().modify(|w| {
|
||||
let iolock = w.iolock();
|
||||
let csusp = w.csusp();
|
||||
let spe = w.cstart();
|
||||
let cstart = w.cstart();
|
||||
w.0 = 0;
|
||||
w.set_iolock(iolock);
|
||||
w.set_csusp(csusp);
|
||||
w.set_spe(spe);
|
||||
w.set_cstart(cstart);
|
||||
});
|
||||
|
||||
regs.cr2().write(|w| w.0 = 0);
|
||||
|
||||
regs.cfg1().modify(|w| {
|
||||
let txdmaen = w.txdmaen();
|
||||
let rxdmaen = w.rxdmaen();
|
||||
let fthlv = w.fthlv();
|
||||
w.0 = 0;
|
||||
w.set_txdmaen(txdmaen);
|
||||
w.set_rxdmaen(rxdmaen);
|
||||
w.set_fthlv(fthlv);
|
||||
});
|
||||
|
||||
regs.cfg2().modify(|w| {
|
||||
let afcntr = w.afcntr();
|
||||
let lsbfirst = w.lsbfirst();
|
||||
let ioswp = w.ioswp();
|
||||
w.0 = 0;
|
||||
w.set_afcntr(afcntr);
|
||||
w.set_lsbfirst(lsbfirst);
|
||||
w.set_ioswp(ioswp);
|
||||
});
|
||||
|
||||
regs.ier().modify(|w| {
|
||||
let tifreie = w.tifreie();
|
||||
let ovrie = w.ovrie();
|
||||
let udrie = w.udrie();
|
||||
let txpie = w.txpie();
|
||||
let rxpie = w.rxpie();
|
||||
|
||||
w.0 = 0;
|
||||
|
||||
w.set_tifreie(tifreie);
|
||||
w.set_ovrie(ovrie);
|
||||
w.set_udrie(udrie);
|
||||
w.set_txpie(txpie);
|
||||
w.set_rxpie(rxpie);
|
||||
});
|
||||
|
||||
regs.ifcr().write(|w| {
|
||||
w.set_suspc(true);
|
||||
w.set_tifrec(true);
|
||||
w.set_ovrc(true);
|
||||
w.set_udrc(true);
|
||||
});
|
||||
|
||||
regs.crcpoly().write(|w| w.0 = 0x107);
|
||||
regs.txcrc().write(|w| w.0 = 0);
|
||||
regs.rxcrc().write(|w| w.0 = 0);
|
||||
regs.udrdr().write(|w| w.0 = 0);
|
||||
}
|
||||
|
|
|
@ -83,7 +83,7 @@ pub mod hrtim;
|
|||
pub mod hsem;
|
||||
#[cfg(i2c)]
|
||||
pub mod i2c;
|
||||
#[cfg(all(spi_v1, rcc_f4))]
|
||||
#[cfg(any(all(spi_v1, rcc_f4), spi_v3))]
|
||||
pub mod i2s;
|
||||
#[cfg(stm32wb)]
|
||||
pub mod ipcc;
|
||||
|
|
|
@ -15,14 +15,13 @@ async fn main(_spawner: Spawner) {
|
|||
let p = embassy_stm32::init(Default::default());
|
||||
info!("Hello World!");
|
||||
|
||||
let mut i2s = I2S::new(
|
||||
let mut i2s = I2S::new_txonly(
|
||||
p.SPI2,
|
||||
p.PC3, // sd
|
||||
p.PB12, // ws
|
||||
p.PB10, // ck
|
||||
p.PC6, // mck
|
||||
p.DMA1_CH4,
|
||||
p.DMA1_CH3,
|
||||
Hertz(1_000_000),
|
||||
Config::default(),
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue