třída DMA – přístup k DMA řadiči RP2040

Třída DMA nabízí přístup k řadiči Direct Memory Access (DMA) čipu RP2040 a poskytuje možnost přesouvat data mezi paměťovými bloky a/nebo IO registry. Řadič DMA má vlastní, samostatné připojení k čtecí a zapisovací sběrnici (bus master) k propojovací sběrnici a každý DMA kanál může nezávisle číst data z jedné adresy a zapisovat je na jinou adresu, volitelně s inkrementací jednoho nebo obou ukazatelů, což mu umožňuje provádět přenosy za procesor, zatímco procesor vykonává jiné úlohy nebo přejde do stavu nízké spotřeby. Řadič DMA čipu RP2040 má 12 nezávislých DMA kanálů, které mohou běžet souběžně. Úplné podrobnosti o systému DMA čipu RP2040 najdete v části 2.5 dokumentu RP2040 Datasheet.

Příklady

Nejjednodušším použitím řadiče DMA je přesun dat z jednoho paměťového bloku do druhého. Toho lze dosáhnout následujícím kódem:

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

Všimněte si, že zatímco tento příklad setrvává v nečinné smyčce, dokud čeká na dokončení přenosu, program by mohl v této době stejně dobře vykonávat nějakou užitečnou práci.

Dalším, snad běžnějším použitím řadiče DMA je přenos mezi pamětí a IO periferií. V této situaci se adresa IO registru pro každý přenos nemění, ale paměťová adresa musí být inkrementována. Je rovněž nutné řídit tempo přenosu tak, aby se data nezapisovala dříve, než je periferie může přijmout, nebo nečetla dříve, než jsou data připravena; to lze řídit polem treq_sel řídicího registru DMA kanálu. Jednotlivá pole řídicího registru každého DMA kanálu lze sbalit metodou DMA.pack_ctrl() a rozbalit statickou metodou DMA.unpack_ctrl(). Kód pro přenos dat z bajtového pole do TX FIFO PIO stavového automatu, vždy po jednom bajtu, vypadá takto:

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

Všimněte si, že v tomto příkladu je hodnota zadaná pro zapisovací adresu pouze PIO stavový automat, kterému data posíláme. Funguje to proto, že PIO stavové automaty poskytují protokol buffer, který umožňuje přímý přístup k jejich datovým FIFO registrům.

Konstruktor

class rp2.DMA

Vyhradí jeden z kanálů řadiče DMA pro výhradní použití.

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

Nakonfiguruje DMA registry pro kanál a volitelně spustí přenos. Parametry jsou:

  • read: Adresa, ze které řadič DMA začne číst data, nebo objekt, který poskytne data ke čtení. Může to být celé číslo nebo libovolný objekt podporující protokol buffer.

  • write: Adresa, na kterou řadič DMA začne zapisovat, nebo objekt, do kterého se budou data zapisovat. Může to být celé číslo nebo libovolný objekt podporující protokol buffer.

  • count: Počet sběrnicových přenosů, které proběhnou, než se tento kanál zastaví. Všimněte si, že se jedná o počet přenosů, nikoli o počet bajtů. Pokud jsou přenosy 2 nebo 4 bajty široké, je třeba celkové množství přesunutých dat (a tedy velikost potřebného bufferu) odpovídajícím způsobem vynásobit.

  • ctrl: Hodnota pro řídicí registr DMA. Jedná se o celočíselnou hodnotu, která se obvykle sbalí pomocí DMA.pack_ctrl().

  • trigger: Volitelně okamžitě zahájí přenos.

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

Vrátí objekt IRQ pro tento DMA kanál a volitelně jej nakonfiguruje.

close() None

Uvolní nárok na podkladový DMA kanál a uvolní obsluhu přerušení. Objekt DMA nelze po této operaci použít.

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

Sbalí hodnoty zadané v pojmenovaných argumentech do pojmenovaných polí nové hodnoty řídicího registru. Jakékoli pole, které není zadáno, bude nastaveno na výchozí hodnotu. Výchozí hodnota bude buď převzata ze zadané hodnoty default, nebo pokud ta není zadána, použije se výchozí hodnota vhodná pro aktuální kanál; nastavení této hodnoty na aktuální hodnotu atributu DMA.ctrl poskytuje snadný způsob, jak přepsat podmnožinu polí.

