class DMA -- truy cập bộ điều khiển DMA của RP2040

Lớp DMA cung cấp quyền truy cập vào bộ điều khiển Direct Memory Access (DMA) của RP2040, cho phép di chuyển dữ liệu giữa các khối bộ nhớ và/hoặc các thanh ghi IO. Bộ điều khiển DMA có kết nối bus master đọc và ghi riêng biệt với fabric bus, và mỗi kênh DMA có thể độc lập đọc dữ liệu từ một địa chỉ và ghi lại vào địa chỉ khác, tùy chọn tăng một hoặc cả hai con trỏ, cho phép thực hiện các lần truyền thay cho bộ xử lý trong khi bộ xử lý thực hiện các tác vụ khác hoặc chuyển sang trạng thái tiết kiệm điện. Bộ điều khiển DMA của RP2040 có 12 kênh DMA độc lập có thể chạy đồng thời. Để biết chi tiết đầy đủ về hệ thống DMA của RP2040, hãy xem mục 2.5 của RP2040 Datasheet.

Ví dụ

Cách sử dụng đơn giản nhất của bộ điều khiển DMA là di chuyển dữ liệu từ một khối bộ nhớ sang khối khác. Điều này có thể thực hiện bằng đoạn mã sau:

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

Lưu ý rằng trong ví dụ này, chương trình chờ trong vòng lặp nhàn rỗi cho đến khi quá trình truyền hoàn tất; tuy nhiên, chương trình hoàn toàn có thể thực hiện một số công việc hữu ích trong thời gian đó thay vì chờ đợi.

Một cách sử dụng phổ biến hơn của bộ điều khiển DMA là truyền dữ liệu giữa bộ nhớ và ngoại vi IO. Trong trường hợp này, địa chỉ của thanh ghi IO không thay đổi cho mỗi lần truyền nhưng địa chỉ bộ nhớ cần được tăng dần. Ngoài ra, cần kiểm soát tốc độ truyền để không ghi dữ liệu trước khi ngoại vi có thể chấp nhận hoặc đọc dữ liệu trước khi sẵn sàng; điều này có thể được kiểm soát bằng trường treq_sel của thanh ghi điều khiển kênh DMA. Các trường khác nhau của thanh ghi điều khiển cho mỗi kênh DMA có thể được đóng gói bằng phương thức DMA.pack_ctrl() và giải nén bằng phương thức tĩnh DMA.unpack_ctrl(). Đoạn mã để truyền dữ liệu từ mảng byte đến TX FIFO của máy trạng thái PIO, mỗi lần một byte, trông như sau:

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

Lưu ý rằng trong ví dụ này, giá trị được cung cấp cho địa chỉ ghi chính là máy trạng thái PIO mà chúng ta đang gửi dữ liệu đến. Điều này hoạt động được vì các máy trạng thái PIO hỗ trợ giao thức bộ đệm, cho phép truy cập trực tiếp vào các thanh ghi FIFO dữ liệu của chúng.

Hàm khởi tạo

class rp2.DMA

Chiếm quyền sử dụng độc quyền một trong các kênh của bộ điều khiển DMA.

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

Cấu hình các thanh ghi DMA cho kênh và tùy chọn bắt đầu quá trình truyền. Các tham số bao gồm:

  • read: Địa chỉ từ đó bộ điều khiển DMA sẽ bắt đầu đọc dữ liệu hoặc một đối tượng sẽ cung cấp dữ liệu để đọc. Có thể là số nguyên hoặc bất kỳ đối tượng nào hỗ trợ giao thức bộ đệm.

  • write: Địa chỉ mà bộ điều khiển DMA sẽ bắt đầu ghi vào hoặc một đối tượng mà dữ liệu sẽ được ghi vào đó. Có thể là số nguyên hoặc bất kỳ đối tượng nào hỗ trợ giao thức bộ đệm.

  • count: Số lần truyền bus sẽ thực hiện trước khi kênh này dừng. Lưu ý đây là số lần truyền, không phải số byte. Nếu mỗi lần truyền rộng 2 hoặc 4 byte thì tổng lượng dữ liệu được di chuyển (và do đó kích thước bộ đệm cần thiết) cần nhân tương ứng.

  • ctrl: Giá trị cho thanh ghi điều khiển DMA. Đây là giá trị số nguyên thường được đóng gói bằng DMA.pack_ctrl().

  • trigger: Tùy chọn bắt đầu quá trình truyền ngay lập tức.

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

