MSPM0G3519 - イーサネット (ENC28J60)

提供: MochiuWiki : SUSE, EC, PCB

2025年12月22日 (月) 14:42時点におけるWiki (トーク | 投稿記録)による版

📢 Webサイト閉鎖と移転のお知らせ
このWebサイトは2026年9月に閉鎖いたします。
新しい記事は移転先で追加しております。(旧サイトでは記事を追加しておりません)

概要

MSPM0G3519は、Texas Instruments社の32ビットARM Cortex-M0+コアを搭載したマイコンである。
最大80[MHz]で動作し、512[KB]のフラッシュROMと128[KB]のSRAMを搭載している。
しかし、イーサネット機能は内蔵していないため、TCP/IP通信を行うには、外付けのイーサネットコントローラIC (ENC28J60等) とlwIPまたはuIPライブラリを組み合わせて使用する。

lwIPは、スウェーデンのAdam Dunkels氏が開発した軽量TCP/IPスタックで、uIPの後継として位置付けられている。
MSPM0G3519は十分なRAM容量 (128[KB]) を持つため、lwIPの使用が推奨される。

lwIPは、より高速で安定したTCP/IP通信を実現し、多数の同時接続に対応できる。

主な構成は以下の通りである。

  • MSPM0G3519 (マイコン)
  • ENC28J60 (イーサネットコントローラ)
  • SPIインターフェース
  • lwIPライブラリ (TCP/IPスタック)
    推奨
  • uIPライブラリ (TCP/IPスタック)
    軽量版 : 非推奨 (メンテナンスされていない)


この構成により、MSPM0G3519を使用したWebサーバ、HTTPクライアント、Telnetサーバ、UDPアプリケーション、DHCPクライアント、DNSクライアント等の構築が可能となる。


ENC28J60 と W5500

イーサネット通信を実現するには、大きく分けて2つの階層がある。

  • 上位層
    ネットワーク層以上で、TCP/IPプロトコルの処理を行う。
  • 下位層
    物理層 および データリンク層 で、イーサネットフレームの送受信を担当する。


ENC28J60は下位層のみを持つICである。
イーサネットフレームの送受信はできるが、TCP/IPプロトコルの処理機能は持たない。
これを イーサネットMAC/PHYコントローラ と呼ぶ。

一方、W5500は、上位層および下位層の両方をIC内に持つため、TCP/IPの複雑な処理も含めて全てW5500が自動的に処理する。
これを ハードウェアTCP/IPスタック内蔵型 と呼ぶ。


lwIPライブラリの特徴

lwIP (Lightweight IP) は、スウェーデンのAdam Dunkels氏が開発したTCP/IPプロトコルスタックで、uIPの後継として位置付けられている。
lwIPは、uIPよりも高機能であり、より多くのメモリを使用するが、その分、高いスループットと多数の同時接続に対応できる設計となっている。

lwIPの特徴を以下に示す。

  • 高機能なTCP/IP実装
    完全なTCP/IP実装で約40〜60[KB]程度のコードサイズ
    uIPと比較して、より高速で安定した通信が可能
  • 適度なRAM使用量
    通常構成で約20〜40[KB]程度のRAM使用量
    メモリプール方式による効率的なバッファ管理
  • RFC準拠
    TCP、UDP、IP、ICMP、ARP、DHCP、DNSプロトコルに対応
    HTTP、SNMP、MQTT等の上位プロトコルも実装可能
  • マルチスレッド対応
    RTOSと組み合わせた使用が可能
    ただし、MSPM0G3519では通常はシングルスレッド構成 (NO_SYS=1) で使用する
  • 移植性
    C言語で記述されており、様々なマイコンに移植可能
    ハードウェア依存部分が明確に分離されている


※MSPM0G3519でのlwIP使用について
MSPM0G3519は、512[KB]のフラッシュと128[KB]のSRAMを持つため、lwIPを標準構成で十分に使用できる。
これにより、以下に示すメリットが得られる。

  • メリット
    より多くの同時接続に対応 (通常10〜50接続以上)
    高速なデータ転送が可能
    DHCP、DNS等の上位機能を標準サポート
    より安定したTCP実装
    パケットの分割送信と再送処理が効率的
    ゼロコピー機能によるメモリ効率の向上

  • デメリット
    uIPと比較して、初期設定がやや複雑
    コードサイズが大きい (ただし、MSPM0G3519のフラッシュ容量で十分に収まる)


MSPM0G3519では、lwIPの使用を推奨する。
RAMとフラッシュROMに十分な余裕があるため、lwIPの高機能性を最大限活用できる。
軽量なアプリケーションにはuIPも使用可能であるが、将来的な機能拡張を考慮すると、lwIPの採用がより適切である。


uIPライブラリの特徴

uIPは既にメンテナンスされていないため、使用は非推奨である。

uIPは、組み込みシステム向けに最適化された軽量TCP/IPプロトコルスタックである。

uIPの特徴を以下に示す。

  • 小さなコードサイズ
    完全なTCP/IP実装で約10〜20[KB]程度のコードサイズ
  • 低RAM使用量
    最小構成で数[KB]程度のRAM使用量
    バッファ管理が効率的に設計されている
  • RFC準拠
    TCP、UDP、IP、ICMP、ARPプロトコルに対応
    HTTP 1.0サーバ実装を含む
  • 移植性
    C言語で記述されており、様々なマイコンに移植可能
    ハードウェア依存部分が明確に分離されている


※注意
uIPは軽量化のため、いくつかの制約がある。
同時接続数が限られており (通常1〜10接続程度)、スループットよりも省メモリを優先した設計となっている。
高速通信や多数の同時接続が必要な場合は、lwIP等のより高機能なスタックの使用を検討する必要がある。

