class DMA – pääsy RP2040:n DMA-ohjaimeen

DMA-luokka tarjoaa pääsyn RP2040:n suoran muistipääsyn (DMA) ohjaimeen, mikä mahdollistaa datan siirtämisen muistilohkojen ja/tai IO-rekisterien välillä. DMA-ohjaimella on omat, erilliset luku- ja kirjoitusväyläisäntäyhteydet väylärakenteeseen, ja jokainen DMA-kanava voi itsenäisesti lukea dataa yhdestä osoitteesta ja kirjoittaa sen takaisin toiseen osoitteeseen, valinnaisesti kasvattaen toista tai molempia osoittimia, mikä mahdollistaa siirtojen suorittamisen prosessorin puolesta samalla, kun prosessori suorittaa muita tehtäviä tai siirtyy virransäästötilaan. RP2040:n DMA-ohjaimessa on 12 itsenäistä DMA-kanavaa, jotka voivat toimia samanaikaisesti. RP2040:n DMA-järjestelmän täydelliset tiedot löytyvät RP2040 Datasheet -dokumentin osasta 2.5.

Esimerkkejä

DMA-ohjaimen yksinkertaisin käyttötapa on datan siirtäminen muistilohkosta toiseen. Tämä voidaan toteuttaa seuraavalla koodilla:

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

Huomaa, että vaikka tämä esimerkki on joutotilan silmukassa odottaessaan siirron valmistumista, ohjelma voisi yhtä hyvin tehdä jotain hyödyllistä työtä tänä aikana.

Toinen, ehkä yleisempi DMA-ohjaimen käyttötapa on siirto muistin ja IO-oheislaitteen välillä. Tässä tilanteessa IO-rekisterin osoite ei muutu jokaisella siirrolla, mutta muistiosoitetta on kasvatettava. On myös tarpeen hallita siirron tahtia, jotta dataa ei kirjoiteta ennen kuin oheislaite voi ottaa sen vastaan tai lueta ennen kuin data on valmiina, ja tätä voidaan hallita DMA-kanavan ohjausrekisterin treq_sel-kentällä. Kunkin DMA-kanavan ohjausrekisterin eri kentät voidaan pakata DMA.pack_ctrl() -metodilla ja purkaa DMA.unpack_ctrl() -staattisella metodilla. Koodi, joka siirtää dataa tavutaulukosta PIO-tilakoneen TX FIFO:hon yksi tavu kerrallaan, näyttää tältä:

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

Huomaa, että tässä esimerkissä kirjoitusosoitteeksi annettu arvo on vain PIO-tilakone, johon dataa lähetetään. Tämä toimii, koska PIO-tilakoneet tarjoavat puskuriprotokollan, mikä mahdollistaa suoran pääsyn niiden data-FIFO-rekistereihin.

Konstruktori

class rp2.DMA

Varaa yhden DMA-ohjaimen kanavista yksinomaiseen käyttöön.

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

Määrittää kanavan DMA-rekisterit ja valinnaisesti aloittaa siirron. Parametrit ovat:

  • read: Osoite, josta DMA-ohjain alkaa lukea dataa, tai objekti, joka tarjoaa luettavan datan. Se voi olla kokonaisluku tai mikä tahansa objekti, joka tukee puskuriprotokollaa.

  • write: Osoite, johon DMA-ohjain alkaa kirjoittaa, tai objekti, johon data kirjoitetaan. Se voi olla kokonaisluku tai mikä tahansa objekti, joka tukee puskuriprotokollaa.

  • count: Väyläsiirtojen lukumäärä, jotka suoritetaan ennen kuin tämä kanava pysähtyy. Huomaa, että tämä on siirtojen lukumäärä, ei tavujen lukumäärä. Jos siirrot ovat 2 tai 4 tavua leveitä, siirretyn datan kokonaismäärä (ja siten tarvittavan puskurin koko) on kerrottava vastaavasti.

  • ctrl: DMA-ohjausrekisterin arvo. Tämä on kokonaislukuarvo, joka tyypillisesti pakataan käyttäen DMA.pack_ctrl().

  • trigger: Valinnaisesti aloita siirto välittömästi.

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

