🧑‍🔧

ESP32開発ボートを使ってRS232C代理サーバを作成する

2023/12/13に公開

ESP32 は安価で高機能な SoC です。デフォルトで WiFi 機能が使えることから、Arduino よりも可能性が拡がります。筆者の本業はシステム開発ですが、ESP32には趣味として興味を持っています。でも LED を点滅させるだけでは満足できず、システム的に利用できないかと考えていました。そこで ESP32 を使った RS232C の代理サーバ(エージェント)を思いつきました。

写真1
写真1. 壁付けした RTX-1200(OEM 機)と RS232C エージェント(左上)

筆者は Yamaha RTX-1200 を用いて IPv6 環境を 2 拠点で構築し、拠点間を VPN 接続しています。RTX-1200 はとても良いルータですが、なにぶん古い機種なのでシリアル接続(RS232C)としては DSUB9 コネクタだけを持っています。普段は ssh を利用していますからシリアル接続を用いることは無いのですが、IPv6 の設定をいじっている際、TCP/IP 接続ができなくなるような失敗もあります。

そんなときは壁付けしている RTX-1200 のところまでノート PC をもっていって RS232C ケーブルで接続しなければなりません。ESP32 を RS232C の代理サーバにすれば、WiFi で接続することができます。小さいので壁につけたフックに吊るしておくことができます。

ハードウェア

写真2
写真2. ブレッドボードに挿した ESP32-WROOM-32Dと RS232C コネクタボード(裏向き)。
右の写真は上からみたもの(RS232C ケーブルを挿した状態)。
左は横からみたもの(ケーブルは挿していない)

ESP32-DevKitC-32D という Espressif 社純正の開発ボードを使っています。ボード付属の USB(Micro-B)コネクタで給電しています。

Arduino 用の ESP32 ライブラリでは、3種類のシリアル接続の方法があります。

変数名 TXD ピン RXD ピン RTS ピン CTS ピン
Serial TX RX 22 19
Serial1 D3 D2 CMD SCK
Serial2 17 16 D0 D1

Serial、Serial1、Serial2 は、HardwareSerial クラスのグローバルインスタンスとして定義されています。ESP32-DevKitC-32D には38個のIOピンがありますが、上表第2列から第5列の記号はピンの側に印字された記号です。RTS と CTS は今回は使いません。ピンの役割についての詳しい情報は Espressif 社が公開している ESP32-DevKitC の公式ガイドを参照してください。

ESP32-DevKitC-32D 開発ボードでは、D2/D3 ピンは SoC とフラッシュメモリ間の通信に使用しているらしく、公式ガイドには「使用するな」と書かれています。そのため、そのピンに TXD と RXD が割り当てられている Serial1 は使えません[1]。また Serial は、IDE がシリアルモニタやプログラム転送に使用しています。結果としてそのままの形で使用できるのは Serial2 だけです。

DSUB9のメス型コネクタを両端にもつクロスケーブルで ESP32-DevKitC-32D と RTX-1200 を接続します。それに用いたのが、インテルのMAX3232というチップを載せた RX232C コネクタボードです[2]

写真3
写真3. MAX3232 を載せた RS232C ボード。DSUB9オス型。
4本のピンをブレッドボードに挿す

使用しているケーブルは両端ともメス型なのでボードはオス型です。このボードには TX/RX/GND/VCC の4本のピンがあり、コネクタと直角方向に並んでいます。ブレッドボードに縦方向に4本のピンを指すと、横に並んだピン穴を接続に利用できます[3]。Serial2 を使いますので、下表のように結線します。

TXD RXD 電源(-) 電源(+)
ESP32-DevKitC 17 16 GND 5V
RS232C ボード TX RX GND VCC

組み立てはブレッドボードを使うととても簡単です。ただ、ESP32-DevKitC の幅は 1000mil なので[4]、普通のブレッドボードに指すと縦列をほとんど埋めてしまい、接続のためのジャンパ線を指すピン穴が隠れてしまいます。そこで左右の島(写真4では上下)の真ん中(写真4の青線)でブレッドボードを切り離して、ESP32-DevKitC のピンがブレッドボードの c列と h列に挿せるようにします[5]。これにより、ジャンパ線を指す余地ができますので、RS232C ボードの書く端子と接続ができます。

写真4
写真4. 一般的なブレッドボード。青線で切り離す。
緑枠に ESP32-DevKitC、赤枠に RS232C ボードを挿す

代理サーバ(エージェント)用プログラム

ESP32 に搭載するプログラムは、VSCode のエクステンションである PlatformIO IDE で作成しました[6]。ソースは以下の github のレポジトリに

https://github.com/cnloni/esp32-tools.git

においてあります。serial-agent-wifi/ サブフォルダ内に以下のようなファイルがあります。

