ESP32開発ボードをESP-PROGとPlatform IOを使ってデバッグする

| 10 min read
Author: shuichi-takatsu shuichi-takatsuの画像

お手軽なIoTデバイス「ESP32開発ボード(以降、ESP32と称す)」を使ってプログラミングをしていると
「あー、やっぱり Visual Studio Code(以降、VSCと称す)でデバッグしたいなー」
って思うのは筆者だけでは無いと思います。

これまでのプログラムのデバッグでは、プログラム中にprint文を書いて、確認したい内容をCOMポートに出力したり、OLEDなどの外部表示器に出力してデバッグしていました。
しかし内部変数などの細かい部分の確認をするときに毎回内部変数を出力するprint文を追加するのは手間がかかり、なにより面倒な作業でした。

今回は、そんな面倒事を一気に解消するESP専用JTAGデバッガ「ESP-PROG(Espressif社純正JTAGボード)」を紹介したいと思います。

ESP-PROGとは

#

ESP-PROG外観

Amazonスイッチサイエンスから購入できます。(筆者はもっと安いところから購入しましたけど)

中身は「FT2232HL(デュアルタイプのUSBシリアル)」が載ったボードみたいです。
この機器はUSBシリアルが2ポート使えますが、今回はデバッグ用の1ポートだけを使用し、プログラムの書き込みにはESP32本体が持つUSBポートを使用します。
ESP-PROGの残りのUSBシリアルポートを使ったプログラムの書き込みは、また別の機会に紹介したいと思います。

開発対象には 30ピンのESP32(ESP32 Dev Module)を使用します。
(開発対象の選定理由は、ジャンパーピンを引き出す電源ボードが30ピンのものしか持ち合わせがなかったためです。ブレッドボードの使用も考えましたが、ブレッドボードの使い勝手については、別の記事を参照ください)

ESP32と電源ボード

ドッキングしてセットアップします。

ESP-PROGのピン配置は こちらのURL にあります。
ESP32との接続にはESP-PROGの「JTAG COM n」と書かれたコネクタ部分のピンを使用します。
(ESP-PROGの電源電圧を3.3Vと5Vを切り替えるジャンパーピンは”5V”側に設定します)

ESP32とESP-PROGの結線は以下の図のようにします。

ESP-PROG ESP32
ESP_TMS GPIO14 (D14)
ESP_TCK GPIO13 (D13)
ESP_TDO GPIO15 (D15)
ESP_TDI GPIO12 (D12)
GND GND

ESP32とESP-PROGを接続した状態
(ESP-PROGのJTAGコネクタにちょうど合うケーブルが見つからず・・・。通常のジャンパーケーブルを挿し込んで接続しています)

ESP-PROGとESP32のそれぞれの micro USBを PC と接続します。
(ESP32のタイプによっては Type-C USBコネクタを持った物もあるようですが、まだ一般的にはmicroUSBのタイプのものが流通しているようです)

デバイスマネージャでCOMポートを確認します。

COM12,COM13,COM14 が今回接続したESP32とESP-PROGのものです。
COMポートの接続先は次のようになっていました。

  • COM12 : ESP32のシリアルポート
  • COM13 : ESP-PROGのデバッグポート
  • COM14 : ESP-PROGのUARTポート

※ 今回、ESP32のmicro USB(COM12)から給電+プログラム書き込みを実施しますので、COM14(ESP-PROGのUARTポート)は使用しません。

ESP-PROGがデバッガとして認識されない問題

#

さて、ここが【ハマりポイント その1】です。
実はESP-PROGのCOMポートが上記のように認識されているだけではデバッグができません。
このままESP-PROGを使用しても「デバッガが見つかりません」と言われてエラーになってしまいます。
(ネット上の記事では接続後すぐに使用できたという記事も見かけますが)

ESP-PROGのデバッグポートを「デバッガ」として認識させるためにはひと手間必要です。
ここで「Zadig」というツールを使います。

このURLから Zadig をダウンロードします。

ダウンロードした「Zadig-2.8.exe」(2024-01-02現在の最新)を実行します。
Zadig が起動します。

Options から「List All Devices」を選択します。  

リストの中に以下の2つのインターフェースがあると思います。

  • Dual RS232-HS (Interface 0) ← ESP-PROG Debug用
  • Dual RS232-HS (Interface 1) ← ESP-PROG UART用

Dual RS232-HS の「Interface 0」側を選択して「Replace Driver」ボタンを押します。
(Interface 0側が DEBUG用です)

インストールが始まります。

インストールが終了した後、デバイスマネージャーで「シリアル バス デバイス」を確認します。
「ユニバーサル シリアル バス デバイス」に「Dual RS2332-HS」が登録されていればOKです。
最初ESP-PROGをUSB接続した時はInterface0に相当するデバイスがCOM13 として認識されていましたが、COM13が消去され、バス デバイス側に登録が移動しています。

