Skip to content

Commit 8effff4

Browse files
committed
Add DMA support to I2C API
1 parent c11058d commit 8effff4

File tree

2 files changed

+207
-3
lines changed

2 files changed

+207
-3
lines changed

src/dma.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,10 @@ impl_target!(
493493
serial::Tx<USART2>, Channel7, 4;
494494
serial::Rx<USART2>, Channel5, 4;
495495
serial::Rx<USART2>, Channel6, 4;
496+
);
496497

498+
#[cfg(feature = "stm32l0x2")]
499+
impl_target!(
497500
// I2C1
498501
i2c::Tx<I2C1>, Channel2, 6;
499502
i2c::Rx<I2C1>, Channel3, 6;

src/i2c.rs

Lines changed: 204 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
11
//! I2C
22
3+
use core::ops::Deref;
4+
5+
#[cfg(feature = "stm32l0x2")]
36
use core::{
47
marker::PhantomData,
5-
ops::Deref,
8+
ops::DerefMut,
9+
pin::Pin,
10+
};
11+
12+
#[cfg(feature = "stm32l0x2")]
13+
use as_slice::{
14+
AsSlice,
15+
AsMutSlice,
616
};
717

818
use crate::hal::blocking::i2c::{Read, Write, WriteRead};
919

20+
#[cfg(feature = "stm32l0x2")]
21+
use crate::dma;
1022
use crate::gpio::gpioa::{PA10, PA9};
1123
use crate::gpio::gpiob::{PB6, PB7};
1224
use crate::gpio::{AltMode, OpenDrain, Output};
@@ -130,8 +142,15 @@ where
130142
.bits(scldel)
131143
});
132144

133-
// Enable the peripheral
134-
i2c.cr1.write(|w| w.pe().set_bit());
145+
i2c.cr1.write(|w|
146+
w
147+
// Enable DMA reception
148+
.rxdmaen().set_bit()
149+
// Enable DMA transmission
150+
.txdmaen().set_bit()
151+
// Enable peripheral
152+
.pe().set_bit()
153+
);
135154

136155
I2c { i2c, sda, scl }
137156
}
@@ -186,6 +205,100 @@ where
186205
let value = self.i2c.rxdr.read().rxdata().bits();
187206
Ok(value)
188207
}
208+
209+
#[cfg(feature = "stm32l0x2")]
210+
pub fn write_all<Channel, Buffer>(mut self,
211+
dma: &mut dma::Handle,
212+
channel: Channel,
213+
address: u8,
214+
buffer: Pin<Buffer>,
215+
)
216+
-> Transfer<Self, Tx<I>, Channel, Buffer, dma::Ready>
217+
where
218+
Tx<I>: dma::Target<Channel>,
219+
Channel: dma::Channel,
220+
Buffer: Deref + 'static,
221+
Buffer::Target: AsSlice<Element=u8>,
222+
{
223+
self.start_transfer(address, buffer.as_slice().len(), RD_WRNW::WRITE);
224+
225+
// This token represents the transmission capability of I2C and this is
226+
// what the `dma::Target` trait is implemented for. It can't be
227+
// implemented for `I2c` itself, as that would allow for the user to
228+
// pass, for example, a channel that can do I2C RX to `write_all`.
229+
//
230+
// Theoretically, one could create both `Rx` and `Tx` at the same time,
231+
// or create multiple tokens of the same type, and use that to create
232+
// multiple simultaneous DMA transfers, which would be wrong and is not
233+
// supported by the I2C peripheral. We prevent that by only ever
234+
// creating an `Rx` or `Tx` token while we have ownership of `I2c`, and
235+
// dropping the token before returning ownership of `I2c` ot the user.
236+
let token = Tx(PhantomData);
237+
238+
// Safe, because we're only taking the address of a register.
239+
let address = &unsafe { &*I::ptr() }.txdr as *const _ as u32;
240+
241+
// Safe, because the trait bounds of this method guarantee that the
242+
// buffer can be read from.
243+
let transfer = unsafe {
244+
dma::Transfer::new(
245+
dma,
246+
token,
247+
channel,
248+
buffer,
249+
address,
250+
dma::Priority::high(),
251+
dma::Direction::memory_to_peripheral(),
252+
)
253+
};
254+
255+
Transfer {
256+
target: self,
257+
inner: transfer,
258+
}
259+
}
260+
261+
#[cfg(feature = "stm32l0x2")]
262+
pub fn read_all<Channel, Buffer>(mut self,
263+
dma: &mut dma::Handle,
264+
channel: Channel,
265+
address: u8,
266+
buffer: Pin<Buffer>,
267+
)
268+
-> Transfer<Self, Rx<I>, Channel, Buffer, dma::Ready>
269+
where
270+
Rx<I>: dma::Target<Channel>,
271+
Channel: dma::Channel,
272+
Buffer: DerefMut + 'static,
273+
Buffer::Target: AsMutSlice<Element=u8>,
274+
{
275+
self.start_transfer(address, buffer.as_slice().len(), RD_WRNW::READ);
276+
277+
// See explanation of tokens in `write_all`.
278+
let token = Rx(PhantomData);
279+
280+
// Safe, because we're only taking the address of a register.
281+
let address = &unsafe { &*I::ptr() }.rxdr as *const _ as u32;
282+
283+
// Safe, because the trait bounds of this method guarantee that the
284+
// buffer can be written to.
285+
let transfer = unsafe {
286+
dma::Transfer::new(
287+
dma,
288+
token,
289+
channel,
290+
buffer,
291+
address,
292+
dma::Priority::high(),
293+
dma::Direction::peripheral_to_memory(),
294+
)
295+
};
296+
297+
Transfer {
298+
target: self,
299+
inner: transfer,
300+
}
301+
}
189302
}
190303

