クラス 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:
bool
True
を指定すると、ワードやハーフワードのバイトを書き込む前に逆順になります(デフォルト:True
)。sniff_en:
bool
データがチップのスニフハードウェアによってアクセス可能になるようにする(デフォルト:False
)。write_err:
bool
True
を指定すると、以前に報告された書き出しエラーがクリアされます。read_err:
bool
True
を指定すると、以前に報告された読み込みエラーがクリアされます。
これらのフィールドの詳細については、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)
この例では、転送が完了するのを待ってアイドル状態になりますが、代わりに割り込みハンドラを設定してすぐに返すこともできます。