kelas DMA -- akses ke kontroler DMA RP2040

Kelas DMA menyediakan akses ke kontroler Direct Memory Access (DMA) RP2040, memberikan kemampuan untuk memindahkan data antara blok memori dan/atau register IO. Kontroler DMA memiliki koneksi bus master baca dan tulis terpisah ke fabric bus, dan setiap saluran DMA dapat secara independen membaca data dari satu alamat dan menulisnya kembali ke alamat lain, dengan opsi menaikkan satu atau kedua pointer, memungkinkannya melakukan transfer atas nama prosesor sementara prosesor menjalankan tugas lain atau memasuki kondisi daya rendah. Kontroler DMA RP2040 memiliki 12 saluran DMA independen yang dapat berjalan secara bersamaan. Untuk detail lengkap sistem DMA RP2040, lihat bagian 2.5 dari RP2040 Datasheet.

Contoh

Penggunaan paling sederhana dari kontroler DMA adalah memindahkan data dari satu blok memori ke blok memori lainnya. Hal ini dapat dilakukan dengan kode berikut:

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

Perhatikan bahwa meskipun contoh ini menunggu dalam loop menganggur sambil menunggu transfer selesai, program dapat saja melakukan pekerjaan yang berguna selama waktu itu.

Penggunaan kontroler DMA yang mungkin lebih umum lainnya adalah transfer antara memori dan periferal IO. Dalam situasi ini, alamat register IO tidak berubah untuk setiap transfer, tetapi alamat memori perlu dinaikkan. Juga perlu mengontrol kecepatan transfer agar tidak menulis data sebelum dapat diterima oleh periferal atau membacanya sebelum data siap, dan ini dapat dikontrol dengan field treq_sel dari register kontrol saluran DMA. Berbagai field dari register kontrol untuk setiap saluran DMA dapat dikemas menggunakan metode DMA.pack_ctrl() dan dibongkar menggunakan metode statik DMA.unpack_ctrl(). Kode untuk mentransfer data dari array byte ke TX FIFO dari mesin status PIO, satu byte sekaligus, terlihat seperti ini:

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

Perhatikan bahwa dalam contoh ini nilai yang diberikan untuk alamat tulis hanyalah mesin status PIO yang kami kirimkan datanya. Ini berfungsi karena mesin status PIO menyajikan protokol buffer, memungkinkan akses langsung ke register data FIFO mereka.

Konstruktor

class rp2.DMA

Klaim salah satu saluran kontroler DMA untuk penggunaan eksklusif.

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

Konfigurasikan register DMA untuk saluran tersebut dan opsional mulai transfer. Parameternya adalah:

  • read: Alamat dari mana kontroler DMA akan mulai membaca data atau objek yang akan menyediakan data untuk dibaca. Bisa berupa integer atau objek apa pun yang mendukung protokol buffer.

  • write: Alamat ke mana kontroler DMA akan mulai menulis atau objek ke mana data akan ditulis. Bisa berupa integer atau objek apa pun yang mendukung protokol buffer.

  • count: Jumlah transfer bus yang akan dieksekusi sebelum saluran ini berhenti. Perhatikan bahwa ini adalah jumlah transfer, bukan jumlah byte. Jika transfer selebar 2 atau 4 byte, maka total jumlah data yang dipindahkan (dan dengan demikian ukuran buffer yang diperlukan) perlu dikalikan sesuai.

  • ctrl: Nilai untuk register kontrol DMA. Ini adalah nilai integer yang biasanya dikemas menggunakan DMA.pack_ctrl().

  • trigger: Opsional mulai transfer segera.

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

Mengembalikan objek IRQ untuk saluran DMA ini dan opsional mengonfigurasinya.

close() None

Lepaskan klaim pada saluran DMA yang mendasari dan bebaskan penanganan interupsi. Objek DMA tidak dapat digunakan setelah operasi ini.

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

Kemas nilai yang disediakan dalam argumen kata kunci ke dalam field bernama dari nilai register kontrol baru. Field mana pun yang tidak disediakan akan diatur ke nilai default. Default akan diambil dari nilai default yang disediakan, atau jika tidak diberikan, default yang sesuai untuk saluran saat ini; mengatur ini ke nilai saat ini dari atribut DMA.ctrl memberikan cara mudah untuk mengganti sebagian field.

