このドキュメンテーションは、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章を参照してください。

コンパニオンクラス DMATimer は、DMA コントローラのペースタイマーへのアクセスを提供します。これらのタイマーは、独自の転送要求シグナリングを持たないメモリまたは周辺機器への転送速度を制御するために使えます。

サンプルコード

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 フラグで制御されます。値がゼロの場合はアドレスのラップを無効にします(デフォルト: 0)。

  • ring_sel: bool リングサイズを読み込みアドレスに適用するか、書き出しアドレスに適用するかを指定。 False を指定すると、リングサイズが読み込みアドレスに適用します。 True を指定すると、書き出しアドレスに適用します(デフォルト: False)。

  • chain_to: int この転送が完了した後にトリガーするチャネルのチャネル番号。この値をこの DMA オブジェクトの自身のチャネル番号に設定すると、チェイニングが無効になります(これがデフォルト)。

  • treq_sel: int 転送リクエスト信号を選択します。詳細については、RP2040 データシートの2.5.3章を参照してください。 DMATimer インスタンスを渡して、そのタイマーを転送のペース制御に使うこともできます。デフォルトは 0x3f (PERMANENT)で、これは DMA チャネルが外部からの要求を待たずにフルバス速度で動作することを意味します。

  • irq_quiet: bool 各転送の終了時に割り込みを生成させない。代わりに、トリガーレジスタにゼロ値が書き出されると割り込みが生成され、チェインされた転送シーケンスが停止します(デフォルト: True)。

  • bswap: bool True を指定すると、ワードやハーフワードのバイトを書き出す前に逆順になります(デフォルト: False)。

  • sniff_en: bool データがチップのスニフハードウェアによってアクセス可能になるようにする(デフォルト: False)。

  • write_err: bool True を指定すると、以前に報告された書き出しエラーがクリアされます(デフォルト: False)。

  • read_err: bool True を指定すると、以前に報告された読み込みエラーがクリアされます(デフォルト: False)。

これらのフィールドの詳細については、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)

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

クラス DMATimer -- DMA 転送用のペーシングタイマー

RP2040 および RP2350 DMAコントローラは、DMA転送の速度を制御するために使える4つの「ペーシング」タイマーを備えています。制御構成の treq_sel パラメータを使って転送要求信号を指定しない場合、DMAコントローラはバスが許容する限り高速にデータを転送しようとします。これはシステムクロック速度と同程度の速度になる可能性があります。多くの場合、I/O 操作は低速で行われる必要があり、バスの過負荷を避けるためにメモリ間の転送速度を調整することも有効な場合があります(特に、オンチップ SRAM よりもはるかに低速な外部 PSRAM を使う場合)。 DMATimer を使うことで、システムクロック速度の有理数の割合となる速度を選択できます。各タイマーは、システムクロックの X/Y 倍の速度で転送要求を個別にトリガーできます(X < Y)。

DMATimer オブジェクトは DMA.pack_ctrl() 関数に渡される treq_sel の値として直接使用できます。 int(dma_timer) の値は、タイマーの転送要求セレクタのインデックスだからです。したがって、転送を毎秒 10000 オペレーションで実行するようにペースを調整したい場合は、次のように使用できます:

dma = rp2.DMA()
timer = rp2.DMATimer(freq=10000)
ctrl = d.pack_ctrl(treq_sel=timer)  # タイマーによってペースが調整されるデフォルトの制御値
dma.config(read=src, write=dst, count=length, ctrl=ctrl, trigger=True)

注記: DMATimer がガベージコレクションされると、基となる DMA ペーシングタイマーは解放されます。タイマーオブジェクトがスコープ外になる前に完了しないと予想されるDMA転送を開始する場合は、タイマーが他の呼び出し元に再割り当てされないように、タイマーへの参照を保持しておくことをお勧めします(再割り当てされると、タイマーの周波数が変わってしまう可能性があるためです)。

コンストラクタ

class rp2.DMATimer(timer_id=None, *, freq=None, ratio=None)

DMA ペースタイマーのうち1つを専用に割り当て、必要に応じて周波数または比率を設定します。

  • timer_id: 使用するタイマーを指定します。空欄のままにすると、未割り当てのタイマーがどれでも選択されます。

  • freq: freq 属性に割り当てるオプションの値。

  • ratio: ratio 属性に割り当てるオプションの値。

freqratio``の両方が指定されている場合は ``ratio が適用されます。

メソッド

DMATimer.close()

基盤となるタイマーの独占的権利を解放します。

属性

DMATimer.ratio

システムクロックの分周比を表す (X, Y) タプルを設定または読み取ります。分周比を設定する際は、X と Yの両方が 0 < X, Y < 65536 の範囲内である必要があります。

DMATimer.freq

DMATimer の周波数を Hz 単位で設定または読み取ります。設定時には、分周器で実現可能な最も近い周波数に設定されます。周波数を読み取ると、実際に選択された周波数が最も近い 1Hz 単位で表示されます。要求される値は、システムクロック速度以下かつシステムクロックの 1/65535 以上である必要があります。