MicroPython的午睡(6) micro:bit、ファイルシステムとメモリダンプ

Joseph Halfmoon

MicroPythonはMicroと言いつつも、見た目「普通の」Python同様に書ける部分が多いので嬉しくもあり、また無意識に「外れ」たところを触ってしまってビックリすることもあり。今回は気になっていたmicro:bit上のMicroPythonのファイルシステムあたりをエクササイズしてみます。

※「MicroPython的午睡」投稿順 Indexはこちら

さて、このファイルシステムには荘厳なお名前があります。以下にAPIリファレンスへのURLを貼り付けさせていただきます。

ローカル永続ファイルシステム

ローカルなファイルシステム、しかし永続であります。永続の意味はといえば

電源きってもファイルは消えない

という意味みたいです。でもFlashメモリを再プログラミングすると消えてしまう儚い「永続」であります。MicroPythonの実装では必ず装備されている機能のようですが、他のMicroPythonの実装に比べ micro:bit 上の実装はかなり小さいようです。上記の説明文書から1箇所引用させていただきましょう。

メモリの制約のため、ファイルシステムの 記憶域は約 30 KB となっています

他の実装では階層化されたディレクトリ構造などファイルシステムらしい機能がサポートされていたりしますがmicro:bit上の実装にそれはありません。

さて micro:bit をUSBポートに接続するとドライブに見えますが、ローカル永続ファイルシステムはそこにはありません。RESETボタンを押しながらUSBポートに接続するとまた別なドライブが現れますが、そこでもありません。ローカル永続ファイルシステムへのアクセスはMicroPythonのインタプリタを介して行わないと駄目なようです。幸い、Raspberry Pi上のMuエディタはこの機能を持っています。micro:bitのホームページからアクセスできる Python Editor Version 2 もこの機能を持っているようです。以下は Raspberry Pi上のMuエディタを使っています。Muエディタは micro:bit の接続を自動的に検出して micro:bit モードに入るか選択できるので便利です。ローカル永続ファイルシステムへのアクセスはREPLを使っていない状態で「ファイル」メニューを押せばできます。以下の画面キャプチャのようにmicro:bit側とRaspberry Pi側のディレクトリ(mu_code)を並べてファイルをリストしてくれるので、転送したいファイルをドラッグアンドドロップでOKであります。

MuFMなお、使用しておりますMicroPythonのバージョンは以下のようです、念のため。

>>> import os
>>> os.uname()
(sysname='microbit', nodename='microbit', release='1.0.1', version='micro:bit v1.0.1+unknown on 2019-01-19; MicroPython v1.9.2-34-gd64154c73 on 2017-09-01', machine='micro:bit with nRF51822')

今回使用のコードは末尾にまとめてあります。

まずは、MicroPythonのREPLから、ローカル永続ファイルシステム内のテキストファイルの内容を確認したい、と思います。「転送」ボタンでエディタから送ったスクリプトもローカル永続ファイルシステム上のテキストファイルになっています。Muエディタ上のファイル名に関わらず自動実行の対象なので main.py という名で格納されています。その他、上の方法で送ったPythonスクリプトはそのままの名のテキストファイルです。

PCやラズパイ上のPython3であればイテレータで回してファイル内容を表示できるでしょう。こんな感じ。

$ python3 f000.py
def fileDump(fnam):
    with open(fnam, 'r') as robj:
        for lin in robj:
            print(lin, end='')

fileDump('f000.py')

でもmicro:bit上のMicroPythonでやるとエラーになります。こんなエラー。

TypeError: 'TextIO' object is not iterable

イテレータでやらず1行ごとに読むことにしたいです。なお、普通のPythonの場合ファイル丸ごとメモリ上に持ってくる手がありますが、RAM容量が極めて厳しい micro:bit なのでそれはパスです。テキストの1行入力(行末まで読み込み)の関数は readline()というものがあります。ちと曖昧だったので以下2つ確認しておきたいです。

  1. EOF(End Of File)になったらどうなるの?
  2. 行末にLFが無かったらどうなるの?

1については、readline()関数については明確に書いてなかったですが、read()関数に書いてあるのと同様、EOFになったら長さ0のオブジェクトが返りました。2については、行末にLFが無くてもEOFまで読みこまれました。そこで以下の関数を用意。

fileDump()関数

末尾のコードをmbFU.pyとしてローカル永続ファイルシステム上においておけばREPLプロンプトより、以下で引数ファイルを表示することができます。

>>> import mbFU
>>> mbFU.fileDump('mbFU.py')

次にファイル名の表示にちょっとカッコつけたいです。ファイルシステムの中のファイル名は os.listdir()でリストで返ります。各ファイルの大きさは、os.size() にファイル名を渡せば分かります。この2つを合わせてファイル名とサイズで一覧にしたい。ついでに使っているファイルサイズの合計も知りたい。ファイルシステムの容量がとても小さいのだから。

fileDir()関数

こちらはプロンプトから以下のようにすればファイル名とサイズ、合計サイズが表示されます。

>>> import mbFU
>>> mbFU.fileDir()
test.txt (3 Bytes)
mbFU.py (546 Bytes)
main.py (61 Bytes)
Total File Size: 610 Byte