UART接続用のCOM14 のみCOMポート側に残っている状態になっています。(今回はこちらのポートは使用しません)
(画像の中のCOM12はESP32を接続している側のCOMポートです)

Platform IOでサンプルプロジェクト作成

#

開発環境として、以前執筆した記事「IoT を使ってみる(その14:有機ELディスプレイ(OLED)SSD1306編)」の中で紹介した「Platform IO(以降、PIOと称す)」を使っていきます。

では、ESP32用のプロジェクトを作成します。
作成したプロジェクトは以下です。

  • プロジェクト名: ESP32_ESP-PROG
  • ボード: Espressif ESP32 Dev Module
  • フレームワーク: Arduino
  • ロケーション: <デフォルトを使用しない!>

さて、ここでさらに【ハマりポイント その2】です。
今回、プロジェクトのロケーションを
 c:/opt/ESP32_ESP-PROG/
に変更しました。
実は最初、プロジェクトのロケーションを
 C:/Users/<username>/OneDrive/ドキュメント/PlatformIO/Projects
に置いところ、デバッガが「ドキュメント」フォルダを含むパス名を正常に認識しなかったので、仕方なく別の場所に移動しました。

プロジェクト外観は以下のようになりました。

デバックするサンプルプログラムとして Bluetooth を扱うものをコードサンプルから取得しました。
サンプルプログラムの「String device_name;」の部分だけ、任意の文字列に書き換えています。
(今回Bluetoothデバイス名は「BT-ESP32-Slave」としました)

#include <Arduino.h>

//This example code is in the Public Domain (or CC0 licensed, at your option.)
//By Evandro Copercini - 2018
//
//This example creates a bridge between Serial and Classical Bluetooth (SPP)
//and also demonstrate that SerialBT have the same functionalities of a normal Serial

#include "BluetoothSerial.h"

//#define USE_PIN // Uncomment this to use PIN during pairing. The pin is specified on the line below
const char *pin = "1234"; // Change this to more secure PIN.

String device_name = "BT-ESP32-Slave";

#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif

#if !defined(CONFIG_BT_SPP_ENABLED)
#error Serial Bluetooth not available or not enabled. It is only available for the ESP32 chip.
#endif

BluetoothSerial SerialBT;

void setup() {
  Serial.begin(115200);
  SerialBT.begin(device_name); //Bluetooth device name
  #ifdef USE_PIN
    SerialBT.setPin(pin);
    Serial.println("Using PIN");
  #endif
}

void loop() {
  if (Serial.available()) {
    SerialBT.write(Serial.read());
  }
  if (SerialBT.available()) {
    Serial.write(SerialBT.read());
  }
  delay(20);
}

デバッグ設定(platformio.ini)

#

今回はESP-PROGでデバッグを行うため、platformio.ini にいくつかオプションを追加していきます。
platformio.ini ファイルの内容を以下のようにしました。

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps = mbed-seeed/BluetoothSerial@0.0.0+sha.f56002898ee8
build_flags = 
    ; LED_BUILTIN を別のピンにアサイン
    -D LED_BUILTIN=2
monitor_speed = 115200
upload_port = COM12
upload_speed = 921600
debug_tool = esp-prog
debug_init_break = tbreak setup
;debug_init_break = tbreak loop
build_type = debug

platformio.ini の詳細を説明していきます。
以下の3つの設定は基本なので今回特に変更はありません。

  • platform : PIOでプロジェクト作成時、ボードに「Espressif ESP32 Dev Module」を選択すると「espressif32」が設定されます。
  • board : 上記と同様の操作で「esp32dev」が設定されます。
  • framework : 開発フレームワークに「Arduino」を選択したので「arduino」が設定されます。

Bluetoothを使うのに必要なライブラリを追加しています。

  • lib_deps : 今回はBluetoothを使用するので「BluetoothSerial」ライブラリをインストールしています。(ライブラリの設定方法は こちら を参照ください)

ESP-PROGでデバッグするために追加したオプションは以下です。

  • build_flags : ビルド時に設定するオプションです。今回利用したESP32はボード上に制御できるLEDを持っていないのでピン設定を変更しています。(今回の記事では利用しませんが、念の為)
  • monitor_speed : シリアル通信のボーレートを指定します。(Bluetooth通信の結果をCOMポートのモニタで確認するため)
  • upload_port : PIOはプログラムをアップロードするCOMポートを自動検出しますが、ESP-PROGのUARTとESP32側の2つのCOMポートが存在するので、今回はESP32側のCOMポートを明示的に指定しています。
  • upload_speed : アップロードのスピードを指定します。

今回の記事の肝と言ってもいい部分です。

  • debug_tool : 「esp-prog」を指定します。
  • debug_init_break : デバッグ実行時に最初に停止(ブレーク)させたい関数を指定します。今回は「setup」関数で止まるように指定しました。
  • build_type : ビルドオプションに「debug」を指定します。debug以外に release、testの指定ができるようです。

デバッグ設定(VSC)

#