MSPM0G3519は128[KB]の十分なRAMを持つため、特別な理由がない限りlwIPの使用を推奨する。


ENC28J60イーサネットコントローラ

MSPM0G3519はイーサネットコントローラを内蔵していないため、外付けのイーサネットコントローラチップが必要となる。

Microchip社のENC28J60は、SPI接続のイーサネットコントローラで、組み込みシステムで広く使用されている。

  • IEEE 802.3準拠の10BASE-T Ethernetコントローラ
  • SPIインターフェース (最大20[MHz])
  • 内蔵8KBのバッファRAM
  • MACおよびPHY機能を統合
  • パケットフィルタリング機能
  • 低消費電力設計 (スリープモード対応)
  • 3.3V単一電源動作
  • 28ピンSSOP、QFNパッケージ


メリット

  • SPIインターフェースのため、配線が簡単 (4本の信号線)
  • 入手性が良好で、価格も比較的安価
  • 豊富なサンプルコードとドキュメント
  • MSPM0G3519のSPIペリフェラルで容易に接続可能


デメリット

  • 10[Mbps]のみの対応 (100[Mbps]非対応)
  • SPIのオーバーヘッドによりスループットが制限される。


上記のデメリットはあるものの、多くの組み込みアプリケーションでは10[Mbps]で十分であり、
配線の簡便性と入手性の良さからENC28J60は組み込みイーサネット実装において人気の高い選択肢となっている。


MSPM0G3519の特徴

MSPM0G3519は、Texas Instruments社の32ビットARM Cortex-M0+コアを搭載したマイコンで、MSPM0ファミリの上位モデルである。

MSPM0G3519の主な特徴を以下に示す。

  • コア
    ARM 32ビット Cortex-M0+ CPU (最大80[MHz])
    メモリ保護ユニット (MPU) 搭載
  • メモリ
    512[KB] フラッシュROM (デュアルバンク、ECC付き)
    128[KB] SRAM (合計)
    • SRAM バンク0: 64[KB] (ECC保護またはハードウェアパリティ、スタンバイモードまで保持)
      SRAM バンク1: 64[KB] (STOPモードまで保持)
    16[KB] データフラッシュバンク (ECC保護)
  • 高性能アナログペリフェラル
    2個の同時サンプリング12ビット4[Msps] ADC (最大27外部チャネル)
    1個の12ビット1Msps DAC (出力バッファ内蔵)
    3個の高速コンパレータ (8ビットリファレンスDAC内蔵)
    内部共有電圧リファレンス (1.4[V] または 2.5[V])
    温度センサ内蔵
  • 通信インターフェース
    7個のUARTインターフェース
    • 2個はLIN、IrDA、DALI、スマートカード、マンチェスターをサポート
      3個はスタンバイモードでの低消費電力動作をサポート
    3個のI2Cインターフェース (FM+ 1[Mbit/s]対応、STOP モードからのウェークアップ対応)
    3個のSPIインターフェース (1個は最大32[Mbit/s])
    2個のCAN-FDインターフェース (CAN 2.0 A/B、CAN-FD対応)
  • タイマ / DMA
    9個のタイマ (最大28個のPWMチャネル対応)
    12チャネルDMAコントローラ
  • セキュリティ機能
    AES-128/256アクセラレータ (GCM/GMAC、CCM/CBC-MAC、CBC、CTR対応)
    セキュアキーストレージ (最大4個のAESキー)
    真性乱数生成器 (TRNG)
    フレキシブルなファイアウォール
  • その他
    最大94個のGPIO
    RTC (アラームおよびカレンダーモード付き)
    ウォッチドッグタイマ
    低消費電力モード対応
    動作電圧: 1.62[V]〜3.6[V]
    動作温度範囲: -40[℃]〜125[℃]


lwIP実装に必要なリソース要件は以下の通りである。

  • フラッシュROM
    約50〜80[KB] (lwIP本体+ドライバ+アプリケーション)
  • RAM
    約20〜40[KB] (バッファ+メモリプール+スタック変数)


MSPM0G3519のリソースは、これらの要件を十分に満たしているため、lwIPの実装に適している。
MSP430F5529と比較して、以下の点で大幅に優位性がある。

  • RAMが16倍 (8[KB] → 128[KB]) で、多数の同時接続とバッファを確保可能
  • フラッシュが4倍 (128[KB] → 512[KB]) で、より多くの機能を実装可能
  • 32ビットアーキテクチャにより、TCP/IP処理が高速
  • 高速クロック (80[MHz]) により、SPIスループットが向上
  • 12チャネルDMAコントローラにより、CPUを介さずにSPIデータ転送が可能
  • CAN-FD対応により、車載やFA分野での応用が可能



ハードウェア接続

MSPM0G3519とENC28J60は、SPIインターフェースで接続する。

下表にMSPM0G3519のSPI0モジュールを使用する接続例を示す。

ピン接続表 (ENC28J60)
MSPM0G3519 ENC28J60 機能 説明
PB8 (SPI0_PICO) MOSI (SI) SPI コントローラ出力/ペリフェラル入力
PB7 (SPI0_POCI) MISO (SO) SPI コントローラ入力/ペリフェラル出力
PB9 (SPI0_SCLK) SCLK (SCK) SPI シリアルクロック
PB6 (GPIO) CS# 制御信号 チップセレクト
(アクティブLow)
PA18 (GPIO) RESET# 制御信号 リセット信号
(アクティブLow)
PA17 (GPIO) INT 割り込み 割り込み信号
(アクティブLow)
VDD VCC 電源 3.3V
VSS VSS GND グランド


