post-cover

第2回では、本装置の通信を担う Raspberry Pi Pico と ENC28J60 を用いたLAN制御の実装方法について解説します。

この記事では、

  1. 市販モジュールでLAN通信を確認
  2. Picoにテストプログラムを書き込み疎通確認
  3. EasyEDAで自作基板を作成
  4. 試作基板でトラブル発生
  5. データシートの重要性を実感

という流れで開発を進めました。

フェーズ1:市販モジュールでのブレッドボード検証

まずは前回の記事のおさらいです。

今回の開発は、ある顧客から

「24点入力と8点リレー出力を持つ制御装置が廃盤になってしまい、代替品に困っている」

という相談を受けたことがきっかけでした。

既存製品の代替として、

  • 24点入力
  • 8点リレー出力
  • LAN経由で制御

が可能な装置を自作することになりました。

システム全体の構成は次の通りです。

  • LAN通信:ENC28J60
  • 制御マイコン:Raspberry Pi Pico
  • 24ch入力:TCA6424A
  • 出力:8chリレー

今回はこの中でも、LAN通信部分の実装について説明します。

使用マイコン:Raspberry Pi Pico

制御の中核には Raspberry Pi Pico を採用しています。

採用理由は次の通りです。

  • GPIOが豊富
  • SPI / I2C / PWMなど周辺機能が充実
  • 低価格で入手性が良い
  • 消費電力が小さい

今回の装置では

  • SPI:ENC28J60との通信
  • I2C:TCA6424A入力拡張IC
  • GPIO:リレー制御

という役割分担になっています。

小型の制御装置としては、非常にバランスの良いマイコンです。

LANコントローラ:ENC28J60

Ethernet通信には ENC28J60 を使用しています。

ENC28J60はSPI接続型のEthernetコントローラで、 マイコンからLAN通信を比較的簡単に実装できます。

主な特徴は次の通りです。

  • SPI接続
  • 低コスト
  • TCP/IPスタックはマイコン側で実装
  • 組み込み用途で実績多数

今回のような制御装置用途では、十分な性能を持つチップです。

次の章では、まず市販のENC28J60モジュールを使ってLAN通信の動作確認を行う方法を解説します。

まず必要な部品を準備する

LAN通信を実装するために、まず以下の部品を準備しました。

  • Raspberry Pi Pico
  • ENC28J60 LANモジュール
  • LANケーブル
  • ブレッドボード
  • ジャンパーワイヤ
  • PC(テスト用)

今回使用したモジュールはこちらです。 Hailege ENC28J60 Ethernet LAN Module https://amzn.asia/d/039YIfHK

いきなり基板を作るのではなく、まずは市販モジュールでLAN通信が成立するか確認します。

PicoとENC28J60をSPI接続する

ENC28J60は3.3V動作のため、レベル変換などは不要でPicoのGPIOと直結できます。

SPIの配線は以下の通りです。

Pico ENC28J60
GP16 MISO
GP19 MOSI
GP18 SCK
GP17 CS
3.3V VCC
GND GND

PicoにLANテストプログラムを書き込む

次に、LAN通信が成立するか確認するため、 Picoに簡単なテストプログラムを書き込みます。

今回のプログラムは

  • PicoがUDPサーバとして待機
  • PCからUDPコマンドを送信
  • GPIOをON/OFF
  • 応答を返信

という動作を行います。

つまり LAN通信 + GPIO制御 の両方を確認できます。

1.Pico用LANテストプログラム

以下が実際に使用したテストプログラムです。

