class DMA – גישה לבקר ה-DMA של ה-RP2040

המחלקה DMA מציעה גישה לבקר ה-Direct Memory Access (DMA) של ה-RP2040, ומספקת את היכולת להעביר נתונים בין בלוקי זיכרון ו/או אוגרי IO. לבקר ה-DMA יש חיבורים נפרדים משלו כ-bus master לקריאה ולכתיבה אל מרקם האפיק, וכל ערוץ DMA יכול לקרוא נתונים מכתובת אחת ולכתוב אותם בחזרה לכתובת אחרת באופן עצמאי, תוך אפשרות להגדיל אחד מהמצביעים או את שניהם, מה שמאפשר לו לבצע העברות בשם המעבד בעוד המעבד מבצע משימות אחרות או נכנס למצב צריכת חשמל נמוכה. לבקר ה-DMA של ה-RP2040 יש 12 ערוצי DMA עצמאיים שיכולים לרוץ במקביל. לפרטים מלאים על מערכת ה-DMA של ה-RP2040 ראה סעיף 2.5 ב-RP2040 Datasheet.

דוגמאות

השימוש הפשוט ביותר בבקר ה-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 מציגות את פרוטוקול החוצץ (buffer), מה שמאפשר גישה ישירה לאוגרי ה-FIFO של הנתונים שלהן.

בנאי (Constructor)

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 יתחיל לקרוא נתונים או אובייקט שיספק נתונים לקריאה. זה יכול להיות מספר שלם או כל אובייקט שתומך בפרוטוקול החוצץ (buffer).

  • write: הכתובת שאליה בקר ה-DMA יתחיל לכתוב או אובייקט שאליו ייכתבו הנתונים. זה יכול להיות מספר שלם או כל אובייקט שתומך בפרוטוקול החוצץ (buffer).

  • 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

תכונה זו משקפת את הכתובת שממנה תקרא העברת האפיק הבאה. ניתן לכתוב לה מספר שלם או אובייקט שתומך בפרוטוקול החוצץ (buffer), ופעולה זו נכנסת לתוקף מיידית.

write: int

תכונה זו משקפת את הכתובת שאליה תכתוב העברת האפיק הבאה. ניתן לכתוב לה מספר שלם או אובייקט שתומך בפרוטוקול החוצץ (buffer), ופעולה זו נכנסת לתוקף מיידית.

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 של ערוץ אחר, זה מאפשר ביצוע טרנזקציות מורכבות ללא כל התערבות מצד ה-CPU.

להלן דוגמה לשימוש הן בהשרשרת והן בהפעלה באמצעות אוגרים כדי לממש איסוף של בלוקים מרובים של נתונים אל יעד יחיד. ניתן למצוא פרטים מלאים על תכונות אלה בסעיף 2.5 בגיליון הנתונים של ה-RP2040, והקוד שלהלן הוא גרסה פייתונית של הדוגמה בתת-סעיף 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)

דוגמה זו נמצאת במצב סרק בזמן ההמתנה להשלמת ההעברה; לחלופין, היא יכלה להגדיר מטפל פסיקות ולחזור מיד.