※注意

  • 水晶振動子
    ENC28J60には25MHzの水晶振動子が必要 (ピン23、24に接続)
  • デカップリングコンデンサ
    各電源ピン近くに0.1[uF]を配置
  • RJ45コネクタ
    トランス内蔵型を使用 (Pulse社 J0011D21BNL等)
  • LEDインジケータ
    LEDA (ピン15)、LEDB (ピン14) に接続可能
  • ピン配置
    上記のピン配置は一例であり、MSPM0G3519の他のSPIペリフェラルやGPIOピンも使用可能
    使用するパッケージに応じて適切なピンを選択する
    MSPM0G3519は最大100ピンのパッケージがあり、ピン配置の自由度が高い


SPIインターフェースは、MSPM0G3519のSPI0ペリフェラルを使用する。
このペリフェラルはハードウェアSPIをサポートしており、DMAと組み合わせることで効率的な通信が可能である。
MSPM0G3519のSPI0は最大32[Mbit/s]に対応しているが、ENC28J60の最大速度は20[MHz]であるため、実際の動作速度はENC28J60の制限となる。

制御信号 (CS#、RESET#) および 割り込み信号 (INT) は任意のGPIOピンに接続できるが、割り込み機能を使用する場合は、割り込み対応ピンを選択する必要がある。

ENC28J60の電源は3.3[V]であり、MSPM0G3519と同じ電圧で動作するため、レベル変換回路は不要である。
ただし、電源の安定性を確保するため、各電源ピン近くに適切なデカップリングコンデンサを配置することが重要である。


lwIPライブラリの構成

lwIPライブラリは、以下に示すような階層構造を持っている。

  • アプリケーション層
    ユーザアプリケーション (Webサーバ、HTTPクライアント等)
  • API層
    Sequential API (netconn API) : ※RTOS使用時
    Raw API (コールバックベース、低レベルAPI) : ※NO_SYS=1の時
  • lwIPコア層
    TCP、UDP、IP、ICMP、ARPプロトコル実装
  • ネットワークインターフェース層
    イーサネットコントローラドライバ (ENC28J60)
  • メモリ管理層
    メモリプール、バッファ管理 (pbuf)


lwIPの主要なソースファイルを以下に示す。

  • lwip/src/core/
    lwIPコア実装ファイル群
    tcp.c、udp.c、ip.c、icmp.c、arp.c等
  • lwip/src/api/
    Sequential API実装 (RTOS使用時)
    netconn.c、api_lib.c、api_msg.c等
  • lwip/src/netif/
    ネットワークインターフェース
    ethernet.c、etharp.c等
  • lwip/src/include/lwip/
    lwIPヘッダファイル群
  • lwip/src/apps/
    アプリケーション層実装
    httpd.c、mqtt.c、snmp.c等
  • arch/
    アーキテクチャ依存部分 (移植先マイコン依存)
    cc.h、perf.h、sys_arch.h等
  • lwipopts.h
    lwIP設定ファイル (移植先マイコン依存)
  • enc28j60_lwip.c / enc28j60_lwip.h
    ENC28J60ドライバ (移植先マイコン依存)


この階層構造により、lwIPの中核部分とハードウェア依存部分が明確に分離されている。
MSPM0G3519では、主にアーキテクチャ依存部分 (arch/)、ネットワークインターフェース層 (enc28j60_lwip.c)、設定ファイル (lwipopts.h) を実装または修正する必要がある。

MSPM0G3519では、一般的にRaw APIを使用して実装する。(NO_SYS=1時)
Raw APIは、コールバック関数ベースの低レベルAPIで、RTOSを必要とせずに動作する。
Sequential APIは、よりシンプルな記述が可能であるが、RTOSが必要となる。

MSPM0G3519のリソースは十分であるため、将来的にRTOSの導入を検討する場合は、Sequential APIへの移行も容易である。



uIPライブラリの構成

uIPライブラリは、以下に示すような階層構造を持っている。

  • アプリケーション層
    ユーザアプリケーション (Webサーバ、HTTPクライアント等)
  • uIPコア層
    TCP、UDP、IP、ICMP、ARPプロトコル実装
  • ドライバ層
    イーサネットコントローラドライバ (ENC28J60)
  • タイマ管理
    ARPタイマ、TCP再送タイマ等


uIPの主要なソースファイルを以下に示す。

  • uip.c / uip.h
    uIPライブラリ
    uIPコア実装
  • uip_arp.c / uip_arp.h
    uIPライブラリ
    ARPプロトコル実装
  • timer.c / timer.h / timer-arch.h
    uIPライブラリ
    タイマ管理
  • clock.c / clock-arch.c / clock-arch.h
    uIPライブラリ
    システムクロック実装 (移植先マイコン依存)
  • tapdev.c / tapdev.h
    uIPライブラリ
    ネットワークデバイスインターフェース
  • uip-conf.h
    uIPライブラリ
    uIP設定ファイル (移植先マイコン依存)
  • enc28j60.c / enc28j60.h
    ENC28J60ドライバ (移植先マイコン依存)


この階層構造により、uIPの中核部分とハードウェア依存部分が明確に分離されている。
MSPM0G3519への移植では、主にドライバ層 (enc28j60.c)、タイマ管理 (clock-arch.c)、設定ファイル (uip-conf.h) を追加または修正する必要がある。
また、多くの場合、uIPコア層はそのまま使用できる。



lwIP : サンプルコード

以下の例では、ENC28J60およびlwIPを使用して、簡単なWebサーバを構築してHTTPリクエストに応答している。
MSPM0G3519の豊富なリソースを活用し、lwIPのRaw APIを使用している。

lwIP設定ファイル (lwipopts.h)

まず、lwIPの動作パラメータを設定する。

この設定ファイルでは、NO_SYS=1によりRTOSを使用しないスタンドアロン構成を指定している。
MSPM0G3519の128[KB] RAMを活用し、複数の同時接続とDHCPクライアント機能を有効化している。

  • メモリプール設定
    MSPM0G3519の豊富なRAM容量に応じて、メモリプールサイズを最適化している。
    pbufプールやヒープサイズは、アプリケーションの要件に応じて柔軟に変更可能である。
  • TCP接続数
    16接続に設定しているが、これはアプリケーションの要件に応じて調整できる。
    MSPM0G3519では、20〜30接続以上も十分に対応可能である。
  • DHCP機能
    DHCPクライアント機能を有効化することで、IPアドレスの自動取得が可能となる。


 #ifndef __LWIPOPTS_H__
 #define __LWIPOPTS_H__
 
 // NO_SYS==1: RTOSを使用しないスタンドアロン構成
 #define NO_SYS                  1
 
 // メモリ管理設定 (MSPM0G3519の128KB RAMを活用)
 #define MEM_ALIGNMENT           4
 #define MEM_SIZE                (32*1024)   // 32KB ヒープ (十分なRAMがあるため大きめに設定)
 #define MEMP_NUM_PBUF           32
 #define MEMP_NUM_TCP_PCB        16
 #define MEMP_NUM_TCP_PCB_LISTEN 8
 #define MEMP_NUM_TCP_SEG        32
 #define MEMP_NUM_SYS_TIMEOUT    16
 
 // pbuf設定
 #define PBUF_POOL_SIZE          32
 #define PBUF_POOL_BUFSIZE       512
 
 // TCP設定
 #define LWIP_TCP                1
 #define TCP_MSS                 1460
 #define TCP_WND                 (8 * TCP_MSS)     // 8 MSS ウィンドウサイズ
 #define TCP_SND_BUF             (8 * TCP_MSS)     // 8 MSS 送信バッファ
 #define TCP_SND_QUEUELEN        (4 * TCP_SND_BUF/TCP_MSS)
 
 // UDP設定
 #define LWIP_UDP                1
 #define MEMP_NUM_UDP_PCB        8
 
 // ICMP設定
 #define LWIP_ICMP               1
 
 // DHCP設定
 #define LWIP_DHCP               1
 #define LWIP_NETIF_HOSTNAME     1
 
 // DNS設定
 #define LWIP_DNS                1         // DNS有効化
 #define DNS_TABLE_SIZE          4
 #define DNS_MAX_SERVERS         2
 
 // ARP設定
 #define LWIP_ARP                1
 #define ARP_TABLE_SIZE          10
 #define ARP_QUEUEING            1
 
 // IP設定
 #define LWIP_IPV4               1
 #define LWIP_IPV6               0
 #define IP_REASSEMBLY           1         // IPフラグメント再構築を有効化
 #define IP_FRAG                 1         // IPフラグメント分割を有効化
 
 // チェックサム設定
 #define CHECKSUM_GEN_IP         1
 #define CHECKSUM_GEN_UDP        1
 #define CHECKSUM_GEN_TCP        1
 #define CHECKSUM_CHECK_IP       1
 #define CHECKSUM_CHECK_UDP      1
 #define CHECKSUM_CHECK_TCP      1
 
 // 統計情報 (開発時は有効化、製品版では無効化してRAM節約)
 #define LWIP_STATS              1
 #define LWIP_STATS_DISPLAY      1
 
 // デバッグ設定 (開発時は有効化可能)
 #define LWIP_DEBUG              0
 
 // その他
 #define LWIP_NETCONN            0  // Raw API使用のため無効化
 #define LWIP_SOCKET             0  // ソケットAPI不使用
 #define LWIP_NETIF_LINK_CALLBACK 1
 #define LWIP_NETIF_STATUS_CALLBACK 1
 
 // プラットフォーム依存の型定義
 #define U16_F "hu"
 #define S16_F "hd"
 #define X16_F "hx"
 #define U32_F "u"
 #define S32_F "d"
 #define X32_F "x"
 
 #endif /* __LWIPOPTS_H__ */


ENC28J60ドライバ (enc28j60.c)

ENC28J60との通信を行うドライバ制御 (SPIを使用したレジスタアクセスやパケット送受信等) を記述する。

このドライバでは、MSPM0G3519のSPIペリフェラルを使用してENC28J60を制御する。
MSPM0G3519のSPI通信速度は最大32[Mbit/s]であり、ENC28J60の最大速度である20[MHz]に近い動作が可能である。

以下の例では、基本的なレジスタアクセスとパケット送受信機能を提供している。
必要に応じて、DMAを使用した高速データ転送や、割り込み駆動の受信処理を追加することも可能である。

 // enc28j60.c : ENC28J60ドライバ
 
 #include "ti_msp_dl_config.h"  // MSPM0G3519 SDK設定
 #include "enc28j60.h"
 #include <stdint.h>
 
 // ENC28J60レジスタアドレス定義
 #define ERDPTL    0x00
 #define ERDPTH    0x01
 #define EWRPTL    0x02
 #define EWRPTH    0x03
 #define ETXSTL    0x04
 #define ETXSTH    0x05
 #define ETXNDL    0x06
 #define ETXNDH    0x07
 #define ERXSTL    0x08
 #define ERXSTH    0x09
 #define ERXNDL    0x0A
 #define ERXNDH    0x0B
 #define ERXRDPTL  0x0C
 #define ERXRDPTH  0x0D
 #define EIE       0x1B
 #define EIR       0x1C
 #define ESTAT     0x1D
 #define ECON2     0x1E
 #define ECON1     0x1F
 
 // MACレジスタ
 #define MACON1    0x00 | 0x40
 #define MACON3    0x02 | 0x40
 #define MAMXFLL   0x0A | 0x40
 #define MAMXFLH   0x0B | 0x40
 #define MABBIPG   0x04 | 0x40
 #define MAIPGL    0x06 | 0x40
 #define MAIPGH    0x07 | 0x40
 
 // SPIコマンド定義
 #define ENC28J60_READ_CTRL_REG  0x00
 #define ENC28J60_WRITE_CTRL_REG 0x40
 #define ENC28J60_BIT_FIELD_SET  0x80
 #define ENC28J60_BIT_FIELD_CLR  0xA0
 #define ENC28J60_READ_BUF_MEM   0x3A
 #define ENC28J60_WRITE_BUF_MEM  0x7A
 #define ENC28J60_SOFT_RESET     0xFF
 
 // ビット定義
 #define ECON1_TXRTS  0x08
 #define ECON1_RXEN   0x04
 #define ECON2_AUTOINC 0x80
 #define ECON2_PKTDEC 0x40
 #define ESTAT_CLKRDY 0x01
 #define MACON1_MARXEN 0x01
 #define MACON3_PADCFG0 0x20
 #define MACON3_TXCRCEN 0x10
 #define MACON3_FRMLNEN 0x02
 
 // チップセレクト制御 (実際のピン定義に応じて変更)
 #define ENC28J60_CS_LOW()   DL_GPIO_clearPins(GPIO_ENC_CS_PORT, GPIO_ENC_CS_PIN)
 #define ENC28J60_CS_HIGH()  DL_GPIO_setPins(GPIO_ENC_CS_PORT, GPIO_ENC_CS_PIN)
 
 static uint8_t current_bank = 0;
 
 // 遅延関数 (80[MHz]駆動時)
 static void delay_us(uint32_t us)
 {
    // 80[MHz] = 80 cycles/us
    // 簡易的な遅延実装 (正確に計測する場合はタイマを使用すること)
    volatile uint32_t cycles = us * 80;
    while (cycles--);
 }
 
 // SPIバイト送受信
 static uint8_t spi_transfer(uint8_t data)
 {
    // SPIにデータ送信
    DL_SPI_transmitData8(SPI_0_INST, data);
 
    // 送信完了待ち
    while (DL_SPI_isBusy(SPI_0_INST));
 
    // 受信データ取得
    return DL_SPI_receiveData8(SPI_0_INST);
 }
 
 // バンク選択
 static void enc28j60_set_bank(uint8_t address)
 {
    uint8_t bank = (address & 0x60) >> 5;
 
    if (bank != current_bank) {
       enc28j60_write_op(ENC28J60_BIT_FIELD_CLR, ECON1, 0x03);
       enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON1, bank);
       current_bank = bank;
    }
 }
 
 // レジスタ読み出し
 uint8_t enc28j60_read(uint8_t address)
 {
    uint8_t data;
 
    enc28j60_set_bank(address);
 
    ENC28J60_CS_LOW();
    spi_transfer(ENC28J60_READ_CTRL_REG | (address & 0x1F));
 
    // MACおよびMIIレジスタの場合、ダミーバイトを読む
    if (address & 0x80) {
       spi_transfer(0x00);
    }
 
    data = spi_transfer(0x00);
    ENC28J60_CS_HIGH();
 
    return data;
 }
 
 // レジスタ書き込み
 void enc28j60_write(uint8_t address, uint8_t data)
 {
    enc28j60_set_bank(address);
 
    ENC28J60_CS_LOW();
    spi_transfer(ENC28J60_WRITE_CTRL_REG | (address & 0x1F));
    spi_transfer(data);
    ENC28J60_CS_HIGH();
 }
 
 // ビット操作
 void enc28j60_write_op(uint8_t op, uint8_t address, uint8_t data)
 {
    ENC28J60_CS_LOW();
    spi_transfer(op | (address & 0x1F));
    spi_transfer(data);
    ENC28J60_CS_HIGH();
 }
 
 // バッファメモリ読み出し
 void enc28j60_read_buffer(uint8_t *buffer, uint16_t len)
 {
    ENC28J60_CS_LOW();
    spi_transfer(ENC28J60_READ_BUF_MEM);
 
    while (len--) {
       *buffer++ = spi_transfer(0x00);
    }
 
    ENC28J60_CS_HIGH();
 }
 
 // バッファメモリ書き込み
 void enc28j60_write_buffer(const uint8_t *buffer, uint16_t len)
 {
    ENC28J60_CS_LOW();
    spi_transfer(ENC28J60_WRITE_BUF_MEM);
 
    while (len--) {
       spi_transfer(*buffer++);
    }
 
    ENC28J60_CS_HIGH();
 }
 
 // ENC28J60初期化
 void enc28j60_init(const uint8_t *mac_address)
 {
    // ソフトウェアリセット
    ENC28J60_CS_LOW();
    spi_transfer(ENC28J60_SOFT_RESET);
    ENC28J60_CS_HIGH();
 
    delay_us(1000);  // 1ms待機
 
    // クロックが安定するまで待機
    while (!(enc28j60_read(ESTAT) & ESTAT_CLKRDY));
 
    // 受信バッファ設定 (0x0000 - 0x19FF)
    enc28j60_write(ERXSTL, 0x00);
    enc28j60_write(ERXSTH, 0x00);
    enc28j60_write(ERXNDL, 0xFF);
    enc28j60_write(ERXNDH, 0x19);
    enc28j60_write(ERXRDPTL, 0x00);
    enc28j60_write(ERXRDPTH, 0x00);
 
    // 送信バッファ設定 (0x1A00 - 0x1FFF)
    enc28j60_write(ETXSTL, 0x00);
    enc28j60_write(ETXSTH, 0x1A);
    enc28j60_write(ETXNDL, 0xFF);
    enc28j60_write(ETXNDH, 0x1F);
 
    // MAC初期化
    enc28j60_write(MACON1, MACON1_MARXEN);
    enc28j60_write(MACON3, MACON3_PADCFG0 | MACON3_TXCRCEN | MACON3_FRMLNEN);
    enc28j60_write(MAMXFLL, 0xEE);
    enc28j60_write(MAMXFLH, 0x05);
    enc28j60_write(MABBIPG, 0x12);
    enc28j60_write(MAIPGL, 0x12);
    enc28j60_write(MAIPGH, 0x0C);
 
    // MACアドレス設定
    enc28j60_write(0x00 | 0x60, mac_address[0]);  // MAADR1
    enc28j60_write(0x01 | 0x60, mac_address[1]);  // MAADR2
    enc28j60_write(0x02 | 0x60, mac_address[2]);  // MAADR3
    enc28j60_write(0x03 | 0x60, mac_address[3]);  // MAADR4
    enc28j60_write(0x04 | 0x60, mac_address[4]);  // MAADR5
    enc28j60_write(0x05 | 0x60, mac_address[5]);  // MAADR6
 
    // 割り込み有効化
    enc28j60_write(EIE, 0xC0);  // INTIE | PKTIE
 
    // 受信有効化
    enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);
 }
 
 // パケット送信
 void enc28j60_packet_send(const uint8_t *buffer, uint16_t len)
 {
    // 送信バッファポインタ設定
    enc28j60_write(EWRPTL, 0x00);
    enc28j60_write(EWRPTH, 0x1A);
 
    // 制御バイト書き込み
    enc28j60_write_buffer((uint8_t[]){0x00}, 1);
 
    // パケットデータ書き込み
    enc28j60_write_buffer(buffer, len);
 
    // 送信終了ポインタ設定
    enc28j60_write(ETXNDL, (0x1A00 + len) & 0xFF);
    enc28j60_write(ETXNDH, ((0x1A00 + len) >> 8) & 0xFF);
 
    // 送信開始
    enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS);
 
    // 送信完了待ち (オプション: タイムアウト処理を追加することを推奨)
    while (enc28j60_read(ECON1) & ECON1_TXRTS);
 }
 
 // パケット受信
 uint16_t enc28j60_packet_receive(uint8_t *buffer, uint16_t max_len)
 {
    uint16_t len = 0;
    uint8_t header[6];
 
    // 受信パケット数確認
    if (enc28j60_read(0x19) == 0) {  // EPKTCNT
       return 0;
    }
 
    // ヘッダ読み出し
    enc28j60_read_buffer(header, 6);
 
    // パケット長取得
    len = (header[3] << 8) | header[2];
    len -= 4;  // CRCを除く
 
    // バッファに収まる場合のみデータを読み出し
    if (len <= max_len) {
       enc28j60_read_buffer(buffer, len);
    }
 
    // 次のパケットポインタ更新
    uint16_t next_packet = (header[1] << 8) | header[0];
    enc28j60_write(ERXRDPTL, next_packet & 0xFF);
    enc28j60_write(ERXRDPTH, (next_packet >> 8) & 0xFF);
 
    // パケットデクリメント
    enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC);
 
    return (len <= max_len) ? len : 0;
 }