VSCのデバッグ設定「launch.json」はPIOが出力した初期値のままを使用します。
(<username>の部分は、自分のアカウントに読み替えてください)

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "platformio-debug",
            "request": "launch",
            "name": "PIO Debug",
            "executable": "c:/opt/ESP32_ESP-PROG/.pio/build/esp32dev/firmware.elf",
            "projectEnvName": "esp32dev",
            "toolchainBinDir": "C:/Users/<username>/.platformio/packages/toolchain-xtensa-esp32/bin",
            "internalConsoleOptions": "openOnSessionStart",
            "preLaunchTask": {
                "type": "PlatformIO",
                "task": "Pre-Debug"
            }
        },
        {
            "type": "platformio-debug",
            "request": "launch",
            "name": "PIO Debug (skip Pre-Debug)",
            "executable": "c:/opt/ESP32_ESP-PROG/.pio/build/esp32dev/firmware.elf",
            "projectEnvName": "esp32dev",
            "toolchainBinDir": "C:/Users/<username>/.platformio/packages/toolchain-xtensa-esp32/bin",
            "internalConsoleOptions": "openOnSessionStart"
        },
        {
            "type": "platformio-debug",
            "request": "launch",
            "name": "PIO Debug (without uploading)",
            "executable": "c:/opt/ESP32_ESP-PROG/.pio/build/esp32dev/firmware.elf",
            "projectEnvName": "esp32dev",
            "toolchainBinDir": "C:/Users/<username>/.platformio/packages/toolchain-xtensa-esp32/bin",
            "internalConsoleOptions": "openOnSessionStart",
            "loadMode": "manual"
        }
    ]
}

ビルド、アップロード、そしてデバッグ

#

PIOでプログラムをビルドし、ESP32にアップロードします。
ESP32は時々プログラム書き込みに失敗することもありますが、ESP32をリセットするなどすると書き込めるようになります。
ESP32にプログラムが書き込めない問題については、いろいろな方が対策に取り組んでいるようです。
(参考:ESP32にプログラムを書き込めないときに試す対処法

VSCのデバッグを選択し「実行とデバッグ」から「PIO Debug」を実行します。

デバッガーが起動され「Setup関数」で停止します。
(INIファイルの「debug_init_break」オプションで指定しているため)

画面右下の「デバッグコンソール」上でも 26行目の「void setup()」で停止していることが見て取れます。

そのまま実行させると、設定したブレークポイントで停止します。
「これぞデバッグ!」って感じですね。
print文でちまちまデバッグするよりも効率が良いと思います。

ステップ実行も可能です。

Bluetoothで接続し、どんな動作をするかなど細かい部分の確認ができるようになりました。

ちょっとした「謎」

#

プログラムを少し書き換えてみましょう。
プログラムにグローバル変数「counter」とローカル変数「_counter」を設け、loop関数の中でインクリメントするだけの単純な変更です。

プログラムの一部抜粋

// グローバル変数の定義
int counter = 0;

void setup() {
  Serial.begin(115200);
  SerialBT.begin(device_name); //Bluetooth device name
  #ifdef USE_PIN
    SerialBT.setPin(pin);
    Serial.println("Using PIN");
  #endif
}

void loop() {
  // ローカル変数の定義
  int _counter = 0;

  // グローバル変数の演算
  counter++;
  // ローカル変数の演算
  _counter++;

  if (Serial.available()) {
    SerialBT.write(Serial.read());
  }
  if (SerialBT.available()) {
    Serial.write(SerialBT.read());
  }
  delay(100);
}

loop関数の中で counter, _counter のインクリメント部分にブレークポイントを設定してみました。
グローバル変数に設定したブレークポイントは機能しました。

あれ?ローカル変数に設定したブレークポイントがスキップされて、なぜか次の IF文まで飛ばされてしまいました。

ステップ実行でステップごとに進めようとしても、どうしてもローカル変数部分(宣言部分も演算部分も)に止まってくれません。

おそらく変数が最適化されてアドレスが振られていないのかと想像します(この程度のプログラムならレジスタ上の処理で済んでしまうのでしょう)。
最適化オプションの関係だと思ったので、デバッグビルドのオプションを調べました。
PIOのINIファイルでは、デフォルトで以下の指定がされているようです。

debug_build_flags = -Og -g2 -ggdb2

ただし、最適化オプションを変更すると Bluetoothライブラリのリンクでエラーになるなどしてしてビルドが成功しませんでした。
このあたりについてはもう少し調査が必要のようです。
詳細が分かったら、また別の記事で紹介したいと思います。

まとめ

#

今回、ESP-PROGを使ってESP32のプログラムを VSC からデバッグすることが出来ました。
print文を使ってデバッグするよりも格段に効率がよいと思います。

まあ、筆者の場合は「デバッガーを使わないとデバッグ出来ないほど難しいプログラムを書いているか?」という別の問題がありますが。

豆蔵では共に高め合う仲間を募集しています!

recruit

具体的な採用情報はこちらからご覧いただけます。