#include <SPI.h>#include <EthernetENC.h>#include <EthernetUdp.h>// ====== ピン設定 ======static const int PIN_CS = 17;// リレー出力用(8ch)static const int NUM_CH = 8;static const int GPIO_PINS[NUM_CH] = {0, 1, 2, 3, 4, 5, 6, 7};// ====== ネットワーク設定 ======byte mac[] = {0x02, 0xAA, 0xBB, 0xCC, 0xDD, 0x01};IPAddress ip(192, 168, 40, 50);IPAddress dnsIP(192, 168, 40, 1);IPAddress gateway(192, 168, 40, 1);IPAddress subnet(255, 255, 255, 0);const unsigned int LOCAL_PORT = 5005;EthernetUDP Udp;void setup() {  // リレー制御用GPIOの初期化(すべてLOWに設定)  for(int i = 0; i < NUM_CH; i++){    pinMode(GPIO_PINS[i], OUTPUT);    digitalWrite(GPIO_PINS[i], LOW);  }  // デバッグ用シリアル通信の開始(USB経由)  Serial.begin(115200);  // SPIピンの明示的なルーティング (ENC28J60接続用)  SPI.setSCK(18);  SPI.setTX(19);  // MOSI  SPI.setRX(16);  // MISO  SPI.begin();  // ENC28J60の初期化とネットワーク起動  Ethernet.init(PIN_CS);  Ethernet.begin(mac, ip, dnsIP, gateway, subnet);  Udp.begin(LOCAL_PORT);  Serial.println("=================================");  Serial.println(" Pico LAN Control Started");  Serial.print(" IP Address: ");  Serial.println(Ethernet.localIP());  Serial.println(" Waiting for UDP packets...");  Serial.println("=================================");}void loop() {  int packetSize = Udp.parsePacket();  if(packetSize) {    char buf[128];    int len = Udp.read(buf, 127);    buf[len] = 0; // null終端    // 受信データをString型に変換して改行コード等を除去    String cmd = String(buf);    cmd.trim();    // USB経由のシリアルモニタへ受信結果を出力    Serial.print("\n[Received Command]: ");    Serial.println(cmd);    // ======= GPIOのON/OFF制御ロジック =======    // 1. 全チャンネルON    if(cmd == "ALLON") {      for(int i = 0; i < NUM_CH; i++){        digitalWrite(GPIO_PINS[i], HIGH);      }      Serial.println(" -> Action: ALL GPIO (GP0-GP7) turned HIGH");    }    // 2. 全チャンネルOFF    else if(cmd == "ALLOFF") {      for(int i = 0; i < NUM_CH; i++){        digitalWrite(GPIO_PINS[i], LOW);      }      Serial.println(" -> Action: ALL GPIO (GP0-GP7) turned LOW");    }    // 3. 個別チャンネル制御(例: "CH0_ON", "CH7_OFF")    else if(cmd.startsWith("CH") && cmd.indexOf("_") > 0) {      // "CH"と"_"の間にある数字を抽出してインデックス化      int chIndex = cmd.substring(2, cmd.indexOf("_")).toInt();      // 指定されたチャンネル番号が範囲内かチェック      if(chIndex >= 0 && chIndex < NUM_CH) {        if(cmd.endsWith("_ON")) {          digitalWrite(GPIO_PINS[chIndex], HIGH);          Serial.print(" -> Action: GP");          Serial.print(GPIO_PINS[chIndex]);          Serial.println(" turned HIGH");        }        else if(cmd.endsWith("_OFF")) {          digitalWrite(GPIO_PINS[chIndex], LOW);          Serial.print(" -> Action: GP");          Serial.print(GPIO_PINS[chIndex]);          Serial.println(" turned LOW");        } else {          Serial.println(" -> Error: Invalid suffix (Use _ON or _OFF)");        }      } else {        Serial.println(" -> Error: Channel out of range (Use CH0 - CH7)");      }    }    // 4. 未知のコマンド    else {      Serial.println(" -> Action: Unknown Command");    }  }}

2. PC側テスト用(Python)

標準ライブラリの socket のみで動作します。コマンドプロンプトやターミナルから実行して使用します。

import socketimport sys# Pico側の設定に合わせたIPアドレスとポート番号TARGET_IP = "192.168.40.50"TARGET_PORT = 5005def main():    # UDP通信用のソケットを作成 (IPv4, UDP)    try:        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)    except Exception as e:        print(f"ソケットの作成に失敗しました: {e}")        sys.exit(1)    # 起動時のUI表示    print("=========================================")    print("       Pico UDP Control Test Tool        ")    print("=========================================")    print(f" Target : {TARGET_IP}:{TARGET_PORT}")    print("-----------------------------------------")    print(" [Commands]")    print("  - ALLON   : 全リレー(GP0-GP7)をON")    print("  - ALLOFF  : 全リレー(GP0-GP7)をOFF")    print("  - CHx_ON  : 個別ON  (例: CH0_ON, CH7_ON)")    print("  - CHx_OFF : 個別OFF (例: CH2_OFF)")    print("  - exit    : テスト終了")    print("=========================================")    try:        while True:            # ターミナルからの入力待ち            cmd = input("Send Command > ").strip()            # 終了コマンドの処理            if cmd.lower() in ['exit', 'quit']:                print("テストを終了します。")                break            # 空エンターの場合はスキップ            if not cmd:                continue            # 入力された文字列をUTF-8のバイト列に変換して送信            try:                sock.sendto(cmd.encode('utf-8'), (TARGET_IP, TARGET_PORT))                print(f" -> [Sent] {cmd}")            except Exception as e:                print(f" -> [Error] 送信失敗: {e}")    except KeyboardInterrupt:        # Ctrl+Cで強制終了された場合の処理        print("\nテストを強制終了します。")    finally:        # プログラム終了時に必ずソケットを閉じる        sock.close()if __name__ == "__main__":    main()

