klasse DMA – toegang tot de DMA-controller van de RP2040¶
De klasse DMA biedt toegang tot de Direct Memory Access (DMA)-controller van de RP2040 en maakt het mogelijk om gegevens te verplaatsen tussen geheugenblokken en/of IO-registers. De DMA-controller heeft zijn eigen, afzonderlijke lees- en schrijfbusmasterverbindingen met de busstructuur en elk DMA-kanaal kan onafhankelijk gegevens van het ene adres lezen en naar het andere adres terugschrijven, waarbij optioneel een of beide pointers worden verhoogd, zodat het overdrachten kan uitvoeren namens de processor terwijl de processor andere taken uitvoert of een toestand met laag stroomverbruik aanneemt. De DMA-controller van de RP2040 heeft 12 onafhankelijke DMA-kanalen die gelijktijdig kunnen draaien. Voor de volledige details van het DMA-systeem van de RP2040, zie sectie 2.5 van de RP2040 Datasheet.
Voorbeelden¶
Het eenvoudigste gebruik van de DMA-controller is het verplaatsen van gegevens van het ene geheugenblok naar het andere. Dit kan worden bereikt met de volgende 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
Merk op dat hoewel dit voorbeeld in een inactieve lus blijft terwijl het wacht tot de overdracht is voltooid, het programma in deze tijd net zo goed nuttig werk zou kunnen verrichten.
Een ander, wellicht vaker voorkomend gebruik van de DMA-controller is het overdragen tussen geheugen en een IO-randapparaat. In deze situatie verandert het adres van het IO-register niet bij elke overdracht, maar moet het geheugenadres worden verhoogd. Het is ook nodig om het tempo van de overdracht te regelen om geen gegevens te schrijven voordat een randapparaat ze kan accepteren of ze te lezen voordat de gegevens gereed zijn, en dit kan worden geregeld met het veld treq_sel van het controleregister van het DMA-kanaal. De verschillende velden van het controleregister voor elk DMA-kanaal kunnen worden ingepakt met de methode DMA.pack_ctrl() en uitgepakt met de statische methode DMA.unpack_ctrl(). Code om gegevens van een byte-array naar de TX FIFO van een PIO-statemachine over te dragen, een byte tegelijk, ziet er als volgt uit:
# 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
)
Merk op dat in dit voorbeeld de waarde die voor het schrijfadres wordt opgegeven gewoon de PIO-statemachine is waarnaar we de gegevens sturen. Dit werkt omdat PIO-statemachines het bufferprotocol aanbieden, waardoor directe toegang tot hun data-FIFO-registers mogelijk is.
Constructor¶
- class rp2.DMA¶
Claimt een van de kanalen van de DMA-controller voor exclusief gebruik.
- config(read: 'int | _AnyReadableBuf | None' = None, write: 'int | _AnyWritableBuf | None' = None, count: int | None = None, ctrl: int | None = None, trigger: bool = False) None¶
Configureert de DMA-registers voor het kanaal en start optioneel de overdracht. De parameters zijn:
read: Het adres vanwaar de DMA-controller begint met het lezen van gegevens, of een object dat de te lezen gegevens levert. Het kan een geheel getal zijn of elk object dat het bufferprotocol ondersteunt.
write: Het adres waarnaar de DMA-controller begint met schrijven, of een object waarin gegevens worden geschreven. Het kan een geheel getal zijn of elk object dat het bufferprotocol ondersteunt.
count: Het aantal busoverdrachten dat wordt uitgevoerd voordat dit kanaal stopt. Merk op dat dit het aantal overdrachten is, niet het aantal bytes. Als de overdrachten 2 of 4 bytes breed zijn, moet de totale hoeveelheid verplaatste gegevens (en dus de grootte van de vereiste buffer) dienovereenkomstig worden vermenigvuldigd.
ctrl: De waarde voor het DMA-controleregister. Dit is een geheeltallige waarde die doorgaans wordt ingepakt met
DMA.pack_ctrl().trigger: Start de overdracht optioneel onmiddellijk.
- irq(handler: Callable[[DMA], None] | None = None, hard: bool = False) Callable¶
Geeft het IRQ-object voor dit DMA-kanaal terug en configureert het optioneel.
- close() None¶
Geeft de claim op het onderliggende DMA-kanaal vrij en geeft de interrupt-handler vrij. Het
DMA-object kan na deze bewerking niet meer worden gebruikt.
- pack_ctrl(default: int | None = None, **kwargs) int¶
Pakt de waarden die in de keyword-argumenten zijn opgegeven in de benoemde velden van een nieuwe controleregisterwaarde. Elk veld dat niet wordt opgegeven, wordt op een standaardwaarde ingesteld. De standaardwaarde wordt ofwel ontleend aan de opgegeven
default-waarde, of, als die niet is opgegeven, aan een standaardwaarde die geschikt is voor het huidige kanaal; door dit in te stellen op de huidige waarde van het attribuutDMA.ctrlheb je een eenvoudige manier om een deelverzameling van de velden te overschrijven.De sleutels voor de keyword-argumenten kunnen elke sleutel zijn die door de methode
DMA.unpack_ctrl()wordt teruggegeven. De schrijfbare waarden zijn:enable:
boolInstellen om het kanaal in te schakelen (standaard:True).high_pri:
boolGeeft het busverkeer van dit kanaal hoge prioriteit (standaard:False).size:
intOverdrachtsgrootte: 0=byte, 1=half woord, 2=woord (standaard: 2).inc_read:
boolVerhoogt het leesadres na elke overdracht (standaard:True).inc_write:
boolVerhoogt het schrijfadres na elke overdracht (standaard:True).ring_size:
intAls dit niet nul is, veranderen alleen de onderstering_sizebits van een adresregister wanneer een adres wordt verhoogd, waardoor het adres terugloopt bij de volgende1 << ring_size-bytegrens. Welk adres terugloopt, wordt geregeld door de vlagring_sel. Een nulwaarde schakelt het teruglopen van adressen uit.ring_sel:
boolStel in opFalseom dering_sizetoe te passen op het leesadres of opTrueom deze toe te passen op het schrijfadres.chain_to:
intHet kanaalnummer van een kanaal dat moet worden getriggerd nadat deze overdracht is voltooid. Door deze waarde in te stellen op het eigen kanaalnummer van dit DMA-object wordt het koppelen uitgeschakeld (dit is de standaardinstelling).treq_sel:
intSelecteert een Transfer Request-signaal. Zie sectie 2.5.3 in de RP2040-datasheet voor details.irq_quiet:
boolGenereer geen interrupt aan het einde van elke overdracht. In plaats daarvan worden interrupts gegenereerd wanneer een nulwaarde naar het triggerregister wordt geschreven, wat een reeks gekoppelde overdrachten stopt (standaard:True).bswap:
boolIndien ingesteld op true, worden bytes in woorden of halve woorden omgekeerd voordat ze worden geschreven (standaard:True).sniff_en:
boolStel in opTrueom gegevens toegankelijk te maken voor de sniff-hardware van de chip (standaard:False).write_err:
boolDoor dit opTruein te stellen, wordt een eerder gemelde schrijffout gewist.read_err:
boolDoor dit opTruein te stellen, wordt een eerder gemelde leesfout gewist.
Zie de beschrijving van het register
CH0_CTRL_TRIGin sectie 2.5.7 van de RP2040-datasheet voor details over al deze velden.
- unpack_ctrl(value: int) dict¶
Pakt een waarde voor een DMA-kanaalcontroleregister uit in een dictionary met sleutel/waarde-paren voor elk van de velden in het controleregister. value is de uit te pakken
ctrl-registerwaarde.Deze methode geeft waarden terug voor alle sleutels die aan
DMA.pack_ctrlkunnen worden doorgegeven. Daarnaast geeft het ook de alleen-lezen vlaggen in het controleregister terug:busy, die hoog wordt wanneer een overdracht begint en laag wanneer deze eindigt, enahb_err, dat de logische OR is van de vlaggenread_errenwrite_err. Deze waarden worden genegeerd bij het inpakken, zodat de dictionary die wordt gemaakt door het uitpakken van een controleregister direct kan worden gebruikt als de keyword-argumenten voor het inpakken.
- active(value: bool | None = None, /) bool¶
Haalt op of stelt in of het DMA-kanaal momenteel actief is.
>>> sm.active() 0 >>> sm.active(1) >>> while sm.active(): ... pass
- read: int¶
Dit attribuut weerspiegelt het adres vanwaar de volgende busoverdracht zal lezen. Het kan worden geschreven met ofwel een geheel getal ofwel een object dat het bufferprotocol ondersteunt, en dit heeft onmiddellijk effect.
- write: int¶
Dit attribuut weerspiegelt het adres waarnaar de volgende busoverdracht zal schrijven. Het kan worden geschreven met ofwel een geheel getal ofwel een object dat het bufferprotocol ondersteunt, en dit heeft onmiddellijk effect.
- count: int¶
Het lezen van dit attribuut geeft het aantal resterende busoverdrachten in de huidige overdrachtsreeks terug. Het schrijven van dit attribuut stelt het totale aantal overdrachten voor de volgende overdrachtsreeks in.
- ctrl: int¶
Dit attribuut weerspiegelt het DMA-kanaalcontroleregister. Het wordt doorgaans geschreven met een geheel getal dat is ingepakt met de methode
DMA.pack_ctrl(). De teruggegeven registerwaarde kan worden uitgepakt met de methodeDMA.unpack_ctrl().
- channel: int¶
Het kanaalnummer van het DMA-kanaal. Dit kan worden doorgegeven in het argument
chain_tovanDMA.pack_ctrl()op een ander kanaal om DMA-koppeling mogelijk te maken.
- registers: 'memoryview'¶
Dit attribuut is een array-achtig object dat directe toegang biedt tot de registers van het DMA-kanaal. De index is per woord, niet per byte, dus de registerindices zijn de registeradresoffsets gedeeld door 4. Zie de RP2040-datasheet voor registerdetails.
Koppeling en toegang tot triggerregisters¶
De DMA-controller in de RP2040 biedt een aantal geavanceerde functies waarmee een DMA-kanaal een overdracht op een ander kanaal kan starten. De ene is het gebruik van de waarde chain_to in het controleregister en de andere is het schrijven naar een van de registers van het DMA-kanaal dat een triggereffect heeft. In combinatie met de mogelijkheid om een DMA-kanaal rechtstreeks naar de DMA.registers van een ander kanaal te laten schrijven, maakt dit het mogelijk om complexe transacties uit te voeren zonder enige CPU-tussenkomst.
Hieronder staat een voorbeeld van het gebruik van zowel koppeling als registertriggering om het verzamelen van meerdere gegevensblokken in een enkele bestemming te implementeren. De volledige details van deze functies zijn te vinden in sectie 2.5 van de RP2040-datasheet en de onderstaande code is een Pythonische versie van het voorbeeld in subsectie 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)
Dit voorbeeld blijft inactief terwijl het wacht tot de overdracht is voltooid; als alternatief zou het een interrupt-handler kunnen instellen en onmiddellijk terugkeren.