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

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

1. ヒントとコツ

以下は、インラインアセンブラの使用例と、その制限を回避する方法についての情報です。この文書では、「アセンブラ関数」という用語は @micropython.asm_thumb デコレータを付けて Python で宣言された関数を指し、「サブルーチン」はアセンブラ関数の中から呼ばれるアセンブラコードを指します。

1.1. コードの分岐とサブルーチン

ラベルはアセンブラ関数のローカルなものである、という点を理解しておくことが重要です。現在のところ、ある関数で定義したサブルーチンを別の関数から呼び出す方法はありません。

サブルーチンを呼び出すには bl(LABEL) 命令を使います。これは label(LABEL) ディレクティブに続く命令に制御を移し、戻りアドレスをリンクレジスタ ( lr または r14 ) に格納します。戻るには bx(lr) 命令を使い、サブルーチン呼び出しの次の命令から実行が続きます。この仕組みから、サブルーチンが別のサブルーチンを呼び出す場合は、呼び出しの前にリンクレジスタを保存し、終了する前に復元しなければなりません。

次の少し作為的な例は、関数呼び出しを示しています。最初にすべてのサブルーチン呼び出しを飛び越える分岐が必要な点に注意してください。サブルーチンは bx(lr) で実行を終えるのに対し、外側の関数は Python の関数のように単に「末尾から抜け出す」だけです。

@micropython.asm_thumb
def quad(r0):
    b(START)
    label(DOUBLE)
    add(r0, r0, r0)
    bx(lr)
    label(START)
    bl(DOUBLE)
    bl(DOUBLE)

print(quad(10))

次のコード例は、入れ子になった(再帰的な)呼び出しを示しています。古典的なフィボナッチ数列です。ここでは再帰呼び出しの前に、プログラムの論理上保存が必要な他のレジスタとともに、リンクレジスタを保存しています。

@micropython.asm_thumb
def fib(r0):
    b(START)
    label(DOFIB)
    push({r1, r2, lr})
    cmp(r0, 1)
    ble(FIBDONE)
    sub(r0, 1)
    mov(r2, r0) # r2 = n -1
    bl(DOFIB)
    mov(r1, r0) # r1 = fib(n -1)
    sub(r0, r2, 1)
    bl(DOFIB)   # r0 = fib(n -2)
    add(r0, r0, r1)
    label(FIBDONE)
    pop({r1, r2, lr})
    bx(lr)
    label(START)
    bl(DOFIB)

for n in range(10):
    print(fib(n))

1.2. 引数の受け渡しと戻り値

チュートリアルで詳しく説明しているとおり、アセンブラ関数は 0 個から 3 個の引数をサポートします。引数を使う場合は r0 、 r1 、 r2 という名前でなければなりません。コードの実行時には、これらのレジスタがその値で初期化されます。

この方法で渡せるデータ型は、整数とメモリアドレスです。現在のファームウェアでは、32 ビットの取りうるすべての値を渡したり返したりできます。戻り値の最上位ビットが立っている可能性がある場合は、その値を符号付き整数と解釈するか符号なし整数と解釈するかを MicroPython が判断できるように、Python の型ヒントを使うべきです。型は int または uint です。

@micropython.asm_thumb
def uadd(r0, r1) -> uint:
    add(r0, r0, r1)

hex(uadd(0x40000000,0x40000000)) は 0x80000000 を返し、ビット 30 と 31 が異なる整数の受け渡しと返却ができることを示しています。

引数の数と戻り値の数の制限は、任意の型の値に任意の個数アクセスできる array モジュールを使えば回避できます。

1.2.1. 複数の引数

Python の整数の配列をアセンブラ関数の引数として渡すと、関数は連続した整数の集まりのアドレスを受け取ります。したがって、1つの配列の要素として複数の引数を渡せます。同様に、配列の要素に代入することで関数から複数の値を返せます。アセンブラ関数には配列の長さを知る手段がないので、これは関数に渡す必要があります。