Klíče pro pojmenované argumenty mohou být libovolné klíče vrácené metodou DMA.unpack_ctrl(). Zapisovatelné hodnoty jsou:

  • enable: bool Nastavte pro povolení kanálu (výchozí: True).

  • high_pri: bool Označí sběrnicový provoz tohoto kanálu jako vysoce prioritní (výchozí: False).

  • size: int Velikost přenosu: 0=bajt, 1=půlslovo, 2=slovo (výchozí: 2).

  • inc_read: bool Inkrementuje čtecí adresu po každém přenosu (výchozí: True).

  • inc_write: bool Inkrementuje zapisovací adresu po každém přenosu (výchozí: True).

  • ring_size: int Je-li nenulové, při inkrementaci adresy se změní pouze spodní ring_size bitů jednoho adresního registru, což způsobí zalomení adresy na nejbližší hranici 1 << ring_size bajtů. Která adresa se zalomí, řídí příznak ring_sel. Nulová hodnota zalomení adresy vypne.

  • ring_sel: bool Nastavte na False, aby se ring_size aplikovalo na čtecí adresu, nebo na True pro aplikaci na zapisovací adresu.

  • chain_to: int Číslo kanálu, který se má spustit po dokončení tohoto přenosu. Nastavením této hodnoty na číslo kanálu samotného tohoto DMA objektu se řetězení vypne (toto je výchozí nastavení).

  • treq_sel: int Vybere signál Transfer Request. Podrobnosti viz část 2.5.3 datasheetu RP2040.

  • irq_quiet: bool Negeneruje přerušení na konci každého přenosu. Přerušení se místo toho vygenerují, když se do trigger registru zapíše nulová hodnota, což zastaví sekvenci zřetězených přenosů (výchozí: True).

  • bswap: bool Pokud je nastaveno na true, bajty ve slovech nebo půlslovech budou před zápisem převráceny (výchozí: True).

  • sniff_en: bool Nastavte na True, aby k datům mohl přistupovat sniff hardware čipu (výchozí: False).

  • write_err: bool Nastavení na True vymaže dříve nahlášenou chybu zápisu.

  • read_err: bool Nastavení na True vymaže dříve nahlášenou chybu čtení.

Podrobnosti o všech těchto polích najdete v popisu registru CH0_CTRL_TRIG v části 2.5.7 datasheetu RP2040.

unpack_ctrl(value: int) dict

Rozbalí hodnotu řídicího registru DMA kanálu do slovníku s páry klíč/hodnota pro každé z polí řídicího registru. value je hodnota registru ctrl, kterou je třeba rozbalit.

Tato metoda vrátí hodnoty pro všechny klíče, které lze předat do DMA.pack_ctrl. Navíc vrátí také příznaky řídicího registru určené pouze pro čtení: busy, který je vysoký, když přenos začne, a nízký, když skončí, a ahb_err, což je logické OR příznaků read_err a write_err. Tyto hodnoty budou při sbalování ignorovány, takže slovník vytvořený rozbalením řídicího registru lze přímo použít jako pojmenované argumenty pro sbalení.

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

Získá nebo nastaví, zda DMA kanál právě běží.

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

Tento atribut odráží adresu, ze které bude číst další sběrnicový přenos. Lze do něj zapsat buď celé číslo, nebo objekt podporující protokol buffer, a takový zápis má okamžitý účinek.

write: int

Tento atribut odráží adresu, na kterou bude zapisovat další sběrnicový přenos. Lze do něj zapsat buď celé číslo, nebo objekt podporující protokol buffer, a takový zápis má okamžitý účinek.

count: int

Čtení tohoto atributu vrátí počet zbývajících sběrnicových přenosů v aktuální sekvenci přenosů. Zápis do tohoto atributu nastaví celkový počet přenosů pro následující sekvenci přenosů.

ctrl: int

Tento atribut odráží řídicí registr DMA kanálu. Obvykle se do něj zapisuje celé číslo sbalené metodou DMA.pack_ctrl(). Vrácenou hodnotu registru lze rozbalit metodou DMA.unpack_ctrl().

channel: int

Číslo kanálu DMA kanálu. Lze jej předat v argumentu chain_to metody DMA.pack_ctrl() na jiném kanálu, čímž se umožní řetězení DMA.

registers: 'memoryview'

Tento atribut je objekt podobný poli, který umožňuje přímý přístup k registrům DMA kanálu. Indexuje se po slovech, nikoli po bajtech, takže indexy registrů jsou offsety adres registrů dělené 4. Podrobnosti o registrech najdete v datasheetu RP2040.

Řetězení a přístup k trigger registru

Řadič DMA v RP2040 nabízí několik pokročilých funkcí, které umožňují jednomu DMA kanálu zahájit přenos na jiném kanálu. Jednou z nich je použití hodnoty chain_to v řídicím registru a druhou je zápis do jednoho z registrů DMA kanálu, který má spouštěcí (trigger) účinek. Ve spojení se schopností nechat jeden DMA kanál zapisovat přímo do DMA.registers jiného kanálu to umožňuje provádět složité transakce zcela bez zásahu CPU.

Níže je příklad použití řetězení i spouštění registrem k implementaci shromáždění více bloků dat do jednoho cíle. Úplné podrobnosti o těchto funkcích najdete v části 2.5 datasheetu RP2040 a níže uvedený kód je pythonovskou verzí příkladu z pododdílu 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)

Tento příklad setrvává v nečinnosti, zatímco čeká na dokončení přenosu; alternativně by mohl nastavit obsluhu přerušení a okamžitě se vrátit.