Merge pull request #2992 from liarokapisv/i2s-spi_v3

Add spi v3 & Full-duplex support to the I2S driver.
This commit is contained in:
Dario Nieuwenhuis 2024-05-30 12:31:59 +00:00 committed by GitHub
commit 694ac3a515
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 268 additions and 74 deletions

View file

@ -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);
}

View file

@ -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;

View file

@ -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(),
);