miyoshitan’s blog

岐阜発。自動車サービス現場とPython・Flask・Raspberry Pi・M5Stackの実践記録。

AI・IoT・個人開発の実験ログと考察をまとめる技術ブログ

M5Stack CoreS3でラズパイ救出:SSH不可&HDMI真っ黒からUARTターミナルでIP復旧するまで

はじめに:ラズパイが突然SSHできなくなった話

ある日いつものようにPCからSSHでRaspberry Piに入ろうとしたところ、急に接続できなくなりました。Wi‑Fiは生きているし、ラズパイ本体の電源も入っているのに、pingすら通らない状態です。さらに、以前UARTでM5Stack CoreS3と接続してからHDMI出力も映らなくなっており、「画面も出ない・SSHも打てない・pingも通らない」という、なかなかハードなトラブルコンボになりました。この記事では、Raspberry PiのIPアドレスがわからなくなりHDMIも映らない状況から、M5Stack CoreS3をシリアルターミナルとして使って復旧するまでの手順を、コード付きでまとめます。


原因候補を整理する:ネットワークと表示まわり

まず疑ったのは「IPアドレスが変わった」か「raspberrypi.local の名前解決が壊れた」パターンです。同じ家庭内LAN上にいても、DHCPの再割り当てやmDNSの不調でSSHできなくなることはよくあります。さらに今回の環境では、以前にUARTコンソールを有効化してからHDMIモニタに信号が出なくなっていたため、「OSは起動しているけれど表示系だけ死んでいる」可能性もありました。ネットワークと表示のどちらから攻めるか悩みましたが、まずはWindows PC側からローカルネットワーク上にRaspberry Piが存在するかどうかを確認するところから始めました。


第1ラウンド:PC側からラズパイを探す

最初のアプローチは、WindowsのPowerShellからLAN内のIPを総ざらいする方法です。192.168.0.1〜254 に対して一斉にpingを送り、そのあとARPテーブルを確認して「応答のあった端末一覧」を洗い出しました。具体的には、次のようなスクリプトを使います。

powershell
# 192.168.0.1〜254 に1回ずつpingを投げる 1..254 | ForEach-Object { ping -n 1 192.168.0.$_ | Out-Null } # ARPテーブルに溜まったIPとMACアドレスを確認 arp -a

ここに出てきたIPアドレスに対して ssh ユーザー名@IP を順に試していきましたが、どれもタイムアウトか接続拒否で、Raspberry Piらしい挙動をするホストが見つかりません。MACアドレスのOUIからもそれっぽいものが見つからず、この時点で「192.168.0.xにはいない」「別SSIDか別セグメントにいる可能性が高い」と判断し、PC側からの捜索は一旦打ち切ることにしました。


方針転換:CoreS3をシリアルターミナルにしてしまう

ネットワーク側からの探索が行き詰まったので、方針を切り替えてRaspberry Piのシリアルコンソールに直接アクセスすることにしました。手元にはM5Stack CoreS3があり、以前からUART経由でラズパイと通信するための配線はすでに用意してあります。HDMI表示が死んでいても、ブートログとログインプロンプトさえシリアルに出ていれば、そこからIPアドレスを確認してSSHに持ち込めます。そこでCoreS3を“USBシリアル↔UART2のブリッジ”として動かし、PCのシリアルモニタ経由でラズパイのコンソールを操作する構成を作ることにしました。GPIO17/18をTX/RXに割り当て、CoreS3のディスプレイにもログを表示させることで、PCなしでも状態を眺められるようにします。


CoreS3側の実装:シリアルブリッジスケッチ

CoreS3用には、M5UnifiedとHardwareSerialを使った簡単なシリアルブリッジスケッチを書きました。USB接続されたPC側から受信した文字をUART2へ流し、ラズパイからUART2で受信した文字はそのままPCのUSBシリアルへ返しつつ、CoreS3の画面にも緑色のテキストで表示します。画面上は、一定行数を超えたらスクロールさせるだけのシンプルなコンソール風UIです。この仕組みによって、ラズパイのログインプロンプトやdmesg相当のメッセージをCoreS3経由でリアルタイムに確認できるようになり、HDMIモニタがなくてもブート状態を観察できるようになりました。

CoreS3 シリアルブリッジのコード

※個人情報に関わるホスト名やユーザー名は一般的な名前に差し替えています。

