LoginSignup
1

Arduino-CLIでESP32の開発環境を整える

Last updated at Posted at 2023-11-20

はじめに

「おうちハック」で定番のESP32。ESP-IDFをご利用の方も多いとは思いますが、秋月や千石、マルツで買ってきたセンサーを手っ取り早く使うにはArduinoのライブラリをそのまま使えるArduino IDEのほうが都合が良かったりします。

そんなわけで私もESP32をArduinoで使っております、が、しかし、ArduinoのIDEは使いにくい...

また、esp32のArduinoボードにはバージョン1系統(2023秋時点で最新はv1.0.6)とバージョン2系統(同2.0.14)がございますが、微妙に2系では安定して動かないということもあります。

ということで、本記事では以下のことを目指します。

  • CLIでESP32 Arduinoの開発を行う(GUIは使わない)
  • Makefile を使う
  • 「ボード」のバージョン切り替えを可能にする
  • OTAもmakeから可能とする
  • 実行時例外発生時の遺言からの犯人探しをする(シリアル接続時・リモート時どちらも)
  • macosまたはlinux環境(窓?知らない子ですね...)

ディレクトリ構成

path 説明
~/src/arduino/ 開発環境のroot
~/src/arduino/cli-config/ arduino-cliのyamlの場所
~/src/arduino/core/esp32/x.y/ ボードインストール先(x.yはバージョン)
~/src/arduino/core/espota.py espota.py
~/src/arduino/esp-idf/ esp-idf (coredump解読にのみ使用)
~/src/arduino/core/decoder/ 死因解明ツールの置き場
~/src/arduino/core/download/ ダウンロードしたものを置く場所

インストールと初期設定

Arduino-CLIインストール

homebrewなどで適当にインストールします。

$ brew install arduino-cli

yamlの用意

arduino-cli config init で雛形を作ります。 macosの場合、~/Library/Arduino15/arduino-cli.yaml にできます。

残念ながら環境変数は展開してくれないようですのでバージョンに合わせて複数つくります。

$ arduino-cli config init
$ cp ~/Library/Arduino15/arduino-cli.yaml \
     ~/src/arduino/cli-config/arduino-cli-1.0.yaml
$ cp ~/Library/Arduino15/arduino-cli.yaml \
     ~/src/arduino/cli-config/arduino-cli-2.0.yaml
~/src/arduino/cli-config/arduino-cli-1.0.yaml
board_manager:
  additional_urls:
    - https://espressif.github.io/arduino-esp32/package_esp32_index.json
directories:
  data: /home/sweethome/src/arduino/core/esp32/1.0/
  user: /home/sweethome/src/arduino/
  downloads: /home/sweethome/src/arduino/core/download/
~/src/arduino/cli-config/arduino-cli-2.0.yaml
board_manager:
  additional_urls:
    - https://espressif.github.io/arduino-esp32/package_esp32_index.json
directories:
  data: /home/sweethome/src/arduino/core/esp32/2.0/
  user: /home/sweethome/src/arduino/
  downloads: /home/sweethome/src/arduino/core/download/

esp32のボードのURLはこちらに記載。

ボードのインストール

$ arduino-cli --config-file ~/src/arduino/cli-config/arduino-cli-1.0.yaml \
  core install esp32:esp32@1.0.6
$ arduino-cli --config-file ~/src/arduino/cli-config/arduino-cli-2.0.yaml \
  core install esp32:esp32@2.0.14

ccacheの追加

ccacheを追加してコンパイルを高速化します。ccacheなんて使うの、linux kernelの自前ビルドをしていた頃以来ですわ(老害発言)。

find ~/src/arduino/core/ -name platform.txt して見つけ出し、recipe.c.o.patternrecipe.cpp.o.patternの頭にccacheを追加します。

OTAの用意

http(s)やmqttではないotaをするには、esp32のSSIDのLANと開発マシンの間でいくつかtcpが通る必要があります。 以下では、開発マシン->esp32に18266/tcp, esp32→開発マシンに28266/tcpを通しています(家庭内でVLANを切る変態)。

スケッチを格納しているディレクトリの名前でesp32のホスト名を登録しておきます。 つまり、hoge/hoge.inoを書き込んだesp32にはping hogeができるということです。

Makefileの中で使いやすいようにsymlinkしておきます。

$ cd ~/src/arduino/core/
$ ln -s $(find esp32/2.0 -name espota.py) .

私の場合、常時otaを待機させるのではなく特定のmqttを送った後にのみota出来るようにしてメモリの節約をしています。 (wifi, ble, webserver, otaを全部同時に使うのは結構きついです...) そのため、espota.pyは直接Makefileから呼ばずにmqttを含んだラッパーを作っています。

stack trace decoderのインストール

実行時クラッシュ時、シリアルに出てくるesp32の遺言を解析してくれる検視官を雇います。

$ mkdir ~/src/arduino/core/decoder/
$ cd !$
$ wget -nd https://github.com/littleyoda/EspStackTraceDecoder/releases/download/untagged-59a763238a6cedfe0362/EspStackTraceDecoder.jar
$ mkdir 1.0 2.0
$ cd 1.0
$ find ../../esp32/1.0/ -name *addr2line
#適切なものを自分で選ぶ
$ ln -s ${みつけたもの} addr2line
$ cd -
$ cd 2.0
$ find ../../esp32/2.0/ -name *addr2line
#適切なものを自分で選ぶ
$ ln -s ${みつけたもの} addr2line

