คลาส DMA -- การเข้าถึงตัวควบคุม DMA ของ RP2040¶
คลาส DMA ให้การเข้าถึงตัวควบคุม Direct Memory Access (DMA) ของ RP2040 โดยมีความสามารถในการย้ายข้อมูลระหว่างบล็อกหน่วยความจำและ/หรือรีจิสเตอร์ IO ตัวควบคุม DMA มีการเชื่อมต่อบัสมาสเตอร์อ่านและเขียนแยกต่างหากบนผ้าบัส และแต่ละช่องทาง DMA สามารถอ่านข้อมูลจากที่อยู่หนึ่งและเขียนไปยังที่อยู่อื่นได้อย่างอิสระ โดยเลือกเพิ่มหรือลดพอยน์เตอร์อย่างใดอย่างหนึ่งหรือทั้งสอง ทำให้สามารถดำเนินการถ่ายโอนแทนโปรเซสเซอร์ได้ในขณะที่โปรเซสเซอร์ทำงานอื่นหรืออยู่ในสถานะประหยัดพลังงาน ตัวควบคุม DMA ของ RP2040 มี 12 ช่องทาง DMA อิสระที่ทำงานพร้อมกันได้ สำหรับรายละเอียดทั้งหมดของระบบ DMA ของ RP2040 ดูส่วนที่ 2.5 ของ RP2040 Datasheet
ตัวอย่าง¶
การใช้งาน DMA controller ที่ง่ายที่สุดคือการย้ายข้อมูลจากบล็อกหน่วยความจำหนึ่งไปยังอีกบล็อกหนึ่ง สามารถทำได้ด้วยโค้ดดังต่อไปนี้:
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 controller อีกแบบที่พบได้บ่อยกว่าคือการถ่ายโอนระหว่างหน่วยความจำและอุปกรณ์ IO ต่อพ่วง ในกรณีนี้ที่อยู่ของรีจิสเตอร์ IO จะไม่เปลี่ยนแปลงในแต่ละการถ่ายโอน แต่ที่อยู่หน่วยความจำต้องถูกเพิ่มขึ้น นอกจากนี้ยังจำเป็นต้องควบคุมความเร็วของการถ่ายโอนเพื่อไม่ให้เขียนข้อมูลก่อนที่อุปกรณ์ต่อพ่วงจะยอมรับได้ หรืออ่านก่อนที่ข้อมูลจะพร้อม และสามารถควบคุมได้ด้วยฟิลด์ treq_sel ของรีจิสเตอร์ควบคุมของช่องทาง DMA ฟิลด์ต่างๆ ของรีจิสเตอร์ควบคุมสำหรับแต่ละช่องทาง DMA สามารถบรรจุด้วยเมธอด DMA.pack_ctrl() และแกะออกด้วยเมธอด static DMA.unpack_ctrl() โค้ดสำหรับถ่ายโอนข้อมูลจาก byte array ไปยัง TX FIFO ของ PIO state machine ทีละหนึ่งไบต์มีลักษณะดังนี้:
# 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 state machine ที่เราส่งข้อมูลไป ซึ่งใช้งานได้เนื่องจาก PIO state machine รองรับโปรโตคอล buffer ทำให้สามารถเข้าถึงรีจิสเตอร์ data FIFO ได้โดยตรง
ตัวสร้าง¶
- class rp2.DMA¶
จองช่องทาง DMA controller หนึ่งช่องเพื่อใช้งานโดยเฉพาะ
- 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 controller จะเริ่มอ่านข้อมูล หรือออบเจ็กต์ที่จะให้ข้อมูลที่ต้องการอ่าน อาจเป็นจำนวนเต็มหรือออบเจ็กต์ใดก็ตามที่รองรับโปรโตคอล buffer
write: ที่อยู่ที่ DMA controller จะเริ่มเขียน หรือออบเจ็กต์ที่จะเขียนข้อมูลลงไป อาจเป็นจำนวนเต็มหรือออบเจ็กต์ใดก็ตามที่รองรับโปรโตคอล 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ไม่สร้างอินเทอร์รัปต์เมื่อสิ้นสุดแต่ละการถ่ายโอน อินเทอร์รัปต์จะถูกสร้างแทนเมื่อเขียนค่าศูนย์ไปยังรีจิสเตอร์ trigger ซึ่งจะหยุดลำดับการถ่ายโอนแบบเชื่อมต่อ (ค่าเริ่มต้น: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 ออกเป็น dictionary ที่มีคู่คีย์/ค่าสำหรับแต่ละฟิลด์ในรีจิสเตอร์ควบคุม value คือค่ารีจิสเตอร์
ctrlที่ต้องการแกะเมธอดนี้จะคืนค่าสำหรับคีย์ทั้งหมดที่สามารถส่งผ่านไปยัง
DMA.pack_ctrlนอกจากนี้ยังคืนค่าแฟล็กอ่านอย่างเดียวในรีจิสเตอร์ควบคุม:busyซึ่งจะสูงเมื่อการถ่ายโอนเริ่มต้นและต่ำเมื่อสิ้นสุด และahb_errซึ่งเป็น OR ทางตรรกะของแฟล็กread_errและwrite_errค่าเหล่านี้จะถูกละเว้นเมื่อบรรจุ ดังนั้น dictionary ที่สร้างโดยการแกะรีจิสเตอร์ควบคุมสามารถใช้โดยตรงเป็นอาร์กิวเมนต์คีย์เวิร์ดสำหรับการบรรจุได้
- 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 chaining
- registers: 'memoryview'¶
แอตทริบิวต์นี้เป็นออบเจ็กต์คล้าย array ที่อนุญาตให้เข้าถึงรีจิสเตอร์ของช่องทาง DMA โดยตรง ดัชนีเป็นแบบคำ ไม่ใช่ไบต์ ดังนั้นดัชนีรีจิสเตอร์จึงเป็น offset ที่อยู่รีจิสเตอร์หารด้วย 4 ดูเอกสารข้อมูล RP2040 สำหรับรายละเอียดรีจิสเตอร์
การเชื่อมต่อและการเข้าถึงรีจิสเตอร์ trigger¶
DMA controller ใน RP2040 มีฟีเจอร์ขั้นสูงสองอย่างเพื่ออนุญาตให้ช่องทาง DMA หนึ่งเริ่มการถ่ายโอนบนช่องทางอื่น หนึ่งคือการใช้ค่า chain_to ในรีจิสเตอร์ควบคุม และอีกหนึ่งคือการเขียนไปยังรีจิสเตอร์ของช่องทาง DMA หนึ่งที่มีผล trigger เมื่อรวมกับความสามารถให้ช่องทาง DMA หนึ่งเขียนโดยตรงไปยัง DMA.registers ของช่องทางอื่น จะทำให้สามารถดำเนินการธุรกรรมที่ซับซ้อนได้โดยไม่ต้องมีการแทรกแซงของ CPU
ด้านล่างเป็นตัวอย่างการใช้ทั้ง chaining และ register triggering เพื่อรวบรวมข้อมูลหลายบล็อกไปยังปลายทางเดียว รายละเอียดทั้งหมดของฟีเจอร์เหล่านี้สามารถดูได้ในส่วนที่ 2.5 ของ RP2040 data sheet และโค้ดด้านล่างเป็นเวอร์ชัน Pythonic ของตัวอย่างในหัวข้อย่อย 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)
ตัวอย่างนี้วนรอว่างขณะรอให้การถ่ายโอนเสร็จสิ้น หรืออาจตั้งค่าตัวจัดการอินเทอร์รัปต์และคืนค่าทันทีก็ได้