cpp
#include <M5Unified.h> #include <HardwareSerial.h> // CoreS3 ピン (Raspberry Pi のUARTと接続) static const int CORE_RX_PIN = 18; // CoreS3が受信する(Raspberry Pi TX) static const int CORE_TX_PIN = 17; // CoreS3が送信する(Raspberry Pi RX) HardwareSerial Serial2_port(2); // 画面用カーソル int16_t cursor_x = 0; int16_t cursor_y = 0; const int16_t lineHeight = 16; void printCharToDisplay(char c) { auto &disp = M5.Display; if (c == '\r') { return; } else if (c == '\n') { cursor_x = 0; cursor_y += lineHeight; if (cursor_y >= disp.height()) { disp.scroll(0, lineHeight); cursor_y -= lineHeight; } disp.setCursor(cursor_x, cursor_y); } else { disp.print(c); cursor_x = disp.getCursorX(); cursor_y = disp.getCursorY(); } } void setup() { auto cfg = M5.config(); M5.begin(cfg); auto &disp = M5.Display; disp.fillScreen(TFT_BLACK); disp.setTextColor(TFT_GREEN, TFT_BLACK); disp.setTextSize(1); disp.setFont(&fonts::lgfxJapanGothic_16); disp.setCursor(0, 0); disp.println("CoreS3 Serial Bridge"); disp.println("USB <-> UART2 (Pi)"); disp.println("--------------------"); cursor_x = 0; cursor_y = disp.getCursorY(); // PC側とのUSBシリアル Serial.begin(115200); // Raspberry Pi側 UART2 Serial2_port.begin(115200, SERIAL_8N1, CORE_RX_PIN, CORE_TX_PIN); } void loop() { M5.update(); // PC(USB) -> Raspberry Pi(UART2) while (Serial.available()) { char c = (char)Serial.read(); Serial2_port.write(c); } // Raspberry Pi(UART2) -> PC(USB) & 画面 while (Serial2_port.available()) { char c = (char)Serial2_port.read(); Serial.write(c); printCharToDisplay(c); } }

ラズパイのシリアルコンソールを有効化する

CoreS3側の準備ができたら、Raspberry Pi OS側でシリアルコンソールを有効にしておきます。一般的な手順としては、raspi-config で「シリアルコンソールログインを有効化」するか、/boot/firmware/config.txtcmdline.txt を手動で編集します。config.txtenable_uart=1 を追加してUARTを有効化し、cmdline.txt のカーネルパラメータに console=serial0,115200 を含めることで、ブートログやログインプロンプトがUARTに出力されるようになります。HDMI側の設定をいじりすぎるとモニタとの相性で「信号なし」が出ることもあるため、まずはシリアルから確実にログインできる状態を作り、その後必要に応じて表示設定を微調整する流れがおすすめです。


実際の復旧手順:CoreS3経由でIPアドレスを奪い返す

CoreS3にシリアルブリッジを焼き込み、Raspberry PiとUART接続した状態で電源を入れると、CoreS3の画面に起動メッセージと raspberrypi login: が表示されました。PC側ではCoreS3のUSBシリアルポートに115200bpsで接続し、シリアルモニタからユーザー名とパスワードを送信すると、シェルプロンプト user@raspberrypi:~$ が現れます。ここで次のコマンドを実行して、現在のIPアドレスを取得しました。

bash
hostname -I

この結果として 192.168.0.14 のようなアドレスが表示され、ついにRaspberry PiがどのIPでLANにぶら下がっているのかが判明しました。あとはWindows側のPowerShellから ssh user@192.168.0.14 と接続すれば、普段通りSSHでログインできます。CoreS3がシリアルターミナルとして機能したことで、HDMIもネットワークも怪しい状況から安全に情報を引き出せました。


systemdサービスの健診:全部落ちてるように見えたけど…

SSHに成功したら、次は常駐サービスが正しく動いているかを確認しました。systemctl list-units --type=service --state=running を実行すると、m5_switchbot_bridge.service やカスタムHTTP API、Flaskアプリ、リバーストンネルなど、自分で作ったサービスが active (running) として並んでいます。一方、systemctl list-units --failed を見ると chromium-kiosk.service だけが failed になっており、ブラウザキオスク用のサービスが落ちていることがわかりました。水換えリマインダー用の water_change_reminder.serviceinactive (dead) ですが、TriggeredBy: water_change_reminder.timer と表示されており、timerユニットから必要なタイミングだけ起動される設計になっているため、こちらは正常です。systemdまわりをざっと点検することで、どのサービスを優先的に直すべきか整理できました。


CoreS3シリアルブリッジの活用と今後の改善案

今回のトラブルシュートで強く感じたのは、「Raspberry Pi用の専用シリアルターミナルを1セット用意しておくと、ネットワークやHDMIに依存せず復旧できる」という安心感です。M5Stack CoreS3を使えば、UARTの入出力を画面に視覚化しつつ、PCからのキーボード入力もそのままブリッジできるので、初期セットアップから障害発生時のデバッグまで幅広く使えます。今後は、画面レイアウトをもう少し整えてログのカーソルジャンプや簡単なソフトキーボードを実装したり、Raspberry Piの再起動ボタンやサービス状態のショートカット表示を追加するなど、「小さな運用ダッシュボード」として育てていくのも面白そうです。ヘッドレス運用が多い人ほど、こうしたシリアルブリッジ用ガジェットを1台持っておくと、いざというときの復旧スピードが一気に上がります。