Skip to content

Commit

Permalink
working async i2c slave
Browse files Browse the repository at this point in the history
  • Loading branch information
jrmoulton committed May 15, 2024
1 parent be2a650 commit d1e6455
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 77 deletions.
26 changes: 23 additions & 3 deletions embassy-stm32/src/i2c/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
pub enum SendStatus {
Done,
LeftoverBytes(usize),
MoreBytesRequested,
}

#[derive(Debug, Clone, PartialEq, Eq)]
Expand All @@ -180,6 +181,22 @@ pub enum ReceiveStatus {
// contains the number of bytes received
SendRequested(usize),
}
impl ReceiveStatus {
pub fn len(&self) -> usize {
match self {
ReceiveStatus::Done(n) => *n,
ReceiveStatus::SendRequested(n) => *n,
}
}

pub fn is_done(&self) -> bool {
matches!(self, ReceiveStatus::Done(_))
}

pub fn has_request(&self) -> bool {
matches!(self, ReceiveStatus::SendRequested(_))
}
}

#[repr(u8)]
#[derive(Copy, Clone)]
Expand Down Expand Up @@ -299,9 +316,10 @@ impl<'d, T: Instance> I2cSlave<'d, T, Async> {
+ 'd,
tx_dma: impl Peripheral<P = impl TxDma<T>> + 'd,
rx_dma: impl Peripheral<P = impl RxDma<T>> + 'd,
freq: Hertz,
config: SlaveConfig,
) -> Self {
Self::new_inner(peri, scl, sda, new_dma!(tx_dma), new_dma!(rx_dma), config)
Self::new_inner(peri, scl, sda, new_dma!(tx_dma), new_dma!(rx_dma), freq, config)
}
}