Trả về đối tượng IRQ cho kênh DMA này và tùy chọn cấu hình nó.

close() None

Giải phóng quyền sở hữu kênh DMA bên dưới và giải phóng trình xử lý ngắt. Đối tượng DMA không thể được sử dụng sau thao tác này.

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

Đóng gói các giá trị được cung cấp trong các đối số từ khóa vào các trường được đặt tên của một giá trị thanh ghi điều khiển mới. Bất kỳ trường nào không được cung cấp sẽ được đặt về giá trị mặc định. Giá trị mặc định sẽ được lấy từ giá trị default được cung cấp, hoặc nếu không được cung cấp, một giá trị mặc định phù hợp cho kênh hiện tại; đặt giá trị này bằng giá trị hiện tại của thuộc tính DMA.ctrl cung cấp cách dễ dàng để ghi đè một tập hợp con các trường.

Các khóa cho đối số từ khóa có thể là bất kỳ khóa nào được trả về bởi phương thức DMA.unpack_ctrl(). Các giá trị có thể ghi bao gồm:

  • enable: bool Đặt để bật kênh (mặc định: True).

  • high_pri: bool Đặt lưu lượng bus của kênh này ở mức ưu tiên cao (mặc định: False).

  • size: int Kích thước truyền: 0=byte, 1=nửa từ, 2=từ (mặc định: 2).

  • inc_read: bool Tăng địa chỉ đọc sau mỗi lần truyền (mặc định: True).

  • inc_write: bool Tăng địa chỉ ghi sau mỗi lần truyền (mặc định: True).

  • ring_size: int Nếu khác không, chỉ ring_size bit thấp nhất của một thanh ghi địa chỉ sẽ thay đổi khi địa chỉ được tăng dần, khiến địa chỉ quay vòng tại ranh giới byte 1 << ring_size tiếp theo. Địa chỉ nào được quay vòng được điều khiển bởi cờ ring_sel. Giá trị không vô hiệu hóa quay vòng địa chỉ.

  • ring_sel: bool Đặt thành False để ring_size áp dụng cho địa chỉ đọc hoặc True để áp dụng cho địa chỉ ghi.

  • chain_to: int Số kênh cần kích hoạt sau khi quá trình truyền này hoàn tất. Đặt giá trị này bằng số kênh của chính đối tượng DMA này để tắt chaining (đây là mặc định).

  • treq_sel: int Chọn tín hiệu Transfer Request. Xem mục 2.5.3 trong tài liệu kỹ thuật RP2040 để biết chi tiết.

  • irq_quiet: bool Không tạo ngắt ở cuối mỗi lần truyền. Thay vào đó, ngắt sẽ được tạo khi một giá trị không được ghi vào thanh ghi trigger, điều này sẽ dừng một chuỗi các lần truyền có xích (mặc định: True).

  • bswap: bool Nếu đặt thành true, các byte trong các từ hoặc nửa từ sẽ được đảo ngược trước khi ghi (mặc định: True).

  • sniff_en: bool Đặt thành True để cho phép dữ liệu được truy cập bởi phần cứng sniff của chip (mặc định: False).

  • write_err: bool Đặt thành True sẽ xóa lỗi ghi đã được báo cáo trước đó.

  • read_err: bool Đặt thành True sẽ xóa lỗi đọc đã được báo cáo trước đó.

Xem mô tả về thanh ghi CH0_CTRL_TRIG trong mục 2.5.7 của tài liệu kỹ thuật RP2040 để biết chi tiết về tất cả các trường này.

