ESP32-DevKitC + MicroPythonを試してみた


Wi-FiもBLEも使えて開発もしやすいとっても素敵なマイコンESP32のプログラムを今までCとC++で書いていたけれども、MicroPythonという便利なものがあるということで試してみた。最初はけっこう面倒だったので記録を残しておく。すべてmacOS上でやっている。

今回使ったボードはこちら。

秋月電子でも買えるよ!

MicroPythonというのはどんなものか

まず、MicroPythonがどんなものなのか概要を説明する。

  • マイコン上でPythonスクリプトを動かせる環境
    • PCでコンパイルしてから書き込むのではなく、マイコンにPython実行環境が載ってるPythonスクリプトをそのままマイコンに書き込むと実行してくれる
    • ハードウェア系のライブラリが揃っている
    • ファームウェアを書きこむ必要がある
  • REPL (read-eval-print-loop) というインターフェイスが用意されている
    • シリアル通信もしくはWi-Fiで利用できる
    • Pythonの対話型インタプリタと同様のことができる
    • ファイルをやりとりしたりすることも簡単にできる
  • さまざまなプラットフォームがサポートされている
    • 私はESP32を使ってます
    • ESP8266でも使えるよ

ここにMicroPython公式のチュートリアルがある。だいたいこの通りにやればいいのだが、途中ちょっと迷いそうな箇所もある。

https://docs.micropython.org/en/latest/esp32/quickref.html

これからやることの流れを把握する

最初に流れを確認しておこう。MicroPythonのコンセプトを把握していないと、自分は今なんのために何をやっているのか、どのレイヤーをいじっているのかがわからなくなってしまう。

  • esptoolを使って
  • MicroPythonのファームウェアを書き込むと
  • シリアルやWi-FiでESP32と通信ができるようになって
  • 好きなPythonスクリプトをESP32に書き込んで動かすことができる

ファームウェアを書き込む

買ったばかりのESP32にesptoolを使ってMicroPythonのファームウェアを書き込む必要がある。esptoolは、ESP32などEspressifチップのブートローダと通信して書き込んだりデバッグしたりするツール。Espressifのチップを使って開発するなら、MicroPythonを使わない場合でも必需品。すでにインストール済みなら特に何もしなくていいが、なければインストールする。インストールの方法は色々あるけれどpipを使う場合は

pip install esptool

でOK。インストールがうまくいくとesptool.pyにパスが通って、コマンドラインから呼び出せるようになる。

次に、ファームウェアのバイナリファイルをダウンロードする。公式チュートリアルではMicroPythonのダウンロードページに案内されるが、どれをダウンロードすればいいのか、ちょっとわかりにくい。更にESP32のページに行って、下の方の “Firmware” というところを見てみよう。“Releases” の一番上 (末尾にlatestと書いてあるもの) が最も新しい安定版なので、.binファイルをダウンロードしてローカルに保存する。

USBケーブルでボードを接続する。ボードがちゃんと認識されると、macの “System Information.app” の “Hardware > USB” 欄にこんな感じのデバイスが出現する。

System Information.appでUSBデバイスをチェック

そして、/dev以下に

/dev/tty.usbserial-1140

(数字の部分は状況によって変わる) というような名前のデバイスが見えればOK。

esptool.pyを使って内容を消去する。”/dev/tty.usbserial-1140” の部分は適宜変えて実行してほしい。

$ esptool.py --port /dev/tty.usbserial-1140 erase_flash

消去は一瞬で終わる。うまくいったときの出力はこんな感じ。

$ esptool.py --port /dev/tty.usbserial-1140 erase_flash
esptool.py v3.2
Serial port /dev/tty.usbserial-140
Connecting.........
Detecting chip type... Unsupported detection protocol, switching and trying again...
Connecting.....
Detecting chip type... ESP32
Chip is ESP32-D0WD-V3 (revision 3)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: 08:3a:f2:2d:56:1c
Uploading stub...
Running stub...
Stub running...
Erasing flash (this may take a while)...
Chip erase completed successfully in 0.5s
Hard resetting via RTS pin...

