DMA osztály – hozzáférés az RP2040 DMA vezérlőjéhez

A DMA osztály hozzáférést biztosít az RP2040 közvetlen memóriaelérési (DMA) vezérlőjéhez, lehetővé téve az adatok mozgatását memóriablokkok és/vagy IO regiszterek között. A DMA vezérlőnek saját, külön olvasási és írási busz-master kapcsolatai vannak a buszrendszerhez, és minden DMA csatorna egymástól függetlenül tud adatot olvasni egy címről és visszaírni egy másik címre, opcionálisan az egyik vagy mindkét mutatót növelve, így a processzor helyett tud átviteleket végrehajtani, miközben a processzor más feladatokat végez vagy alacsony fogyasztású állapotba lép. Az RP2040 DMA vezérlője 12 független DMA csatornával rendelkezik, amelyek egyidejűleg futhatnak. Az RP2040 DMA rendszerének teljes részleteit lásd az RP2040 Datasheet 2.5. szakaszában.

Példák

A DMA vezérlő legegyszerűbb felhasználása az adatok egyik memóriablokkból a másikba való mozgatása. Ez a következő kóddal valósítható meg:

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

Vegye figyelembe, hogy bár ez a példa egy üresjárati ciklusban várakozik az átvitel befejezésére, a program ehelyett ezalatt akár valamilyen hasznos munkát is végezhetne.

A DMA vezérlő egy másik, talán gyakoribb felhasználása a memória és egy IO periféria közötti átvitel. Ebben a helyzetben az IO regiszter címe nem változik az egyes átviteleknél, de a memóriacímet növelni kell. Szükséges továbbá az átvitel ütemének szabályozása is, hogy az adat ne kerüljön kiírásra, mielőtt egy periféria fogadni tudná, illetve ne kerüljön beolvasásra, mielőtt az adat készen áll; ezt a DMA csatorna vezérlőregiszterének treq_sel mezőjével lehet szabályozni. Az egyes DMA csatornák vezérlőregiszterének különböző mezői a DMA.pack_ctrl() metódussal csomagolhatók be, és a DMA.unpack_ctrl() statikus metódussal csomagolhatók ki. Egy bájttömbből egy PIO állapotgép TX FIFO-jába egyesével történő bájtátvitel kódja így néz ki:

# 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
)

Vegye figyelembe, hogy ebben a példában az írási címhez megadott érték egyszerűen az a PIO állapotgép, amelynek az adatokat küldjük. Ez azért működik, mert a PIO állapotgépek megvalósítják a puffer protokollt, lehetővé téve az adat FIFO regisztereik közvetlen elérését.

Konstruktor

class rp2.DMA

Lefoglalja a DMA vezérlő egyik csatornáját kizárólagos használatra.

config(read: 'int | _AnyReadableBuf | None' = None, write: 'int | _AnyWritableBuf | None' = None, count: int | None = None, ctrl: int | None = None, trigger: bool = False) None

Beállítja a csatorna DMA regisztereit, és opcionálisan elindítja az átvitelt. A paraméterek a következők:

  • read: A cím, ahonnan a DMA vezérlő elkezdi olvasni az adatokat, vagy egy objektum, amely az olvasandó adatokat szolgáltatja. Lehet egész szám vagy bármely olyan objektum, amely támogatja a puffer protokollt.

  • write: A cím, ahová a DMA vezérlő elkezdi az írást, vagy egy objektum, amelybe az adatok kerülnek. Lehet egész szám vagy bármely olyan objektum, amely támogatja a puffer protokollt.

  • count: A busz-átvitelek száma, amelyek lefutnak, mielőtt ez a csatorna leáll. Vegye figyelembe, hogy ez az átvitelek száma, nem a bájtok száma. Ha az átvitelek 2 vagy 4 bájt szélesek, akkor a mozgatott adatok teljes mennyiségét (és így a szükséges puffer méretét) ennek megfelelően kell megszorozni.

  • ctrl: A DMA vezérlőregiszter értéke. Ez egy egész szám, amelyet jellemzően a DMA.pack_ctrl() segítségével csomagolnak be.

  • trigger: Opcionálisan azonnal elindítja az átvitelt.

irq(handler: Callable[[DMA], None] | None = None, hard: bool = False) Callable

Visszaadja ehhez a DMA csatornához tartozó IRQ objektumot, és opcionálisan konfigurálja azt.

close() None

Felszabadítja az alapul szolgáló DMA csatorna lefoglalását, és felszabadítja a megszakításkezelőt. A DMA objektum nem használható ezen művelet után.

pack_ctrl(default: int | None = None, **kwargs) int

Becsomagolja a kulcsszó-argumentumokban megadott értékeket egy új vezérlőregiszter-érték elnevezett mezőibe. Minden meg nem adott mező egy alapértelmezett értéket kap. Az alapértelmezett értéket vagy a megadott default értékből veszi, vagy ha ez nincs megadva, akkor az aktuális csatornához megfelelő alapértelmezett értéket; ennek a DMA.ctrl attribútum aktuális értékére állítása egyszerű módot kínál a mezők egy részhalmazának felülírására.

