class DMA – RP2040’ın DMA denetleyicisine erişim

DMA sınıfı, RP2040’ın Doğrudan Bellek Erişimi (DMA) denetleyicisine erişim sunar; bellek blokları ve/veya GÇ yazmaçları arasında veri taşıma yeteneği sağlar. DMA denetleyicisinin veri yolu yapısına kendine ait, ayrı okuma ve yazma veri yolu yöneticisi bağlantıları vardır ve her DMA kanalı bağımsız olarak bir adresten veri okuyup başka bir adrese geri yazabilir; isteğe bağlı olarak işaretçilerden birini veya her ikisini artırarak, işlemci başka görevler yürütürken veya düşük güç durumuna girerken işlemci adına aktarımlar gerçekleştirmesine olanak tanır. RP2040’ın DMA denetleyicisinin eşzamanlı çalışabilen 12 bağımsız DMA kanalı vardır. RP2040’ın DMA sisteminin tüm ayrıntıları için RP2040 Veri Sayfası belgesinin 2.5 bölümüne bakın.

Örnekler

DMA denetleyicisinin en basit kullanımı, verileri bir bellek bloğundan başka bir bloğa taşımaktır. Bu, aşağıdaki kodla gerçekleştirilebilir:

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

Bu örneğin aktarımın tamamlanmasını beklerken bir boşta döngüde durduğunu, ancak programın bu süre içinde yararlı bir iş de yapabileceğini unutmayın.

DMA denetleyicisinin belki de daha yaygın olan bir başka kullanımı, bellek ile bir GÇ çevre birimi arasında aktarım yapmaktır. Bu durumda GÇ yazmacının adresi her aktarım için değişmez, ancak bellek adresinin artırılması gerekir. Ayrıca, verileri bir çevre birimi tarafından kabul edilmeden önce yazmamak veya veri hazır olmadan önce okumamak için aktarımın hızını kontrol etmek de gereklidir ve bu, DMA kanalının kontrol yazmacının treq_sel alanıyla kontrol edilebilir. Her DMA kanalının kontrol yazmacının çeşitli alanları DMA.pack_ctrl() yöntemi kullanılarak paketlenebilir ve DMA.unpack_ctrl() statik yöntemi kullanılarak çözülebilir. Bir bayt dizisinden bir PIO durum makinesinin TX FIFO’suna her seferinde bir bayt veri aktaran kod şöyle görünür:

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

Bu örnekte yazma adresi için verilen değerin yalnızca verileri gönderdiğimiz PIO durum makinesi olduğunu unutmayın. Bu çalışır çünkü PIO durum makineleri arabellek protokolünü sunarak veri FIFO yazmaçlarına doğrudan erişime izin verir.

Kurucu

class rp2.DMA

DMA denetleyicisi kanallarından birini özel kullanım için talep eder.

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

Kanal için DMA yazmaçlarını yapılandırır ve isteğe bağlı olarak aktarımı başlatır. Parametreler şunlardır:

  • read: DMA denetleyicisinin veri okumaya başlayacağı adres veya okunacak verileri sağlayacak bir nesne. Bir tam sayı veya arabellek protokolünü destekleyen herhangi bir nesne olabilir.

  • write: DMA denetleyicisinin yazmaya başlayacağı adres veya verinin yazılacağı bir nesne. Bir tam sayı veya arabellek protokolünü destekleyen herhangi bir nesne olabilir.

  • count: Bu kanal durmadan önce yürütülecek veri yolu aktarımlarının sayısı. Bunun bayt sayısı değil, aktarım sayısı olduğunu unutmayın. Aktarımlar 2 veya 4 bayt genişliğindeyse, taşınan toplam veri miktarının (ve dolayısıyla gereken arabelleğin boyutunun) buna göre çarpılması gerekir.

  • ctrl: DMA kontrol yazmacının değeri. Bu, tipik olarak DMA.pack_ctrl() kullanılarak paketlenen bir tam sayı değeridir.

  • trigger: İsteğe bağlı olarak aktarımı hemen başlatır.

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

