mirror of
https://github.com/embassy-rs/embassy.git
synced 2024-11-25 08:12:30 +00:00
Merge pull request #3023 from liarokapisv/i2s-ring-buffered
Revise I2S interface to ring-buffered.
This commit is contained in:
commit
0225c2a0f2
@ -1,12 +1,13 @@
|
|||||||
//! Inter-IC Sound (I2S)
|
//! Inter-IC Sound (I2S)
|
||||||
|
|
||||||
|
use embassy_futures::join::join;
|
||||||
use embassy_hal_internal::into_ref;
|
use embassy_hal_internal::into_ref;
|
||||||
|
use stm32_metapac::spi::vals;
|
||||||
|
|
||||||
use crate::dma::ChannelAndRequest;
|
use crate::dma::{ringbuffer, ChannelAndRequest, ReadableRingBuffer, TransferOptions, WritableRingBuffer};
|
||||||
use crate::gpio::{AfType, AnyPin, OutputType, SealedPin, Speed};
|
use crate::gpio::{AfType, AnyPin, OutputType, SealedPin, Speed};
|
||||||
use crate::mode::Async;
|
use crate::mode::Async;
|
||||||
use crate::pac::spi::vals;
|
use crate::spi::{Config as SpiConfig, RegsExt as _, *};
|
||||||
use crate::spi::{Config as SpiConfig, *};
|
|
||||||
use crate::time::Hertz;
|
use crate::time::Hertz;
|
||||||
use crate::{Peripheral, PeripheralRef};
|
use crate::{Peripheral, PeripheralRef};
|
||||||
|
|
||||||
@ -19,6 +20,19 @@ pub enum Mode {
|
|||||||
Slave,
|
Slave,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
}
|
||||||
|
|
||||||
/// I2C standard
|
/// I2C standard
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub enum Standard {
|
pub enum Standard {
|
||||||
@ -34,6 +48,30 @@ pub enum Standard {
|
|||||||
PcmShortSync,
|
PcmShortSync,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// SAI error
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum Error {
|
||||||
|
/// `write` called on a SAI in receive mode.
|
||||||
|
NotATransmitter,
|
||||||
|
/// `read` called on a SAI in transmit mode.
|
||||||
|
NotAReceiver,
|
||||||
|
/// Overrun
|
||||||
|
Overrun,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ringbuffer::Error> for Error {
|
||||||
|
fn from(#[allow(unused)] err: ringbuffer::Error) -> Self {
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
{
|
||||||
|
if err == ringbuffer::Error::DmaUnsynced {
|
||||||
|
defmt::error!("Ringbuffer broken invariants detected!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Overrun
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Standard {
|
impl Standard {
|
||||||
#[cfg(any(spi_v1, spi_v3, spi_f1))]
|
#[cfg(any(spi_v1, spi_v3, spi_f1))]
|
||||||
const fn i2sstd(&self) -> vals::I2sstd {
|
const fn i2sstd(&self) -> vals::I2sstd {
|
||||||
@ -142,31 +180,62 @@ impl Default for Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// I2S driver writer. Useful for moving write functionality across tasks.
|
||||||
|
pub struct Writer<'s, 'd, W: Word>(&'s mut WritableRingBuffer<'d, W>);
|
||||||
|
|
||||||
|
impl<'s, 'd, W: Word> Writer<'s, 'd, W> {
|
||||||
|
/// Write data to the I2S ringbuffer.
|
||||||
|
/// This appends the data to the buffer and returns immediately. The data will be transmitted in the background.
|
||||||
|
/// If thfre’s no space in the buffer, this waits until there is.
|
||||||
|
pub async fn write(&mut self, data: &[W]) -> Result<(), Error> {
|
||||||
|
self.0.write_exact(data).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset the ring buffer to its initial state.
|
||||||
|
/// Can be used to recover from overrun.
|
||||||
|
/// The ringbuffer will always auto-reset on Overrun in any case.
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.0.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// I2S driver reader. Useful for moving read functionality across tasks.
|
||||||
|
pub struct Reader<'s, 'd, W: Word>(&'s mut ReadableRingBuffer<'d, W>);
|
||||||
|
|
||||||
|
impl<'s, 'd, W: Word> Reader<'s, 'd, W> {
|
||||||
|
/// Read data from the I2S ringbuffer.
|
||||||
|
/// SAI is always receiving data in the background. This function pops already-received data from the buffer.
|
||||||
|
/// If there’s less than data.len() data in the buffer, this waits until there is.
|
||||||
|
pub async fn read(&mut self, data: &mut [W]) -> Result<(), Error> {
|
||||||
|
self.0.read_exact(data).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset the ring buffer to its initial state.
|
||||||
|
/// Can be used to prevent overrun.
|
||||||
|
/// The ringbuffer will always auto-reset on Overrun in any case.
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.0.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// I2S driver.
|
/// I2S driver.
|
||||||
pub struct I2S<'d> {
|
pub struct I2S<'d, W: Word> {
|
||||||
_peri: Spi<'d, Async>,
|
#[allow(dead_code)]
|
||||||
|
mode: Mode,
|
||||||
|
spi: Spi<'d, Async>,
|
||||||
txsd: Option<PeripheralRef<'d, AnyPin>>,
|
txsd: Option<PeripheralRef<'d, AnyPin>>,
|
||||||
rxsd: Option<PeripheralRef<'d, AnyPin>>,
|
rxsd: Option<PeripheralRef<'d, AnyPin>>,
|
||||||
ws: Option<PeripheralRef<'d, AnyPin>>,
|
ws: Option<PeripheralRef<'d, AnyPin>>,
|
||||||
ck: Option<PeripheralRef<'d, AnyPin>>,
|
ck: Option<PeripheralRef<'d, AnyPin>>,
|
||||||
mck: Option<PeripheralRef<'d, AnyPin>>,
|
mck: Option<PeripheralRef<'d, AnyPin>>,
|
||||||
|
tx_ring_buffer: Option<WritableRingBuffer<'d, W>>,
|
||||||
|
rx_ring_buffer: Option<ReadableRingBuffer<'d, W>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// I2S function
|
impl<'d, W: Word> I2S<'d, W> {
|
||||||
#[derive(Copy, Clone)]
|
/// Create a transmitter driver.
|
||||||
#[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> {
|
|
||||||
/// Create a transmitter driver
|
|
||||||
pub fn new_txonly<T: Instance>(
|
pub fn new_txonly<T: Instance>(
|
||||||
peri: impl Peripheral<P = T> + 'd,
|
peri: impl Peripheral<P = T> + 'd,
|
||||||
sd: impl Peripheral<P = impl MosiPin<T>> + 'd,
|
sd: impl Peripheral<P = impl MosiPin<T>> + 'd,
|
||||||
@ -174,18 +243,18 @@ impl<'d> I2S<'d> {
|
|||||||
ck: impl Peripheral<P = impl CkPin<T>> + 'd,
|
ck: impl Peripheral<P = impl CkPin<T>> + 'd,
|
||||||
mck: impl Peripheral<P = impl MckPin<T>> + 'd,
|
mck: impl Peripheral<P = impl MckPin<T>> + 'd,
|
||||||
txdma: impl Peripheral<P = impl TxDma<T>> + 'd,
|
txdma: impl Peripheral<P = impl TxDma<T>> + 'd,
|
||||||
|
txdma_buf: &'d mut [W],
|
||||||
freq: Hertz,
|
freq: Hertz,
|
||||||
config: Config,
|
config: Config,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
into_ref!(sd);
|
|
||||||
Self::new_inner(
|
Self::new_inner(
|
||||||
peri,
|
peri,
|
||||||
new_pin!(sd, AfType::output(OutputType::PushPull, Speed::VeryHigh)),
|
new_pin!(sd, AfType::output(OutputType::PushPull, Speed::VeryHigh)),
|
||||||
None,
|
None,
|
||||||
ws,
|
ws,
|
||||||
ck,
|
ck,
|
||||||
mck,
|
new_pin!(mck, AfType::output(OutputType::PushPull, Speed::VeryHigh)),
|
||||||
new_dma!(txdma),
|
new_dma!(txdma).map(|d| (d, txdma_buf)),
|
||||||
None,
|
None,
|
||||||
freq,
|
freq,
|
||||||
config,
|
config,
|
||||||
@ -193,42 +262,61 @@ impl<'d> I2S<'d> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a receiver driver
|
/// Create a transmitter driver without a master clock pin.
|
||||||
|
pub fn new_txonly_nomck<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,
|
||||||
|
txdma: impl Peripheral<P = impl TxDma<T>> + 'd,
|
||||||
|
txdma_buf: &'d mut [W],
|
||||||
|
freq: Hertz,
|
||||||
|
config: Config,
|
||||||
|
) -> Self {
|
||||||
|
Self::new_inner(
|
||||||
|
peri,
|
||||||
|
new_pin!(sd, AfType::output(OutputType::PushPull, Speed::VeryHigh)),
|
||||||
|
None,
|
||||||
|
ws,
|
||||||
|
ck,
|
||||||
|
None,
|
||||||
|
new_dma!(txdma).map(|d| (d, txdma_buf)),
|
||||||
|
None,
|
||||||
|
freq,
|
||||||
|
config,
|
||||||
|
Function::Transmit,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a receiver driver.
|
||||||
pub fn new_rxonly<T: Instance>(
|
pub fn new_rxonly<T: Instance>(
|
||||||
peri: impl Peripheral<P = T> + 'd,
|
peri: impl Peripheral<P = T> + 'd,
|
||||||
sd: impl Peripheral<P = impl MisoPin<T>> + 'd,
|
sd: impl Peripheral<P = impl MisoPin<T>> + 'd,
|
||||||
ws: impl Peripheral<P = impl WsPin<T>> + 'd,
|
ws: impl Peripheral<P = impl WsPin<T>> + 'd,
|
||||||
ck: impl Peripheral<P = impl CkPin<T>> + 'd,
|
ck: impl Peripheral<P = impl CkPin<T>> + 'd,
|
||||||
mck: impl Peripheral<P = impl MckPin<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,
|
rxdma: impl Peripheral<P = impl RxDma<T>> + 'd,
|
||||||
|
rxdma_buf: &'d mut [W],
|
||||||
freq: Hertz,
|
freq: Hertz,
|
||||||
config: Config,
|
config: Config,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
into_ref!(sd);
|
|
||||||
Self::new_inner(
|
Self::new_inner(
|
||||||
peri,
|
peri,
|
||||||
None,
|
None,
|
||||||
new_pin!(sd, AfType::output(OutputType::PushPull, Speed::VeryHigh)),
|
new_pin!(sd, AfType::output(OutputType::PushPull, Speed::VeryHigh)),
|
||||||
ws,
|
ws,
|
||||||
ck,
|
ck,
|
||||||
mck,
|
new_pin!(mck, AfType::output(OutputType::PushPull, Speed::VeryHigh)),
|
||||||
#[cfg(not(spi_v3))]
|
|
||||||
new_dma!(txdma),
|
|
||||||
#[cfg(spi_v3)]
|
|
||||||
None,
|
None,
|
||||||
new_dma!(rxdma),
|
new_dma!(rxdma).map(|d| (d, rxdma_buf)),
|
||||||
freq,
|
freq,
|
||||||
config,
|
config,
|
||||||
#[cfg(not(spi_v3))]
|
|
||||||
Function::Transmit,
|
|
||||||
#[cfg(spi_v3)]
|
|
||||||
Function::Receive,
|
Function::Receive,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(spi_v3)]
|
#[cfg(spi_v3)]
|
||||||
/// Create a full duplex transmitter driver
|
/// Create a full duplex driver.
|
||||||
pub fn new_full_duplex<T: Instance>(
|
pub fn new_full_duplex<T: Instance>(
|
||||||
peri: impl Peripheral<P = T> + 'd,
|
peri: impl Peripheral<P = T> + 'd,
|
||||||
txsd: impl Peripheral<P = impl MosiPin<T>> + 'd,
|
txsd: impl Peripheral<P = impl MosiPin<T>> + 'd,
|
||||||
@ -237,44 +325,144 @@ impl<'d> I2S<'d> {
|
|||||||
ck: impl Peripheral<P = impl CkPin<T>> + 'd,
|
ck: impl Peripheral<P = impl CkPin<T>> + 'd,
|
||||||
mck: impl Peripheral<P = impl MckPin<T>> + 'd,
|
mck: impl Peripheral<P = impl MckPin<T>> + 'd,
|
||||||
txdma: impl Peripheral<P = impl TxDma<T>> + 'd,
|
txdma: impl Peripheral<P = impl TxDma<T>> + 'd,
|
||||||
|
txdma_buf: &'d mut [W],
|
||||||
rxdma: impl Peripheral<P = impl RxDma<T>> + 'd,
|
rxdma: impl Peripheral<P = impl RxDma<T>> + 'd,
|
||||||
|
rxdma_buf: &'d mut [W],
|
||||||
freq: Hertz,
|
freq: Hertz,
|
||||||
config: Config,
|
config: Config,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
into_ref!(txsd, rxsd);
|
|
||||||
Self::new_inner(
|
Self::new_inner(
|
||||||
peri,
|
peri,
|
||||||
new_pin!(txsd, AfType::output(OutputType::PushPull, Speed::VeryHigh)),
|
new_pin!(txsd, AfType::output(OutputType::PushPull, Speed::VeryHigh)),
|
||||||
new_pin!(rxsd, AfType::output(OutputType::PushPull, Speed::VeryHigh)),
|
new_pin!(rxsd, AfType::output(OutputType::PushPull, Speed::VeryHigh)),
|
||||||
ws,
|
ws,
|
||||||
ck,
|
ck,
|
||||||
mck,
|
new_pin!(mck, AfType::output(OutputType::PushPull, Speed::VeryHigh)),
|
||||||
new_dma!(txdma),
|
new_dma!(txdma).map(|d| (d, txdma_buf)),
|
||||||
new_dma!(rxdma),
|
new_dma!(rxdma).map(|d| (d, rxdma_buf)),
|
||||||
freq,
|
freq,
|
||||||
config,
|
config,
|
||||||
Function::FullDuplex,
|
Function::FullDuplex,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write audio data.
|
/// Start I2S driver.
|
||||||
pub async fn read<W: Word>(&mut self, data: &mut [W]) -> Result<(), Error> {
|
pub fn start(&mut self) {
|
||||||
self._peri.read(data).await
|
self.spi.info.regs.cr1().modify(|w| {
|
||||||
|
w.set_spe(false);
|
||||||
|
});
|
||||||
|
self.spi.set_word_size(W::CONFIG);
|
||||||
|
if let Some(tx_ring_buffer) = &mut self.tx_ring_buffer {
|
||||||
|
tx_ring_buffer.start();
|
||||||
|
|
||||||
|
set_txdmaen(self.spi.info.regs, true);
|
||||||
|
}
|
||||||
|
if let Some(rx_ring_buffer) = &mut self.rx_ring_buffer {
|
||||||
|
rx_ring_buffer.start();
|
||||||
|
// SPIv3 clears rxfifo on SPE=0
|
||||||
|
#[cfg(not(any(spi_v3, spi_v4, spi_v5)))]
|
||||||
|
flush_rx_fifo(self.spi.info.regs);
|
||||||
|
|
||||||
|
set_rxdmaen(self.spi.info.regs, true);
|
||||||
|
}
|
||||||
|
self.spi.info.regs.cr1().modify(|w| {
|
||||||
|
w.set_spe(true);
|
||||||
|
});
|
||||||
|
#[cfg(any(spi_v3, spi_v4, spi_v5))]
|
||||||
|
self.spi.info.regs.cr1().modify(|w| {
|
||||||
|
w.set_cstart(true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write audio data.
|
/// Reset the ring buffer to its initial state.
|
||||||
pub async fn write<W: Word>(&mut self, data: &[W]) -> Result<(), Error> {
|
/// Can be used to recover from overrun.
|
||||||
self._peri.write(data).await
|
pub fn clear(&mut self) {
|
||||||
|
if let Some(rx_ring_buffer) = &mut self.rx_ring_buffer {
|
||||||
|
rx_ring_buffer.clear();
|
||||||
|
}
|
||||||
|
if let Some(tx_ring_buffer) = &mut self.tx_ring_buffer {
|
||||||
|
tx_ring_buffer.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transfer audio data.
|
/// Stop I2S driver.
|
||||||
pub async fn transfer<W: Word>(&mut self, read: &mut [W], write: &[W]) -> Result<(), Error> {
|
pub async fn stop(&mut self) {
|
||||||
self._peri.transfer(read, write).await
|
let regs = self.spi.info.regs;
|
||||||
|
|
||||||
|
let tx_f = async {
|
||||||
|
if let Some(tx_ring_buffer) = &mut self.tx_ring_buffer {
|
||||||
|
tx_ring_buffer.stop().await;
|
||||||
|
|
||||||
|
set_txdmaen(regs, false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let rx_f = async {
|
||||||
|
if let Some(rx_ring_buffer) = &mut self.rx_ring_buffer {
|
||||||
|
rx_ring_buffer.stop().await;
|
||||||
|
|
||||||
|
set_rxdmaen(regs, false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
join(rx_f, tx_f).await;
|
||||||
|
|
||||||
|
#[cfg(any(spi_v3, spi_v4, spi_v5))]
|
||||||
|
{
|
||||||
|
if let Mode::Master = self.mode {
|
||||||
|
regs.cr1().modify(|w| {
|
||||||
|
w.set_csusp(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
while regs.cr1().read().cstart() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
regs.cr1().modify(|w| {
|
||||||
|
w.set_spe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transfer audio data in place.
|
/// Split the driver into a Reader/Writer pair.
|
||||||
pub async fn transfer_in_place<W: Word>(&mut self, data: &mut [W]) -> Result<(), Error> {
|
/// Useful for splitting the reader/writer functionality across tasks or
|
||||||
self._peri.transfer_in_place(data).await
|
/// for calling the read/write methods in parallel.
|
||||||
|
pub fn split<'s>(&'s mut self) -> Result<(Reader<'s, 'd, W>, Writer<'s, 'd, W>), Error> {
|
||||||
|
match (&mut self.rx_ring_buffer, &mut self.tx_ring_buffer) {
|
||||||
|
(None, _) => Err(Error::NotAReceiver),
|
||||||
|
(_, None) => Err(Error::NotATransmitter),
|
||||||
|
(Some(rx_ring), Some(tx_ring)) => Ok((Reader(rx_ring), Writer(tx_ring))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read data from the I2S ringbuffer.
|
||||||
|
/// SAI is always receiving data in the background. This function pops already-received data from the buffer.
|
||||||
|
/// If there’s less than data.len() data in the buffer, this waits until there is.
|
||||||
|
pub async fn read(&mut self, data: &mut [W]) -> Result<(), Error> {
|
||||||
|
match &mut self.rx_ring_buffer {
|
||||||
|
Some(ring) => Reader(ring).read(data).await,
|
||||||
|
_ => Err(Error::NotAReceiver),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write data to the I2S ringbuffer.
|
||||||
|
/// This appends the data to the buffer and returns immediately. The data will be transmitted in the background.
|
||||||
|
/// If thfre’s no space in the buffer, this waits until there is.
|
||||||
|
pub async fn write(&mut self, data: &[W]) -> Result<(), Error> {
|
||||||
|
match &mut self.tx_ring_buffer {
|
||||||
|
Some(ring) => Writer(ring).write(data).await,
|
||||||
|
_ => Err(Error::NotATransmitter),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write data directly to the raw I2S ringbuffer.
|
||||||
|
/// This can be used to fill the buffer before starting the DMA transfer.
|
||||||
|
pub async fn write_immediate(&mut self, data: &mut [W]) -> Result<(usize, usize), Error> {
|
||||||
|
match &mut self.tx_ring_buffer {
|
||||||
|
Some(ring) => Ok(ring.write_immediate(data)?),
|
||||||
|
_ => return Err(Error::NotATransmitter),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_inner<T: Instance>(
|
fn new_inner<T: Instance>(
|
||||||
@ -283,23 +471,23 @@ impl<'d> I2S<'d> {
|
|||||||
rxsd: Option<PeripheralRef<'d, AnyPin>>,
|
rxsd: Option<PeripheralRef<'d, AnyPin>>,
|
||||||
ws: impl Peripheral<P = impl WsPin<T>> + 'd,
|
ws: impl Peripheral<P = impl WsPin<T>> + 'd,
|
||||||
ck: impl Peripheral<P = impl CkPin<T>> + 'd,
|
ck: impl Peripheral<P = impl CkPin<T>> + 'd,
|
||||||
mck: impl Peripheral<P = impl MckPin<T>> + 'd,
|
mck: Option<PeripheralRef<'d, AnyPin>>,
|
||||||
txdma: Option<ChannelAndRequest<'d>>,
|
txdma: Option<(ChannelAndRequest<'d>, &'d mut [W])>,
|
||||||
rxdma: Option<ChannelAndRequest<'d>>,
|
rxdma: Option<(ChannelAndRequest<'d>, &'d mut [W])>,
|
||||||
freq: Hertz,
|
freq: Hertz,
|
||||||
config: Config,
|
config: Config,
|
||||||
function: Function,
|
function: Function,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
into_ref!(ws, ck, mck);
|
into_ref!(ws, ck);
|
||||||
|
|
||||||
ws.set_as_af(ws.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
ws.set_as_af(ws.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||||
ck.set_as_af(ck.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
ck.set_as_af(ck.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
||||||
mck.set_as_af(mck.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh));
|
|
||||||
|
|
||||||
let mut spi_cfg = SpiConfig::default();
|
let spi = Spi::new_internal(peri, None, None, {
|
||||||
spi_cfg.frequency = freq;
|
let mut config = SpiConfig::default();
|
||||||
|
config.frequency = freq;
|
||||||
let spi = Spi::new_internal(peri, txdma, rxdma, spi_cfg);
|
config
|
||||||
|
});
|
||||||
|
|
||||||
let regs = T::info().regs;
|
let regs = T::info().regs;
|
||||||
|
|
||||||
@ -390,22 +578,29 @@ impl<'d> I2S<'d> {
|
|||||||
w.set_i2se(true);
|
w.set_i2se(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
#[cfg(spi_v3)]
|
let mut opts = TransferOptions::default();
|
||||||
regs.cr1().modify(|w| w.set_spe(true));
|
opts.half_transfer_ir = true;
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
_peri: spi,
|
mode: config.mode,
|
||||||
txsd: txsd.map(|w| w.map_into()),
|
spi,
|
||||||
rxsd: rxsd.map(|w| w.map_into()),
|
txsd: txsd.map(|w| w.map_into()),
|
||||||
ws: Some(ws.map_into()),
|
rxsd: rxsd.map(|w| w.map_into()),
|
||||||
ck: Some(ck.map_into()),
|
ws: Some(ws.map_into()),
|
||||||
mck: Some(mck.map_into()),
|
ck: Some(ck.map_into()),
|
||||||
|
mck: mck.map(|w| w.map_into()),
|
||||||
|
tx_ring_buffer: txdma.map(|(ch, buf)| unsafe {
|
||||||
|
WritableRingBuffer::new(ch.channel, ch.request, regs.tx_ptr(), buf, opts)
|
||||||
|
}),
|
||||||
|
rx_ring_buffer: rxdma.map(|(ch, buf)| unsafe {
|
||||||
|
ReadableRingBuffer::new(ch.channel, ch.request, regs.rx_ptr(), buf, opts)
|
||||||
|
}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'d> Drop for I2S<'d> {
|
impl<'d, W: Word> Drop for I2S<'d, W> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.txsd.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.rxsd.as_ref().map(|x| x.set_as_disconnected());
|
||||||
|
@ -311,8 +311,7 @@ impl<'d, M: PeriMode> Spi<'d, M> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set SPI word size. Disables SPI if needed, you have to enable it back yourself.
|
pub(crate) fn set_word_size(&mut self, word_size: word_impl::Config) {
|
||||||
fn set_word_size(&mut self, word_size: word_impl::Config) {
|
|
||||||
if self.current_word_size == word_size {
|
if self.current_word_size == word_size {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -895,7 +894,7 @@ fn compute_frequency(kernel_clock: Hertz, br: Br) -> Hertz {
|
|||||||
kernel_clock / div
|
kernel_clock / div
|
||||||
}
|
}
|
||||||
|
|
||||||
trait RegsExt {
|
pub(crate) trait RegsExt {
|
||||||
fn tx_ptr<W>(&self) -> *mut W;
|
fn tx_ptr<W>(&self) -> *mut W;
|
||||||
fn rx_ptr<W>(&self) -> *mut W;
|
fn rx_ptr<W>(&self) -> *mut W;
|
||||||
}
|
}
|
||||||
@ -983,7 +982,7 @@ fn spin_until_rx_ready(regs: Regs) -> Result<(), Error> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush_rx_fifo(regs: Regs) {
|
pub(crate) fn flush_rx_fifo(regs: Regs) {
|
||||||
#[cfg(not(any(spi_v3, spi_v4, spi_v5)))]
|
#[cfg(not(any(spi_v3, spi_v4, spi_v5)))]
|
||||||
while regs.sr().read().rxne() {
|
while regs.sr().read().rxne() {
|
||||||
#[cfg(not(spi_v2))]
|
#[cfg(not(spi_v2))]
|
||||||
@ -997,7 +996,7 @@ fn flush_rx_fifo(regs: Regs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_txdmaen(regs: Regs, val: bool) {
|
pub(crate) fn set_txdmaen(regs: Regs, val: bool) {
|
||||||
#[cfg(not(any(spi_v3, spi_v4, spi_v5)))]
|
#[cfg(not(any(spi_v3, spi_v4, spi_v5)))]
|
||||||
regs.cr2().modify(|reg| {
|
regs.cr2().modify(|reg| {
|
||||||
reg.set_txdmaen(val);
|
reg.set_txdmaen(val);
|
||||||
@ -1008,7 +1007,7 @@ fn set_txdmaen(regs: Regs, val: bool) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_rxdmaen(regs: Regs, val: bool) {
|
pub(crate) fn set_rxdmaen(regs: Regs, val: bool) {
|
||||||
#[cfg(not(any(spi_v3, spi_v4, spi_v5)))]
|
#[cfg(not(any(spi_v3, spi_v4, spi_v5)))]
|
||||||
regs.cr2().modify(|reg| {
|
regs.cr2().modify(|reg| {
|
||||||
reg.set_rxdmaen(val);
|
reg.set_rxdmaen(val);
|
||||||
@ -1169,7 +1168,7 @@ impl<'d, W: Word> embedded_hal_async::spi::SpiBus<W> for Spi<'d, Async> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait SealedWord {
|
pub(crate) trait SealedWord {
|
||||||
const CONFIG: word_impl::Config;
|
const CONFIG: word_impl::Config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
use core::fmt::Write;
|
|
||||||
|
|
||||||
use defmt::*;
|
use defmt::*;
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_stm32::i2s::{Config, I2S};
|
use embassy_stm32::i2s::{Config, I2S};
|
||||||
use embassy_stm32::time::Hertz;
|
use embassy_stm32::time::Hertz;
|
||||||
use heapless::String;
|
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
#[embassy_executor::main]
|
#[embassy_executor::main]
|
||||||
@ -15,6 +12,8 @@ async fn main(_spawner: Spawner) {
|
|||||||
let p = embassy_stm32::init(Default::default());
|
let p = embassy_stm32::init(Default::default());
|
||||||
info!("Hello World!");
|
info!("Hello World!");
|
||||||
|
|
||||||
|
let mut dma_buffer = [0x00_u16; 128];
|
||||||
|
|
||||||
let mut i2s = I2S::new_txonly(
|
let mut i2s = I2S::new_txonly(
|
||||||
p.SPI2,
|
p.SPI2,
|
||||||
p.PC3, // sd
|
p.PC3, // sd
|
||||||
@ -22,13 +21,13 @@ async fn main(_spawner: Spawner) {
|
|||||||
p.PB10, // ck
|
p.PB10, // ck
|
||||||
p.PC6, // mck
|
p.PC6, // mck
|
||||||
p.DMA1_CH4,
|
p.DMA1_CH4,
|
||||||
|
&mut dma_buffer,
|
||||||
Hertz(1_000_000),
|
Hertz(1_000_000),
|
||||||
Config::default(),
|
Config::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
for n in 0u32.. {
|
for i in 0_u16.. {
|
||||||
let mut write: String<128> = String::new();
|
i2s.write(&mut [i * 2; 64]).await.ok();
|
||||||
core::write!(&mut write, "Hello DMA World {}!\r\n", n).unwrap();
|
i2s.write(&mut [i * 2 + 1; 64]).await.ok();
|
||||||
i2s.write(&mut write.as_bytes()).await.ok();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user