この配列の使い方を拡張すると、3つを超える配列を使えます。これは間接参照によって行います。 uctypes モジュールは addressof() をサポートしており、引数として渡された配列のアドレスを返します。したがって、整数の配列に他の配列のアドレスを詰めることができます:

from uctypes import addressof
@micropython.asm_thumb
def getindirect(r0):
    ldr(r0, [r0, 0]) # 渡された配列から配列のアドレスをロード
    ldr(r0, [r0, 4]) # 間接配列の要素1 (24) を返す

def testindirect():
    a = array.array('i',[23, 24])
    b = array.array('i',[0,0])
    b[0] = addressof(a)
    print(getindirect(b))

1.2.2. 整数以外のデータ型

これらは、適切なデータ型の配列によって扱えます。たとえば、単精度の浮動小数点データは次のように処理できます。このコード例は float の配列を受け取り、その内容をそれぞれの2乗で置き換えます。

from array import array

@micropython.asm_thumb
def square(r0, r1):
    label(LOOP)
    vldr(s0, [r0, 0])
    vmul(s0, s0, s0)
    vstr(s0, [r0, 0])
    add(r0, 4)
    sub(r1, 1)
    bgt(LOOP)

a = array('f', (x for x in range(10)))
square(a, len(a))
print(a)

uctypes モジュールは、単純な配列にとどまらないデータ構造の利用をサポートします。Python のデータ構造を bytearray のインスタンスに対応付けられるので、それをアセンブラ関数に渡せます。

1.3. 名前付き定数

コードに数値を散りばめる代わりに名前付き定数を使うと、アセンブラのコードは読みやすく保守しやすくなります。これは次のようにして実現できます:

MYDATA = const(33)

@micropython.asm_thumb
def foo():
    mov(r0, MYDATA)

const() の構文により、MicroPython はコンパイル時に変数名をその値で置き換えます。定数を外側の Python スコープで宣言すれば、複数のアセンブラ関数の間で、また Python のコードとの間で共有できます。

1.4. クラスのメソッドとしてのアセンブラコード

MicroPython は、クラスのメソッドの第1引数としてオブジェクトのインスタンスのアドレスを渡します。これは通常、アセンブラ関数にとってほとんど役に立ちません。次のように関数を静的メソッドとして宣言すれば回避できます:

class foo:
  @staticmethod
  @micropython.asm_thumb
  def bar(r0):
    add(r0, r0, r0)

1.5. サポートされていない命令の使用

これらは、以下に示すように data 文を使って記述できます。 push() と pop() はサポートされていますが、以下の例はその原理を示すものです。必要なマシンコードは ARM v7-M アーキテクチャリファレンスマニュアルにあります。次のような data 呼び出しの最初の引数は

data(2, 0xe92d, 0x0f00) # push r8,r9,r10,r11

後続の各引数が2バイトの量であることを示しています。

1.6. MicroPython の整数の制限を克服する

Pyboard のチップには CRC ジェネレータが含まれています。返される値が 32 ビットの値の全域にわたるのに対し、MicroPython の小さな整数はビット 30 と 31 で異なる値を持てないため、これを使うと MicroPython では問題になります。この制限は次のコードで克服できます。ここでは、アセンブラで結果を配列に入れ、Python のコードでその結果を任意精度の符号なし整数に変換しています。

from array import array
import stm

def enable_crc():
    stm.mem32[stm.RCC + stm.RCC_AHB1ENR] |= 0x1000

def reset_crc():
    stm.mem32[stm.CRC+stm.CRC_CR] = 1

@micropython.asm_thumb
def getval(r0, r1):
    movwt(r3, stm.CRC + stm.CRC_DR)
    str(r1, [r3, 0])
    ldr(r2, [r3, 0])
    str(r2, [r0, 0])

def getcrc(value):
    a = array('i', [0])
    getval(a, value)
    return a[0] & 0xffffffff # 任意精度に変換

enable_crc()
reset_crc()
for x in range(20):
    print(hex(getcrc(0)))