A kulcsszó-argumentumok kulcsai a DMA.unpack_ctrl() metódus által visszaadott bármely kulcs lehetnek. Az írható értékek a következők:

  • enable: bool Állítsa be a csatorna engedélyezéséhez (alapértelmezett: True).

  • high_pri: bool Ennek a csatornának a busz-forgalmát magas prioritásúvá teszi (alapértelmezett: False).

  • size: int Átviteli méret: 0=bájt, 1=félszó, 2=szó (alapértelmezett: 2).

  • inc_read: bool Növeli az olvasási címet minden átvitel után (alapértelmezett: True).

  • inc_write: bool Növeli az írási címet minden átvitel után (alapértelmezett: True).

  • ring_size: int Ha nem nulla, akkor az egyik címregiszternek csak az alsó ring_size bitje változik a cím növelésekor, ami azt eredményezi, hogy a cím a következő 1 << ring_size bájtos határnál körbefordul. Hogy melyik cím fordul körbe, azt a ring_sel jelző szabályozza. A nulla érték kikapcsolja a cím körbefordulását.

  • ring_sel: bool Állítsa False értékre, hogy a ring_size az olvasási címre vonatkozzon, vagy True értékre, hogy az írási címre vonatkozzon.

  • chain_to: int Annak a csatornának a száma, amelyet ezen átvitel befejezése után el kell indítani. Ezen érték a saját DMA objektum csatornaszámára állítása kikapcsolja a láncolást (ez az alapértelmezett).

  • treq_sel: int Kiválaszt egy Transfer Request jelet. A részletekért lásd az RP2040 adatlap 2.5.3. szakaszát.

  • irq_quiet: bool Ne generáljon megszakítást minden átvitel végén. Ehelyett megszakítások akkor generálódnak, amikor egy nulla érték kerül a trigger regiszterbe, ami megállítja a láncolt átvitelek sorozatát (alapértelmezett: True).

  • bswap: bool Ha igazra van állítva, a szavakban vagy félszavakban lévő bájtok megfordulnak az írás előtt (alapértelmezett: True).

  • sniff_en: bool Állítsa True értékre, hogy az adatokat a chip sniff hardvere elérhesse (alapértelmezett: False).

  • write_err: bool Ennek True értékre állítása törli a korábban jelentett írási hibát.

  • read_err: bool Ennek True értékre állítása törli a korábban jelentett olvasási hibát.

Ezen mezők részleteit lásd a CH0_CTRL_TRIG regiszter leírásában, az RP2040 adatlap 2.5.7. szakaszában.

unpack_ctrl(value: int) dict

Kicsomagol egy DMA csatorna vezérlőregiszter értéket egy szótárba, amely kulcs/érték párokat tartalmaz a vezérlőregiszter minden mezőjéhez. A value a kicsomagolandó ctrl regiszter értéke.

Ez a metódus visszaadja az összes olyan kulcs értékét, amelyek átadhatók a DMA.pack_ctrl függvénynek. Ezen felül visszaadja a vezérlőregiszter csak olvasható jelzőit is: a busy jelzőt, amely magasra vált, amikor egy átvitel elindul, és alacsonyra, amikor véget ér, valamint az ahb_err jelzőt, amely a read_err és write_err jelzők logikai VAGY kapcsolata. Ezek az értékek figyelmen kívül maradnak a becsomagoláskor, így egy vezérlőregiszter kicsomagolásával létrehozott szótár közvetlenül használható a becsomagolás kulcsszó-argumentumaiként.

active(value: bool | None = None, /) bool

Lekérdezi vagy beállítja, hogy a DMA csatorna éppen fut-e.

>>> sm.active()
0
>>> sm.active(1)
>>> while sm.active():
...     pass
read: int

Ez az attribútum azt a címet tükrözi, ahonnan a következő busz-átvitel olvasni fog. Beírható egész számmal vagy a puffer protokollt támogató objektummal is, és ez azonnali hatású.

write: int

Ez az attribútum azt a címet tükrözi, ahová a következő busz-átvitel írni fog. Beírható egész számmal vagy a puffer protokollt támogató objektummal is, és ez azonnali hatású.

count: int

Ennek az attribútumnak az olvasása az aktuális átviteli sorozatban hátralévő busz-átvitelek számát adja vissza. Ennek az attribútumnak az írása a következő átviteli sorozat átviteleinek teljes számát állítja be.

ctrl: int

Ez az attribútum a DMA csatorna vezérlőregiszterét tükrözi. Jellemzően a DMA.pack_ctrl() metódussal becsomagolt egész számmal írják. A visszaadott regiszter-érték a DMA.unpack_ctrl() metódussal csomagolható ki.

channel: int

A DMA csatorna csatornaszáma. Ez átadható egy másik csatorna DMA.pack_ctrl() függvényének chain_to argumentumaként a DMA láncolás engedélyezéséhez.

registers: 'memoryview'

Ez az attribútum egy tömbszerű objektum, amely közvetlen hozzáférést tesz lehetővé a DMA csatorna regisztereihez. Az indexelés szavanként történik, nem bájtonként, így a regiszter-indexek a regiszter-címek eltolásai 4-gyel osztva. A regiszterek részleteit lásd az RP2040 adatlapban.

Láncolás és trigger regiszter elérése

Az RP2040 DMA vezérlője néhány fejlett funkciót kínál, amelyek lehetővé teszik, hogy egy DMA csatorna átvitelt kezdeményezzen egy másik csatornán. Az egyik a chain_to érték használata a vezérlőregiszterben, a másik pedig az írás a DMA csatorna egyik olyan regiszterébe, amelynek trigger hatása van. Azzal a képességgel párosítva, hogy egy DMA csatorna közvetlenül egy másik csatorna DMA.registers regisztereibe írhat, ez lehetővé teszi összetett tranzakciók végrehajtását mindenféle CPU beavatkozás nélkül.

Az alábbiakban egy példa látható a láncolás és a regiszter-triggerelés együttes használatára, amely több adatblokk egyetlen célhelyre való gyűjtését valósítja meg. Ezen funkciók teljes részletei az RP2040 adatlap 2.5. szakaszában találhatók, az alábbi kód pedig a 2.5.6.2. alszakaszban szereplő példa Pythonos változata.

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)

Ez a példa üresjáratban várakozik az átvitel befejezésére; ehelyett beállíthatna egy megszakításkezelőt, és azonnal visszatérhetne.