unpack_ctrl(value: int) dict

Giải nén một giá trị của thanh ghi điều khiển kênh DMA thành từ điển với các cặp khóa/giá trị cho từng trường trong thanh ghi điều khiển. value là giá trị thanh ghi ctrl cần giải nén.

Phương thức này sẽ trả về các giá trị cho tất cả các khóa có thể được truyền cho DMA.pack_ctrl. Ngoài ra, nó cũng sẽ trả về các cờ chỉ đọc trong thanh ghi điều khiển: busy, tăng lên khi bắt đầu truyền và giảm xuống khi kết thúc, và ahb_err, là phép OR logic của các cờ read_errwrite_err. Các giá trị này sẽ bị bỏ qua khi đóng gói, để từ điển được tạo bằng cách giải nén một thanh ghi điều khiển có thể được sử dụng trực tiếp làm đối số từ khóa cho việc đóng gói.

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

Lấy hoặc đặt trạng thái hiện tại của kênh DMA (đang chạy hay không).

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

Thuộc tính này phản ánh địa chỉ mà lần truyền bus tiếp theo sẽ đọc từ đó. Có thể ghi bằng số nguyên hoặc đối tượng hỗ trợ giao thức bộ đệm và việc ghi có hiệu lực ngay lập tức.

write: int

Thuộc tính này phản ánh địa chỉ mà lần truyền bus tiếp theo sẽ ghi vào. Có thể ghi bằng số nguyên hoặc đối tượng hỗ trợ giao thức bộ đệm và việc ghi có hiệu lực ngay lập tức.

count: int

Đọc thuộc tính này sẽ trả về số lần truyền bus còn lại trong chuỗi truyền hiện tại. Ghi vào thuộc tính này sẽ đặt tổng số lần truyền cho chuỗi truyền tiếp theo.

ctrl: int

Thuộc tính này phản ánh thanh ghi điều khiển kênh DMA. Thường được ghi bằng số nguyên được đóng gói bằng phương thức DMA.pack_ctrl(). Giá trị thanh ghi được trả về có thể được giải nén bằng phương thức DMA.unpack_ctrl().

channel: int

Số kênh của kênh DMA. Giá trị này có thể được truyền vào đối số chain_to của DMA.pack_ctrl() trên một kênh khác để cho phép DMA chaining.

registers: 'memoryview'

Thuộc tính này là đối tượng giống mảng cho phép truy cập trực tiếp vào các thanh ghi của kênh DMA. Chỉ số được tính theo từ, không phải theo byte, vì vậy chỉ số thanh ghi là địa chỉ offset của thanh ghi chia cho 4. Xem tài liệu kỹ thuật RP2040 để biết chi tiết về thanh ghi.

Chaining và truy cập thanh ghi trigger

Bộ điều khiển DMA trong RP2040 cung cấp một vài tính năng nâng cao cho phép một kênh DMA khởi tạo quá trình truyền trên một kênh khác. Một là sử dụng giá trị chain_to trong thanh ghi điều khiển và cái còn lại là ghi vào một trong các thanh ghi của kênh DMA có hiệu ứng trigger. Khi kết hợp với khả năng để một kênh DMA ghi trực tiếp vào DMA.registers của một kênh khác, điều này cho phép thực hiện các giao dịch phức tạp mà không cần bất kỳ sự can thiệp nào của CPU.

Dưới đây là ví dụ sử dụng cả chaining và trigger thanh ghi để triển khai việc thu thập nhiều khối dữ liệu vào một đích duy nhất. Chi tiết đầy đủ về các tính năng này có thể tìm thấy trong mục 2.5 của tài liệu kỹ thuật RP2040 và đoạn mã dưới đây là phiên bản Pythonic của ví dụ trong tiểu mục 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)

Ví dụ này chờ trong vòng lặp nhàn rỗi cho đến khi quá trình truyền hoàn tất; ngoài ra, nó có thể đặt trình xử lý ngắt và trả về ngay lập tức.