lwIPネットワークインターフェース (netif_enc28j60.c)

lwIPとENC28J60ドライバを接続するネットワークインターフェース層を実装する。
この層は、lwIPのpbuf構造体とENC28J60のパケットバッファ間でデータを変換する役割を持つ。

 // netif_enc28j60.c : lwIPネットワークインターフェース
 
 #include "lwip/opt.h"
 #include "lwip/def.h"
 #include "lwip/mem.h"
 #include "lwip/pbuf.h"
 #include "lwip/sys.h"
 #include "lwip/stats.h"
 #include "netif/etharp.h"
 #include "enc28j60.h"
 #include <string.h>
 
 #define IFNAME0 'e'
 #define IFNAME1 'n'
 
 // パケット送信
 static err_t low_level_output(struct netif *netif, struct pbuf *p)
 {
    struct pbuf *q;
    uint8_t buffer[1518];
    uint16_t len = 0;
 
    // pbufチェーンからバッファにコピー
    for (q = p; q != NULL; q = q->next) {
       if (len + q->len > sizeof(buffer)) {
          return ERR_BUF;
       }
       memcpy(&buffer[len], q->payload, q->len);
       len += q->len;
    }
 
    // ENC28J60で送信
    enc28j60_packet_send(buffer, len);
 
    return ERR_OK;
 }
 
 // パケット受信
 static struct pbuf *low_level_input(struct netif *netif)
 {
    struct pbuf *p, *q;
    uint8_t buffer[1518];
    uint16_t len;
 
    // ENC28J60からパケット受信
    len = enc28j60_packet_receive(buffer, sizeof(buffer));
 
    if (len == 0) {
       return NULL;
    }
 
    // pbuf割り当て
    p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
 
    if (p != NULL) {
       uint16_t offset = 0;
       // pbufチェーンにデータをコピー
       for (q = p; q != NULL; q = q->next) {
          memcpy(q->payload, &buffer[offset], q->len);
          offset += q->len;
       }
    }
 
    return p;
 }
 
 // ネットワークインターフェース初期化
 err_t netif_enc28j60_init(struct netif *netif)
 {
    netif->name[0] = IFNAME0;
    netif->name[1] = IFNAME1;
    netif->output = etharp_output;
    netif->linkoutput = low_level_output;
 
    netif->mtu = 1500;
    netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;
 
    // MACアドレス設定
    netif->hwaddr_len = 6;
    netif->hwaddr[0] = 0x02;
    netif->hwaddr[1] = 0x00;
    netif->hwaddr[2] = 0x00;
    netif->hwaddr[3] = 0x00;
    netif->hwaddr[4] = 0x00;
    netif->hwaddr[5] = 0x01;
 
    // ENC28J60初期化
    enc28j60_init(netif->hwaddr);
 
    return ERR_OK;
 }
 
 // パケット受信処理 (メインループから定期的に呼び出す)
 void netif_enc28j60_input(struct netif *netif)
 {
    struct pbuf *p;
 
    // 受信可能なパケットを全て処理
    while ((p = low_level_input(netif)) != NULL) {
       if (netif->input(p, netif) != ERR_OK) {
          pbuf_free(p);
       }
    }
 }