Bu DMA kanalı için IRQ nesnesini döndürür ve isteğe bağlı olarak yapılandırır.

close() None

Temel DMA kanalı üzerindeki talebi serbest bırakır ve kesme işleyicisini boşaltır. Bu işlemden sonra DMA nesnesi kullanılamaz.

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

Anahtar kelime bağımsız değişkenlerinde sağlanan değerleri yeni bir kontrol yazmacı değerinin adlandırılmış alanlarına paketler. Sağlanmayan herhangi bir alan varsayılan bir değere ayarlanır. Varsayılan değer ya sağlanan default değerinden alınır ya da bu verilmezse mevcut kanal için uygun bir varsayılandan alınır; bunu DMA.ctrl özniteliğinin geçerli değerine ayarlamak, alanların bir alt kümesini geçersiz kılmanın kolay bir yolunu sağlar.

Anahtar kelime bağımsız değişkenleri için anahtarlar, DMA.unpack_ctrl() yöntemi tarafından döndürülen herhangi bir anahtar olabilir. Yazılabilir değerler şunlardır:

  • enable: bool Kanalı etkinleştirmek için ayarlayın (varsayılan: True).

  • high_pri: bool Bu kanalın veri yolu trafiğini yüksek öncelikli yapar (varsayılan: False).

  • size: int Aktarım boyutu: 0=bayt, 1=yarım sözcük, 2=sözcük (varsayılan: 2).

  • inc_read: bool Her aktarımdan sonra okuma adresini artırır (varsayılan: True).

  • inc_write: bool Her aktarımdan sonra yazma adresini artırır (varsayılan: True).

  • ring_size: int Sıfır değilse, bir adres yazmacının yalnızca en alttaki ring_size biti bir adres artırıldığında değişir ve adresin bir sonraki 1 << ring_size bayt sınırında sarmalanmasına neden olur. Hangi adresin sarmalandığı ring_sel bayrağıyla kontrol edilir. Sıfır değer adres sarmalamayı devre dışı bırakır.

  • ring_sel: bool ring_size değerinin okuma adresine uygulanması için False, yazma adresine uygulanması için True olarak ayarlayın.

  • chain_to: int Bu aktarım tamamlandıktan sonra tetiklenecek bir kanalın kanal numarası. Bu değeri bu DMA nesnesinin kendi kanal numarasına ayarlamak zincirlemeyi devre dışı bırakır (bu varsayılandır).

  • treq_sel: int Bir Aktarım İsteği sinyali seçer. Ayrıntılar için RP2040 veri sayfasındaki 2.5.3 bölümüne bakın.

  • irq_quiet: bool Her aktarımın sonunda kesme oluşturmaz. Bunun yerine, tetik yazmacına sıfır değer yazıldığında kesmeler oluşturulur ve bu, zincirlenmiş aktarımlar dizisini durdurur (varsayılan: True).

  • bswap: bool Doğru olarak ayarlanırsa, sözcüklerdeki veya yarım sözcüklerdeki baytlar yazılmadan önce ters çevrilir (varsayılan: True).

  • sniff_en: bool Verilere yongaların sniff donanımı tarafından erişilmesine izin vermek için True olarak ayarlayın (varsayılan: False).

  • write_err: bool Bunu True olarak ayarlamak, daha önce bildirilen bir yazma hatasını temizler.

  • read_err: bool Bunu True olarak ayarlamak, daha önce bildirilen bir okuma hatasını temizler.

Tüm bu alanların ayrıntıları için RP2040 veri sayfasının 2.5.7 bölümündeki CH0_CTRL_TRIG yazmacının açıklamasına bakın.

unpack_ctrl(value: int) dict