さて次は少しディープな方向にシフトしてメモリダンプしたいです。メモリダンプ(16進+アスキー)用には以下の関数用意しました。なお開始番地もバイト数も常に16の倍数にまるめられます。

memDump(開始番地、表示バイト数)

>> import mbFU
>>> mbFU.memDump(0x3DE00,0x100)
0x0003de00 | 00 4c 07 6d 62 46 55 2e 70 79 66 72 6f 6d 20 6d .L.mbFU.pyfrom m
0x0003de10 | 69 63 72 6f 62 69 74 20 69 6d 70 6f 72 74 20 2a icrobit import *
0x0003de20 | 0a 0a 64 65 66 20 66 69 6c 65 44 75 6d 70 28 66 ..def fileDump(f
0x0003de30 | 6e 61 6d 29 3a 0a 20 20 20 20 77 69 74 68 20 6f nam):.    with o
0x0003de40 | 70 65 6e 28 66 6e 61 6d 2c 20 27 72 27 29 20 61 pen(fnam, 'r') a
0x0003de50 | 73 20 72 6f 62 6a 3a 0a 20 20 20 20 20 20 20 20 s robj:.        
0x0003de60 | 6c 69 6e 20 3d 20 72 6f 62 6a 2e 72 65 61 64 6c lin = robj.readl
0x0003de70 | 69 6e 65 28 29 0a 20 20 20 20 20 20 20 20 77 56 ine().        wV
0x0003de80 | 00 68 69 6c 65 20 6c 65 6e 28 6c 69 6e 29 20 21 .hile len(lin) !
0x0003de90 | 3d 20 30 3a 0a 20 20 20 20 20 20 20 20 20 20 20 = 0:.           
0x0003dea0 | 20 70 72 69 6e 74 28 6c 69 6e 29 0a 20 20 20 20  print(lin).    
0x0003deb0 | 20 20 20 20 20 20 20 20 6c 69 6e 20 3d 20 72 6f         lin = ro
0x0003dec0 | 62 6a 2e 72 65 61 64 6c 69 6e 28 29 0a ff ff ff bj.readlin()....

ローカル永続ファイルシステムは、256KB(micro:bit v1.5)のフラッシュメモリの末尾付近にあるようです。上のダンプ結果の右の方のアスキーダンプを見れば、まさにダンプにつかったモジュールのソースが見えている?感じです。でもよく見るとここは古いバージョンのファイルの残骸っぽいです。本物は下のアドレスにある方でしょうか。

0x0003f480 | fe 41 07 6d 62 46 55 2e 70 79 66 72 6f 6d 20 6d .A.mbFU.pyfrom m
0x0003f490 | 69 63 72 6f 62 69 74 20 69 6d 70 6f 72 74 20 2a icrobit import *
0x0003f4a0 | 0a 69 6d 70 6f 72 74 20 6f 73 0a 69 6d 70 6f 72 .import os.impor
0x0003f4b0 | 74 20 6d 61 63 68 69 6e 65 20 61 73 20 48 44 0a t machine as HD.
0x0003f4c0 | 0a 64 65 66 20 66 69 6c 65 44 75 6d 70 28 66 6e .def fileDump(fn
0x0003f4d0 | 61 6d 29 3a 0a 20 20 20 20 77 69 74 68 20 6f 70 am):.    with op
0x0003f4e0 | 65 6e 28 66 6e 61 6d 2c 20 27 72 27 29 20 61 73 en(fnam, 'r') as

MicroPythonのファイルシステムのソースを読めばどんな管理しているのか分かるでしょうが、今日はメンドイです。とりあえず直ぐに知らんでも問題なさそうだし。。。

MicroPython的午睡(5) LSM303AGRへアクセス、micro:bit へ戻る

MicroPython的午睡(7) M5StickV、MicroPython再々復活 へ進む

今回使用した import 用モジュール
from microbit import *
import os
import machine as HD

def fileDump(fnam):
    with open(fnam, 'r') as robj:
        lin = robj.readline()
        while len(lin) != 0:
            print(lin, end='')
            lin = robj.readline()

def fileWrite(fnam, arg):
    with open(fnam, 'w') as wobj:
        print(wobj.write(arg))

def fileDir():
    fLis = os.listdir()
    totSize = 0
    for fil in fLis:
        fSize = os.size(fil)
        totSize += fSize
        print("{0}   ({1} Bytes)".format(fil, fSize))
    print("Total File Size: {0} Bytes".format(totSize))

def memDump(startAdr, siz):
    endAdr = (startAdr + siz + 15) & 0xFFFFFFF0
    startAdr &= 0xFFFFFFF0
    for adr in range(startAdr, endAdr, 16):
        print("0x{0:08x} | ".format(adr), end='')
        sLis = []
        for ladr in range(0, 16):
            mm = HD.mem8[adr+ladr]
            if (mm >= 0x20) and (mm < 0x7F):
                sLis.append(chr(mm))
            else:
                sLis.append('.')
            print("{0:02x} ".format(mm), end='')
        print(''.join(sLis))