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