このドキュメンテーションは、MicroPython の最新開発ブランチのためのものです。 リリースバージョンでは利用できない機能に言及することがあります。

特定のリリースのドキュメントをお探しの場合は、左側のドロップダウンメニューを使って、 望みのバージョンを選択します。

クラス 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.close()

基礎となる DMA チャネルの所有権を解放し、割り込みハンドラを解放します。この操作後、 DMA オブジェクトは使えなくなります。

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_errread_errwrite_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)

この例では、転送が完了するのを待ってアイドル状態になりますが、代わりに割り込みハンドラを設定してすぐに返すこともできます。