class DMA – åtkomst till RP2040:s DMA-styrenhet¶
Klassen DMA ger åtkomst till RP2040:s styrenhet för direktminnesåtkomst (DMA), vilket gör det möjligt att flytta data mellan minnesblock och/eller IO-register. DMA-styrenheten har sina egna, separata bussmaster-anslutningar för läsning och skrivning till bussväven och varje DMA-kanal kan oberoende läsa data från en adress och skriva tillbaka den till en annan adress, och eventuellt inkrementera en eller båda pekarna, vilket gör det möjligt att utföra överföringar å processorns vägnar medan processorn utför andra uppgifter eller går in i ett lågeffektsläge. RP2040:s DMA-styrenhet har 12 oberoende DMA-kanaler som kan köras samtidigt. För fullständiga detaljer om RP2040:s DMA-system, se avsnitt 2.5 i RP2040 Datasheet.
Exempel¶
Den enklaste användningen av DMA-styrenheten är att flytta data från ett minnesblock till ett annat. Detta kan åstadkommas med följande kod:
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
Observera att även om detta exempel väntar i en tomgångsslinga medan det väntar på att överföringen ska slutföras, skulle programmet lika gärna kunna utföra något nyttigt arbete under denna tid istället.
En annan, kanske vanligare användning av DMA-styrenheten är att överföra mellan minne och en IO-kringutrustning. I denna situation ändras inte adressen till IO-registret för varje överföring, men minnesadressen behöver inkrementeras. Det är också nödvändigt att kontrollera överföringstakten så att data inte skrivs innan det kan accepteras av en kringutrustning eller läses innan datan är redo, och detta kan kontrolleras med fältet treq_sel i DMA-kanalens kontrollregister. De olika fälten i kontrollregistret för varje DMA-kanal kan packas med metoden DMA.pack_ctrl() och packas upp med den statiska metoden DMA.unpack_ctrl(). Kod för att överföra data från en bytearray till TX-FIFO:n hos en PIO-tillståndsmaskin, en byte i taget, ser ut så här:
# 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
)
Observera att i detta exempel är värdet som anges för skrivadressen helt enkelt den PIO-tillståndsmaskin till vilken vi skickar datan. Detta fungerar eftersom PIO-tillståndsmaskiner uppvisar buffertprotokollet, vilket ger direkt åtkomst till deras data-FIFO-register.
Konstruktor¶
- class rp2.DMA¶
Reservera en av DMA-styrenhetens kanaler för exklusiv användning.
- config(read: 'int | _AnyReadableBuf | None' = None, write: 'int | _AnyWritableBuf | None' = None, count: int | None = None, ctrl: int | None = None, trigger: bool = False) None¶
Konfigurera DMA-registren för kanalen och starta eventuellt överföringen. Parametrar är:
read: Adressen från vilken DMA-styrenheten börjar läsa data, eller ett objekt som tillhandahåller data som ska läsas. Det kan vara ett heltal eller vilket objekt som helst som stöder buffertprotokollet.
write: Adressen till vilken DMA-styrenheten börjar skriva, eller ett objekt till vilket data ska skrivas. Det kan vara ett heltal eller vilket objekt som helst som stöder buffertprotokollet.
count: Antalet bussöverföringar som utförs innan denna kanal stoppar. Observera att detta är antalet överföringar, inte antalet bytes. Om överföringarna är 2 eller 4 bytes breda måste den totala mängden data som flyttas (och därmed storleken på den buffert som krävs) multipliceras i motsvarande grad.
ctrl: Värdet för DMA-kontrollregistret. Detta är ett heltalsvärde som vanligtvis packas med
DMA.pack_ctrl().trigger: Påbörja eventuellt överföringen omedelbart.
- irq(handler: Callable[[DMA], None] | None = None, hard: bool = False) Callable¶
Returnerar IRQ-objektet för denna DMA-kanal och konfigurerar det eventuellt.
- close() None¶
Frigör reservationen på den underliggande DMA-kanalen och frigör avbrottshanteraren.
DMA-objektet kan inte användas efter denna operation.
- pack_ctrl(default: int | None = None, **kwargs) int¶
Packa värdena som anges i nyckelordsargumenten in i de namngivna fälten i ett nytt kontrollregistervärde. Varje fält som inte anges sätts till ett standardvärde. Standardvärdet hämtas antingen från det angivna
default-värdet, eller om det inte anges, ett standardvärde som är lämpligt för den aktuella kanalen; att sätta detta till det aktuella värdet av attributetDMA.ctrlger ett enkelt sätt att åsidosätta en delmängd av fälten.Nycklarna för nyckelordsargumenten kan vara vilken nyckel som helst som returneras av metoden
DMA.unpack_ctrl(). De skrivbara värdena är:enable:
boolSätt för att aktivera kanalen (standard:True).high_pri:
boolGör denna kanals busstrafik till hög prioritet (standard:False).size:
intÖverföringsstorlek: 0=byte, 1=halvord, 2=ord (standard: 2).inc_read:
boolInkrementera läsadressen efter varje överföring (standard:True).inc_write:
boolInkrementera skrivadressen efter varje överföring (standard:True).ring_size:
intOm värdet är skilt från noll kommer endast de nederstaring_sizebitarna i ett adressregister att ändras när en adress inkrementeras, vilket gör att adressen viras runt vid nästa1 << ring_size-bytegräns. Vilken adress som viras runt styrs av flagganring_sel. Ett nollvärde inaktiverar adressomvridning.ring_sel:
boolSätt tillFalseför att låtaring_sizegälla läsadressen ellerTrueför att gälla skrivadressen.chain_to:
intKanalnumret för en kanal som ska utlösas efter att denna överföring slutförts. Att sätta detta värde till detta DMA-objekts eget kanalnummer inaktiverar kedjning (detta är standard).treq_sel:
intVälj en Transfer Request-signal. Se avsnitt 2.5.3 i RP2040-databladet för detaljer.irq_quiet:
boolGenerera inte avbrott i slutet av varje överföring. Avbrott genereras istället när ett nollvärde skrivs till utlösarregistret, vilket stoppar en sekvens av kedjade överföringar (standard:True).bswap:
boolOm satt till true kommer bytes i ord eller halvord att kastas om innan skrivning (standard:True).sniff_en:
boolSätt tillTrueför att tillåta att data kan nås av chipets sniff-hårdvara (standard:False).write_err:
boolAtt sätta detta tillTruerensar ett tidigare rapporterat skrivfel.read_err:
boolAtt sätta detta tillTruerensar ett tidigare rapporterat läsfel.
Se beskrivningen av registret
CH0_CTRL_TRIGi avsnitt 2.5.7 i RP2040-databladet för detaljer om alla dessa fält.
- unpack_ctrl(value: int) dict¶
Packa upp ett värde för en DMA-kanals kontrollregister till en ordbok med nyckel/värde-par för vart och ett av fälten i kontrollregistret. value är det
ctrl-registervärde som ska packas upp.Denna metod returnerar värden för alla nycklar som kan skickas till
DMA.pack_ctrl. Dessutom returnerar den även de skrivskyddade flaggorna i kontrollregistret:busy, som blir hög när en överföring startar och låg när den avslutas, ochahb_err, som är den logiska OR-funktionen av flaggornaread_errochwrite_err. Dessa värden ignoreras vid packning, så att ordboken som skapas genom att packa upp ett kontrollregister kan användas direkt som nyckelordsargument för packning.
- active(value: bool | None = None, /) bool¶
Hämtar eller ställer in huruvida DMA-kanalen för närvarande körs.
>>> sm.active() 0 >>> sm.active(1) >>> while sm.active(): ... pass
- read: int¶
Detta attribut speglar adressen från vilken nästa bussöverföring kommer att läsa. Det kan skrivas med antingen ett heltal eller ett objekt som stöder buffertprotokollet och att göra så får omedelbar verkan.
- write: int¶
Detta attribut speglar adressen till vilken nästa bussöverföring kommer att skriva. Det kan skrivas med antingen ett heltal eller ett objekt som stöder buffertprotokollet och att göra så får omedelbar verkan.
- count: int¶
Att läsa detta attribut returnerar antalet återstående bussöverföringar i den aktuella överföringssekvensen. Att skriva detta attribut ställer in det totala antalet överföringar för den nästa överföringssekvensen.
- ctrl: int¶
Detta attribut speglar DMA-kanalens kontrollregister. Det skrivs vanligtvis med ett heltal som packats med metoden
DMA.pack_ctrl(). Det returnerade registervärdet kan packas upp med metodenDMA.unpack_ctrl().
- channel: int¶
DMA-kanalens kanalnummer. Detta kan skickas i argumentet
chain_totillDMA.pack_ctrl()på en annan kanal för att möjliggöra DMA-kedjning.
- registers: 'memoryview'¶
Detta attribut är ett array-liknande objekt som ger direkt åtkomst till DMA-kanalens register. Indexeringen sker per ord, snarare än per byte, så registerindexen är registeradressförskjutningarna dividerade med 4. Se RP2040-databladet för registerdetaljer.
Kedjning och åtkomst till utlösarregister¶
DMA-styrenheten i RP2040 erbjuder ett par avancerade funktioner som gör att en DMA-kanal kan initiera en överföring på en annan kanal. Den ena är användningen av värdet chain_to i kontrollregistret och den andra är att skriva till ett av DMA-kanalens register som har en utlösareffekt. När detta kombineras med möjligheten att låta en DMA-kanal skriva direkt till DMA.registers hos en annan kanal, möjliggör detta att komplexa transaktioner utförs utan någon CPU-inblandning.
Nedan följer ett exempel på användning av både kedjning och registerutlösning för att implementera insamling av flera datablock till en enda destination. Fullständiga detaljer om dessa funktioner finns i avsnitt 2.5 i RP2040-databladet och koden nedan är en Pythonsk version av exemplet i underavsnitt 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)
Detta exempel går på tomgång medan det väntar på att överföringen ska slutföras; alternativt skulle det kunna ställa in en avbrottshanterare och returnera omedelbart.