メイン処理

メイン処理では、MSPM0G3519の初期化、lwIPの初期化、HTTPサーバの起動を記述する。

 // main.c : メイン処理
 
 #include "ti_msp_dl_config.h"
 #include "lwip/init.h"
 #include "lwip/netif.h"
 #include "lwip/timeouts.h"
 #include "lwip/dhcp.h"
 #include "netif/etharp.h"
 #include "netif_enc28j60.h"
 #include "lwip/apps/httpd.h"
 
 // lwIP用タイマ (1[ms]周期)
 volatile uint32_t lwip_timer_ms = 0;
 
 // タイマ割り込みハンドラ (1[ms]周期)
 void TIMER_0_INST_IRQHandler(void)
 {
    lwip_timer_ms++;
 }
 
 int main(void)
 {
    struct netif netif;
    ip4_addr_t ipaddr, netmask, gw;
 
    // MSPM0G3519ペリフェラル初期化
    SYSCFG_DL_init();
 
    // タイマ割り込み有効化
    NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN);
 
    // lwIP初期化
    lwip_init();
 
    // 静的IPアドレス設定 (DHCPを使用しない場合)
    IP4_ADDR(&ipaddr, 192, 168, 1, 100);
    IP4_ADDR(&netmask, 255, 255, 255, 0);
    IP4_ADDR(&gw, 192, 168, 1, 1);
 
    // ネットワークインターフェース追加
    netif_add(&netif, &ipaddr, &netmask, &gw, NULL, netif_enc28j60_init, ethernet_input);
    netif_set_default(&netif);
    netif_set_up(&netif);
 
    // DHCPクライアント起動 (オプション)
    // dhcp_start(&netif);
 
    // HTTPサーバ初期化
    httpd_init();
 
    // メインループ
    while (1) {
       // パケット受信処理
       netif_enc28j60_input(&netif);
 
       // lwIPタイムアウト処理 (周期的なタイマイベント処理)
       sys_check_timeouts();
    }
 }


