الفئة 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)
يبقى هذا المثال خاملًا أثناء انتظار اكتمال النقل؛ بدلًا من ذلك كان بإمكانه ضبط معالج مقاطعة والعودة فورًا.