class DMA – Zugriff auf den DMA-Controller des RP2040¶
Die Klasse DMA bietet Zugriff auf den Direct-Memory-Access-Controller (DMA) des RP2040 und ermöglicht es, Daten zwischen Speicherblöcken und/oder IO-Registern zu verschieben. Der DMA-Controller verfügt über eigene, separate Lese- und Schreib-Busmaster-Verbindungen zur Bus-Fabric, und jeder DMA-Kanal kann unabhängig Daten von einer Adresse lesen und sie an eine andere Adresse zurückschreiben, wobei optional einer oder beide Zeiger inkrementiert werden, sodass er Übertragungen im Namen des Prozessors durchführen kann, während der Prozessor andere Aufgaben erledigt oder in einen Energiesparzustand wechselt. Der DMA-Controller des RP2040 verfügt über 12 unabhängige DMA-Kanäle, die gleichzeitig laufen können. Vollständige Details zum DMA-System des RP2040 finden Sie in Abschnitt 2.5 des RP2040-Datenblatts.
Beispiele¶
Die einfachste Verwendung des DMA-Controllers besteht darin, Daten von einem Speicherblock in einen anderen zu verschieben. Dies kann mit dem folgenden Code erreicht werden:
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
Beachten Sie, dass dieses Beispiel zwar in einer Leerlaufschleife verharrt, während es auf den Abschluss der Übertragung wartet, das Programm aber in dieser Zeit ebenso gut nützliche Arbeit verrichten könnte.
Eine andere, vielleicht häufigere Verwendung des DMA-Controllers ist die Übertragung zwischen Speicher und einem IO-Peripheriegerät. In dieser Situation ändert sich die Adresse des IO-Registers bei jeder Übertragung nicht, aber die Speicheradresse muss inkrementiert werden. Es ist außerdem notwendig, das Tempo der Übertragung zu steuern, um keine Daten zu schreiben, bevor sie von einem Peripheriegerät angenommen werden können, oder sie zu lesen, bevor die Daten bereit sind; dies kann mit dem Feld treq_sel des Steuerregisters des DMA-Kanals gesteuert werden. Die verschiedenen Felder des Steuerregisters für jeden DMA-Kanal können mit der Methode DMA.pack_ctrl() gepackt und mit der statischen Methode DMA.unpack_ctrl() entpackt werden. Code, um Daten aus einem Byte-Array byteweise in die TX-FIFO einer PIO-State-Machine zu übertragen, sieht so aus:
# 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
)
Beachten Sie, dass in diesem Beispiel der für die Schreibadresse angegebene Wert einfach die PIO-State-Machine ist, an die wir die Daten senden. Dies funktioniert, weil PIO-State-Machines das Buffer-Protokoll bereitstellen und so direkten Zugriff auf ihre Daten-FIFO-Register ermöglichen.
Konstruktor¶
- class rp2.DMA¶
Beansprucht einen der DMA-Controller-Kanäle zur exklusiven Nutzung.
- config(read: 'int | _AnyReadableBuf | None' = None, write: 'int | _AnyWritableBuf | None' = None, count: int | None = None, ctrl: int | None = None, trigger: bool = False) None¶
Konfiguriert die DMA-Register für den Kanal und startet optional die Übertragung. Die Parameter sind:
read: Die Adresse, von der der DMA-Controller mit dem Lesen von Daten beginnt, oder ein Objekt, das die zu lesenden Daten bereitstellt. Es kann eine Ganzzahl oder ein beliebiges Objekt sein, das das Buffer-Protokoll unterstützt.
write: Die Adresse, an die der DMA-Controller mit dem Schreiben beginnt, oder ein Objekt, in das Daten geschrieben werden. Es kann eine Ganzzahl oder ein beliebiges Objekt sein, das das Buffer-Protokoll unterstützt.
count: Die Anzahl der Busübertragungen, die ausgeführt werden, bevor dieser Kanal stoppt. Beachten Sie, dass dies die Anzahl der Übertragungen ist, nicht die Anzahl der Bytes. Wenn die Übertragungen 2 oder 4 Byte breit sind, muss die Gesamtmenge der verschobenen Daten (und damit die Größe des benötigten Puffers) entsprechend multipliziert werden.
ctrl: Der Wert für das DMA-Steuerregister. Dies ist ein Ganzzahlwert, der typischerweise mit
DMA.pack_ctrl()gepackt wird.trigger: Startet die Übertragung optional sofort.
- irq(handler: Callable[[DMA], None] | None = None, hard: bool = False) Callable¶
Gibt das IRQ-Objekt für diesen DMA-Kanal zurück und konfiguriert es optional.
- close() None¶
Gibt den Anspruch auf den zugrunde liegenden DMA-Kanal frei und gibt den Interrupt-Handler frei. Das
DMA-Objekt kann nach diesem Vorgang nicht mehr verwendet werden.
- pack_ctrl(default: int | None = None, **kwargs) int¶
Packt die in den Schlüsselwortargumenten bereitgestellten Werte in die benannten Felder eines neuen Steuerregisterwerts. Jedes Feld, das nicht bereitgestellt wird, erhält einen Standardwert. Der Standardwert wird entweder dem bereitgestellten
default-Wert entnommen oder, falls dieser nicht angegeben ist, ein für den aktuellen Kanal geeigneter Standardwert; das Setzen dieses Werts auf den aktuellen Wert des AttributsDMA.ctrlbietet eine einfache Möglichkeit, eine Teilmenge der Felder zu überschreiben.Die Schlüssel für die Schlüsselwortargumente können beliebige Schlüssel sein, die von der Methode
DMA.unpack_ctrl()zurückgegeben werden. Die beschreibbaren Werte sind:enable:
boolSetzen, um den Kanal zu aktivieren (Standard:True).high_pri:
boolMacht den Busverkehr dieses Kanals zu hoher Priorität (Standard:False).size:
intÜbertragungsgröße: 0=Byte, 1=Halbwort, 2=Wort (Standard: 2).inc_read:
boolInkrementiert die Leseadresse nach jeder Übertragung (Standard:True).inc_write:
boolInkrementiert die Schreibadresse nach jeder Übertragung (Standard:True).ring_size:
intWenn ungleich null, ändern sich beim Inkrementieren einer Adresse nur die unterstenring_sizeBits eines Adressregisters, wodurch die Adresse an der nächsten1 << ring_size-Byte-Grenze umbricht. Welche Adresse umbricht, wird durch das Flagring_selgesteuert. Ein Wert von null deaktiviert das Adress-Wrapping.ring_sel:
boolAufFalsesetzen, damitring_sizeauf die Leseadresse angewendet wird, oder aufTrue, um es auf die Schreibadresse anzuwenden.chain_to:
intDie Kanalnummer eines Kanals, der nach Abschluss dieser Übertragung ausgelöst werden soll. Das Setzen dieses Werts auf die eigene Kanalnummer dieses DMA-Objekts deaktiviert die Verkettung (dies ist der Standard).treq_sel:
intWählt ein Transfer-Request-Signal aus. Details finden Sie in Abschnitt 2.5.3 des RP2040-Datenblatts.irq_quiet:
boolGeneriert am Ende jeder Übertragung keinen Interrupt. Stattdessen werden Interrupts generiert, wenn ein Wert von null in das Trigger-Register geschrieben wird, was eine Sequenz verketteter Übertragungen anhält (Standard:True).bswap:
boolWenn auf true gesetzt, werden Bytes in Wörtern oder Halbwörtern vor dem Schreiben umgekehrt (Standard:True).sniff_en:
boolAufTruesetzen, um den Zugriff auf die Daten durch die Sniff-Hardware des Chips zu erlauben (Standard:False).write_err:
boolDas Setzen aufTruelöscht einen zuvor gemeldeten Schreibfehler.read_err:
boolDas Setzen aufTruelöscht einen zuvor gemeldeten Lesefehler.
Eine Beschreibung aller dieser Felder finden Sie in der Beschreibung des Registers
CH0_CTRL_TRIGin Abschnitt 2.5.7 des RP2040-Datenblatts.
- unpack_ctrl(value: int) dict¶
Entpackt einen Wert für ein DMA-Kanal-Steuerregister in ein Dictionary mit Schlüssel/Wert-Paaren für jedes der Felder im Steuerregister. value ist der zu entpackende Wert des Registers
ctrl.Diese Methode gibt Werte für alle Schlüssel zurück, die an
DMA.pack_ctrlübergeben werden können. Darüber hinaus gibt sie auch die schreibgeschützten Flags im Steuerregister zurück:busy, das hoch wird, wenn eine Übertragung beginnt, und niedrig, wenn sie endet, undahb_err, das die logische ODER-Verknüpfung der Flagsread_errundwrite_errist. Diese Werte werden beim Packen ignoriert, sodass das durch Entpacken eines Steuerregisters erstellte Dictionary direkt als Schlüsselwortargumente für das Packen verwendet werden kann.
- active(value: bool | None = None, /) bool¶
Liest oder setzt, ob der DMA-Kanal gerade läuft.
>>> sm.active() 0 >>> sm.active(1) >>> while sm.active(): ... pass
- read: int¶
Dieses Attribut spiegelt die Adresse wider, von der die nächste Busübertragung liest. Es kann entweder mit einer Ganzzahl oder einem Objekt beschrieben werden, das das Buffer-Protokoll unterstützt, und dies hat sofortige Wirkung.
- write: int¶
Dieses Attribut spiegelt die Adresse wider, an die die nächste Busübertragung schreibt. Es kann entweder mit einer Ganzzahl oder einem Objekt beschrieben werden, das das Buffer-Protokoll unterstützt, und dies hat sofortige Wirkung.
- count: int¶
Das Lesen dieses Attributs gibt die Anzahl der verbleibenden Busübertragungen in der aktuellen Übertragungssequenz zurück. Das Schreiben dieses Attributs setzt die Gesamtzahl der Übertragungen für die nächste Übertragungssequenz.
- ctrl: int¶
Dieses Attribut spiegelt das DMA-Kanal-Steuerregister wider. Es wird typischerweise mit einer Ganzzahl beschrieben, die mit der Methode
DMA.pack_ctrl()gepackt wurde. Der zurückgegebene Registerwert kann mit der MethodeDMA.unpack_ctrl()entpackt werden.
- channel: int¶
Die Kanalnummer des DMA-Kanals. Diese kann im Argument
chain_tovonDMA.pack_ctrl()eines anderen Kanals übergeben werden, um DMA-Verkettung zu ermöglichen.
- registers: 'memoryview'¶
Dieses Attribut ist ein array-ähnliches Objekt, das direkten Zugriff auf die Register des DMA-Kanals ermöglicht. Die Indizierung erfolgt nach Wort statt nach Byte, sodass die Registerindizes die durch 4 geteilten Registeradress-Offsets sind. Details zu den Registern finden Sie im RP2040-Datenblatt.
Verkettung und Zugriff auf das Trigger-Register¶
Der DMA-Controller im RP2040 bietet einige fortgeschrittene Funktionen, mit denen ein DMA-Kanal eine Übertragung auf einem anderen Kanal initiieren kann. Eine ist die Verwendung des Werts chain_to im Steuerregister und die andere das Schreiben in eines der Register des DMA-Kanals, das einen Trigger-Effekt hat. In Kombination mit der Fähigkeit, dass ein DMA-Kanal direkt in die DMA.registers eines anderen Kanals schreiben kann, ermöglicht dies die Durchführung komplexer Transaktionen ohne jegliches Eingreifen der CPU.
Nachfolgend ein Beispiel für die Verwendung von sowohl Verkettung als auch Register-Triggering, um das Sammeln mehrerer Datenblöcke in einem einzigen Ziel zu implementieren. Vollständige Details zu diesen Funktionen finden Sie in Abschnitt 2.5 des RP2040-Datenblatts, und der Code unten ist eine pythonische Version des Beispiels in Unterabschnitt 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)
Dieses Beispiel verharrt im Leerlauf, während es auf den Abschluss der Übertragung wartet; alternativ könnte es einen Interrupt-Handler setzen und sofort zurückkehren.