clasa DMA – acces la controlerul DMA al RP2040¶
Clasa DMA oferă acces la controlerul de Acces Direct la Memorie (DMA) al RP2040, oferind posibilitatea de a muta date între blocuri de memorie și/sau registre IO. Controlerul DMA are propriile conexiuni separate de magistrală (bus master) pentru citire și scriere către structura de magistrală, iar fiecare canal DMA poate citi independent date de la o adresă și le poate scrie înapoi la o altă adresă, incrementând opțional unul sau ambii pointeri, ceea ce îi permite să efectueze transferuri în numele procesorului în timp ce procesorul îndeplinește alte sarcini sau intră într-o stare de consum redus. Controlerul DMA al RP2040 are 12 canale DMA independente care pot rula concurent. Pentru detalii complete despre sistemul DMA al RP2040, consultați secțiunea 2.5 din RP2040 Datasheet.
Exemple¶
Cea mai simplă utilizare a controlerului DMA este mutarea datelor dintr-un bloc de memorie în altul. Acest lucru poate fi realizat cu următorul cod:
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
De reținut că, deși în acest exemplu se stă într-o buclă inactivă în timp ce se așteaptă finalizarea transferului, programul ar putea la fel de bine să efectueze o muncă utilă în acest timp.
O altă utilizare, poate mai frecventă, a controlerului DMA este transferul între memorie și un periferic IO. În această situație, adresa registrului IO nu se modifică pentru fiecare transfer, dar adresa de memorie trebuie incrementată. Este, de asemenea, necesar să se controleze ritmul transferului pentru a nu scrie date înainte ca acestea să poată fi acceptate de un periferic sau a le citi înainte ca datele să fie pregătite, iar acest lucru poate fi controlat cu câmpul treq_sel al registrului de control al canalului DMA. Diferitele câmpuri ale registrului de control pentru fiecare canal DMA pot fi împachetate folosind metoda DMA.pack_ctrl() și despachetate folosind metoda statică DMA.unpack_ctrl(). Codul pentru transferul datelor dintr-un tablou de octeți în FIFO-ul TX al unei mașini de stare PIO, câte un octet pe rând, arată astfel:
# 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
)
De reținut că în acest exemplu valoarea dată pentru adresa de scriere este pur și simplu mașina de stare PIO către care trimitem datele. Acest lucru funcționează deoarece mașinile de stare PIO expun protocolul de tampon (buffer), permițând accesul direct la registrele FIFO ale datelor lor.
Constructor¶
- class rp2.DMA¶
Revendică unul dintre canalele controlerului DMA pentru utilizare exclusivă.
- config(read: 'int | _AnyReadableBuf | None' = None, write: 'int | _AnyWritableBuf | None' = None, count: int | None = None, ctrl: int | None = None, trigger: bool = False) None¶
Configurează registrele DMA pentru canal și, opțional, pornește transferul. Parametrii sunt:
read: Adresa de la care controlerul DMA va începe citirea datelor sau un obiect care va furniza datele de citit. Poate fi un întreg sau orice obiect care acceptă protocolul de tampon (buffer).
write: Adresa la care controlerul DMA va începe scrierea sau un obiect în care vor fi scrise datele. Poate fi un întreg sau orice obiect care acceptă protocolul de tampon (buffer).
count: Numărul de transferuri de magistrală care se vor executa înainte ca acest canal să se oprească. De reținut că acesta este numărul de transferuri, nu numărul de octeți. Dacă transferurile au o lățime de 2 sau 4 octeți, atunci cantitatea totală de date mutate (și astfel dimensiunea tamponului necesar) trebuie înmulțită corespunzător.
ctrl: Valoarea pentru registrul de control DMA. Aceasta este o valoare întreagă care este de obicei împachetată folosind
DMA.pack_ctrl().trigger: Inițiază opțional transferul imediat.
- irq(handler: Callable[[DMA], None] | None = None, hard: bool = False) Callable¶
Returnează obiectul IRQ pentru acest canal DMA și, opțional, îl configurează.
- close() None¶
Eliberează revendicarea asupra canalului DMA subiacent și eliberează handler-ul de întrerupere. Obiectul
DMAnu poate fi folosit după această operație.
- pack_ctrl(default: int | None = None, **kwargs) int¶
Împachetează valorile furnizate în argumentele cuvânt-cheie în câmpurile denumite ale unei noi valori de registru de control. Orice câmp care nu este furnizat va fi setat la o valoare implicită. Valoarea implicită va fi fie preluată din valoarea
defaultfurnizată, fie, dacă aceasta nu este dată, o valoare implicită potrivită pentru canalul curent; setarea acesteia la valoarea curentă a atributuluiDMA.ctrloferă o modalitate ușoară de a suprascrie o submulțime a câmpurilor.Cheile pentru argumentele cuvânt-cheie pot fi orice cheie returnată de metoda
DMA.unpack_ctrl(). Valorile care pot fi scrise sunt:enable:
boolSetați pentru a activa canalul (implicit:True).high_pri:
boolFace traficul de magistrală al acestui canal de prioritate înaltă (implicit:False).size:
intDimensiunea transferului: 0=octet, 1=jumătate de cuvânt, 2=cuvânt (implicit: 2).inc_read:
boolIncrementează adresa de citire după fiecare transfer (implicit:True).inc_write:
boolIncrementează adresa de scriere după fiecare transfer (implicit:True).ring_size:
intDacă este diferit de zero, doar cei mai de josring_sizebiți ai unui registru de adresă se vor modifica atunci când o adresă este incrementată, făcând ca adresa să se rotească la următoarea limită de1 << ring_sizeocteți. Care adresă este rotită este controlat de indicatorulring_sel. O valoare zero dezactivează rotirea adresei.ring_sel:
boolSetați laFalsepentru caring_sizesă se aplice adresei de citire sau laTruepentru a se aplica adresei de scriere.chain_to:
intNumărul de canal al unui canal care va fi declanșat după finalizarea acestui transfer. Setarea acestei valori la numărul de canal propriu al acestui obiect DMA dezactivează înlănțuirea (aceasta este valoarea implicită).treq_sel:
intSelectează un semnal Transfer Request. Consultați secțiunea 2.5.3 din fișa tehnică RP2040 pentru detalii.irq_quiet:
boolNu generează întrerupere la sfârșitul fiecărui transfer. În schimb, întreruperile vor fi generate atunci când o valoare zero este scrisă în registrul de declanșare, ceea ce va opri o secvență de transferuri înlănțuite (implicit:True).bswap:
boolDacă este setat la true, octeții din cuvinte sau jumătăți de cuvânt vor fi inversați înainte de scriere (implicit:True).sniff_en:
boolSetați laTruepentru a permite accesarea datelor de către hardware-ul de inspecție (sniff) al cipului (implicit:False).write_err:
boolSetarea acestuia laTrueva șterge o eroare de scriere raportată anterior.read_err:
boolSetarea acestuia laTrueva șterge o eroare de citire raportată anterior.
Consultați descrierea registrului
CH0_CTRL_TRIGdin secțiunea 2.5.7 a fișei tehnice RP2040 pentru detalii despre toate aceste câmpuri.
- unpack_ctrl(value: int) dict¶
Despachetează o valoare pentru un registru de control al canalului DMA într-un dicționar cu perechi cheie/valoare pentru fiecare dintre câmpurile registrului de control. value este valoarea registrului
ctrlde despachetat.Această metodă va returna valori pentru toate cheile care pot fi transmise către
DMA.pack_ctrl. În plus, va returna și indicatorii doar pentru citire din registrul de control:busy, care trece în starea înaltă atunci când un transfer începe și în starea joasă atunci când se termină, șiahb_err, care este SAU logic al indicatorilorread_errșiwrite_err. Aceste valori vor fi ignorate la împachetare, astfel încât dicționarul creat prin despachetarea unui registru de control poate fi folosit direct ca argumente cuvânt-cheie pentru împachetare.
- active(value: bool | None = None, /) bool¶
Obține sau setează dacă canalul DMA rulează în prezent.
>>> sm.active() 0 >>> sm.active(1) >>> while sm.active(): ... pass
- read: int¶
Acest atribut reflectă adresa de la care va citi următorul transfer de magistrală. Poate fi scris fie cu un întreg, fie cu un obiect care acceptă protocolul de tampon (buffer), iar acest lucru are efect imediat.
- write: int¶
Acest atribut reflectă adresa la care va scrie următorul transfer de magistrală. Poate fi scris fie cu un întreg, fie cu un obiect care acceptă protocolul de tampon (buffer), iar acest lucru are efect imediat.
- count: int¶
Citirea acestui atribut va returna numărul de transferuri de magistrală rămase în secvența de transfer curentă. Scrierea acestui atribut setează numărul total de transferuri pentru secvența de transfer următoare.
- ctrl: int¶
Acest atribut reflectă registrul de control al canalului DMA. Este de obicei scris cu un întreg împachetat folosind metoda
DMA.pack_ctrl(). Valoarea registrului returnată poate fi despachetată folosind metodaDMA.unpack_ctrl().
- channel: int¶
Numărul de canal al canalului DMA. Acesta poate fi transmis în argumentul
chain_toalDMA.pack_ctrl()pe un alt canal pentru a permite înlănțuirea DMA.
- registers: 'memoryview'¶
Acest atribut este un obiect de tip tablou care permite accesul direct la registrele canalului DMA. Indexarea se face pe cuvânt, nu pe octet, astfel încât indicii registrelor sunt offset-urile adreselor registrelor împărțite la 4. Consultați fișa tehnică RP2040 pentru detalii despre registre.
Înlănțuire și acces la registrul de declanșare¶
Controlerul DMA din RP2040 oferă câteva funcții avansate care permit unui canal DMA să inițieze un transfer pe un alt canal. Una este utilizarea valorii chain_to în registrul de control, iar cealaltă este scrierea într-unul dintre registrele canalului DMA care are un efect de declanșare. Combinat cu capacitatea de a face ca un canal DMA să scrie direct în DMA.registers ale altui canal, acest lucru permite efectuarea de tranzacții complexe fără nicio intervenție a CPU-ului.
Mai jos este un exemplu de utilizare atât a înlănțuirii, cât și a declanșării prin registru pentru a implementa colectarea mai multor blocuri de date într-o singură destinație. Detalii complete despre aceste funcții pot fi găsite în secțiunea 2.5 a fișei tehnice RP2040, iar codul de mai jos este o versiune pythonică a exemplului din sub-secțiunea 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)
Acest exemplu rămâne inactiv în timp ce așteaptă finalizarea transferului; alternativ, ar putea seta un handler de întrerupere și reveni imediat.