الفئة DMA -- الوصول إلى وحدة تحكم DMA في RP2040

توفّر الفئة DMA الوصول إلى وحدة تحكم الوصول المباشر إلى الذاكرة (DMA) في RP2040، مما يتيح إمكانية نقل البيانات بين كتل الذاكرة و/أو سجلات IO. تمتلك وحدة تحكم DMA اتصالاتها الخاصة المنفصلة لإتقان ناقل القراءة والكتابة على بنية الناقل، ويمكن لكل قناة DMA أن تقرأ البيانات بصورة مستقلة من عنوان واحد وتعيد كتابتها إلى عنوان آخر، مع إمكانية زيادة أحد المؤشرين أو كليهما، مما يسمح لها بإجراء عمليات النقل نيابة عن المعالج بينما يقوم المعالج بمهام أخرى أو يدخل في حالة استهلاك منخفض للطاقة. تحتوي وحدة تحكم DMA في RP2040 على 12 قناة DMA مستقلة يمكن تشغيلها بالتزامن. للحصول على التفاصيل الكاملة لنظام DMA في RP2040 راجع القسم 2.5 من ورقة بيانات RP2040.

أمثلة

إن أبسط استخدام لوحدة تحكم DMA هو نقل البيانات من كتلة ذاكرة إلى أخرى. يمكن تحقيق ذلك بالكود التالي:

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

لاحظ أنه بينما يبقى هذا المثال في حلقة خمول أثناء انتظار اكتمال النقل، فإن البرنامج كان بإمكانه القيام ببعض العمل المفيد في هذا الوقت بدلًا من ذلك.

هناك استخدام آخر، ربما أكثر شيوعًا، لوحدة تحكم DMA وهو النقل بين الذاكرة وطرفية IO. في هذه الحالة لا يتغير عنوان سجل IO في كل عملية نقل لكن عنوان الذاكرة يحتاج إلى زيادة. من الضروري أيضًا التحكم في وتيرة النقل بحيث لا تُكتَب البيانات قبل أن يتمكن الطرفية من قبولها أو تُقرأ قبل أن تكون البيانات جاهزة، ويمكن التحكم في هذا باستخدام حقل treq_sel من سجل التحكم بقناة DMA. يمكن حزم الحقول المختلفة لسجل التحكم لكل قناة DMA باستخدام طريقة DMA.pack_ctrl() وفكها باستخدام الطريقة الساكنة DMA.unpack_ctrl(). يبدو الكود الذي ينقل البيانات من مصفوفة بايتات إلى TX FIFO الخاص بآلة حالات PIO، بايتًا واحدًا في كل مرة، على النحو التالي:

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

لاحظ أنه في هذا المثال القيمة المعطاة لعنوان الكتابة هي ببساطة آلة حالات PIO التي نرسل البيانات إليها. ينجح هذا لأن آلات حالات PIO تقدّم بروتوكول المخزن المؤقت، مما يسمح بالوصول المباشر إلى سجلات FIFO الخاصة ببياناتها.

المُنشئ

class rp2.DMA

يطالب بإحدى قنوات وحدة تحكم DMA للاستخدام الحصري.

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

يُكوِّن سجلات DMA للقناة ويبدأ النقل اختياريًا. المعاملات هي:

  • read: العنوان الذي ستبدأ وحدة تحكم DMA بقراءة البيانات منه أو كائن يوفّر البيانات المراد قراءتها. يمكن أن يكون عددًا صحيحًا أو أي كائن يدعم بروتوكول المخزن المؤقت.

  • write: العنوان الذي ستبدأ وحدة تحكم DMA بالكتابة إليه أو كائن ستُكتَب البيانات فيه. يمكن أن يكون عددًا صحيحًا أو أي كائن يدعم بروتوكول المخزن المؤقت.

  • count: عدد عمليات نقل الناقل التي ستُنفَّذ قبل أن تتوقف هذه القناة. لاحظ أن هذا هو عدد عمليات النقل، وليس عدد البايتات. إذا كانت عمليات النقل بعرض 2 أو 4 بايتات فإن إجمالي كمية البيانات المنقولة (وبالتالي حجم المخزن المؤقت المطلوب) يحتاج إلى الضرب وفقًا لذلك.

  • ctrl: قيمة سجل التحكم بـ DMA. وهي قيمة عددية صحيحة تُحزَم عادةً باستخدام DMA.pack_ctrl().

  • trigger: يبدأ النقل فورًا اختياريًا.

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