次はMicroPythonのファームウェアを書き込んでみる。数分かかるので、肩や首のストレッチをしながら待つと良い。

$ esptool.py --chip esp32 --port /dev/tty.usbserial-1140 write_flash -z 0x1000 PATH_TO_FIRMWARE_BIN

シリアル通信で対話してみる

書き込み終わったら、PCとESP32でお話をしてみよう。シリアル通信ができるツールを用意する。Arduino IDEのシリアルモニタを使った。制御文字が使えないなど不便な点もあるけれど、そのあとでWebREPLを使えるようになればそっちで何でもできるようになるのでしばらくは我慢する。(screenコマンドでもいいんだけれどちょっとトラブルが発生したので今回は使用せず。)

ポートは今までと同様に “/dev/tty.usbserial-xxxx” を選択し、baud rateは115200を、改行は “Carriage return” を選ぶ。”No line ending” や “Newline” を選ぶと送信したメッセージがいつまでたっても受け付けてもらえないので注意が必要。

シリアルモニタの設定

シリアルモニタを開いた後、ボードのENボタンを押してリセットするとこんな感じで表示される。どこかに”MicroPython” という文字が出てくればOK。

この状態でESP32と通信し、Pythonの対話型インタプリタと同じようなことができるようになった。ちょっと足し算をしてみる。

1 + 1

とテキストボックスに打ち込んで送信してみよう。

足し算ができた。もし何も反応がなければ、改行コードがCRになっているかどうか確認すると良い。

WebREPLを使ってWi-Fi接続する

WebREPLを使うと、今シリアルモニタでやりとりしたような内容をWi-Fi経由で行うことができる。ブラウザアプリ (ローカルでも動く) を使えばプログラムのアップロードやダウンロードもさくさくできる。

まず、ボードの側をセットアップする。シリアルで

import webrepl_setup

と送ると、WebREPLの設定のための問答が始まる。

import webrepl_setup
WebREPL daemon auto-start status: disabled

Would you like to (E)nable or (D)isable it running on boot?
(Empty line to quit)
> E
Would you like to change WebREPL password? (y/n) n
Changes will be activated after reboot
Would you like to reboot now? (y/n) y

再起動すると、今度は “Started webrepl in normal mode” という文字が見える。

Started webrepl in normal mode
MicroPython v1.18 on 2022-01-17; ESP32 module with ESP32
Type "help()" for more information.
>>>

ただし! これだけだとWebREPLデーモンは起動したがネットワーク機能はオフになっている状態。アクセスポイントモードでネットワークインターフェイスを起動しないといけない。

以下のコードを1行ずつシリアルモニタから送信すると、ESP32がアクセスポイントになる。

import network
ap = network.WLAN(network.AP_IF)
ap.config(essid="ESP32_WebREPL")
ap.active(True)

PCのWi-Fi設定から SSID=ESP32_WebREPL のアクセスポイントが見えれば成功。接続してみよう。一応pingが返ってくるか確認。コマンドラインで192.168.4.1にpingを打ってみよう。

$ ping 192.168.4.1
PING 192.168.4.1 (192.168.4.1): 56 data bytes
64 bytes from 192.168.4.1: icmp_seq=0 ttl=255 time=13.234 ms
64 bytes from 192.168.4.1: icmp_seq=1 ttl=255 time=16.547 ms
64 bytes from 192.168.4.1: icmp_seq=2 ttl=255 time=24.740 ms

192.168.4.1から反応があったら、WebREPLのブラウザインターフェイスを使ってさっそく通信してみよう。Webでアクセスしてもいいし

https://micropython.org/webrepl/

githubからダウンロードしておけばローカルで “webrepl.html” を開けば使える。

https://github.com/micropython/webrepl

アドレス欄はデフォルトの

ws://192.168.4.1

のままで “Connect” を押し、必要ならパスワードを入れる。”>>>” と表示されれば、今までシリアルモニタでやっていたような対話モードが使えるようになる。