サブフォルダ内のファイル構成(デフォルトで作成されるファイルは除く)
serial-agent-wifi/
+-- README.md
+-- include
|   +-- CnlWiFi.h   # WiFi 用の自家製クラス類
+-- platformio.ini  # PlatformIO 用プロジェクトの定義ファイル
+-- scripts
|   +-- client.py   # ホストからエージェントに接続ためのクライアントスクリプト
+-- src
    +-- config.h    # WiFi 情報の定義ファイル
    +-- main.cpp    # エージェントプログラム

コード自体はとても簡単です。WiFi を接続した後、LAN からのソケット接続を待ち受けます。接続後は、シリアル入力とソケット入力を非同期で待受け、シリアル入力があればソケット出力へ、ソケット入力があればシリアル出力へそのままデータを流すだけです。データ待受けのループを終了するために「cnl:quit」というコマンドを使っていますが、特に意味はありません。ソケットクライアントとの約束があれば何でもよろしい。

リスト1 main.cpp
#include <Arduino.h>
#include "CnlWiFi.h"
#include "config.h"

cnl::WiFi wifi();
HardwareSerial &serial = Serial2;

void setup()
{
  serial.begin(9600);
  // cnl::configs は config.h で定義
  int csize = sizeof(cnl::configs) / sizeof(cnl::WiFiConfig);
  wifi.scan(cnl::configs, csize);
}

void loop()
{
  if (wifi.connected())
  {
    WiFiClient client = wifi.listen();
    if (client.connected())
    {
      client.printf("Hello new client\n");
      client.printf("Me = %s\n", wifi.getServerIP().c_str());
    }

    while (client.connected())
    {
      // WiFi ソケットから入力があるか
      if (client.available())
      {
        String s = client.readString();
        if (s.startsWith("cnl:quit"))
        {
          client.stop();
          break;
        }
        serial.print(s);
      }
      // シリアルから入力があるか
      if (serial.available())
      {
        String s = serial.readString();
        client.print(s);
      }
    }
  }
}

ここで config.h は、例えば次のような内容です。

リスト2 config.h
#include "CnlWiFi.h"

namespace cnl {
  cnl::WiFiConfig configs[] = {
      {"{ssid_1}",
      "{pass_1}",
      "192.168.1.66",
      "192.168.1.1"},
      {"{ssid_2}",
      "{pass_2}",
      "192.168.129.66",
      "192.168.129.1"}};
}

WiFi ルータへの接続情報、ならびに、EXP32 の静的アドレスとゲートウェイ情報です。私は2拠点間でこのエージェントボードを持ち歩いているので、2種類の設定情報を設定しています。どちらの拠点に持っていってもリコンパイルしなくてよいように配列にそれぞれの情報を格納しています。エージェントボードは、SSID とパスフレーズで WiFi 接続を何回かトライし、接続できた方の設定情報からボードのIPアドレスを決定します。拠点1なら「192.168.1.66」を、拠点2なら「192.168.129.66」が使われます。

CnlWiFi.h では、オリジナルの WiFi クラスをラップしたような記述をしています。オリジナルのクラスとは、github で公開されている Espressif 製の ESP32 用 Arduino ライブラリに含まれる WiFi クラスのことです。ESP32 用ライブラリ全体は Arduino API にできるだけ合わせていますが、全てをサポートするわけではありません。とはいえ簡単なプログラムなら、ほとんどソースの改変なしに動作するようです。ライブラリ用ドキュメントは充実しているので安心です。

ただ、ESP32 の本来の機能をすべて発揮するためには、Arduino 用ライブラリではなく、ESP32 専用のプラットフォームである ESP-IDF を使ったほうがよさそうです。今回 Arduino 用のライブラリのソースを見てみたのですが、とてもよく出来ていて、Espressif 社は信用できると感じました。次は ESP-IDF を利用しようと考えています。

あとがき

最後にオチになりますが、このエージェントボードを実用的に使ったことは、まだありません。別の記事で書きますが、IPv6 回線の運用のために RTX-1200 の設定を試行錯誤していたのはずいぶん前のことで、いまではルータへ ssh ログインできないような致命的な障害は起きたことがないのです。ありがたいような残念なような。

脚注
  1. HardwareSerial のインスタンスを別途作成してピンを再割り当てすれば使えます ↩︎

  2. Amazon や AliExpress で「MAX3232」で検索すると類似のボードがたくさん見つかります ↩︎

  3. Amazon などには MAX3232 を載せたコネクタボードがたくさんありますが、ほとんどがメス型であり ピンはコネクタと平行に並んでいます ↩︎

  4. 一般的なブレッドボードの穴間隔は 100mil です。100mil = 0.1inch = 2.54mm ↩︎

  5. 左右の島はPPシートに貼り付けて固定しています ↩︎

  6. PlatformIO は安定かつプロジェクト管理もしっかりしているので、もう Arduino IDE には戻れません ↩︎

Discussion