Expand description
§Direct Memory Access Controller
This library provides a type-safe API with compile-time guarantees that the peripheral and individual DMA channels are correctly configured before launching a DMA transfer.
This module currently supports most basic DMA functions, including memory-to-memory, memory-to-peripheral, peripheral-to-memory, and peripheral-to-peripheral transfers. One-shot and circular transfers are supported. More complex transfer configurations, including multi-buffer (linked-list descriptor) transfers, are not currently supported.
Transfers are supported for i8
, u8
, i16
, u16
, i32
, u32
and f32
beat sizes.
§Enabling DMA support
You must enable the dma
feature in your board support crate
or final executable.
Add this to your Cargo.toml
:
[features]
dma = ["atsamd-hal/dma"]
§Channels and RAM
Using DMA channels require a certain amount of RAM - 32 bytes per channel,
to be exact. RAM will be not allocated unless the dma
feature is enabled
for the HAL. By default, half the channels available on the chip are
enabled. If you need all DMA channels enabled, enable the max-channels
feature in your board support crate or final executable.
Cargo.toml
[features]
dma = ["atsamd-hal/dma"]
max-channels = ["dma", "atsamd-hal/max-channels"]
RAM usage per chip family:
-
ATSAMD11
- 3 channels (default): 96 bytes -
ATSAMD11
- 6 channels (max): 192 bytes -
ATSAMD21
- 6 channels (default): 192 bytes -
ATSAMD21
: - 12 channels (max): 384 bytes -
ATSAMD51/ATSAME5x
: - 16 channels (default): 512 bytes -
ATSAMD51/ATSAME5x
: - 32 channels (max): 1024 bytes
§Priority levels and Arbitration
The DMAC features 4 priority levels. Level 3 has the highest priority and level 0 has the lowest. Each channel can be assigned to one priority level. If two channels with the same priority level are requested to execute a transfer at the same time, the lowest channel number will have priority (in the default, ie static, arbitration scheme).
By default, all priority levels are enabled when initializing the DMAC
(see DmaController::init
). Levels
can be enabled or disabled through the
DmaController::enable_levels
and
DmaController::disable_levels
methods. These methods must be supplied a
PriorityLevelMask
.
Round-Robin Arbitration can be enabled for multiple priority levels
simultaneously by using the
DmaController::round_robin_arbitration
and
DmaController::static_arbitration
methods. These methods must be
supplied a RoundRobinMask
. By default, all priority levels are
initialized with a static arbitration scheme. See ATSAMD21 datasheet section
19.6.2.4 for more information.
§Interrupts
This driver does not use or manage interrupts issued by the DMAC. Individual channels can be configured to generate interrupts when the transfer is complete, an error is detected or the channel is suspended. However, these interrupts will not be triggered unless the DMAC interrupt is unmasked in the NVIC. You will be responsible for clearing the interrupt flags in the ISR.
§About static lifetimes
The safe API this driver offers requires all buffers (source and
destination) to have 'static
lifetimes. This is because
mem::forget
is a safe API, and therefore relying on
mem::drop
to terminate or abort a transfer
does not guarantee the transfer will be terminated (specifically if
mem::forget
is called on a Transfer
containaing
a Channel<Id, Busy>
). This could cause the compiler to reclaim
stack-allocated buffers for reuse while the DMAC is still writing to/reading
from them! Needless to say that is very unsafe.
Refer here
or here for more information.
You may choose to forgo the 'static
lifetimes by using the unsafe API and
the Transfer::new_unchecked
method.
§Unsafe API
This driver also offers an unsafe
API through the
Transfer::new_unchecked
method. It
does not enforce 'static
lifetimes, and allow using buffers of different
lengths. If you choose to use these methods, you MUST prove that
a Transfer
containing a Channel<Id, Busy>
will NEVER be dropped. You
must call wait()
or stop()
manually on every
Transfer
that has been created using the unsafe API. No destructor or
Drop
implementation is offered for Transfer
s.
Additionally, you can (unsafely) implement your own buffer types through the
unsafe Buffer
trait.
§Example
let mut peripherals = Peripherals::take().unwrap();
let mut dmac = DmaController::init(peripherals.DMAC, &mut peripherals.PM);
// Get individual handles to DMA channels
let channels = dmac.split();
// Initialize DMA Channel 0
let chan0 = channels.0.init(PriorityLevel::LVL0, false, &mut dmac);
// Setup a DMA transfer (memory-to-memory -> incrementing source, incrementing destination)
// NOTE: buf_src and buf_dest should be either:
// &'static mut T, &'static mut [T], or &'static mut [T; N] where T: BeatSize
let xfer = Transfer::new(chan0, buf_src, buf_dest, false).begin(
&mut dmac,
TriggerSource::DISABLE,
TriggerAction::BLOCK,
);
// Wait for transfer to complete and grab resulting buffers
let (chan0, buf_src, buf_dest, _) = xfer.wait(&mut dmac);
// (Optional) free the [`DmaController`] struct and return the underlying PAC struct
channels.0 = chan0.into();
let dmac = dmac.free(channels, &mut peripherals.PM);
§Transfer
recycling
A common use-case with DMAC transfers is to trigger a new transfer as soon
as the old transfer is completed. To avoid having to
stop
a Transfer
, build a new Transfer
(with
new
or new_from_arrays
)
then call begin
, a Transfer::recycle
method
is provided. If the buffer lengths match and the previous transfer is
completed, a new transfer will immediately be triggered using the provided
source and destination buffers. If the recycling operation is succesful,
Ok((source, destination))
containing the old source and destination
buffers is returned. Otherwise, Err(_)
is returned.
let new_source = produce_source();
let new_destination = produce_destination();
// Assume xfer is a `Busy` `Transfer`
let (old_source, old_dest) = xfer.recycle(new_source, new_destination).unwrap();
§Waker operation
A Transfer
can also accept a function or closure that will be called on
completion of the transaction, acting like a waker.
fn wake_up() {
//...
}
fn use_waker<const N: usize>(dmac: DmaController,
source: &'static mut [u8; N],
destination: &'static mut [u8; N]
){
let chan0 = dmac.split().0;
let xfer = Transfer::new_from_arrays(chan0, source, destination, false)
.with_waker(wake_up)
.begin();
//...
}
§RTIC example
The RTIC framework provides a convenient way to store a static
ally
allocated Transfer
, so that it can be accessed by both the interrupt
handlers and user code. The following example shows how Transfer
s might
be used for a series of transactions. It uses features from the latest
release of RTIC, v0.6-alpha.4
.
use atsamd_hal::dmac::*;
const LENGTH: usize = 50;
type TransferBuffer = &'static mut [u8; LENGTH];
type Xfer = Transfer<Channel<Ch0, Busy>, TransferBuffer, TransferBuffer>;
#[resources]
struct Resources {
#[lock_free]
#[init(None)]
opt_xfer: Option<Xfer>,
#[lock_free]
#[init(None)]
opt_channel: Option<Channel<Ch0, Ready>>,
}
// Note: Assume interrupts have already been enabled for the concerned channel
#[task(resources = [opt_xfer, opt_channel])]
fn task(ctx: task::Context) {
let task::Context { opt_xfer } = ctx;
match opt_xfer {
Some(xfer) => {
if xfer.complete() {
let (chan0, _source, dest, _payload) = xfer.take().unwrap().stop();
*opt_channel = Some(chan0);
consume_data(buf);
}
}
None => {
if let Some(chan0) = opt_channel.take() {
let source: [u8; 50] = produce_source();
let dest: [u8; 50] = produce_destination();
let xfer = opt_xfer.get_or_insert(
Transfer::new_from_arrays(channel0, source, destination)
.with_waker(|| { task::spawn().ok(); })
.begin()
);
}
}
}
}
#[task(binds = DMAC, resources = [opt_future])]
fn tcmpl(ctx: tcmpl::Context) {
ctx.resources.opt_xfer.as_mut().unwrap().callback();
}
Re-exports§
pub use channel::*;
pub use dma_controller::*;
pub use transfer::*;
pub use async_api::*;
Modules§
- APIs for async DMAC operations.
- Abstractions over individual DMA channels
- Abstractions to setup and use the DMA controller
- DMA transfer abstractions
Enums§
- Runtime errors that may occur when dealing with DMA transfers.
Constants§
- Number of DMA channels used by the driver
Type Aliases§
- Result for DMAC operations