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 attributet DMA.ctrl ger 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: bool Sätt för att aktivera kanalen (standard: True).

  • high_pri: bool Gör denna kanals busstrafik till hög prioritet (standard: False).

  • size: int Överföringsstorlek: 0=byte, 1=halvord, 2=ord (standard: 2).

  • inc_read: bool Inkrementera läsadressen efter varje överföring (standard: True).

  • inc_write: bool Inkrementera skrivadressen efter varje överföring (standard: True).

  • ring_size: int Om värdet är skilt från noll kommer endast de nedersta ring_size bitarna i ett adressregister att ändras när en adress inkrementeras, vilket gör att adressen viras runt vid nästa 1 << ring_size-bytegräns. Vilken adress som viras runt styrs av flaggan ring_sel. Ett nollvärde inaktiverar adressomvridning.

  • ring_sel: bool Sätt till False för att låta ring_size gälla läsadressen eller True för att gälla skrivadressen.

  • chain_to: int Kanalnumret 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: int Välj en Transfer Request-signal. Se avsnitt 2.5.3 i RP2040-databladet för detaljer.

  • irq_quiet: bool Generera 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: bool Om satt till true kommer bytes i ord eller halvord att kastas om innan skrivning (standard: True).

  • sniff_en: bool Sätt till True för att tillåta att data kan nås av chipets sniff-hårdvara (standard: False).

  • write_err: bool Att sätta detta till True rensar ett tidigare rapporterat skrivfel.

  • read_err: bool Att sätta detta till True rensar ett tidigare rapporterat läsfel.

Se beskrivningen av registret CH0_CTRL_TRIG i 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, och ahb_err, som är den logiska OR-funktionen av flaggorna read_err och write_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 metoden DMA.unpack_ctrl().

channel: int

DMA-kanalens kanalnummer. Detta kan skickas i argumentet chain_to till DMA.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.