esp-coredumpのインストール

$ git clone https://github.com/espressif/esp-coredump.git
$ cd esp-coredump
$ pip3 install .

coreダンプ救出コードはこちらが参考になります。

起動した時、前世で生成したcoredumpがフラッシュにあったら読み出してネットワーク経由で吐き出すようにしてあげます。
syslogに出すのが良いでしょう。

xtensa-esp32-elf-gdbcore/esp32/2.0/ の下にあるものを使えばokです。

上記の方法でhex dumpした場合、最初の20バイトはヘッダーのようですのでスキップするとELFになります。

$ esp-coredump info_corefile \
    --core ${corefile} \
    --core-format elf \
    build-esp32/${name}.ino.elf
===============================================================
==================== ESP32 CORE DUMP START ====================

================== CURRENT THREAD REGISTERS ===================
pc             0x40094164          0x40094164 <tlsf_malloc+308>
lbeg           0x4008a9d4          1074309588
lend           0x4008a9de          1074309598
lcount         0x0                 0
sar            0x1d                29
ps             0x60823             395299
threadptr      <unavailable>
br             <unavailable>
...

rsyslogdが動いているマシンからlogをscpしてきて、hexdumpをbinaryにして20バイトスキップして上記のコマンドを実行するラッパーesp32-coredecoderを作っておきます。

これで、シリアルが繋がっていない時に死亡しても死因の解明ができるようになります。

Makefile

Makefile
VERSION   = 2.0
PARTITION = min_spiffs
PORT      = /dev/cu.usbserial-0001
SPEED     = 74880

ifneq ("$(wildcard env.mk)","")
   include env.mk
endif

SKETCH       = $(notdir $(CURDIR)).ino
NAME         = $(notdir $(CURDIR))
TARGET_DIR   = $(CURDIR)/build-esp32

FQBN         = esp32:esp32:esp32
BOARDOPTIONS = PSRAM=disabled,PartitionScheme=$(PARTITION),CPUFreq=240,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=115200,DebugLevel=none

CLI_YAML     = $(HOME)/src/arduino/cli-config/arduino-cli-$(VERSION).yaml

ESPOTA       = python3 $(HOME)/src/arduino/core/espota.py --debug --port 18266 --host_port 28266 --auth=secret --progress
DECODER      = $(HOME)/src/arduino/core/decoder/EspStackTraceDecoder.jar
ADDR2LINE    = $(HOME)/src/arduino/core/decoder/$(VERSION)/addr2line

# syslogにでたcoreをデコードするラッパーを作っておく
COREDECODER  = $(HOME)/bin/esp32-coredecoder

.PHONY: build

put:  build ota
sput: build upload monitor

build:
    @ mkdir -p $(TARGET_DIR)
    arduino-cli compile \
        --config-file $(CLI_YAML) \
        --fqbn $(FQBN) \
        --board-options $(BOARDOPTIONS) \
        --build-path $(TARGET_DIR) \
        $(CURDIR)

clean:
    rm -rf $(TARGET_DIR)

ota:
    $(ESPOTA) --ip $(NAME) --file $(TARGET_DIR)/$(SKETCH).elf

upload:
    arduino-cli upload \
        --config-file $(CLI_YAML) \
        --fqbn $(FQBN) \
        --board-options $(BOARDOPTIONS) \
        --input-dir $(TARGET_DIR) \
        --port $(PORT)

monitor:
    arduino-cli monitor \
        --config-file $(CLI_YAML) \
        --fqbn $(FQBN) \
        --port $(PORT) \
        --config baudrate=$(SPEED)
        
decode: trace.txt
    java -jar $(DECODER) $(ADDR2LINE) $(TARGET_DIR)/$(SKETCH).elf trace.txt

decodecore:
    $COREDECODER --name $(NAME) --elf $(TARGET_DIR)/$(SKETCH).elf

開発

スケッチのあるディレクトリでその個体固有の設定を env.mk に書きます。

env.mk
VERSION = 1.0
PORT    = /dev/cu.wusbserial-1234

あとはmake putするだけ。

うまく行かないときでも前世のcoreがとれていそうなら、make decodecore です。
「そんなcore産んだ覚えはありません」となったら、泣きながらttyを接続してmake monitorして遺言をtrace.txtに書き、make decodeすると犯人がわかります。
otaができない時も泣きながらシリアルを繋げてmake uploadします。

ところでinoは?

うちの*.inoはほとんど空っぽで、似非シングルトンなc++のクラスの中で全部処理しています。

hoge/hoge.ino
#include "Hoge.h"

HogeBase *hoge=0;

void setup(void)
{
  hoge=new Hoge();
  hoge->setup();
}

void loop(void)
{
  hoge->loop();
}

多数のesp32で遊んでいるので、共通した処理(自分がよく使うセンサー、mqtt処理、influxdbやvictoria metricsへの書き込み、prometheusの/metricsなど)の入った基底クラスをつくっておいて、個体ごとに違うセンサーなどは継承したクラスの中で処理しています。
調子に乗ってなんでも入れるとフラッシュが足りなくなるのですが...

Arduinoのライブラリは使いたいがinoはキモいので嫌い、というわがまま。

おわりに

以上でemacsでc++-modeを使ってM-x compileな人も幸せになれます。

Macは次はARMからコイツに移行するのではないか、といわれているRISC-Vを搭載したESP32C3が混在するときも、同じような方法でMakefileenv.mkに細工すればいけます。

Happy おうちHacking!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1