Palauttaa tämän DMA-kanavan IRQ-objektin ja valinnaisesti määrittää sen.

close() None

Vapauttaa varauksen taustalla olevasta DMA-kanavasta ja vapauttaa keskeytyskäsittelijän. DMA-objektia ei voi käyttää tämän toiminnon jälkeen.

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

Pakkaa avainsana-argumenteissa annetut arvot uuden ohjausrekisteriarvon nimettyihin kenttiin. Mikä tahansa kenttä, jota ei anneta, asetetaan oletusarvoon. Oletus otetaan joko annetusta default-arvosta tai, jos sitä ei anneta, nykyiselle kanavalle sopivasta oletuksesta; tämän asettaminen DMA.ctrl-attribuutin nykyiseen arvoon tarjoaa helpon tavan ohittaa kenttien osajoukko.

Avainsana-argumenttien avaimet voivat olla mitä tahansa avaimia, jotka DMA.unpack_ctrl() -metodi palauttaa. Kirjoitettavat arvot ovat:

  • enable: bool Aseta kanavan käyttöön ottamiseksi (oletus: True).

  • high_pri: bool Tee tämän kanavan väyläliikenteestä korkean prioriteetin (oletus: False).

  • size: int Siirron koko: 0=tavu, 1=puolisana, 2=sana (oletus: 2).

  • inc_read: bool Kasvata lukuosoitetta jokaisen siirron jälkeen (oletus: True).

  • inc_write: bool Kasvata kirjoitusosoitetta jokaisen siirron jälkeen (oletus: True).

  • ring_size: int Jos nollasta poikkeava, vain yhden osoiterekisterin alimmat ring_size bittiä muuttuvat, kun osoitetta kasvatetaan, jolloin osoite kiertyy seuraavalla 1 << ring_size -tavurajalla. Sitä, mikä osoite kiertyy, ohjaa ring_sel-lippu. Nolla-arvo poistaa osoitteen kiertymisen käytöstä.

  • ring_sel: bool Aseta arvoon False, jotta ring_size koskee lukuosoitetta, tai True, jotta se koskee kirjoitusosoitetta.

  • chain_to: int Sen kanavan kanavanumero, joka käynnistetään tämän siirron valmistuttua. Tämän arvon asettaminen tämän DMA-objektin omaan kanavanumeroon poistaa ketjutuksen käytöstä (tämä on oletus).

  • treq_sel: int Valitse Transfer Request -signaali. Katso tarkemmat tiedot RP2040-datalehden osasta 2.5.3.

  • irq_quiet: bool Älä luo keskeytystä jokaisen siirron lopussa. Keskeytyksiä luodaan sen sijaan, kun käynnistysrekisteriin kirjoitetaan nolla-arvo, mikä pysäyttää ketjutettujen siirtojen sarjan (oletus: True).

  • bswap: bool Jos asetettu arvoon true, sanojen tai puolisanojen tavut käännetään ennen kirjoittamista (oletus: True).

  • sniff_en: bool Aseta arvoon True, jotta sirun sniff-laitteisto voi käyttää dataa (oletus: False).

  • write_err: bool Tämän asettaminen arvoon True tyhjentää aiemmin raportoidun kirjoitusvirheen.

  • read_err: bool Tämän asettaminen arvoon True tyhjentää aiemmin raportoidun lukuvirheen.

Katso CH0_CTRL_TRIG-rekisterin kuvaus RP2040-datalehden osasta 2.5.7 saadaksesi tarkemmat tiedot kaikista näistä kentistä.

unpack_ctrl(value: int) dict