191304
impl<I, SDA, SCL> WriteRead for I2c<I, SDA, SCL>
@@ -242,6 +355,7 @@ where
242355
}
243356

244357
pub trait Instance: Deref<Target = RegisterBlock> {
358+
fn ptr() -> *const RegisterBlock;
245359
fn initialize(&self, rcc: &mut Rcc);
246360
}
247361

@@ -310,6 +424,10 @@ macro_rules! i2c {
310424
}
311425

312426
impl Instance for $I2CX {
427+
fn ptr() -> *const RegisterBlock {
428+
$I2CX::ptr()
429+
}
430+
313431
fn initialize(&self, rcc: &mut Rcc) {
314432
// Enable clock for I2C
315433
rcc.rb.apb1enr.modify(|_, w| w.$i2cxen().set_bit());
@@ -391,10 +509,93 @@ i2c!(
391509
///
392510
/// This is an implementation detail. The user doesn't have to deal with this
393511
/// directly.
512+
#[cfg(feature = "stm32l0x2")]
394513
pub struct Tx<I>(PhantomData<I>);
395514

396515
/// Token used for DMA transfers
397516
///
398517
/// This is an implementation detail. The user doesn't have to deal with this
399518
/// directly.
519+
#[cfg(feature = "stm32l0x2")]
400520
pub struct Rx<I>(PhantomData<I>);
521+
522+
523+
/// I2C-specific wrapper around [`dma::Transfer`]
524+
#[cfg(feature = "stm32l0x2")]
525+
pub struct Transfer<Target, Token, Channel, Buffer, State> {
526+
target: Target,
527+
inner: dma::Transfer<Token, Channel, Buffer, State>,
528+
}
529+
530+
#[cfg(feature = "stm32l0x2")]
531+
impl<Target, Token, Channel, Buffer>
532+
Transfer<Target, Token, Channel, Buffer, dma::Ready>
533+
where
534+
Token: dma::Target<Channel>,
535+
Channel: dma::Channel,
536+
{
537+
/// Enables the provided interrupts
538+
///
539+
/// This setting only affects this transfer. It doesn't affect transfer on
540+
/// other channels, or subsequent transfers on the same channel.
541+
pub fn enable_interrupts(&mut self, interrupts: dma::Interrupts) {
542+
self.inner.enable_interrupts(interrupts);
543+
}
544+
545+
/// Start the DMA transfer
546+
///
547+
/// Consumes this instance of `Transfer` and returns a new one, with its
548+
/// state changed to indicate that the transfer has been started.
549+
pub fn start(self)
550+
-> Transfer<Target, Token, Channel, Buffer, dma::Started>
551+
{
552+
Transfer {
553+
target: self.target,
554+
inner: self.inner.start(),
555+
}
556+
}
557+
}
558+
559+
#[cfg(feature = "stm32l0x2")]
560+
impl<Target, Token, Channel, Buffer>
561+
Transfer<Target, Token, Channel, Buffer, dma::Started>
562+
where
563+
Channel: dma::Channel,
564+
{
565+
/// Indicates whether the transfer is still ongoing
566+
pub fn is_active(&self) -> bool {
567+
self.inner.is_active()
568+
}
569+
570+
/// Waits for the transfer to finish and returns the owned resources
571+
///
572+
/// This function will busily wait until the transfer is finished. If you
573+
/// don't want this, please call this function only once you know that the
574+
/// transfer has finished.
575+
///
576+
/// This function will return immediately, if [`Transfer::is_active`]
577+
/// returns `false`.
578+
pub fn wait(self)
579+
-> Result<
580+
dma::TransferResources<Target, Channel, Buffer>,
581+
(dma::TransferResources<Target, Channel, Buffer>, dma::Error)
582+
>
583+
{
584+
// Need to move `target` out of `self`, otherwise the closure captures
585+
// `self` completely.
586+
let target = self.target;
587+
588+
let map_resources = |res: dma::TransferResources<_, _, _>| {
589+
dma::TransferResources {
590+
target: target,
591+
channel: res.channel,
592+
buffer: res.buffer,
593+
}
594+
};
595+
596+
match self.inner.wait() {
597+
Ok(res) => Ok(map_resources(res)),
598+
Err((res, err)) => Err((map_resources(res), err)),
599+
}
600+
}
601+
}

0 commit comments

Comments
 (0)