class DMA – RP2040의 DMA 컨트롤러에 대한 접근¶
DMA 클래스는 RP2040의 직접 메모리 접근(Direct Memory Access, DMA) 컨트롤러에 대한 접근을 제공하여 메모리 블록 및/또는 IO 레지스터 간에 데이터를 이동하는 기능을 제공합니다. DMA 컨트롤러는 버스 패브릭에 자체적인 별도의 읽기 및 쓰기 버스 마스터 연결을 가지고 있으며, 각 DMA 채널은 한 주소에서 데이터를 독립적으로 읽어 다른 주소에 다시 쓸 수 있고, 선택적으로 한쪽 또는 양쪽 포인터를 증가시킬 수 있어, 프로세서가 다른 작업을 수행하거나 저전력 상태에 진입하는 동안 프로세서를 대신하여 전송을 수행할 수 있습니다. RP2040의 DMA 컨트롤러에는 동시에 실행할 수 있는 12개의 독립적인 DMA 채널이 있습니다. RP2040의 DMA 시스템에 대한 자세한 내용은 RP2040 Datasheet의 2.5절을 참조하세요.
예제¶
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 레지스터의 주소가 각 전송마다 변하지 않지만 메모리 주소는 증가해야 합니다. 또한 주변장치가 데이터를 수락하기 전에 데이터를 쓰거나 데이터가 준비되기 전에 읽지 않도록 전송 속도를 제어해야 하며, 이는 DMA 채널 제어 레지스터의 treq_sel 필드로 제어할 수 있습니다. 각 DMA 채널의 제어 레지스터의 다양한 필드는 DMA.pack_ctrl() 메서드를 사용하여 패킹하고 DMA.unpack_ctrl() 정적 메서드를 사용하여 언패킹할 수 있습니다. 바이트 배열에서 PIO 상태 머신의 TX FIFO로 한 번에 한 바이트씩 데이터를 전송하는 코드는 다음과 같습니다:
# 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¶
이 DMA 채널의 IRQ 객체를 반환하고 선택적으로 구성합니다.
- 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:
int0이 아닌 경우, 주소가 증가할 때 한 주소 레지스터의 하위ring_size비트만 변경되어 다음1 << ring_size바이트 경계에서 주소가 래핑됩니다. 어느 주소가 래핑되는지는ring_sel플래그로 제어됩니다. 값이 0이면 주소 래핑이 비활성화됩니다.ring_sel:
boolring_size를 읽기 주소에 적용하려면False로, 쓰기 주소에 적용하려면True로 설정합니다.chain_to:
int이 전송이 완료된 후 트리거할 채널의 채널 번호입니다. 이 값을 이 DMA 객체 자체의 채널 번호로 설정하면 체이닝이 비활성화됩니다(이것이 기본값입니다).treq_sel:
int전송 요청(Transfer Request) 신호를 선택합니다. 자세한 내용은 RP2040 데이터시트의 2.5.3절을 참조하세요.irq_quiet:
bool각 전송이 끝날 때 인터럽트를 생성하지 않습니다. 대신 트리거 레지스터에 0 값이 쓰여질 때 인터럽트가 생성되며, 이는 체인된 전송 시퀀스를 중단합니다(기본값:True).bswap:
booltrue로 설정하면 워드 또는 하프 워드의 바이트가 쓰기 전에 역순으로 바뀝니다(기본값:True).sniff_en:
bool칩의 스니프 하드웨어가 데이터에 접근할 수 있도록 하려면True로 설정합니다(기본값:False).write_err:
bool이를True로 설정하면 이전에 보고된 쓰기 오류가 지워집니다.read_err:
bool이를True로 설정하면 이전에 보고된 읽기 오류가 지워집니다.
이 모든 필드에 대한 자세한 내용은 RP2040 데이터시트의 2.5.7절에 있는
CH0_CTRL_TRIG레지스터 설명을 참조하세요.
- unpack_ctrl(value: int) dict¶
DMA 채널 제어 레지스터의 값을 제어 레지스터의 각 필드에 대한 키/값 쌍을 가진 딕셔너리로 언패킹합니다. value는 언패킹할
ctrl레지스터 값입니다.이 메서드는
DMA.pack_ctrl에 전달할 수 있는 모든 키에 대한 값을 반환합니다. 또한 제어 레지스터의 읽기 전용 플래그도 반환합니다: 전송이 시작될 때 high가 되고 끝날 때 low가 되는busy, 그리고read_err와write_err플래그의 논리 OR인ahb_err. 이 값들은 패킹 시 무시되므로, 제어 레지스터를 언패킹하여 생성된 딕셔너리를 패킹용 키워드 인수로 직접 사용할 수 있습니다.
- active(value: bool | None = None, /) bool¶
DMA 채널이 현재 실행 중인지 여부를 가져오거나 설정합니다.
>>> sm.active() 0 >>> sm.active(1) >>> while sm.active(): ... pass
- ctrl: int¶
이 속성은 DMA 채널 제어 레지스터를 반영합니다. 일반적으로
DMA.pack_ctrl()메서드를 사용하여 패킹된 정수로 쓰여집니다. 반환된 레지스터 값은DMA.unpack_ctrl()메서드를 사용하여 언패킹할 수 있습니다.
- channel: int¶
DMA 채널의 채널 번호입니다. 이는 다른 채널의
DMA.pack_ctrl()의chain_to인수에 전달하여 DMA 체이닝을 허용할 수 있습니다.
- registers: 'memoryview'¶
이 속성은 DMA 채널의 레지스터에 직접 접근할 수 있게 하는 배열형 객체입니다. 인덱스는 바이트 단위가 아니라 워드 단위이므로, 레지스터 인덱스는 레지스터 주소 오프셋을 4로 나눈 값입니다. 레지스터 세부 정보는 RP2040 데이터시트를 참조하세요.
체이닝 및 트리거 레지스터 접근¶
RP2040의 DMA 컨트롤러는 한 DMA 채널이 다른 채널에서 전송을 시작할 수 있게 하는 몇 가지 고급 기능을 제공합니다. 하나는 제어 레지스터의 chain_to 값을 사용하는 것이고, 다른 하나는 트리거 효과가 있는 DMA 채널 레지스터 중 하나에 쓰는 것입니다. 한 DMA 채널이 다른 채널의 DMA.registers에 직접 쓸 수 있는 기능과 결합하면, CPU 개입 없이 복잡한 트랜잭션을 수행할 수 있습니다.
아래는 체이닝과 레지스터 트리거링을 모두 사용하여 여러 데이터 블록을 단일 대상으로 수집하는 것을 구현하는 예제입니다. 이러한 기능에 대한 자세한 내용은 RP2040 데이터시트의 2.5절에서 찾을 수 있으며, 아래 코드는 2.5.6.2 하위 절의 예제를 Python 방식으로 작성한 버전입니다.
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)
이 예제는 전송이 완료되기를 기다리는 동안 유휴 상태로 있습니다. 대안으로 인터럽트 핸들러를 설정하고 즉시 반환할 수도 있습니다.