class DMA -- RP2040 の DMA コントローラへのアクセス¶
DMA クラスは RP2040 のダイレクトメモリアクセス(DMA)コントローラへのアクセスを提供し、メモリブロック間や IO レジスタ間でデータを移動する機能を提供します。DMA コントローラはバスファブリックに対して独自の独立した読み取り用および書き込み用のバスマスタ接続を持ち、各 DMA チャネルは独立して1つのアドレスからデータを読み取り、別のアドレスに書き戻すことができ、一方または両方のポインタをオプションでインクリメントできます。これにより、プロセッサが他のタスクを実行したり低電力状態に入ったりしている間に、プロセッサに代わって転送を実行できます。RP2040 の DMA コントローラには、同時に動作可能な12個の独立した DMA チャネルがあります。RP2040 の DMA システムの詳細については、RP2040 データシート のセクション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 コントローラのもう1つの、おそらくより一般的な使い方は、メモリと IO ペリフェラルの間で転送することです。この状況では、IO レジスタのアドレスは各転送ごとに変化しませんが、メモリアドレスはインクリメントする必要があります。また、ペリフェラルが受け入れる前にデータを書き込んだり、データが準備できる前に読み取ったりしないように、転送のペースを制御する必要があります。これは DMA チャネルの制御レジスタの treq_sel フィールドで制御できます。各 DMA チャネルの制御レジスタの各種フィールドは DMA.pack_ctrl() メソッドを使用してパックでき、DMA.unpack_ctrl() 静的メソッドを使用してアンパックできます。バイト配列から PIO ステートマシンの TX FIFO へ1バイトずつデータを転送するコードは次のようになります:
# 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 コントローラのチャネルの1つを排他使用のために確保します。
- 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転送リクエスト信号を選択します。詳細については RP2040 データシートのセクション2.5.3を参照してください。irq_quiet:
bool各転送の終了時に割り込みを生成しません。代わりに、トリガレジスタに0の値が書き込まれたときに割り込みが生成され、これによってチェーンされた一連の転送が停止します(デフォルト:True)。bswap:
booltrue に設定すると、書き込み前にワードまたはハーフワード内のバイトが反転されます(デフォルト:True)。sniff_en:
boolチップの sniff ハードウェアによるデータアクセスを許可するには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に渡すことができるすべてのキーの値を返します。さらに、制御レジスタ内の読み取り専用フラグも返します。busyは転送が開始すると high になり終了すると low になります。ahb_errはread_errフラグとwrite_errフラグの論理 OR です。これらの値はパック時には無視されるため、制御レジスタをアンパックして作成された辞書を、そのままパック用のキーワード引数として使用できます。
- 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 コントローラは、1つの DMA チャネルが別のチャネルの転送を開始できるようにするいくつかの高度な機能を提供します。1つは制御レジスタの chain_to 値の使用であり、もう1つはトリガ効果を持つ DMA チャネルのレジスタの1つへの書き込みです。1つの 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)
この例は転送の完了を待つ間アイドル状態になります。代わりに割り込みハンドラを設定してただちにリターンすることもできます。