class DMA – access to the RP2040’s DMA controller¶
The DMA
class offers access to the RP2040’s Direct Memory Access (DMA)
controller, providing the ability move data between memory blocks and/or IO registers. The DMA
controller has its own, separate read and write bus master connections onto the bus fabric and
each DMA channel can independently read data from one address and write it back to another
address, optionally incrementing one or both pointers, allowing it to perform transfers on behalf
of the processor while the processor carries out other tasks or enters a low power state. The
RP2040’s DMA controller has 12 independent DMA channels that can run concurrently. For full
details of the RP2040’s DMA system see section 2.5 of the RP2040 Datasheet.
Examples¶
The simplest use of the DMA controller is to move data from one block of memory to another. This can be accomplished with the following code:
a = bytearray(32*1024)
b = bytearray(32*1024)
d = rp2.DMA()
c = d.pack_ctrl() # Just use the default control value.
# The count is in 'transfers', which defaults to four-byte words, so divide length by 4
d.config(read=a, write=b, count=len(a)//4, ctrl=c, trigger=True)
# Wait for completion
while d.active():
pass
Note that while this example sits in an idle loop while it waits for the transfer to complete, the program could just as well do some useful work in this time instead.
Another, perhaps more common use of the DMA controller is to transfer between memory and an IO
peripheral. In this situation the address of the IO register does not change for each transfer but
the memory address needs to be incremented. It is also necessary to control the pace of the
transfer so as to not write data before it can be accepted by a peripheral or read it before the
data is ready, and this can be controlled with the treq_sel
field of the DMA channel’s control
register. The various fields of the control register for each DMA channel can be packed
using the DMA.pack_ctrl()
method and unpacked using the DMA.unpack_ctrl()
static method. Code to transfer data from a byte array to the TX FIFO of a PIO state machine,
one byte at a time, looks like this:
# pio_num is index of the PIO block being used, sm_num is the state machine in that block.
# my_state_machine is an rp2.PIO() instance.
DATA_REQUEST_INDEX = (pio_num << 3) + sm_num
src_data = bytearray(1024)
d = rp2.DMA()
# Transfer bytes, rather than words, don't increment the write address and pace the transfer.
c = d.pack_ctrl(size=0, inc_write=False, treq_sel=DATA_REQUEST_INDEX)
d.config(
read=src_data,
write=my_state_machine,
count=len(src_data),
ctrl=c,
trigger=True
)
Note that in this example the value given for the write address is just the PIO state machine to which we are sending the data. This works because PIO state machines present the buffer protocol, allowing direct access to their data FIFO registers.
Constructor¶
- class rp2.DMA¶
Claim one of the DMA controller channels for exclusive use.
Methods¶
- DMA.config(read=None, write=None, count=None, ctrl=None, trigger=False)¶
Configure the DMA registers for the channel and optionally start the transfer. Parameters are:
read: The address from which the DMA controller will start reading data or an object that will provide data to be read. It can be an integer or any object that supports the buffer protocol.
write: The address to which the DMA controller will start writing or an object into which data will be written. It can be an integer or any object that supports the buffer protocol.
count: The number of bus transfers that will execute before this channel stops. Note that this is the number of transfers, not the number of bytes. If the transfers are 2 or 4 bytes wide then the total amount of data moved (and thus the size of required buffer) needs to be multiplied accordingly.
ctrl: The value for the DMA control register. This is an integer value that is typically packed using the
DMA.pack_ctrl()
.trigger: Optionally commence the transfer immediately.
- DMA.irq(handler=None, hard=False)¶
Returns the IRQ object for this DMA channel and optionally configures it.
- DMA.close()¶
Release the claim on the underlying DMA channel and free the interrupt handler. The
DMA
object can not be used after this operation.
- DMA.pack_ctrl(default=None, **kwargs)¶
Pack the values provided in the keyword arguments into the named fields of a new control register value. Any field that is not provided will be set to a default value. The default will either be taken from the provided
default
value, or if that is not given, a default suitable for the current channel; setting this to the current value of theDMA.ctrl
attribute provides an easy way to override a subset of the fields.The keys for the keyword arguments can be any key returned by the
DMA.unpack_ctrl()
method. The writable values are:enable:
bool
Set to enable the channel (default:True
).high_pri:
bool
Make this channel’s bus traffic high priority (default:False
).size:
int
Transfer size: 0=byte, 1=half word, 2=word (default: 2).inc_read:
bool
Increment the read address after each transfer (default:True
).inc_write:
bool
Increment the write address after each transfer (default:True
).ring_size:
int
If non-zero, only the bottomring_size
bits of one address register will change when an address is incremented, causing the address to wrap at the next1 << ring_size
byte boundary. Which address is wrapped is controlled by thering_sel
flag. A zero value disables address wrapping.ring_sel:
bool
Set toFalse
to have thering_size
apply to the read address orTrue
to apply to the write address.chain_to:
int
The channel number for a channel to trigger after this transfer completes. Setting this value to this DMA object’s own channel number disables chaining (this is the default).treq_sel:
int
Select a Transfer Request signal. See section 2.5.3 in the RP2040 datasheet for details.irq_quiet:
bool
Do not generate interrupt at the end of each transfer. Interrupts will instead be generated when a zero value is written to the trigger register, which will halt a sequence of chained transfers (default:True
).bswap:
bool
If set to true, bytes in words or half-words will be reversed before writing (default:True
).sniff_en:
bool
Set toTrue
to allow data to be accessed by the chips sniff hardware (default:False
).write_err:
bool
Setting this toTrue
will clear a previously reported write error.read_err:
bool
Setting this toTrue
will clear a previously reported read error.
See the description of the
CH0_CTRL_TRIG
register in section 2.5.7 of the RP2040 datasheet for details of all of these fields.
- DMA.unpack_ctrl(value)¶
Unpack a value for a DMA channel control register into a dictionary with key/value pairs for each of the fields in the control register. value is the
ctrl
register value to unpack.This method will return values for all the keys that can be passed to
DMA.pack_ctrl
. In addition, it will also return the read-only flags in the control register:busy
, which goes high when a transfer starts and low when it ends, andahb_err
, which is the logical OR of theread_err
andwrite_err
flags. These values will be ignored when packing, so that the dictionary created by unpacking a control register can be used directly as the keyword arguments for packing.
- DMA.active([value])¶
Gets or sets whether the DMA channel is currently running.
>>> sm.active() 0 >>> sm.active(1) >>> while sm.active(): ... pass
Attributes¶
- DMA.read¶
This attribute reflects the address from which the next bus transfer will read. It may be written with either an integer or an object that supports the buffer protocol and doing so has immediate effect.
- DMA.write¶
This attribute reflects the address to which the next bus transfer will write. It may be written with either an integer or an object that supports the buffer protocol and doing so has immediate effect.
- DMA.count¶
Reading this attribute will return the number of remaining bus transfers in the current transfer sequence. Writing this attribute sets the total number of transfers to be the next transfer sequence.
- DMA.ctrl¶
This attribute reflects DMA channel control register. It is typically written with an integer packed using the
DMA.pack_ctrl()
method. The returned register value can be unpacked using theDMA.unpack_ctrl()
method.
- DMA.channel¶
The channel number of the DMA channel. This can be passed in the
chain_to
argument ofDMA.pack_ctrl()
on another channel to allow DMA chaining.
- DMA.registers¶
This attribute is an array-like object that allows direct access to the DMA channel’s registers. The index is by word, rather than by byte, so the register indices are the register address offsets divided by 4. See the RP2040 data sheet for register details.
Chaining and trigger register access¶
The DMA controller in the RP2040 offers a couple advanced features to allow one DMA channel
to initiate a transfer on another channel. One is the use of the chain_to
value in the
control register and the other is writing to one of the DMA channel’s registers that has a
trigger effect. When coupled with the ability to have one DMA channel write directly to the
DMA.registers
of another channel, this allows for complex transactions to be performed
without any CPU intervention.
Below is an example of using both chaining and register triggering to implement gathering of multiple blocks of data into a single destination. Full details of these features can be found in section 2.5 of the RP2040 data sheet and the code below is a Pythonic version of the example in sub-section 2.5.6.2.
from rp2 import DMA
from uctypes import addressof
from array import array
def gather_strings(string_list, buf):
# We use two DMA channels. The first sends lengths and source addresses from the gather
# list to the registers of the second. The second copies the data itself.
gather_dma = DMA()
buffer_dma = DMA()
# Pack up length/address pairs to be sent to the registers.
gather_list = array("I")
for s in string_list:
gather_list.append(len(s))
gather_list.append(addressof(s))
gather_list.append(0)
gather_list.append(0)
# When writing to the registers of the second DMA channel, we need to wrap the
# write address on an 8-byte (1<<3 bytes) boundary. We write to the ``TRANS_COUNT``
# and ``READ_ADD_TRIG`` registers in the last register alias (registers 14 and 15).
gather_ctrl = gather_dma.pack_ctrl(ring_size=3, ring_sel=True)
gather_dma.config(
read=gather_list, write=buffer_dma.registers[14:16],
count=2, ctrl=gather_ctrl
)
# When copying the data, the transfer size is single bytes, and when completed we need
# to chain back to the start another gather DMA transaction.
buffer_ctrl = buffer_dma.pack_ctrl(size=0, chain_to=gather_dma.channel)
# The read and count values will be set by the other DMA channel.
buffer_dma.config(write=buf, ctrl=buffer_ctrl)
# Set the transfer in motion.
gather_dma.active(1)
# Wait until all the register values have been sent
end_address = addressof(gather_list) + 4 * len(gather_list)
while gather_dma.read != end_address:
pass
input = ["This is ", "a ", "test", " of the scatter", " gather", " process"]
output = bytearray(64)
print(output)
gather_strings(input, output)
print(output)
This example idles while waiting for the transfer to complete; alternatively it could set an interrupt handler and return immediately.