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
DMAobjektum 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 aDMA.ctrlattribú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:
boolEnnek 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:
boolNöveli az olvasási címet minden átvitel után (alapértelmezett:True).inc_write:
boolNöveli az írási címet minden átvitel után (alapértelmezett:True).ring_size:
intHa nem nulla, akkor az egyik címregiszternek csak az alsóring_sizebitje változik a cím növelésekor, ami azt eredményezi, hogy a cím a következő1 << ring_sizebájtos határnál körbefordul. Hogy melyik cím fordul körbe, azt aring_seljelző szabályozza. A nulla érték kikapcsolja a cím körbefordulását.ring_sel:
boolÁllítsaFalseértékre, hogy aring_sizeaz olvasási címre vonatkozzon, vagyTrueértékre, hogy az írási címre vonatkozzon.chain_to:
intAnnak 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:
intKiválaszt egy Transfer Request jelet. A részletekért lásd az RP2040 adatlap 2.5.3. szakaszát.irq_quiet:
boolNe 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:
boolHa 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ítsaTrueértékre, hogy az adatokat a chip sniff hardvere elérhesse (alapértelmezett:False).write_err:
boolEnnekTrueértékre állítása törli a korábban jelentett írási hibát.read_err:
boolEnnekTrueé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_TRIGregiszter 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ó
ctrlregiszter értéke.Ez a metódus visszaadja az összes olyan kulcs értékét, amelyek átadhatók a
DMA.pack_ctrlfüggvénynek. Ezen felül visszaadja a vezérlőregiszter csak olvasható jelzőit is: abusyjelzőt, amely magasra vált, amikor egy átvitel elindul, és alacsonyra, amikor véget ér, valamint azahb_errjelzőt, amely aread_erréswrite_errjelző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 aDMA.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énekchain_toargumentumaké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.