最近のmakersなのかSTEMなのかの盛り上がりの余波で、今だRaspberry Pi PICOもmicro:bit V2も買えておりませぬ。そのためmicro:bit V1.5のBTネタへの移動にともない、本MicroPythonシリーズはM5StickVに担当してもらうことになりました。春の人事異動か?
※「MicroPython的午睡」投稿順 Indexはこちら
(今回使用のスクリプト全文は末尾に)
M5StickVはM5Stack社製の「AIカメラ」であります。小さな筐体、お手頃価格。しかし、その中核のKendryte K210 SoCは、デュアルコアのRISC-Vを頂き、倍精度浮動小数点演算対応、そしてKPUと呼ばれるNeural Network ProcessorやハードウエアFFTまで搭載しています。今までMicroPythonを走らせていた micro:bit に比べると性能的には段違い。過去、何度か「M5StickVネタ」を取り上げさせていただいておりましたが、当然な感じでAIがらみであったのです。しかし今回からしばらくは
MicroPythonネタ、とりあえずAIでない
に絞って行いたいと考えております。同じMicroPythonといいつつ、ハードウエアの違いから micro:bit上の実装とK210での実装はいろいろ違いがあるので。
また、M5StickVのMicroPython処理系である MaixPy を使用する場合、
MaixPy IDE
なるPC上のIDEが使用できるのです。このIDEは、AI向けの画処理に適したもので、リアルタイムでM5StickVのカメラが撮影した画像を解析しながら表示できたりする機能を持ちます。なかなか良いものだと思います。しかし今回は「他のMicroPython処理系」との互換性なども考えて、M5StickV上のMicroPythonのREPLをシリアル端末から直接制御する方法でやりたいと思います。
ファームウエアの更新
M5StickVの再々起動(数えてみたら購入以来3回目でした)にあたり、まずは搭載のファームウエア(MicroPythonの実装の一種であるMaixPy)を再々更新し、最新版にアップデートすることを考えました。ファームウエアの書き込み方法は、以下のページにあります。
方法としては以下の2つあります。
- EasyLoader、Windows版のみ、ハードを接続してボタンを押すだけ。ただし書きこめるファームウエアのバージョンは限定
- Kflash(Kflash_GUI)、Windows/Mac/Linux皆あり。ファームウエアバイナリを直接指定して書きこみ可能
実は1をやってバージョンが古かった(0.5.0 日付2020/7/21)ので、更新作業を行った日の最新版(0.6.2の34版 日付2021/3/16)のバイナリをダウンロード(前にも書きましたがモジュール構成でいろいろ選択肢あり。今回はM5StickVとファイル名にあるもの)して2の方法も行いました。しかし、動くのですが挙動が怪しい。それで結局1の安定版?に戻しました。なお、バイナリファイルは数日に1回くらいのペースで更新されつづけているようです。
REPLでの「開発」支援用のboot.py作成
さて今回REPL中心での「開発」するにあたって作成したのは
boot.py
なるファイルです(末尾に全ソースあり。)MaixPyは、RESET後、microSDカードが挿入されていればカードを自動マウント(/sdという名がマウントポイント)してくれるので、 /sdディレクトリの直下においておくと、自動実行されるファイルです。REPLからは、「CTRL-D」でソフトRESETがかかるので、キーボード一発で起動が可能です。
なお、MicroPython処理系によってはboot.pyが走った後、main.pyが起動されるものもあるのですが、MaixPyのこのバージョンではmain.pyが自動起動されないようです。ターミナルエミュレータでM5StickVに接続した状態でソフトRESETするとこんな感じ。
REPLでなく、何かプロンプトが返ってきますが。これが今回「開発」用にしつらえた(過去のファイルの寄せ集めですが)一種のシェルです。REPLからだと、SDカード内のMicroPythonスクリプトを実行するとき、
with open("xxx.py") as f: exec(f.read())
のようなことを書かないとならないですが、それを、
run xxx.py
などという簡単な形でやりたかった、というだけです。例えば、sdカードの中のファイルやディレクトリは、dirコマンドでリストできます(以前の回で作ってあったものの流用)
勿論、ディレクトリの移動も可能。ちゃんとプロンプト$ の左側に現在ディレクトリが表示されます。こんな感じ。なお、.. とかまだ実装していないので、移動するときは文字列でディレクトリ名を与えないとならないですが。
MaixPyは、pye()エディタを内蔵しているので、
edit xxx.py
などとすればファイルの編集が可能(pyeを呼び出すだけ)。テキストファイルの表示は type コマンドで以下のようにできます。
スクリプトの実行はRunコマンドにファイル名を渡せばOK。
任意のファイルを実行できます。スクリプトから戻るか、CTRL-Cで割り込めばREPLのプロンプトに落ちてきます。先ほど述べたとおり、REPLでCTRL-Dするとboot.pyが再び走り先ほどの$プロンプトがでます。REPLでデバッグしながら「開発」できる?
今のところ使用できるコマンドは以下のとおり。
末尾の led コマンドを打つと、ledとスイッチのデモが走ります。正面のAボタンを押すと背面のLED(結構明るい)の色が変化します。側面のBボタンで $プロンプトに戻ります。このコマンドはハードウエアを操作するコマンド群を追加する予定の原型であります。
この「Shellモドキ」は、SerialポートへのCUIですが、微妙にGUI指向もあり、M5StickVの画面にタイトルやステータスを表示もします(アイキャッチ画像ごらんください。)後でいろいろ使用の予定。M5StackにおけるM5ez的な?を指向。
まあ、これで、端末エミュレータからの操作は一通りできるようになったので、M5StickV上のMicroPythonを動かしていってみたいと思います。
MicroPython的午睡(6) micro:bit、ファイルシステムとメモリダンプ に戻る
MicroPython的午睡(8) M5StickV、ulab行列積、timeitデコレータ へ進む
import uos import time import lcd from Maix import GPIO from fpioa_manager import fm from board import board_info class ucmd: """micro cmd shell for M5StickV/MaixPy v0.1 """ buttonA = None buttonB = None buttonApressed = False buttonBpressed = False buttonAFlag = False buttonBFlag = False LedW = None LedR = None LedG = None LedB = None @classmethod def initKey(cls): fm.register(board_info.BUTTON_A, fm.fpioa.GPIO1) cls.buttonA = GPIO(GPIO.GPIO1, GPIO.IN, GPIO.PULL_UP) fm.register(board_info.BUTTON_B, fm.fpioa.GPIO2) cls.buttonB = GPIO(GPIO.GPIO2, GPIO.IN, GPIO.PULL_UP) @classmethod def initLed(cls): fm.register(board_info.LED_W, fm.fpioa.GPIO3) cls.LedW = GPIO(GPIO.GPIO3, GPIO.OUT) cls.LedW.value(1) fm.register(board_info.LED_R, fm.fpioa.GPIO4) cls.LedR = GPIO(GPIO.GPIO4, GPIO.OUT) cls.LedR.value(1) fm.register(board_info.LED_G, fm.fpioa.GPIO5) cls.LedG = GPIO(GPIO.GPIO5, GPIO.OUT) cls.LedG.value(1) fm.register(board_info.LED_B, fm.fpioa.GPIO6) cls.LedB = GPIO(GPIO.GPIO6, GPIO.OUT) cls.LedB.value(1) @classmethod def setLed(cls, b4): cls.LedW.value( (b4 & 0x8) >> 3 ) cls.LedR.value( (b4 & 0x4) >> 2 ) cls.LedG.value( (b4 & 0x2) >> 1 ) cls.LedB.value( (b4 & 0x1) ) @classmethod def updateKey(cls): if (cls.buttonA.value() == 0) and (not cls.buttonApressed): cls.buttonApressed = True if (cls.buttonA.value() == 1) and cls.buttonApressed: cls.buttonApressed = False cls.buttonAFlag = True if (cls.buttonB.value() == 0) and (not cls.buttonBpressed): cls.buttonBpressed = True if (cls.buttonB.value() == 1) and cls.buttonBpressed: cls.buttonBpressed = False cls.buttonBFlag = True @classmethod def wasButtonApressed(cls): if cls.buttonAFlag: cls.buttonAFlag = False return True return False @classmethod def wasButtonBpressed(cls): if cls.buttonBFlag: cls.buttonBFlag = False return True return False @classmethod def testLED(cls): cls.showStatus(" LED TEST running ") cls.initKey() cls.initLed() led = 15 while(True): cls.updateKey() if cls.wasButtonBpressed(): cls.setLed(0xF) cls.showStatus(" Ready") break if cls.wasButtonApressed(): led = (led - 1) if led > 0 else 15 cls.setLed(led) time.sleep_ms(100) @classmethod def cd(cls, pth): uos.chdir(pth) print('cd : ', uos.getcwd()) @classmethod def dir(cls, pth): for item in uos.ilistdir(pth): if len(item) < 2: print(str(item)) if (item[1] & 0x8000) != 0: print("F ",item[0],item[3]) elif (item[1] & 0x4000) != 0: print("D ",item[0]) else: print(str(item)) @classmethod def run(cls, pth): exec(open(pth).read()) @classmethod def type(cls, pth): with open(pth) as f: for lin in f: print(lin,end='') @classmethod def edit(cls, pth): pye(pth) @classmethod def help(cls): print("help ... show this help.") print("exit/quit ... return to REPL.") print("dir [pth] ... list file and directories.") print("type fname ... show text file contents.") print("run fname ... exec .py file.") print("ed fname ... pye editor.") print("cd pth ... change directory.") print("led ... do LED/KEY demo.") @classmethod def cmd(cls): flag = True while (flag): print("{0} $ ".format(uos.getcwd()),end='') lin = input() linL = lin.split() if len(linL) < 1: continue elif len(linL) == 1: linL.append(uos.getcwd()) try: if linL[0].startswith('exit'): flag = False elif linL[0].startswith('di'): cls.dir(linL[1]) elif linL[0].startswith('ty'): cls.type(linL[1]) elif linL[0].startswith('run'): cls.run(linL[1]) elif linL[0].startswith('ed'): cls.edit(linL[1]) elif linL[0].startswith('cd'): cls.cd(linL[1]) elif linL[0].startswith('led'): cls.testLED() elif linL[0].startswith('help'): cls.help() elif linL[0].startswith('q'): break except: print("ERROR: ", str(linL)) @classmethod def showMessage(cls, lin, arg): lin = 1 if lin < 1 else 6 if lin > 6 else lin lcd.draw_string(0, lin*16, arg, lcd.WHITE, lcd.BLACK) @classmethod def fillLine(cls, arg): nC = len(arg) if nC > 30: return arg[0:30] else: filling = " " * (30-nC) return arg + filling @classmethod def showStatus(cls, arg): lcd.draw_string(0,112,cls.fillLine(arg), lcd.BLACK, lcd.CYAN) @classmethod def showTitle(cls, arg): lcd.draw_string(0,0,cls.fillLine(arg), lcd.WHITE, lcd.BLUE) def startLCD(): lcd.init() lcd.rotation(0) def main(): print("Start uSHELL(in boot.py):") startLCD() ucmd.showTitle(" M5StickV uSHELL v0.1") ucmd.showStatus(" Ready ") ucmd.cmd() if __name__ == "__main__": main()