簡易HTTPサーバの構築

lwIPには、HTTPサーバ実装 (httpd.c) が含まれているため、これを利用することで簡単にWebサーバを構築できる。
以下の例では、カスタムCGIハンドラを追加して、動的なコンテンツを生成している。

 // httpd_cgi.c - CGIハンドラ
 #include "lwip/apps/httpd.h"
 #include "ti_msp_dl_config.h"
 #include <string.h>
 
 // LED状態取得CGI
 const char *led_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[])
 {
    // LEDの状態を確認
    uint32_t led_state = DL_GPIO_readPins(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_1_PIN);
 
    if (led_state) {
       return "/led_on.ssi";
    }
    else {
       return "/led_off.ssi";
    }
 }
 
 // LED制御CGI
 const char *led_control_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[])
 {
    int i;
 
    for (i = 0; i < iNumParams; i++) {
       if (strcmp(pcParam[i], "led") == 0) {
          if (strcmp(pcValue[i], "on") == 0) {
             DL_GPIO_setPins(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_1_PIN);
          }
          else if (strcmp(pcValue[i], "off") == 0) {
             DL_GPIO_clearPins(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_1_PIN);
          }
       }
    }
 
    return "/index.html";
 }
 
 // CGIハンドラテーブル
 static const tCGI cgi_handlers[] = {
    { "/led_status.cgi", led_cgi_handler },
    { "/led_control.cgi", led_control_cgi_handler }
 };
 
 // CGI初期化
 void httpd_cgi_init(void)
 {
    http_set_cgi_handlers(cgi_handlers, sizeof(cgi_handlers) / sizeof(tCGI));
 }