動作確認(シリアルログとGPIO電圧の確認)

PC側のPythonテストツールを起動し、UDPコマンドを送信して動作確認を行います。

例えば、以下のコマンドを送信します。

ALLON

すると、Pico側のUSBシリアルモニタには次のようなログが表示されます。

================================= Pico LAN Control Started IP Address: 192.168.40.50 Waiting for UDP packets...=================================[Received Command]: ALLON -> Action: ALL GPIO (GP0-GP7) turned HIGH

このログから、

  • PC → Pico へのUDP通信が成立している
  • コマンドが正常に解析されている
  • GPIO制御処理が実行されている

ことが確認できます。

次に、個別チャンネルの制御も確認します。PC側から次のコマンドを送信します。

CH0_ON

するとシリアルモニタには次のように表示されます。

[Received Command]: CH0_ON -> Action: GP0 turned HIGH

同様に、

CH0_OFF

を送信すると

[Received Command]: CH0_OFF -> Action: GP0 turned LOW

と表示され、GPIOがOFFに切り替わります。

テスターによるGPIO電圧の確認

ソフトウェア上のログだけでなく、 実際にGPIOが電気的にONになっているか も確認します。

テスター(デジタルマルチメータ)を使用し、

  • 黒プローブ:GND
  • 赤プローブ:GP0

に接続して電圧を測定しました。

CH0_ON コマンド送信後に測定すると、

GP0 ≒ 3.3V

が出力されていることを確認できました。

また、

CH0_OFF

を送信すると

GP0 ≒ 0V

に戻ることも確認できました。

これにより

  • LAN通信
  • SPI通信
  • UDPコマンド処理
  • GPIO制御
  • 実際の電圧出力

のすべてが正常に動作していることが確認できました。

フェーズ2:EasyEDAで基板を設計する

動作確認ができたので、 次は自作基板として実装します。

基板設計には EasyEDA を使用しました。

AmazonのENC28J60モジュールの製品ページには回路図が掲載されていたため、

モジュールの回路図

それを参考にして、EasyEDAで回路図化しました。

その後PCBレイアウトを作成し、 JLCPCBのPCBAサービス で部品実装付き基板を発注しました。

試作基板でトラブル発生

基板が届いたので、Picoと接続して動作確認を行いました。

しかし結果は LANが動きません。

ENC28J60のデータシート を確認して原因を調査しました。

データシートの回路図

原因はRBIAS抵抗

ENC28J60には RBIAS という抵抗があります。

Amazonモジュールの回路では 2.7kΩ になっていました。

しかし、 データシートのリファレンス回路では 2.32kΩ が指定されています。

つまり、 モジュールの回路値をそのままコピーしてしまった ことが原因でした。

raspberry-pi-pico-lan-relay-02-circuit-03

抵抗値を修正すると動作した

RBIASを 2.7kΩ → 2.32kΩ に変更したところ、

LAN通信が正常に動作しました。

つまり今回のトラブルは 抵抗1本の値の違い でした。

得られた教訓

今回の経験から強く感じたことは 既製モジュールの回路をそのまま信用してはいけない ということです。

市販モジュールは データシートと異なる値 が使われていることがあります。

そのため基板設計では 必ずデータシートのリファレンス回路を確認する ことが重要です。

まとめ

今回は

  • PicoとENC28J60によるLAN通信
  • SPI接続
  • UDP疎通テスト
  • EasyEDAによる基板設計
  • RBIAS抵抗トラブル

について紹介しました。

組み込み開発では

  1. 市販モジュールで動作確認
  2. テストプログラムで疎通確認
  3. 基板設計
  4. 試作評価

という段階を踏むことでトラブルの切り分けが容易になります。

今回のトラブルも、 データシートを確認する重要性 を改めて学ぶ良い経験になりました。

次回は 24ch接点入力回路(フォトカプラ絶縁・24V入力設計) について解説します。

SNSでシェアしよう

この記事が役に立ったら、ぜひシェアしてください