يُرجِع كائن IRQ لقناة DMA هذه ويكوّنه اختياريًا.

close() None

يحرّر المطالبة بقناة DMA الأساسية ويحرّر معالج المقاطعة. لا يمكن استخدام كائن DMA بعد هذه العملية.

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

يحزم القيم المقدمة في الوسائط المفتاحية في الحقول المسماة لقيمة سجل تحكم جديدة. أي حقل لا يُقدَّم سيُضبَط على قيمة افتراضية. ستُؤخَذ القيمة الافتراضية إما من قيمة default المقدمة، أو إذا لم تُعطَ، فقيمة افتراضية مناسبة للقناة الحالية؛ وضبط هذه على القيمة الحالية للسمة DMA.ctrl يوفّر طريقة سهلة لتجاوز مجموعة فرعية من الحقول.

يمكن أن تكون مفاتيح الوسائط المفتاحية أي مفتاح تُرجِعه طريقة DMA.unpack_ctrl(). القيم القابلة للكتابة هي:

  • enable: bool اضبطها لتمكين القناة (القيمة الافتراضية: True).

  • high_pri: bool يجعل حركة مرور ناقل هذه القناة ذات أولوية عالية (القيمة الافتراضية: False).

  • size: int حجم النقل: 0=بايت، 1=نصف كلمة، 2=كلمة (القيمة الافتراضية: 2).

  • inc_read: bool يزيد عنوان القراءة بعد كل عملية نقل (القيمة الافتراضية: True).

  • inc_write: bool يزيد عنوان الكتابة بعد كل عملية نقل (القيمة الافتراضية: True).

  • ring_size: int إذا كانت غير صفرية، فإن البتات السفلية ring_size فقط من أحد سجلات العناوين ستتغير عند زيادة عنوان ما، مما يتسبب في التفاف العنوان عند حد البايت 1 << ring_size التالي. يتحكم العلم ring_sel في أي العنوانين يُلَف. تعطّل القيمة الصفرية التفاف العنوان.

  • ring_sel: bool اضبطها على False لجعل ring_size ينطبق على عنوان القراءة أو True لينطبق على عنوان الكتابة.

  • chain_to: int رقم القناة لقناة يجب تشغيلها بعد اكتمال هذا النقل. ضبط هذه القيمة على رقم القناة الخاص بكائن DMA هذا نفسه يعطّل التسلسل (وهذه هي القيمة الافتراضية).

  • treq_sel: int يختار إشارة طلب نقل (Transfer Request). راجع القسم 2.5.3 في ورقة بيانات RP2040 للتفاصيل.

  • irq_quiet: bool لا تولّد مقاطعة عند نهاية كل عملية نقل. بدلًا من ذلك ستُولَّد المقاطعات عند كتابة قيمة صفرية إلى سجل التشغيل، مما سيوقف سلسلة من عمليات النقل المتسلسلة (القيمة الافتراضية: True).

  • bswap: bool إذا ضُبطت على true، فإن البايتات في الكلمات أو أنصاف الكلمات ستُعكَس قبل الكتابة (القيمة الافتراضية: True).

  • sniff_en: bool اضبطها على True للسماح بالوصول إلى البيانات بواسطة عتاد الاستشعار (sniff) في الشريحة (القيمة الافتراضية: False).

  • write_err: bool ضبط هذه على True سيمسح خطأ كتابة مُبلَّغًا عنه سابقًا.

  • read_err: bool ضبط هذه على True سيمسح خطأ قراءة مُبلَّغًا عنه سابقًا.