lwIPのHTTPサーバは、ファイルシステムを持たないため、Webページのコンテンツはプログラムに埋め込む必要がある。
lwIPには、makefsdataツールが付属しており、HTMLファイルをC言語のヘッダファイルに変換することができる。

 <!-- index.html -->
 
 <!DOCTYPE html>
 <html>
 <head>
     <title>MSPM0G3519 Web Server</title>
     <style>
         body { font-family: Arial, sans-serif; margin: 40px; }
         h1 { color: #333; }
         .info { background-color: #f0f0f0; padding: 20px; border-radius: 5px; }
         .controls { margin-top: 20px; }
         button { padding: 10px 20px; margin: 5px; }
     </style>
 </head>
 <body>
     <h1>MSPM0G3519 Web Server</h1>
     <div class="info">
         <p>This is running on lwIP stack with ENC28J60 Ethernet controller.</p>
         <ul>
             <li>MCU: MSPM0G3519 (ARM Cortex-M0+, 80MHz)</li>
             <li>Flash: 512KB</li>
             <li>RAM: 128KB</li>
             <li>Ethernet: ENC28J60 (10Mbps)</li>
         </ul>
     </div>
     <div class="controls">
         <h2>LED Control</h2>
         <form action="/led_control.cgi" method="get">
             <button type="submit" name="led" value="on">LED ON</button>
             <button type="submit" name="led" value="off">LED OFF</button>
         </form>
     </div>
 </body>
 </html>



その他の注意事項

lwIPを使用したTCP/IP通信を実装する場合の注意事項を以下に示す。

タイマ管理

lwIPは、sys_check_timeouts関数を定期的に呼び出す必要がある。
メインループ内で適切な間隔 (推奨 : 数[ms]〜数十[ms]) で呼び出すことを推奨する。

タイマの精度は、TCP通信の品質に影響するため、正確なタイマ実装が重要である。

メモリ管理

lwIPは、メモリプール方式を採用しているため、lwipopts.hで適切なプールサイズを設定する必要がある。
メモリ不足が発生すると、パケットのドロップや接続の失敗が生じる可能性がある。

MSPM0G3519は128[KB]のRAMを持つため、メモリプールサイズは柔軟に調整できる。

pbuf処理

pbuf構造体は、参照カウント方式でメモリ管理されている。
pbuf_free関数を適切に呼び出さないと、メモリリークが発生する。

特に、パケット受信処理やエラー処理時には注意が必要である。

割り込みコンテキスト

lwIPの関数は、基本的に割り込みコンテキストから呼び出すべきではない。
パケット受信処理は、メインループで行うことを推奨する。

ENC28J60の割り込み信号を使用する場合は、割り込みハンドラ内ではフラグを立てるのみとし、実際の処理はメインループで行う。

DHCPの使用

DHCPクライアントを使用する場合、IPアドレスの取得に時間がかかる場合がある。
netif_set_link_callback関数を使用して、リンクアップ時にDHCPを起動することを推奨する。

DHCPによるIPアドレス取得中は、TCP/UDP通信ができないため、アプリケーションの実装時に考慮する必要がある。

エンディアン変換

ネットワークバイトオーダー (ビッグエンディアン) とホストバイトオーダーの変換に注意する。

lwIPは、htons()、ntohs()、htonl()、ntohl()等のマクロを提供している。

ARM Cortex-M0+はリトルエンディアンであるため、適切な変換が必要である。


トラブルシューティング

パケットが受信できない

  • ENC28J60の初期化、MACアドレス設定、ネットワークインターフェースの設定を確認する。
  • netif_set_up()が呼ばれているか、NETIF_FLAG_LINK_UPが設定されているかを確認する。
  • SPIの通信速度が高すぎる場合、通信が不安定になる可能性があるため、速度を下げてみる。


ARPが応答しない

  • MACアドレスが正しく設定されているか、etharp_output()が正しく設定されているかを確認する。
  • ARP_TABLE_SIZEが適切に設定されているかを確認する。


TCP接続が確立しない

  • ファイアウォールやルータの設定を確認する。
  • sys_check_timeouts()が定期的に呼ばれているかを確認する。
  • TCPの3ウェイハンドシェイクが正常に行われているか、パケットキャプチャで確認する。


メモリ不足エラー

  • lwipopts.hのメモリプールサイズを増やす。
  • LWIP_STATS=1を有効化して、メモリ使用状況を確認する。
  • MSPM0G3519は128[KB]のRAMを持つため、十分なメモリを割り当てることができる。


HTTPサーバが応答しない

  • httpd_init関数が呼ばれているか、または、ポート80がリッスンされているかを確認する。
  • fsdata.cが正しく生成されているかを確認する。
  • makefsdata.pyを使用して、HTMLファイルを正しく変換する。



SPI通信の速度

MSPM0G3519のSPI0は最大32[Mbit/s]に対応しているが、ENC28J60の最大速度は20[MHz]である。
そのため、実際の動作速度はENC28J60の制限となる。

SPIクロック周波数は、MSPM0G3519のクロック設定により調整可能である。
通信が不安定な場合は、SPIクロック周波数を下げて (例: 10[MHz]、5[MHz]) 通信の安定性を確認することを推奨する。
配線長やノイズの影響により、実際の動作速度は制限される場合がある。


DMAの活用

MSPM0G3519の12チャネルDMAコントローラを使用することで、CPUの負荷を軽減しつつ、高速なデータ転送が可能となる。
特に、大きなパケットを連続的に送受信する場合、DMAの活用により大幅なパフォーマンス向上が期待できる。

DMAを使用する場合は、以下の点に注意する必要がある。

  • DMAチャネルの割り当て
    他のペリフェラルとDMAチャネルが競合しないように設定する。
    MSPM0G3519は12チャネルのDMAを持つため、柔軟に割り当てが可能である。
  • バッファアライメント
    DMA転送用のバッファは、適切なアライメント (4バイト境界等) に配置する。
  • 割り込みハンドリング
    DMA転送完了割り込みを適切に処理する。
    DMA転送完了後に、次の処理 (パケット受信処理等) を行う。



CAN-FDインターフェースの活用

MSPM0G3519は2個のCAN-FDインターフェースを搭載しているため、イーサネット通信とCAN通信を組み合わせたゲートウェイ応用が可能である。
例えば、以下のような応用が考えられる。

  • CANバスとイーサネットを接続するゲートウェイ
    車載ネットワークやFA機器のデータをイーサネット経由でクラウドに送信
  • CANデータのロギングとWeb表示
    CANバスのデータをリアルタイムでWebブラウザに表示
  • イーサネット経由でのCAN制御
    Webブラウザから車載機器やFA機器を制御



参考資料