Expand All @@ -310,9 +328,10 @@ impl<'d, T: Instance> I2cSlave<'d, T, Blocking> {
peri: impl Peripheral<P = T> + 'd,
scl: impl Peripheral<P = impl SclPin<T>> + 'd,
sda: impl Peripheral<P = impl SdaPin<T>> + 'd,
freq: Hertz,
config: SlaveConfig,
) -> Self {
Self::new_inner(peri, scl, sda, None, None, config)
Self::new_inner(peri, scl, sda, None, None, freq, config)
}
}
impl<'d, T: Instance, M: Mode> I2cSlave<'d, T, M> {
Expand All @@ -323,6 +342,7 @@ impl<'d, T: Instance, M: Mode> I2cSlave<'d, T, M> {
sda: impl Peripheral<P = impl SdaPin<T>> + 'd,
tx_dma: Option<ChannelAndRequest<'d>>,
rx_dma: Option<ChannelAndRequest<'d>>,
freq: Hertz,
config: SlaveConfig,
) -> Self {
into_ref!(peri, scl, sda);
Expand All @@ -344,7 +364,7 @@ impl<'d, T: Instance, M: Mode> I2cSlave<'d, T, M> {
_phantom: PhantomData,
};

this.init(config);
this.init(freq, config);

this
}
Expand Down
138 changes: 64 additions & 74 deletions embassy-stm32/src/i2c/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use cortex_m::asm::nop;
use embassy_embedded_hal::SetConfig;
use embassy_hal_internal::drop::OnDrop;
use embedded_hal_1::i2c::Operation;
use futures_util::FutureExt;
use stm32_metapac::i2c::vals::Addmode;

use super::*;
Expand All @@ -16,13 +17,17 @@ pub(crate) unsafe fn on_interrupt<T: Instance>() {
let regs = T::regs();
let isr = regs.isr().read();

if isr.tcr() || isr.tc() {
if isr.tcr() || isr.tc() || isr.addr() || isr.stopf() | isr.ovr() {
T::state().waker.wake();
}
// The flag can only be cleared by writting to nbytes, we won't do that here, so disable
// the interrupt
critical_section::with(|_| {
regs.cr1().modify(|w| w.set_tcie(false));
regs.cr1().modify(|w| {
w.set_tcie(false);
w.set_addrie(false);
w.set_stopie(false);
});
});
}

Expand Down Expand Up @@ -667,6 +672,12 @@ impl<'d, T: Instance, M: Mode> Drop for I2c<'d, T, M> {
}
}

impl<'d, T: Instance, M: Mode> Drop for I2cSlave<'d, T, M> {
fn drop(&mut self) {
T::disable();
}
}

/// I2C Stop Configuration
///
/// Peripheral options for generating the STOP condition
Expand Down Expand Up @@ -825,13 +836,13 @@ impl<'d, T: Instance, M: Mode> SetConfig for I2cSlave<'d, T, M> {

// I2cSlave methods
impl<'d, T: Instance, M: Mode> I2cSlave<'d, T, M> {
pub(crate) fn init(&mut self, config: SlaveConfig) {
pub(crate) fn init(&mut self, freq: Hertz, config: SlaveConfig) {
T::regs().cr1().modify(|reg| {
reg.set_pe(false);
reg.set_anfoff(false);
});

let timings = Timings::new(T::frequency(), Hertz::khz(400));
let timings = Timings::new(T::frequency(), freq);

T::regs().timingr().write(|reg| {
reg.set_presc(timings.prescale);
Expand Down Expand Up @@ -892,32 +903,19 @@ impl<'d, T: Instance, M: Mode> I2cSlave<'d, T, M> {
pub async fn listen(&mut self) -> Result<Command, Error> {
let state = T::state();

T::regs().icr().write(|reg| reg.addrcf());
T::regs().cr1().modify(|reg| {
reg.set_addrie(true);
reg.set_txie(true);
reg.set_wupen(true);
});


poll_fn(|cx| {
state.waker.register(cx.waker());
let isr = T::regs().isr().read();
let cr1 = T::regs().cr1().read().pe();
let wupen = T::regs().cr1().read().wupen();
#[cfg(feature = "defmt")]
defmt::debug!("{}", isr.0);
#[cfg(feature = "defmt")]
defmt::debug!("Periph enabled: {}", cr1);
#[cfg(feature = "defmt")]
defmt::debug!("wupen enabled: {}", wupen);
if !isr.addr() {
#[cfg(feature = "defmt")]
defmt::warn!("slave returning poll pending");
return Poll::Pending;
} else {
#[cfg(feature = "defmt")]
defmt::warn!("slave had the address match interrupt fire");
// we do not clear the address flag here as it will be cleared by the dma read/write
// if we clear it here the clock stretching will stop and the master will read in data before the slave is ready to send it

match isr.dir() {
i2c::vals::Dir::WRITE => {
return Poll::Ready(Ok(Command {
Expand All @@ -938,8 +936,6 @@ impl<'d, T: Instance, M: Mode> I2cSlave<'d, T, M> {
}

fn determine_matched_address(&self) -> Result<OwnAddress, Error> {
#[cfg(feature = "defmt")]
defmt::warn!("determining matched address");
let matched = T::regs().isr().read().addcode();
let address = if matched >> 3 == 0b11110 {
// is 10-bit address and we need to get the other 8 bits from the rxdr
Expand All @@ -951,8 +947,6 @@ impl<'d, T: Instance, M: Mode> I2cSlave<'d, T, M> {
Ok(OwnAddress::SevenBit(matched))
};

T::regs().icr().write(|reg| reg.addrcf());

address
}

Expand All @@ -972,41 +966,63 @@ impl<'d, T: Instance> I2cSlave<'d, T, Async> {
let timeout = self.timeout();
let size = timeout.with(self.read_dma_internal(buffer, timeout)).await?;

let isr = T::regs().isr().read();
T::regs().cr1().modify(|reg| {
reg.set_addrie(true);
});

if isr.addr() {
if isr.dir() == i2c::vals::Dir::READ {
return Ok(ReceiveStatus::SendRequested(size));
}
let state = T::state();

let send_requested = self
.timeout()
.with(poll_fn(|cx| {
state.waker.register(cx.waker());

let isr = T::regs().isr().read();
if isr.addr() {
if isr.dir() == i2c::vals::Dir::READ {
return Poll::Ready(Ok(ReceiveStatus::SendRequested(size)));
} else {
return Poll::Ready(Ok(ReceiveStatus::Done(size)));
}
};
return Poll::Pending;
}))
.await;

match send_requested {
Ok(ReceiveStatus::SendRequested(size)) => Ok(ReceiveStatus::SendRequested(size)),
Ok(ReceiveStatus::Done(_)) | Err(_) => Ok(ReceiveStatus::Done(size)),
}
Ok(ReceiveStatus::Done(size))
}

pub async fn respond_to_send(&mut self, buffer: &[u8]) -> Result<SendStatus, Error> {
let timeout = self.timeout();
timeout.with(self.write_dma_internal(buffer, timeout, false)).await
}

pub async fn respond_and_fill(&mut self, buffer: &[u8], fill: u8) -> Result<SendStatus, Error> {
let resp_stat = self.respond_to_send(buffer).await?;
// pub async fn respond_and_fill(&mut self, buffer: &[u8], fill: u8) -> Result<SendStatus, Error> {
// let resp_stat = self.respond_to_send(buffer).await?;

// let res = if matches!(resp_stat, SendStatus::MoreBytesRequested) {
// self.write_dma_internal(&[fill], self.timeout(), true).await?;
// Ok(SendStatus::Done)
// } else {
// Ok(resp_stat)
// };

// res
// }

// if resp_stat == SendStatus::NeedMoreBytes {
// self.write_dma_internal(&[fill], self.timeout(), true).await?;
// Ok(SendStatus::Done)
// } else {
Ok(resp_stat)
// }
}
// for data reception in slave mode
async fn read_dma_internal(&mut self, buffer: &mut [u8], timeout: Timeout) -> Result<usize, Error> {
let total_len = buffer.len();

let mut dma_transfer = unsafe {
let regs = T::regs();
regs.cr2().modify(|w| w.set_nbytes(total_len as u8));
regs.cr1().modify(|w| {
w.set_rxdmaen(true);
w.set_stopie(true);
w.set_errie(true);
});
let src = regs.rxdr().as_ptr() as *mut u8;

Expand All @@ -1021,37 +1037,25 @@ impl<'d, T: Instance> I2cSlave<'d, T, Async> {
regs.cr1().modify(|w| {
w.set_rxdmaen(false);
w.set_stopie(false);
w.set_errie(false);
})
});
T::regs().icr().modify(|reg| reg.set_addrcf(true));

let total_received = poll_fn(|cx| {
state.waker.register(cx.waker());

let isr = T::regs().isr().read();
if isr.stopf() {
// if we get a stop condition we need to determine how many bytes were received
T::regs().icr().write(|reg| reg.set_stopcf(true));
let poll = Poll::Ready(Ok(total_len - dma_transfer.get_remaining_transfers() as usize));
dma_transfer.request_stop();
poll
} else if isr.ovr() {
// in the case of a read this is an overrun
Poll::Ready(Err(Error::Overrun))
} else if isr.arlo() {
Poll::Ready(Err(Error::Arbitration))
} else if isr.berr() {
Poll::Ready(Err(Error::Bus))
} else if isr.timeout() {
Poll::Ready(Err(Error::Timeout))
} else if isr.pecerr() {
Poll::Ready(Err(Error::Pec))
} else {
Poll::Pending
}
})
.await?;

// should already be finished
dma_transfer.await;

drop(on_drop);
Expand All @@ -1066,14 +1070,16 @@ impl<'d, T: Instance> I2cSlave<'d, T, Async> {
timeout: Timeout,
circular: bool,
) -> Result<SendStatus, Error> {
let total_len = buffer.len();
let total_len = if circular { 255 } else { buffer.len() };

let timeout = self.timeout();

let mut dma_transfer = unsafe {
let regs = T::regs();
regs.cr2().modify(|w| w.set_nbytes(total_len as u8));
regs.cr1().modify(|w| {
w.set_txdmaen(true);
w.set_stopie(true);
w.set_errie(true);
});
let dst = regs.txdr().as_ptr() as *mut u8;

Expand All @@ -1083,6 +1089,7 @@ impl<'d, T: Instance> I2cSlave<'d, T, Async> {
};
self.tx_dma.as_mut().unwrap().write(buffer, dst, transfer_options)
};
T::regs().icr().modify(|reg| reg.set_addrcf(true));

let state = T::state();
let mut remaining_len = total_len;
Expand All @@ -1092,7 +1099,6 @@ impl<'d, T: Instance> I2cSlave<'d, T, Async> {
regs.cr1().modify(|w| {
w.set_txdmaen(false);
w.set_stopie(false);
w.set_errie(false);
})
});

Expand All @@ -1101,36 +1107,20 @@ impl<'d, T: Instance> I2cSlave<'d, T, Async> {

let isr = T::regs().isr().read();
if isr.stopf() {
T::regs().icr().write(|reg| reg.set_stopcf(true));
let remaining = dma_transfer.get_remaining_transfers();
if remaining > 0 {
dma_transfer.request_stop();
Poll::Ready(Ok(SendStatus::LeftoverBytes(remaining as usize)))
} else {
Poll::Ready(Ok(SendStatus::Done))
}
} else if isr.ovr() {
let remaining = dma_transfer.get_remaining_transfers();
dma_transfer.request_stop();
if remaining > 0 {
Poll::Ready(Ok(SendStatus::LeftoverBytes(remaining as usize)))
} else {
Poll::Ready(Err(Error::Underun))
}
} else if isr.arlo() {
Poll::Ready(Err(Error::Arbitration))
} else if isr.berr() {
Poll::Ready(Err(Error::Bus))
} else if isr.timeout() {
Poll::Ready(Err(Error::Timeout))
} else if isr.pecerr() {
Poll::Ready(Err(Error::Pec))
} else {
Poll::Pending
}
})
.await?;

// should already be finished
dma_transfer.await;

drop(on_drop);
Expand Down

0 comments on commit d1e6455

Please sign in to comment.