Bir DMA kanalı kontrol yazmacının bir değerini, kontrol yazmacındaki alanların her biri için anahtar/değer çiftleri içeren bir sözlüğe çözer. value, çözülecek ctrl yazmacı değeridir.

Bu yöntem, DMA.pack_ctrl ifadesine aktarılabilecek tüm anahtarlar için değerler döndürür. Buna ek olarak, kontrol yazmacındaki salt okunur bayrakları da döndürür: bir aktarım başladığında yükselen ve bittiğinde düşen busy ve read_err ile write_err bayraklarının mantıksal VEYA’sı olan ahb_err. Bu değerler paketleme sırasında yok sayılır, böylece bir kontrol yazmacının çözülmesiyle oluşturulan sözlük doğrudan paketleme için anahtar kelime bağımsız değişkenleri olarak kullanılabilir.

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

DMA kanalının şu anda çalışıp çalışmadığını alır veya ayarlar.

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

Bu öznitelik, bir sonraki veri yolu aktarımının okuyacağı adresi yansıtır. Bir tam sayı veya arabellek protokolünü destekleyen bir nesneyle yazılabilir ve bunu yapmak anında etki eder.

write: int

Bu öznitelik, bir sonraki veri yolu aktarımının yazacağı adresi yansıtır. Bir tam sayı veya arabellek protokolünü destekleyen bir nesneyle yazılabilir ve bunu yapmak anında etki eder.

count: int

Bu özniteliği okumak, geçerli aktarım dizisindeki kalan veri yolu aktarımlarının sayısını döndürür. Bu özniteliği yazmak, toplam aktarım sayısını sonraki aktarım dizisi olarak ayarlar.

ctrl: int

Bu öznitelik, DMA kanalı kontrol yazmacını yansıtır. Tipik olarak DMA.pack_ctrl() yöntemi kullanılarak paketlenmiş bir tam sayıyla yazılır. Döndürülen yazmaç değeri DMA.unpack_ctrl() yöntemi kullanılarak çözülebilir.

channel: int

DMA kanalının kanal numarası. Bu, DMA zincirlemesine izin vermek için başka bir kanalda DMA.pack_ctrl() ifadesinin chain_to bağımsız değişkeninde aktarılabilir.

registers: 'memoryview'

Bu öznitelik, DMA kanalının yazmaçlarına doğrudan erişime izin veren dizi benzeri bir nesnedir. Dizin bayt yerine sözcük cinsindendir, dolayısıyla yazmaç dizinleri yazmaç adres ofsetlerinin 4’e bölünmüş halidir. Yazmaç ayrıntıları için RP2040 veri sayfasına bakın.

Zincirleme ve tetik yazmacı erişimi

RP2040’taki DMA denetleyicisi, bir DMA kanalının başka bir kanalda bir aktarım başlatmasına olanak tanıyan birkaç gelişmiş özellik sunar. Bunlardan biri kontrol yazmacındaki chain_to değerinin kullanılması, diğeri ise DMA kanalının tetik etkisi olan yazmaçlarından birine yazmaktır. Bir DMA kanalının başka bir kanalın DMA.registers yazmaçlarına doğrudan yazma yeteneğiyle birleştiğinde, bu, herhangi bir CPU müdahalesi olmadan karmaşık işlemlerin gerçekleştirilmesine olanak tanır.

Aşağıda, birden çok veri bloğunun tek bir hedefe toplanmasını uygulamak için hem zincirleme hem de yazmaç tetiklemesinin kullanılmasına dair bir örnek bulunmaktadır. Bu özelliklerin tüm ayrıntıları RP2040 veri sayfasının 2.5 bölümünde bulunabilir ve aşağıdaki kod, 2.5.6.2 alt bölümündeki örneğin Python’cu bir sürümüdür.

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)

Bu örnek, aktarımın tamamlanmasını beklerken boşta bekler; alternatif olarak bir kesme işleyicisi ayarlayıp hemen geri dönebilirdi.