Purkaa DMA-kanavan ohjausrekisterin arvon sanakirjaksi, jossa on avain/arvo-parit kullekin ohjausrekisterin kentälle. value on purettava ctrl-rekisterin arvo.

Tämä metodi palauttaa arvot kaikille avaimille, jotka voidaan välittää DMA.pack_ctrl-metodille. Lisäksi se palauttaa myös ohjausrekisterin vain luku -liput: busy, joka nousee korkeaksi, kun siirto alkaa, ja matalaksi, kun se päättyy, sekä ahb_err, joka on read_err- ja write_err-lippujen looginen OR. Nämä arvot jätetään huomiotta pakattaessa, joten ohjausrekisterin purkamisesta luotua sanakirjaa voidaan käyttää suoraan avainsana-argumentteina pakkaamiseen.

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

Hakee tai asettaa, onko DMA-kanava tällä hetkellä käynnissä.

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

Tämä attribuutti kuvastaa osoitetta, josta seuraava väyläsiirto lukee. Siihen voidaan kirjoittaa joko kokonaisluku tai objekti, joka tukee puskuriprotokollaa, ja näin tekeminen vaikuttaa välittömästi.

write: int

Tämä attribuutti kuvastaa osoitetta, johon seuraava väyläsiirto kirjoittaa. Siihen voidaan kirjoittaa joko kokonaisluku tai objekti, joka tukee puskuriprotokollaa, ja näin tekeminen vaikuttaa välittömästi.

count: int

Tämän attribuutin lukeminen palauttaa jäljellä olevien väyläsiirtojen lukumäärän nykyisessä siirtosarjassa. Tämän attribuutin kirjoittaminen asettaa siirtojen kokonaismäärän seuraavalle siirtosarjalle.

ctrl: int

Tämä attribuutti kuvastaa DMA-kanavan ohjausrekisteriä. Siihen kirjoitetaan tyypillisesti kokonaisluku, joka on pakattu DMA.pack_ctrl() -metodilla. Palautettu rekisteriarvo voidaan purkaa DMA.unpack_ctrl() -metodilla.

channel: int

DMA-kanavan kanavanumero. Tämä voidaan välittää toisen kanavan DMA.pack_ctrl()-metodin chain_to-argumenttina DMA-ketjutuksen mahdollistamiseksi.

registers: 'memoryview'

Tämä attribuutti on taulukon kaltainen objekti, joka mahdollistaa suoran pääsyn DMA-kanavan rekistereihin. Indeksointi tapahtuu sanoittain, ei tavuittain, joten rekisteri-indeksit ovat rekisterin osoitesiirtymät jaettuna 4:llä. Katso rekisteritiedot RP2040-datalehdestä.

Ketjutus ja käynnistysrekisterin käyttö

RP2040:n DMA-ohjain tarjoaa pari edistynyttä ominaisuutta, joiden avulla yksi DMA-kanava voi käynnistää siirron toisella kanavalla. Toinen on ohjausrekisterin chain_to-arvon käyttö ja toinen on kirjoittaminen johonkin DMA-kanavan rekisteriin, jolla on käynnistysvaikutus. Yhdistettynä kykyyn, jossa yksi DMA-kanava voi kirjoittaa suoraan toisen kanavan DMA.registers-rekistereihin, tämä mahdollistaa monimutkaisten transaktioiden suorittamisen ilman mitään CPU:n väliintuloa.

Alla on esimerkki sekä ketjutuksen että rekisterin käynnistämisen käytöstä, jolla toteutetaan useiden datalohkojen kokoaminen yhteen kohteeseen. Näiden ominaisuuksien täydelliset tiedot löytyvät RP2040-datalehden osasta 2.5, ja alla oleva koodi on Pythonin tyylinen versio aliosan 2.5.6.2 esimerkistä.

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)

Tämä esimerkki on joutotilassa odottaessaan siirron valmistumista; vaihtoehtoisesti se voisi asettaa keskeytyskäsittelijän ja palata välittömästi.