Kunci untuk argumen kata kunci bisa berupa kunci apa pun yang dikembalikan oleh metode DMA.unpack_ctrl(). Nilai yang dapat ditulis adalah:

  • enable: bool Atur untuk mengaktifkan saluran (default: True).

  • high_pri: bool Jadikan lalu lintas bus saluran ini berprioritas tinggi (default: False).

  • size: int Ukuran transfer: 0=byte, 1=setengah kata, 2=kata (default: 2).

  • inc_read: bool Naikkan alamat baca setelah setiap transfer (default: True).

  • inc_write: bool Naikkan alamat tulis setelah setiap transfer (default: True).

  • ring_size: int Jika bukan nol, hanya bit bawah ring_size dari satu register alamat yang akan berubah saat alamat dinaikkan, menyebabkan alamat membungkus pada batas byte 1 << ring_size berikutnya. Alamat mana yang dibungkus dikontrol oleh flag ring_sel. Nilai nol menonaktifkan pembungkusan alamat.

  • ring_sel: bool Atur ke False agar ring_size berlaku untuk alamat baca atau True untuk berlaku pada alamat tulis.

  • chain_to: int Nomor saluran untuk saluran yang dipicu setelah transfer ini selesai. Mengatur nilai ini ke nomor saluran objek DMA ini sendiri menonaktifkan chaining (ini adalah defaultnya).

  • treq_sel: int Pilih sinyal Transfer Request. Lihat bagian 2.5.3 dalam datasheet RP2040 untuk detailnya.

  • irq_quiet: bool Jangan hasilkan interupsi di akhir setiap transfer. Interupsi akan dihasilkan saat nilai nol ditulis ke register trigger, yang akan menghentikan urutan transfer yang di-chain (default: True).

  • bswap: bool Jika diatur ke true, byte dalam kata atau setengah-kata akan dibalik sebelum ditulis (default: True).

  • sniff_en: bool Atur ke True untuk mengizinkan data diakses oleh hardware sniff chip (default: False).

  • write_err: bool Mengatur ini ke True akan menghapus kesalahan tulis yang sebelumnya dilaporkan.

  • read_err: bool Mengatur ini ke True akan menghapus kesalahan baca yang sebelumnya dilaporkan.

Lihat deskripsi register CH0_CTRL_TRIG di bagian 2.5.7 dari datasheet RP2040 untuk detail semua field ini.

unpack_ctrl(value: int) dict

Bongkar nilai register kontrol saluran DMA menjadi kamus dengan pasangan kunci/nilai untuk setiap field dalam register kontrol. value adalah nilai register ctrl yang akan dibongkar.

Metode ini akan mengembalikan nilai untuk semua kunci yang dapat diteruskan ke DMA.pack_ctrl. Selain itu, ia juga akan mengembalikan flag hanya-baca dalam register kontrol: busy, yang naik tinggi saat transfer dimulai dan rendah saat selesai, dan ahb_err, yang merupakan OR logis dari flag read_err dan write_err. Nilai-nilai ini akan diabaikan saat dikemas, sehingga kamus yang dibuat dengan membongkar register kontrol dapat digunakan langsung sebagai argumen kata kunci untuk pengemasan.

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

Mendapatkan atau mengatur apakah saluran DMA sedang berjalan.

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

Atribut ini mencerminkan alamat dari mana transfer bus berikutnya akan membaca. Bisa ditulis dengan integer atau objek yang mendukung protokol buffer dan melakukan hal itu langsung berlaku.

write: int

Atribut ini mencerminkan alamat ke mana transfer bus berikutnya akan menulis. Bisa ditulis dengan integer atau objek yang mendukung protokol buffer dan melakukan hal itu langsung berlaku.

count: int

Membaca atribut ini akan mengembalikan jumlah transfer bus yang tersisa dalam urutan transfer saat ini. Menulis atribut ini menetapkan total jumlah transfer yang menjadi urutan transfer berikutnya.

ctrl: int

Atribut ini mencerminkan register kontrol saluran DMA. Biasanya ditulis dengan integer yang dikemas menggunakan metode DMA.pack_ctrl(). Nilai register yang dikembalikan dapat dibongkar menggunakan metode DMA.unpack_ctrl().

channel: int

Nomor saluran dari saluran DMA. Ini dapat diteruskan dalam argumen chain_to dari DMA.pack_ctrl() pada saluran lain untuk mengizinkan DMA chaining.

registers: 'memoryview'

Atribut ini adalah objek seperti array yang memungkinkan akses langsung ke register saluran DMA. Indeksnya berdasarkan kata, bukan byte, sehingga indeks register adalah offset alamat register dibagi 4. Lihat lembar data RP2040 untuk detail register.

Chaining dan akses register trigger

Kontroler DMA di RP2040 menawarkan beberapa fitur lanjutan untuk memungkinkan satu saluran DMA memulai transfer pada saluran lain. Salah satunya adalah penggunaan nilai chain_to dalam register kontrol dan yang lainnya adalah menulis ke salah satu register saluran DMA yang memiliki efek trigger. Ketika digabungkan dengan kemampuan satu saluran DMA menulis langsung ke DMA.registers dari saluran lain, ini memungkinkan transaksi kompleks dilakukan tanpa intervensi CPU apa pun.

Di bawah ini adalah contoh penggunaan chaining dan triggering register untuk mengimplementasikan pengumpulan beberapa blok data ke satu tujuan. Detail lengkap fitur-fitur ini dapat ditemukan di bagian 2.5 dari lembar data RP2040 dan kode di bawah ini adalah versi Pythonic dari contoh di sub-bagian 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)

Contoh ini menganggur saat menunggu transfer selesai; sebagai alternatif, bisa mengatur penanganan interupsi dan segera kembali.