クラス DMA -- RP2040 の DMA コントローラーへのアクセス
DMA クラスは、RP2040 の Direct Memory Access (DMA)コントローラへのアクセスを提供し、メモリブロックや IO レジスタ間でデータを移動する機能を提供します。DMA コントローラには、バスファブリック(bus fabric)への独自の読取りと書出しバスマスタ接続があり、各 DMA チャネルは独立して1つのアドレスからデータを読み込み、別のアドレスに書き出せます。オプションで1つまたは両方のポインタをインクリメントすることができ、プロセッサの代わりに転送を実行し、プロセッサが他のタスクを実行するか、低電力状態に入る間に転送を実行できます。RP2040 の DMA コントローラには、同時に実行できる12個の独立した DMA チャネルがあります。RP2040 の DMAシステムの詳細については RP2040 データシート の2.5章を参照してください。
サンプルコード
DMA コントローラーの最もシンプルな使用方法は、メモリの1つのブロックから別のブロックにデータを移動することです。これは次のコードで実行できます:
a = bytearray(32*1024)
b = bytearray(32*1024)
d = rp2.DMA()
c = d.pack_ctrl() # デフォルトの制御値を使用。
# カウントは「転送」単位であり、デフォルトでは4バイトのワードなので長さを4で割る。
d.config(read=a, write=b, count=len(a)//4, ctrl=c, trigger=True)
# 完了を待つ
while d.active():
pass
この例では転送が完了するのを待機していますが、プログラムはこの時に待機する代わりに有用な作業を行うこともできます。
もう1つ、おそらくより一般的なDMAコントローラの使用方法は、メモリと IO ペリフェラル間の転送です。この場合、各転送のたびに IO レジスタのアドレスは変更されませんが、メモリアドレスを増やす必要があります。また、転送のペースを制御する必要があります。つまり、ペリフェラルがデータを受け入れる前にデータを書き出せないようにし、データが準備される前に読み込めないようにする必要があります。これは、DMA チャネルの制御レジスタの treq_sel フィールドで制御できます。各 DMA チャネルの制御レジスタのさまざまなフィールドは DMA.pack_ctrl() メソッドを使ってパックし、 DMA.unpack_ctrl() 静的メソッドを使ってアンパックできます。バイト配列から PIO ステートマシンの TX FIFO にデータを1バイトずつ転送するコードは次のようになります:
# pio_num は使用している PIO ブロックのインデックス、sm_num はそのブロック内のステートマシン。
# my_state_machine は rp2.PIO() インスタンス。
DATA_REQUEST_INDEX = (pio_num << 3) + sm_num
src_data = bytearray(1024)
d = rp2.DMA()
# ワードではなくバイトを転送し、書出しアドレスをインクリメントせず、転送のペースを制御。
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つの排他的な利用を要求します。
メソッド
- DMA.config(read=None, write=None, count=None, ctrl=None, trigger=False)
チャネルの DMA レジスタを設定し、オプションで転送を開始します。パラメータは以下のとおりです:
read: DMA コントローラーがデータの読み込みを開始するアドレス、または読み込むデータを提供するオブジェクト。整数またはバッファプロトコルをサポートする任意のオブジェクトを指定できます。
write: DMA コントローラーが書き出しを開始するアドレス、またはデータが書き出されるオブジェクト。整数またはバッファプロトコルをサポートする任意のオブジェクトを指定できます。
count: このチャネルが停止する前に実行されるバス転送の数。これは転送の数であり、バイト数ではありません。転送が2または4バイト幅である場合、移動されるデータの合計量(つまり必要なバッファのサイズ)はそれに応じて乗算する必要があります。
ctrl: DMA 制御レジスタの値。これは通常
DMA.pack_ctrl()を使ってパックした整数値です。trigger: オプションで転送をすぐに開始します。
- DMA.irq(handler=None, hard=False)
この DMA チャネルの IRQ オブジェクトを返し、オプションで構築します。
- DMA.pack_ctrl(default=None, **kwargs)
キーワード引数で指定した値を新しい制御レジスタ値の名前付きフィールドにパックします。指定していないフィールドはデフォルト値に設定されます。デフォルト値は、決めてある
デフォルト値から取得されるか、それも無い場合は現在のチャネルに適したデフォルト値になります。この値をDMA.ctrl属性の現在の値に設定することで、フィールドのサブセットを簡単にオーバーライドできます。キーワード引数のキーは
DMA.unpack_ctrl()メソッドによって返されるキーである可能性があります。書出し可能な値は以下の通りです:enable:
boolチャネルを有効にするかを設定(デフォルト:True)。high_pri:
boolこのチャネルのバストラフィックを高優先度にする(デフォルト:False)。size:
int転送サイズ: 0=バイト、1=ハーフワード、2=ワード(デフォルト: 2)。inc_read:
bool各転送後に読込みアドレスをインクリメント(デフォルト:Tru)。inc_write:
bool各転送後に書き出しアドレスを増やす(デフォル:True)。ring_size:
intゼロでない場合、アドレスがインクリメントされるときに1つのアドレスレジスタの下位ring_sizeビットのみが変更され、次の1 << ring_sizeバイトの境界でアドレスがラップされます。どのアドレスがラップされるかはring_selフラグで制御されます。値がゼロの場合はアドレスのラップを無効にします。ring_sel:
boolリングサイズを読み込みアドレスに適用するか、書き出しアドレスに適用するかを指定。Falseを指定すると、リングサイズが読み込みアドレスに適用します。Trueを指定すると、書き出しアドレスに適用します。chain_to:
intこの転送が完了した後にトリガーするチャネルのチャネル番号。この値をこの DMA オブジェクトの自身のチャネル番号に設定すると、チェイニングが無効になります(これがデフォルト)。treq_sel:
int転送リクエスト信号を選択します。詳細については、RP2040 データシートの2.5.3章を参照してください。irq_quiet:
bool各転送の終了時に割り込みを生成させない。代わりに、トリガーレジスタにゼロ値が書き出されると割り込みが生成され、チェインされた転送シーケンスが停止します(デフォルト:True)。bswap:
boolTrueを指定すると、ワードやハーフワードのバイトを書き出す前に逆順になります(デフォルト:True)。sniff_en:
boolデータがチップのスニフハードウェアによってアクセス可能になるようにする(デフォルト:False)。write_err:
boolTrueを指定すると、以前に報告された書き出しエラーがクリアされます。read_err:
boolTrueを指定すると、以前に報告された読み込みエラーがクリアされます。
これらのフィールドの詳細については、RP2040 データシートの2.5.7章の
CH0_CTRL_TRIGレジスタの説明を参照してください。
- DMA.unpack_ctrl(value)
DMA チャネル制御レジスタの値を辞書に展開し、制御レジスタの各フィールドに対するキー/値のペアを返します。 value は展開する
ctrlレジスタの値です。このメソッドは
DMA.pack_ctrlに渡すことができるすべてのキーの値を返します。さらに、制御レジスタの読込み専用フラグも返します:busyは転送が開始すると high になり、 終了するとlowになります。ahb_errはread_errとwrite_errフラグの論理和です。これらの値はパッキング時に無視されるため、制御レジスタを展開して作成された辞書は、直接パッキング用のキーワード引数として使えます。
- DMA.active([value])
DMA チャネルが現在実行中かどうかを取得または設定します。
>>> sm.active() 0 >>> sm.active(1) >>> while sm.active(): ... pass
属性
- DMA.read
次のバス転送が読み込むアドレスを反映する属性。整数またはバッファプロトコルをサポートするオブジェクトで書き出すことができ、即時に効果があります。
- DMA.write
次のバス転送が書出しアドレスを反映する属性。整数またはバッファプロトコルをサポートするオブジェクトで書き出すことができ、即時に効果があります。
- DMA.count
この属性を読み込むと、現在の転送シーケンスでの残りのバス転送の数が返されます。この属性に書き出すと、次の転送シーケンスの合計転送数が設定されます。
- DMA.ctrl
この属性は DMA チャネルの制御レジスタを反映します。通常
DMA.pack_ctrl()メソッドを使ってパックした整数で書き出します。返されたレジスタ値はDMA.unpack_ctrl()メソッドを使ってアンパックできます。
- DMA.channel
DMA チャネルのチャネル番号。これは
DMA.pack_ctrl()のchain_to引数に別のチャネルのチャネル番号を渡すことで、DMA チェイニングを許可できます。
- DMA.registers
この属性は、DMA チャネルのレジスタに直接アクセスを可能にする配列のようなオブジェクトです。インデックスはバイトではなくワード単位であり、レジスタのインデックスはレジスタアドレスのオフセットを4で割ったものです。レジスタの詳細については、RP2040 データシートを参照してください。
チェイニングとトリガーレジスタのアクセス
RP2040 の DMAコントローラーには、1つの DMA チャネルが別のチャネルで転送を開始するためのいくつかの高度な機能があります。1つは制御レジスタ内の chain_to 値の使用であり、もう1つはトリガ効果を持つ DMA チャネルのレジスタに書き出すことです。これらの機能は、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):
# 2つのDMAチャネルを使用します。最初のチャネルは、ギャザーリストから長さと
# ソースアドレスを取得して、2番目のチャネルのレジスタに送信します。
# 2番目のチャネルはデータそのものをコピーします。
gather_dma = DMA()
buffer_dma = DMA()
# 長さとアドレスのペアをレジスタに送信するためにパックします。
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)
# 2番目の DMA チャネルのレジスタに書き出す際には、書出しアドレスを8バイト
# (1<<3 バイト)の境界にラップする必要があります。レジスタエイリアスの最後の
# レジスタの ``TRANS_COUNT`` と ``READ_ADD_TRIG`` レジスタ(レジスタ
# 14と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
)
# データをコピーする際、転送サイズは1バイトであり、完了したら別のギャザー DMA
# トランザクションを開始するためにチェインする必要があります。
buffer_ctrl = buffer_dma.pack_ctrl(size=0, chain_to=gather_dma.channel)
# 読込み値とカウント値は、他の DMA チャネルによって設定されます。
buffer_dma.config(write=buf, ctrl=buffer_ctrl)
# 転送を開始。
gather_dma.active(1)
# すべてのレジスタ値が送信されるまで待機
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)
この例では、転送が完了するのを待ってアイドル状態になりますが、代わりに割り込みハンドラを設定してすぐに返すこともできます。