klasa DMA – dostęp do kontrolera DMA układu RP2040¶
Klasa DMA oferuje dostęp do kontrolera bezpośredniego dostępu do pamięci (DMA) układu RP2040, umożliwiając przenoszenie danych między blokami pamięci i/lub rejestrami IO. Kontroler DMA ma własne, oddzielne połączenia magistrali nadrzędnej do odczytu i zapisu w strukturze magistrali, a każdy kanał DMA może niezależnie odczytywać dane z jednego adresu i zapisywać je pod inny adres, opcjonalnie inkrementując jeden lub oba wskaźniki, co pozwala mu wykonywać transfery w imieniu procesora, podczas gdy procesor realizuje inne zadania lub przechodzi w stan niskiego poboru mocy. Kontroler DMA układu RP2040 ma 12 niezależnych kanałów DMA, które mogą działać współbieżnie. Pełne szczegóły dotyczące systemu DMA układu RP2040 znajdują się w sekcji 2.5 dokumentu RP2040 Datasheet.
Przykłady¶
Najprostszym zastosowaniem kontrolera DMA jest przeniesienie danych z jednego bloku pamięci do drugiego. Można to osiągnąć za pomocą następującego kodu:
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
Należy zauważyć, że choć w tym przykładzie program znajduje się w pętli bezczynności, czekając na zakończenie transferu, mógłby równie dobrze wykonywać w tym czasie jakąś użyteczną pracę.
Innym, być może częstszym zastosowaniem kontrolera DMA jest transfer pomiędzy pamięcią a urządzeniem peryferyjnym IO. W takiej sytuacji adres rejestru IO nie zmienia się dla każdego transferu, ale adres pamięci musi być inkrementowany. Konieczne jest również kontrolowanie tempa transferu, aby nie zapisywać danych, zanim mogą zostać przyjęte przez urządzenie peryferyjne, ani nie odczytywać ich, zanim dane będą gotowe; można to kontrolować za pomocą pola treq_sel rejestru kontrolnego kanału DMA. Poszczególne pola rejestru kontrolnego każdego kanału DMA można spakować metodą DMA.pack_ctrl() i rozpakować statyczną metodą DMA.unpack_ctrl(). Kod przenoszący dane z tablicy bajtów do FIFO TX maszyny stanów PIO, po jednym bajcie naraz, wygląda następująco:
# 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
)
Należy zauważyć, że w tym przykładzie wartością podaną jako adres zapisu jest po prostu maszyna stanów PIO, do której wysyłamy dane. Działa to dlatego, że maszyny stanów PIO udostępniają protokół buforowy, umożliwiając bezpośredni dostęp do swoich rejestrów FIFO danych.
Konstruktor¶
- class rp2.DMA¶
Rezerwuje jeden z kanałów kontrolera DMA do wyłącznego użytku.
- config(read: 'int | _AnyReadableBuf | None' = None, write: 'int | _AnyWritableBuf | None' = None, count: int | None = None, ctrl: int | None = None, trigger: bool = False) None¶
Konfiguruje rejestry DMA dla kanału i opcjonalnie rozpoczyna transfer. Parametry to:
read: Adres, od którego kontroler DMA zacznie odczytywać dane, lub obiekt dostarczający dane do odczytu. Może to być liczba całkowita lub dowolny obiekt obsługujący protokół buforowy.
write: Adres, pod który kontroler DMA zacznie zapisywać dane, lub obiekt, do którego dane będą zapisywane. Może to być liczba całkowita lub dowolny obiekt obsługujący protokół buforowy.
count: Liczba transferów magistrali, które wykonają się, zanim ten kanał się zatrzyma. Należy zauważyć, że jest to liczba transferów, a nie liczba bajtów. Jeśli transfery mają szerokość 2 lub 4 bajtów, całkowita ilość przenoszonych danych (a tym samym rozmiar wymaganego bufora) musi zostać odpowiednio przemnożona.
ctrl: Wartość rejestru kontrolnego DMA. Jest to wartość całkowita, którą zwykle pakuje się za pomocą
DMA.pack_ctrl().trigger: Opcjonalnie rozpoczyna transfer natychmiast.
- irq(handler: Callable[[DMA], None] | None = None, hard: bool = False) Callable¶
Zwraca obiekt IRQ dla tego kanału DMA i opcjonalnie go konfiguruje.
- close() None¶
Zwalnia rezerwację bazowego kanału DMA i uwalnia procedurę obsługi przerwań. Obiekt
DMAnie może być używany po tej operacji.
- pack_ctrl(default: int | None = None, **kwargs) int¶
Pakuje wartości podane w argumentach nazwanych do nazwanych pól nowej wartości rejestru kontrolnego. Każde pole, które nie zostanie podane, zostanie ustawione na wartość domyślną. Wartość domyślna zostanie pobrana z podanej wartości
default, a jeśli jej nie podano, zostanie użyta wartość domyślna odpowiednia dla bieżącego kanału; ustawienie jej na bieżącą wartość atrybutuDMA.ctrlstanowi łatwy sposób na nadpisanie podzbioru pól.Kluczami dla argumentów nazwanych mogą być dowolne klucze zwracane przez metodę
DMA.unpack_ctrl(). Wartości zapisywalne to:enable:
boolUstaw, aby włączyć kanał (domyślnie:True).high_pri:
boolNadaje ruchowi magistrali tego kanału wysoki priorytet (domyślnie:False).size:
intRozmiar transferu: 0=bajt, 1=półsłowo, 2=słowo (domyślnie: 2).inc_read:
boolInkrementuje adres odczytu po każdym transferze (domyślnie:True).inc_write:
boolInkrementuje adres zapisu po każdym transferze (domyślnie:True).ring_size:
intJeśli wartość jest niezerowa, podczas inkrementowania adresu zmieniają się tylko najniższe bityring_sizejednego rejestru adresowego, co powoduje zawijanie adresu na następnej granicy1 << ring_sizebajtów. To, który adres jest zawijany, kontroluje flagaring_sel. Wartość zero wyłącza zawijanie adresu.ring_sel:
boolUstaw naFalse, abyring_sizedotyczyło adresu odczytu, lub naTrue, aby dotyczyło adresu zapisu.chain_to:
intNumer kanału, który ma zostać wyzwolony po zakończeniu tego transferu. Ustawienie tej wartości na numer kanału tego samego obiektu DMA wyłącza łańcuchowanie (jest to wartość domyślna).treq_sel:
intWybiera sygnał żądania transferu (Transfer Request). Szczegóły znajdują się w sekcji 2.5.3 karty katalogowej RP2040.irq_quiet:
boolNie generuje przerwania na końcu każdego transferu. Zamiast tego przerwania będą generowane, gdy do rejestru wyzwalającego zostanie zapisana wartość zero, co zatrzyma sekwencję połączonych łańcuchowo transferów (domyślnie:True).bswap:
boolJeśli ustawione na true, bajty w słowach lub półsłowach zostaną odwrócone przed zapisem (domyślnie:True).sniff_en:
boolUstaw naTrue, aby umożliwić dostęp do danych przez sprzęt podsłuchujący (sniff) układu (domyślnie:False).write_err:
boolUstawienie naTruewyczyści wcześniej zgłoszony błąd zapisu.read_err:
boolUstawienie naTruewyczyści wcześniej zgłoszony błąd odczytu.
Szczegóły wszystkich tych pól znajdują się w opisie rejestru
CH0_CTRL_TRIGw sekcji 2.5.7 karty katalogowej RP2040.
- unpack_ctrl(value: int) dict¶
Rozpakowuje wartość rejestru kontrolnego kanału DMA do słownika z parami klucz/wartość dla każdego z pól rejestru kontrolnego. value to wartość rejestru
ctrldo rozpakowania.Ta metoda zwróci wartości dla wszystkich kluczy, które można przekazać do
DMA.pack_ctrl. Dodatkowo zwróci również flagi tylko do odczytu z rejestru kontrolnego:busy, która przyjmuje stan wysoki, gdy transfer się rozpoczyna, i niski, gdy się kończy, orazahb_err, która jest logiczną sumą (OR) flagread_erriwrite_err. Wartości te będą ignorowane podczas pakowania, dzięki czemu słownik utworzony przez rozpakowanie rejestru kontrolnego może być bezpośrednio użyty jako argumenty nazwane do pakowania.
- active(value: bool | None = None, /) bool¶
Pobiera lub ustawia informację o tym, czy kanał DMA jest obecnie uruchomiony.
>>> sm.active() 0 >>> sm.active(1) >>> while sm.active(): ... pass
- read: int¶
Ten atrybut odzwierciedla adres, z którego będzie czytał następny transfer magistrali. Może być zapisany liczbą całkowitą lub obiektem obsługującym protokół buforowy, co odnosi natychmiastowy skutek.
- write: int¶
Ten atrybut odzwierciedla adres, pod który będzie zapisywał następny transfer magistrali. Może być zapisany liczbą całkowitą lub obiektem obsługującym protokół buforowy, co odnosi natychmiastowy skutek.
- count: int¶
Odczyt tego atrybutu zwróci liczbę pozostałych transferów magistrali w bieżącej sekwencji transferu. Zapis tego atrybutu ustawia całkowitą liczbę transferów dla następnej sekwencji transferu.
- ctrl: int¶
Ten atrybut odzwierciedla rejestr kontrolny kanału DMA. Jest zwykle zapisywany liczbą całkowitą spakowaną metodą
DMA.pack_ctrl(). Zwróconą wartość rejestru można rozpakować metodąDMA.unpack_ctrl().
- channel: int¶
Numer kanału DMA. Można go przekazać w argumencie
chain_tometodyDMA.pack_ctrl()na innym kanale, aby umożliwić łańcuchowanie DMA.
- registers: 'memoryview'¶
Ten atrybut jest obiektem przypominającym tablicę, który umożliwia bezpośredni dostęp do rejestrów kanału DMA. Indeksowanie odbywa się słowami, a nie bajtami, więc indeksy rejestrów to przesunięcia adresów rejestrów podzielone przez 4. Szczegóły rejestrów znajdują się w karcie katalogowej RP2040.
Łańcuchowanie i dostęp do rejestrów wyzwalających¶
Kontroler DMA w układzie RP2040 oferuje kilka zaawansowanych funkcji pozwalających jednemu kanałowi DMA zainicjować transfer na innym kanale. Jedną z nich jest użycie wartości chain_to w rejestrze kontrolnym, a drugą jest zapis do jednego z rejestrów kanału DMA, który ma efekt wyzwalający. W połączeniu z możliwością bezpośredniego zapisu przez jeden kanał DMA do DMA.registers innego kanału, pozwala to na wykonywanie złożonych transakcji bez żadnej interwencji procesora.
Poniżej znajduje się przykład użycia zarówno łańcuchowania, jak i wyzwalania przez rejestr w celu zaimplementowania zbierania wielu bloków danych do jednego miejsca docelowego. Pełne szczegóły tych funkcji można znaleźć w sekcji 2.5 karty katalogowej RP2040, a poniższy kod jest pythoniczną wersją przykładu z podsekcji 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)
Ten przykład pozostaje bezczynny, czekając na zakończenie transferu; alternatywnie mógłby ustawić procedurę obsługi przerwań i natychmiast powrócić.