右にはファイルのアップロード/ダウンロードできる欄がある。

ちなみに、Wi-Fi経由で接続すると、すでに接続済のクライアントには次のように表示される。
WebREPL connection from: ('192.168.4.2', 60195)

ESP32をアクセスポイントモードにすると毎回Wi-Fiをつなぎかえるのが面倒だが、普段使っているWi-Fiアクセスポイントに接続することも可能。

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.scan()
wlan.connect('<SSID>', '<PASSPHRASE>')
while not wlan.isconnected() : # 接続が確立するまで待つ
    time.sleep(0.1)
conf = wlan.ifconfig() # 接続情報を取得
print(conf)

wlan.ifconfig() を実行すると、接続情報が表示される。たとえばこんな感じ。

('192.168.1.30', '255.255.255.0', '192.168.1.1', '192.168.1.1')

ESP32自身のIPアドレス、ネットマスク、ゲートウェイ、DNSの順になっている。表示されたIPアドレスには同一ネットワーク内からアクセスできるので、上の例ならWebREPLコンソールから”ws://192.168.1.30:8266″ に接続してみよう。

ちなみに、APモードとクライアントモードを両方同時に利用することもできる。

Lチカしよう!

新しいボードを買ったんだからやっぱりLチカしたいよね。というわけで、このへんでちょっとGPIOをいじってLEDを光らせてみる。ここでは例として14番ピンにLEDをつないでみる。複数行のコードを一気に送信するにはpaste modeというのを利用するのが便利だ。Ctrl-Eを押すとpaste modeに突入するので、次のようなコードをぺたっとペーストし、Ctrl-Cを押す。

import time
from machine import Pin
led=Pin(14,Pin.OUT)
 
while True:
    led.value(1)
    time.sleep(0.5)
    led.value(0)
    time.sleep(0.5)

うまく受け付けられると表示はこんなふうになる。

>>>                                                                                                                                            
paste mode; Ctrl-C to cancel, Ctrl-D to finish                                                                                                 
=== import time                                                                                                                                
=== from machine import Pin                                                                                                                    
=== led=Pin(14,Pin.OUT)                                                                                                                        
===                                                                                                                                            
=== while True:                                                                                                                                
===     led.on()                                                                                                                               
===     time.sleep(0.5)                                                                                                                        
===     led.off()                                                                                                                              
===     time.sleep(0.5)                                                                                                                        
===             
ちかちかするよ!

こうして無限ループに突入するとREPLは応答しなくなるので、ひとしきりLEDの点滅を眺めたらリセットする。

プログラムのファイルを書き込もう

WebREPLを使うとファイルのアップロードも簡単。たとえば

print("Hello")

とだけ書いたmain.pyをアップロードすれば、次に起動したときに

Hello

と挨拶してくれるようになる。MircoPythonでは、boot.pymain.py の順に実行される。そのほかのファイルを置いて呼び出すこともできる。さきほどのネットワーク初期化のコードなどを書いておくとよい。

ファイルシステムがしっかりしているので、ソースコード以外にも色々なファイルを置けるしディレクトリも掘れる。

ここでちょっと注意。boot.pyやmain.pyに無限ループ (たとえばさっきのLチカのコード) なんかを書きこんでしまうとREPLが応答しなくなる。誰もが通る道だと思う。私も通った。そんな場合は、最初の手順に戻ってファームウェアの書き込みからやり直せばOK。

(環境によっては特定のボタンを押しながら起動するとsafe bootでboot.pyやmain.pyの実行をスキップすることができるようだが、ESP32だとそういうのができないっぽい?)

実際無限ループのあるプログラムを走らせたい場合、あるいは意図せず無限ループに突入してしまってもすぐ復帰できるようにするためにはどうするか? 別スレッドで動かすなどの方法はあるけれど、モード変更用のピンを決めて起動時にHighになっていればREPL + WebREPL、Lowになっていればカスタムプログラムを実行という方法がシンプルでけっこう良い。

