class DMA – accesso al controller DMA del RP2040¶
La classe DMA offre l’accesso al controller DMA (Direct Memory Access) del RP2040, fornendo la possibilità di spostare dati tra blocchi di memoria e/o registri IO. Il controller DMA ha le proprie connessioni master di bus separate per la lettura e la scrittura verso la struttura del bus, e ogni canale DMA può leggere in modo indipendente i dati da un indirizzo e riscriverli a un altro indirizzo, incrementando opzionalmente uno o entrambi i puntatori, consentendogli di eseguire trasferimenti per conto del processore mentre questo svolge altre attività o entra in uno stato a basso consumo. Il controller DMA del RP2040 dispone di 12 canali DMA indipendenti che possono essere eseguiti contemporaneamente. Per tutti i dettagli sul sistema DMA del RP2040 si veda la sezione 2.5 del Datasheet del RP2040.
Esempi¶
L’uso più semplice del controller DMA è spostare dati da un blocco di memoria a un altro. Questo può essere realizzato con il codice seguente:
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
Si noti che, sebbene questo esempio resti in un ciclo di inattività mentre attende il completamento del trasferimento, in questo lasso di tempo il programma potrebbe altrettanto bene svolgere qualche lavoro utile.
Un altro uso, forse più comune, del controller DMA è il trasferimento tra la memoria e una periferica IO. In questa situazione l’indirizzo del registro IO non cambia ad ogni trasferimento, ma l’indirizzo di memoria deve essere incrementato. È inoltre necessario controllare il ritmo del trasferimento, in modo da non scrivere dati prima che possano essere accettati da una periferica né leggerli prima che i dati siano pronti, e questo può essere controllato con il campo treq_sel del registro di controllo del canale DMA. I vari campi del registro di controllo di ciascun canale DMA possono essere impacchettati usando il metodo DMA.pack_ctrl() e spacchettati usando il metodo statico DMA.unpack_ctrl(). Il codice per trasferire dati da un array di byte alla FIFO TX di una macchina a stati PIO, un byte alla volta, ha questo aspetto:
# 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
)
Si noti che in questo esempio il valore fornito per l’indirizzo di scrittura è semplicemente la macchina a stati PIO a cui stiamo inviando i dati. Questo funziona perché le macchine a stati PIO presentano il protocollo buffer, consentendo l’accesso diretto ai loro registri FIFO dei dati.
Costruttore¶
- class rp2.DMA¶
Riserva uno dei canali del controller DMA per uso esclusivo.
- config(read: 'int | _AnyReadableBuf | None' = None, write: 'int | _AnyWritableBuf | None' = None, count: int | None = None, ctrl: int | None = None, trigger: bool = False) None¶
Configura i registri DMA per il canale e opzionalmente avvia il trasferimento. I parametri sono:
read: L’indirizzo da cui il controller DMA inizierà a leggere i dati oppure un oggetto che fornirà i dati da leggere. Può essere un intero o qualsiasi oggetto che supporta il protocollo buffer.
write: L’indirizzo in cui il controller DMA inizierà a scrivere oppure un oggetto in cui verranno scritti i dati. Può essere un intero o qualsiasi oggetto che supporta il protocollo buffer.
count: Il numero di trasferimenti di bus che verranno eseguiti prima che questo canale si fermi. Si noti che questo è il numero di trasferimenti, non il numero di byte. Se i trasferimenti sono larghi 2 o 4 byte, la quantità totale di dati spostati (e quindi la dimensione del buffer richiesto) deve essere moltiplicata di conseguenza.
ctrl: Il valore per il registro di controllo DMA. Questo è un valore intero che viene tipicamente impacchettato usando
DMA.pack_ctrl().trigger: Avvia opzionalmente il trasferimento immediatamente.
- irq(handler: Callable[[DMA], None] | None = None, hard: bool = False) Callable¶
Restituisce l’oggetto IRQ per questo canale DMA e opzionalmente lo configura.
- close() None¶
Rilascia la prenotazione sul canale DMA sottostante e libera il gestore dell’interrupt. L’oggetto
DMAnon può essere usato dopo questa operazione.
- pack_ctrl(default: int | None = None, **kwargs) int¶
Impacchetta i valori forniti negli argomenti keyword nei campi denominati di un nuovo valore del registro di controllo. Qualsiasi campo non fornito sarà impostato a un valore predefinito. Il valore predefinito sarà preso dal valore
defaultfornito oppure, se questo non viene dato, da un valore predefinito adatto al canale corrente; impostarlo al valore corrente dell’attributoDMA.ctrlfornisce un modo semplice per sovrascrivere un sottoinsieme dei campi.Le chiavi per gli argomenti keyword possono essere qualsiasi chiave restituita dal metodo
DMA.unpack_ctrl(). I valori scrivibili sono:enable:
boolImpostare per abilitare il canale (predefinito:True).high_pri:
boolRende il traffico di bus di questo canale ad alta priorità (predefinito:False).size:
intDimensione del trasferimento: 0=byte, 1=mezza parola, 2=parola (predefinito: 2).inc_read:
boolIncrementa l’indirizzo di lettura dopo ogni trasferimento (predefinito:True).inc_write:
boolIncrementa l’indirizzo di scrittura dopo ogni trasferimento (predefinito:True).ring_size:
intSe diverso da zero, solo iring_sizebit inferiori di un registro di indirizzo cambieranno quando un indirizzo viene incrementato, facendo sì che l’indirizzo ricominci al successivo limite di1 << ring_sizebyte. Quale indirizzo viene fatto ricominciare è controllato dal flagring_sel. Un valore zero disabilita il wrapping dell’indirizzo.ring_sel:
boolImpostare aFalseper far applicarering_sizeall’indirizzo di lettura oppure aTrueper applicarlo all’indirizzo di scrittura.chain_to:
intIl numero del canale da attivare al completamento di questo trasferimento. Impostando questo valore al numero di canale di questo stesso oggetto DMA si disabilita il concatenamento (questa è l’impostazione predefinita).treq_sel:
intSeleziona un segnale di richiesta di trasferimento (Transfer Request). Si vedano i dettagli nella sezione 2.5.3 del datasheet del RP2040.irq_quiet:
boolNon genera un interrupt alla fine di ogni trasferimento. Gli interrupt verranno invece generati quando un valore zero viene scritto nel registro trigger, il che interromperà una sequenza di trasferimenti concatenati (predefinito:True).bswap:
boolSe impostato a true, i byte nelle parole o nelle mezze parole verranno invertiti prima della scrittura (predefinito:True).sniff_en:
boolImpostare aTrueper consentire l’accesso ai dati da parte dell’hardware di sniff del chip (predefinito:False).write_err:
boolImpostare questo aTruecancellerà un errore di scrittura precedentemente segnalato.read_err:
boolImpostare questo aTruecancellerà un errore di lettura precedentemente segnalato.
Si veda la descrizione del registro
CH0_CTRL_TRIGnella sezione 2.5.7 del datasheet del RP2040 per i dettagli di tutti questi campi.
- unpack_ctrl(value: int) dict¶
Spacchetta un valore per un registro di controllo del canale DMA in un dizionario con coppie chiave/valore per ciascuno dei campi del registro di controllo. value è il valore del registro
ctrlda spacchettare.Questo metodo restituirà i valori per tutte le chiavi che possono essere passate a
DMA.pack_ctrl. Inoltre, restituirà anche i flag di sola lettura nel registro di controllo:busy, che diventa alto quando un trasferimento inizia e basso quando finisce, eahb_err, che è l’OR logico dei flagread_errewrite_err. Questi valori verranno ignorati durante l’impacchettamento, in modo che il dizionario creato spacchettando un registro di controllo possa essere usato direttamente come argomenti keyword per l’impacchettamento.
- active(value: bool | None = None, /) bool¶
Ottiene o imposta se il canale DMA è attualmente in esecuzione.
>>> sm.active() 0 >>> sm.active(1) >>> while sm.active(): ... pass
- read: int¶
Questo attributo riflette l’indirizzo da cui leggerà il prossimo trasferimento di bus. Può essere scritto con un intero oppure con un oggetto che supporta il protocollo buffer, e farlo ha effetto immediato.
- write: int¶
Questo attributo riflette l’indirizzo in cui scriverà il prossimo trasferimento di bus. Può essere scritto con un intero oppure con un oggetto che supporta il protocollo buffer, e farlo ha effetto immediato.
- count: int¶
La lettura di questo attributo restituirà il numero di trasferimenti di bus rimanenti nella sequenza di trasferimento corrente. La scrittura di questo attributo imposta il numero totale di trasferimenti per la prossima sequenza di trasferimento.
- ctrl: int¶
Questo attributo riflette il registro di controllo del canale DMA. Viene tipicamente scritto con un intero impacchettato usando il metodo
DMA.pack_ctrl(). Il valore del registro restituito può essere spacchettato usando il metodoDMA.unpack_ctrl().
- channel: int¶
Il numero del canale DMA. Questo può essere passato nell’argomento
chain_todiDMA.pack_ctrl()su un altro canale per consentire il concatenamento DMA.
- registers: 'memoryview'¶
Questo attributo è un oggetto di tipo array che consente l’accesso diretto ai registri del canale DMA. L’indice è per parola, anziché per byte, quindi gli indici dei registri sono gli offset degli indirizzi dei registri divisi per 4. Si veda il datasheet del RP2040 per i dettagli sui registri.
Concatenamento e accesso al registro trigger¶
Il controller DMA del RP2040 offre un paio di funzionalità avanzate per consentire a un canale DMA di avviare un trasferimento su un altro canale. Una è l’uso del valore chain_to nel registro di controllo e l’altra è la scrittura in uno dei registri del canale DMA che ha un effetto di trigger. Quando combinato con la possibilità di far scrivere a un canale DMA direttamente nei DMA.registers di un altro canale, questo consente di eseguire transazioni complesse senza alcun intervento della CPU.
Di seguito è riportato un esempio di utilizzo sia del concatenamento sia del triggering dei registri per implementare la raccolta di più blocchi di dati in un’unica destinazione. Tutti i dettagli di queste funzionalità sono reperibili nella sezione 2.5 del datasheet del RP2040 e il codice seguente è una versione Pythonic dell’esempio nella sottosezione 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)
Questo esempio resta inattivo mentre attende il completamento del trasferimento; in alternativa potrebbe impostare un gestore di interrupt e ritornare immediatamente.