この記事の概略図
ESP32シリーズの「ESP32-C5」が2.4GHz/5GHzのデュアルバンド対応であることを、Interface 2026/01 のp145-150の記事(by 小池誠氏)で知りました。
私の身近な環境で、5GHz帯のWiFiしか飛んでいない特殊な環境があり、これまでのESP32は使えないまたはLTE WiFiルーターを別途用意して使ったりしていましたが、「ESP32-C5」なら簡単に通信が可能になりますので、この記事を読んだ時、個人的に非常に心が躍りました。
早速、秋月電子通商で購入しました。型番:ESP32-C5-DevKitC-1-N8R4
https://akizukidenshi.com/catalog/g/g131015/

接続確認
Arduino IDEのボード選択でESP32C5 Dev Moduleを選び、スケッチ例のなかにあるWiFiScanを入れて、シリアルモニタで確認してみました。確かに5GHzのWiFiを受信できています。
-------------------------------------
Default wifi band mode scan:
-------------------------------------
Scan start
Scan done
xx networks found
Nr | SSID | RSSI | CH | Encryption
1 | YYYYYYY | -37 | 1 | WPA2
2 | XXXXX-xxxx-a | -37 | 116 | WPA2
3 | XXXXX-xxxx-g | -38 | 12 | WPA2
4 | ZZZZZZ-123456-2G | -41 | 6 | WPA2
-------------------------------------
-------------------------------------
2.4 Ghz wifi band mode scan:
-------------------------------------
Scan start
Scan done
xx networks found
Nr | SSID | RSSI | CH | Encryption
1 | YYYYYYY | -37 | 1 | WPA2
2 | XXXXX-xxxx-g | -38 | 12 | WPA2
3 | ZZZZZZ-123456-2G | -41 | 6 | WPA2
-------------------------------------
-------------------------------------
5 Ghz wifi band mode scan:
-------------------------------------
Scan start
Scan done
xx networks found
Nr | SSID | RSSI | CH | Encryption
1 | XXXXX-xxxx-a | -38 | 116 | WPA2
2 | AAAAAAAA | -44 | 112 | WPA2
-------------------------------------
確認終了。
今回の作成目標
今回は、このESP32-C5を使って、実運用を想定し、SSIDやパスワードをコードに埋め込まず、ブラウザから設定できるSoftAP(アクセスポイント)モードを実装します。
通常、この機能には「WiFiManager」というライブラリが便利ですが、今回はあえてライブラリを使わずにSoftAP設定画面を実装します。
WiFi設定だけでなく、DiscordのWebhook URLのような独自のパラメータも自由にSoftAP設定画面に追加できるようにします。
-
ESP32-C5で5GHz帯のWiFiに接続する
- コード自体は従来のESP32と同じ
WiFi.begin()でOKです。ハードウェアが勝手に5GHzを掴んでくれます。
- コード自体は従来のESP32と同じ
-
ライブラリ(WiFiManager)を使わずにSoftAP設定画面を作る
- スマホやPCでESP32に直接接続し、ブラウザから設定を行います。
-
独自の設定項目(Discord Webhook URL)を追加する
- 通知先を変更するたびに書き込み直す必要がなくなります。
-
設定データは不揮発メモリ(NVS)に保存する
- 電源を切っても設定が消えないようにします。
仕組み
1. 起動フロー
プログラムは以下のようなロジックで動作します。
- 起動: NVS(Preferences)から保存された設定(SSID, Pass, Discord URLなど)を読み込む。
- WiFi接続試行: 保存されたSSIDがあれば接続を試みる。
- 接続失敗 or 未設定: WiFi接続に失敗した場合、またはSSIDが空の場合はSoftAPモードを起動する。
- 手動SoftAP起動: 稼働中でも、物理ボタン(BOOTボタンなど)を長押しすることで強制的にSoftAPモードに入れるようにする(設定変更用)。
2. データ保存 (Preferences)
ESP32にはEEPROMの代わりに推奨されているPreferencesライブラリがあります。これを使ってKey-Value形式でデータを保存します。
#include <Preferences.h>
Preferences prefs;
// 読み込み
prefs.begin("wifi_conf", true); // true = 読み取り専用モード
String ssid = prefs.getString("ssid", "");
prefs.end();
// 書き込み
prefs.begin("wifi_conf", false); // false = 読み書きモード
prefs.putString("ssid", "MyHomeWiFi");
prefs.end();
3. 設定画面 (WebServer)
ESP32自身がサーバーとなり、HTMLフォームを表示します。
今回はシンプルに、SSID、Password、Device Name、Discord Webhook URLなどを入力して「Save」ボタンを押すと、POSTリクエストでデータを受け取り、Preferencesに保存して再起動するという流れです。
プログラムの主要部分
以下が作成したプログラムの主要部分です。
設定画面のHTML
コード内にHTMLを文字列として持たせておきます。レスポンシブデザインにしておくとスマホでも操作しやすくなります。
const char* HTML_HEAD = R"(
<!DOCTYPE html><html><head><meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<style>
body{font-family:sans-serif;padding:20px;background:#222;color:#fff}
input{width:100%;padding:10px;margin:5px 0;box-sizing:border-box;background:#333;color:#fff;border:1px solid #555;}
button{width:100%;padding:10px;background:#007bff;color:white;border:none;border-radius:5px;font-size:16px;margin-top:20px;}
.l{margin-top:15px;display:block;color:#aaa;font-size:0.9em;}
h2{border-bottom:1px solid #444;padding-bottom:10px;}
</style><title>Device Setup</title></head><body><h2>Device Setup</h2>
)";
設定用サーバーの起動 (SoftAP)
WiFi.softAP()でアクセスポイントを立ち上げ、DNSサーバーも起動することで、Captive Portal(接続すると自動で設定画面が出る機能)に近い挙動を実現します(今回は簡易的にIP直接指定またはmDNS想定)。
void startSoftAP() {
Serial.println("Starting SoftAP Mode...");
WiFi.mode(WIFI_AP);
// わかりやすいSSIDの名前にします
WiFi.softAP("ESP32-Config-AP");
// Webサーバーのルートにアクセスがあったら設定画面を出す
server.on("/", handleRoot);
// 保存ボタンが押されたら保存処理へ
server.on("/save", handleSave);
server.begin();
Serial.println("SoftAP started. Connect to 'ESP32-Config-AP' and go to 192.168.4.1");
// ループ処理でクライアントを待受
while (true) {
server.handleClient();
delay(10);
}
}
設定の保存処理
フォームから送られてきた値を取得し、Preferencesに保存します。今回のプログラムでは、WiFiのSSIDとパスワードに加えて、Discord Webhook URLも設定・保存できるのがポイントです。
void handleSave() {
String new_ssid = server.arg("ssid");
String new_pass = server.arg("pass");
String new_durl = server.arg("durl"); // Discord URL
if (new_ssid.length() > 0) {
prefs.begin("wifi_conf", false);
prefs.putString("ssid", new_ssid);
prefs.putString("pass", new_pass);
prefs.putString("durl", new_durl); // 保存!
prefs.end();
server.send(200, "text/html", "Saved! Restarting...");
delay(2000);
ESP.restart(); // 再起動して新しい設定で接続試行
}
}
5GHz帯への接続
5GHz帯への接続は、プログラム側で特別なことは必要ありません。保存されたSSIDが5GHz帯のものであれば、ESP32-C5は自動的に5GHzで接続します。
確認のため、シリアルモニタに接続状況を出力しています。
WiFi.begin(config_ssid.c_str(), config_pass.c_str());
while (WiFi.status() != WL_CONNECTED) {
// 接続待ち...
}
Serial.println("WiFi Connected");
// 必要であれば WiFi.channel() 等でチャンネルを確認して5GHzか確認可能
ボード選択
Arduino IDEで書き込むの時のボードは、ツールからesp32>>ESP32C5 Dev Moduleを選択します。
実際に使ってみる
例として以下の動きをするスケッチを作りました。
- 初回起動時またはWiFi未設定時または起動後10秒以内にBOOTボタンを3回連続クリックした時: SoftAPモード(LED: マゼンタ)が起動します。スマートフォン等で「ESP32-Config-AP」に接続し、192.168.4.1 でWiFi設定とDiscord Webhook URLを設定してください。
- 通常起動時: 設定されたWiFiに接続します(LED: 青)。
- ボタン押下: BOOTボタン(GPIO 28)を押すと、設定されたDiscord Webhook URLに「ESP32C5_Dev_Moduleのボタンが押された」というメッセージを送信します(LED: 送信中緑)。
#include <WiFi.h>
#include <WebServer.h>
#include <DNSServer.h>
#include <Preferences.h>
#include <HTTPClient.h>
#include <Adafruit_NeoPixel.h>
// -----------------------------------------------------------------------------
// 設定・グローバル変数
// -----------------------------------------------------------------------------
Preferences prefs;
WebServer server(80);
DNSServer dnsServer;
String config_ssid = "";
String config_pass = "";
String config_discord_url = "";
// ユーザーアクション用ボタン (ESP32-C5 BOOT Button = GPIO 28)
const int BUTTON_PIN = 28;
// トリプルクリック検出用変数
const unsigned long TRIPLE_CLICK_WINDOW = 10000; // 起動後10秒以内
const unsigned long CLICK_TIMEOUT = 1000; // クリック間隔の最大時間(ms)
int clickCount = 0;
unsigned long lastClickTime = 0;
bool tripleClickChecked = false;
// ステータスLED (ESP32-C5-DevKitC-1 Onboard RGB LED = GPIO 27)
#define RGB_PIN 27
#define NUMPIXELS 1
Adafruit_NeoPixel pixels(NUMPIXELS, RGB_PIN, NEO_GRB + NEO_KHZ800);
// LEDの色定義
enum LedState {
LED_STARTUP, // Yellow
LED_CONNECTED, // Blue
LED_SOFTAP, // Magenta
LED_ACTION, // Green (送信時)
LED_ERROR // Red
};
void updateLed(LedState state) {
pixels.clear();
switch (state) {
case LED_STARTUP: pixels.setPixelColor(0, pixels.Color(255, 255, 0)); break;
case LED_CONNECTED: pixels.setPixelColor(0, pixels.Color(0, 0, 255)); break;
case LED_SOFTAP: pixels.setPixelColor(0, pixels.Color(255, 0, 255)); break;
case LED_ACTION: pixels.setPixelColor(0, pixels.Color(0, 255, 0)); break;
case LED_ERROR: pixels.setPixelColor(0, pixels.Color(255, 0, 0)); break;
}
pixels.show();
}
// -----------------------------------------------------------------------------
// SoftAP & 設定画面
// -----------------------------------------------------------------------------
const char* HTML_HEAD = R"(
<!DOCTYPE html><html><head><meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<style>
body{font-family:sans-serif;padding:20px;background:#222;color:#fff}
input{width:100%;padding:10px;margin:5px 0;box-sizing:border-box;background:#333;color:#fff;border:1px solid #555;}
button{width:100%;padding:10px;background:#007bff;color:white;border:none;border-radius:5px;font-size:16px;margin-top:20px;}
.l{margin-top:15px;display:block;color:#aaa;font-size:0.9em;}
h2{border-bottom:1px solid #444;padding-bottom:10px;}
</style><title>ESP32 Setup</title></head><body><h2>Device Setup</h2>
)";
void handleRoot() {
String html = HTML_HEAD;
html += "<form action='/save' method='POST'>";
html += "<label class='l'>WiFi SSID</label><input type='text' name='ssid' value='" + config_ssid + "'>";
html += "<label class='l'>WiFi Password</label><input type='password' name='pass' value='" + config_pass + "'>";
html += "<label class='l'>Discord Webhook URL</label><input type='text' name='durl' value='" + config_discord_url + "'>";
html += "<button type='submit'>Save & Restart</button></form></body></html>";
server.send(200, "text/html", html);
}
void handleSave() {
String new_ssid = server.arg("ssid");
String new_pass = server.arg("pass");
String new_durl = server.arg("durl");
new_ssid.trim();
new_pass.trim();
new_durl.trim();
if (new_ssid.length() > 0) {
prefs.begin("wifi_conf", false);
prefs.putString("ssid", new_ssid);
prefs.putString("pass", new_pass);
prefs.putString("durl", new_durl);
prefs.end();
String html = HTML_HEAD;
html += "<p>Saved! Restarting...</p></body></html>";
server.send(200, "text/html", html);
delay(2000);
ESP.restart();
} else {
server.send(200, "text/html", "Error: SSID is required. <a href='/'>Back</a>");
}
}
void startSoftAP() {
Serial.println("Starting SoftAP Mode...");
WiFi.mode(WIFI_AP);
WiFi.softAP("ESP32-Config-AP");
dnsServer.start(53, "*", WiFi.softAPIP());
server.on("/", handleRoot);
server.on("/save", handleSave);
server.onNotFound([]() {
server.sendHeader("Location", String("http://") + WiFi.softAPIP().toString(), true);
server.send(302, "text/plain", "");
});
server.begin();
Serial.println("SoftAP started. Connect to 'ESP32-Config-AP' and go to 192.168.4.1");
updateLed(LED_SOFTAP);
while (true) {
dnsServer.processNextRequest();
server.handleClient();
delay(10);
}
}
// -----------------------------------------------------------------------------
// Discord通知
// -----------------------------------------------------------------------------
void sendDiscordMessage(String message) {
if (config_discord_url.length() == 0) {
Serial.println("Discord URL not set.");
return;
}
if (WiFi.status() == WL_CONNECTED) {
updateLed(LED_ACTION); // 緑色点灯
HTTPClient http;
http.begin(config_discord_url);
http.addHeader("Content-Type", "application/json");
String json = "{\"content\":\"" + message + "\"}";
int httpResponseCode = http.POST(json);
if (httpResponseCode > 0) {
Serial.printf("Discord Sent. Code: %d\n", httpResponseCode);
} else {
Serial.printf("Error Sending Discord: %s\n", http.errorToString(httpResponseCode).c_str());
updateLed(LED_ERROR);
delay(1000);
}
http.end();
updateLed(LED_CONNECTED); // 青色に戻す
} else {
Serial.println("WiFi Disconnected. Cannot send.");
updateLed(LED_ERROR);
}
}
// -----------------------------------------------------------------------------
// Main Setup & Loop
// -----------------------------------------------------------------------------
void setup() {
Serial.begin(115200);
// LED Init
pixels.begin();
pixels.setBrightness(10); // 明るさ控えめ
updateLed(LED_STARTUP);
// Button Init
pinMode(BUTTON_PIN, INPUT_PULLUP);
// Config Load
prefs.begin("wifi_conf", true);
config_ssid = prefs.getString("ssid", "");
config_pass = prefs.getString("pass", "");
config_discord_url = prefs.getString("durl", "");
prefs.end();
// トリプルクリック検出: 起動後10秒以内にBOOTボタンを3回押すとSoftAPモード起動
Serial.println("Waiting for triple-click (10 seconds)...");
unsigned long startTime = millis();
while (millis() - startTime < TRIPLE_CLICK_WINDOW) {
// ボタンが押された検出
if (digitalRead(BUTTON_PIN) == LOW) {
delay(50); // デバウンス
if (digitalRead(BUTTON_PIN) == LOW) {
unsigned long currentTime = millis();
// 前回のクリックから500ms以内なら連続クリックとしてカウント
if (clickCount > 0 && (currentTime - lastClickTime) > CLICK_TIMEOUT) {
clickCount = 0; // タイムアウト、カウントリセット
}
clickCount++;
lastClickTime = currentTime;
Serial.printf("Click detected: %d\n", clickCount);
// LEDフィードバック
for (int i = 0; i < clickCount; i++) {
updateLed(LED_ACTION);
delay(100);
updateLed(LED_STARTUP);
delay(100);
}
// トリプルクリック検出
if (clickCount >= 3) {
Serial.println("Triple-click detected! Starting SoftAP mode...");
tripleClickChecked = true;
startSoftAP();
}
// ボタンが離されるまで待機
while (digitalRead(BUTTON_PIN) == LOW) {
delay(10);
}
}
}
delay(10);
}
tripleClickChecked = true;
Serial.println("Triple-click window closed.");
// WiFi Connection
if (config_ssid.length() > 0) {
Serial.printf("Connecting to %s ", config_ssid.c_str());
WiFi.begin(config_ssid.c_str(), config_pass.c_str());
int count = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
count++;
if (count > 20) { // 10秒待機
Serial.println("\nWiFi Connect Failed. Starting SoftAP.");
startSoftAP(); // 接続失敗時はSoftAPへ
}
}
Serial.println("\nWiFi Connected.");
updateLed(LED_CONNECTED); // 青色点灯
} else {
Serial.println("No SSID configured. Starting SoftAP.");
startSoftAP();
}
}
void loop() {
// ボタン監視(トリプルクリック検出期間後のみ)
if (tripleClickChecked && digitalRead(BUTTON_PIN) == LOW) {
// デバウンス処理
delay(50);
if (digitalRead(BUTTON_PIN) == LOW) {
Serial.println("Button Pressed!");
sendDiscordMessage("ESP32C5_Dev_Moduleのボタンが押された");
// ボタンが離されるまで待機
while (digitalRead(BUTTON_PIN) == LOW) {
delay(10);
}
}
}
// WiFi切れ監視(簡易)
if (WiFi.status() != WL_CONNECTED) {
updateLed(LED_ERROR);
WiFi.reconnect();
delay(1000);
if (WiFi.status() == WL_CONNECTED) updateLed(LED_CONNECTED);
}
delay(10);
}
SoftAPモード
初回起動時またはWiFi未設定時または起動後10秒以内にBOOTボタンを3回連続クリックした時、SoftAPモードが立ち上がり、WiFi (SSID名:ESP32-Config-AP)に、スマートフォン等で接続します。接続したら以下の画面が自動に立ち上がります。自動的に立ち上がらないときは、192.168.4.1を開きます。WiFi SSID, パスワードに加えて、Discord webhook URLも設定できます。今回、WiFi SSIDには5GHzのWiFiを指定しました。
動作確認
ボード上のBOOTボタンを押したら登録したDiscord Webhook URLを通じてメッセージが飛んできました。

これで、ArDuino IDEからのESP32への書き込みなしに、設置場所のWiFiを再設定できたり、通知先のDiscordチャンネルを変更したりできるようになりました。
まとめ
(1) ESP32-C5の登場で、電子工作でも5GHzのWiFi通信が使えるようになりました。よかった。(M5Stackでも採用してほしい!)
(2) 既存ライブラリに頼らずSoftAP機能を実装することで、実運用を想定して「ユーザーが設定できる画面」を作ることができました。特に通知系IoTでは、Webhook URLなどを後から変更できるようにしておくと利便性が向上すると思います。起動後10秒以内にBOOTボタンを3回連続クリックした時、SoftAPモードが立ち上がって、再設定できるのもちょっと便利です。