たとえば main.pyをこんな具合に書く。起動時、指定したピン (ここでは32番) を読み取り、Low (0) ならcustom.pyを、High (1) ならネットワークを初期化する。開発中のカスタムスクリプトは直接main.pyやboot.pyに書かずにcustom.pyに書くという具合にすれば、安心して色々と試行錯誤できるようになるだろう。

import time
import network
from machine import Pin

modePin=Pin(32, Pin.IN)

if modePin.value() == 0 : 
    print("Custom script")
    import custom
else : 
    print("WebREPL mode")
    ap = network.WLAN(network.AP_IF)
    ap.config(essid="ESP32_WebREPL")
    ap.active(True)

    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.scan()
    wlan.connect('tori_wifi', '2718281828')
    while not wlan.isconnected() :
        time.sleep(0.1)
    conf = wlan.ifconfig()
    print(conf)

custom.pyをこんな具合の無限ループのLチカにしても大丈夫だし、文法エラーがあったとしてもboot.pyやmain.pyの動作への影響はない。

import time
from machine import Pin
led=Pin(14,Pin.OUT)
 
while True:
    led.value(1)
    time.sleep(0.5)
    led.value(0)
    time.sleep(0.5)

おぼえておくと便利なこと

ファイル一覧を見たい場合は

>>> import os
>>> os.listdir()
['boot.py', 'main.py', 'webrepl_cfg.py']

使えるモジュールをリストアップしたい場合は

>>> help('modules')
__main__          gc                ubinascii         upysh
_boot             inisetup          ubluetooth        urandom
_onewire          machine           ucollections      ure
_thread           math              ucryptolib        urequests
_uasyncio         micropython       uctypes           uselect
_webrepl          neopixel          uerrno            usocket
apa106            network           uhashlib          ussl
btree             ntptime           uheapq            ustruct
builtins          onewire           uio               usys
cmath             uarray            ujson             utime
dht               uasyncio/__init__ umqtt/robust      utimeq
ds18x20           uasyncio/core     umqtt/simple      uwebsocket
esp               uasyncio/event    uos               uzlib
esp32             uasyncio/funcs    upip              webrepl
flashbdev         uasyncio/lock     upip_utarfile     webrepl_setup
framebuf          uasyncio/stream   uplatform         websocket_helper
Plus any modules on the filesystem
>>>

でOK。

help()

で初心者向けの案内が表示される。

>>> help()                                                                      
Welcome to MicroPython on the ESP32!                                            
                                                                                
For generic online docs please visit http://docs.micropython.org/               
                                                                                
For access to the hardware use the 'machine' module:                            
                                                                                
import machine                                                                  
pin12 = machine.Pin(12, machine.Pin.OUT)                                        
pin12.value(1)                                                                  
pin13 = machine.Pin(13, machine.Pin.IN, machine.Pin.PULL_UP)                    
print(pin13.value())                                                            
i2c = machine.I2C(scl=machine.Pin(21), sda=machine.Pin(22))                     
i2c.scan()                                                                      
i2c.writeto(addr, b'1234')                                                      
i2c.readfrom(addr, 4)                                                           
                                                                                
Basic WiFi configuration:                                                       
                                                                                
import network                                                                  
sta_if = network.WLAN(network.STA_IF); sta_if.active(True)                      
sta_if.scan()                             # Scan for available access points    
sta_if.connect("<AP_name>", "<password>") # Connect to an AP                    
sta_if.isconnected()                      # Check for successful connection     
                                                                                
Control commands:                                                               
  CTRL-A        -- on a blank line, enter raw REPL mode                         
  CTRL-B        -- on a blank line, enter normal REPL mode                      
  CTRL-C        -- interrupt a running program                                  
  CTRL-D        -- on a blank line, do a soft reset of the board                
  CTRL-E        -- on a blank line, enter paste mode                            
                                                                                
For further help on a specific object, type help(obj)                           
For a list of available modules, type help('modules') 

MicroPythonたのしい。