راجع وصف سجل CH0_CTRL_TRIG في القسم 2.5.7 من ورقة بيانات RP2040 للحصول على تفاصيل جميع هذه الحقول.

unpack_ctrl(value: int) dict

يفك قيمة سجل تحكم قناة DMA إلى قاموس بأزواج مفتاح/قيمة لكل حقل من حقول سجل التحكم. value هي قيمة سجل ctrl المراد فكها.

ستُرجِع هذه الطريقة قيمًا لجميع المفاتيح التي يمكن تمريرها إلى DMA.pack_ctrl. بالإضافة إلى ذلك، ستُرجِع أيضًا الأعلام للقراءة فقط في سجل التحكم: busy، الذي يرتفع عند بدء النقل وينخفض عند انتهائه، و ahb_err، وهو العملية المنطقية OR لعلَمي read_err و write_err. ستُتجاهَل هذه القيم عند الحزم، بحيث يمكن استخدام القاموس الذي يُنشَأ من فك سجل تحكم مباشرةً بوصفه الوسائط المفتاحية للحزم.

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

يجلب أو يضبط ما إذا كانت قناة DMA قيد التشغيل حاليًا.

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

تعكس هذه السمة العنوان الذي ستقرأ منه عملية نقل الناقل التالية. يمكن أن تُكتَب إما بعدد صحيح أو بكائن يدعم بروتوكول المخزن المؤقت، وفعل ذلك يكون له تأثير فوري.

write: int

تعكس هذه السمة العنوان الذي ستكتب إليه عملية نقل الناقل التالية. يمكن أن تُكتَب إما بعدد صحيح أو بكائن يدعم بروتوكول المخزن المؤقت، وفعل ذلك يكون له تأثير فوري.

count: int

قراءة هذه السمة ستُرجِع عدد عمليات نقل الناقل المتبقية في تسلسل النقل الحالي. كتابة هذه السمة تضبط إجمالي عدد عمليات النقل لتكون تسلسل النقل التالي.

ctrl: int

تعكس هذه السمة سجل التحكم بقناة DMA. تُكتَب عادةً بعدد صحيح مُحزَّم باستخدام طريقة DMA.pack_ctrl(). يمكن فك قيمة السجل المُرجَعة باستخدام طريقة DMA.unpack_ctrl().

channel: int

رقم قناة DMA. يمكن تمريره في الوسيط chain_to لـ DMA.pack_ctrl() على قناة أخرى للسماح بتسلسل DMA.

registers: 'memoryview'

هذه السمة كائن شبيه بالمصفوفة يتيح الوصول المباشر إلى سجلات قناة DMA. الفهرسة بالكلمة، وليس بالبايت، فمؤشرات السجلات هي إزاحات عناوين السجلات مقسومة على 4. راجع ورقة بيانات RP2040 لتفاصيل السجلات.

التسلسل والوصول إلى سجل التشغيل

تقدّم وحدة تحكم DMA في RP2040 ميزتين متقدمتين تسمحان لقناة DMA واحدة ببدء عملية نقل على قناة أخرى. إحداهما استخدام قيمة chain_to في سجل التحكم والأخرى الكتابة إلى أحد سجلات قناة DMA التي لها تأثير تشغيل. عند اقتران ذلك بإمكانية أن تكتب قناة DMA واحدة مباشرةً إلى DMA.registers لقناة أخرى، فإن هذا يسمح بإجراء معاملات معقدة دون أي تدخل من وحدة المعالجة المركزية.

في ما يلي مثال على استخدام كل من التسلسل وتشغيل السجلات لتنفيذ تجميع كتل بيانات متعددة في وجهة واحدة. يمكن العثور على التفاصيل الكاملة لهذه الميزات في القسم 2.5 من ورقة بيانات RP2040 والكود أدناه هو نسخة بأسلوب Python من المثال في القسم الفرعي 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)

يبقى هذا المثال خاملًا أثناء انتظار اكتمال النقل؛ بدلًا من ذلك كان بإمكانه ضبط معالج مقاطعة والعودة فورًا.