「MSPM0G3519 - イーサネット (ENC28J60)」の版間の差分

提供: MochiuWiki : SUSE, EC, PCB

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

ページの作成:「== 概要 == MSP430F5529は、USB機能を内蔵した16ビットRISCマイコンである。<br> しかし、イーサネット機能は内蔵していないため、TCP/IP通信を行うには、外付けのイーサネットコントローラIC (ENC28J60等) とuIPライブラリを組み合わせて使用する。<br> <br> uIPは、スウェーデンのAdam Dunkels氏が開発した軽量TCP/IPスタックで、限られたRAM容量 (数[KB]) で動作するよ…」
 
 
(同じ利用者による、間の11版が非表示)
1行目: 1行目:
== 概要 ==
== 概要 ==
MSP430F5529は、USB機能を内蔵した16ビットRISCマイコンである。<br>
MSPM0G3519は、Texas Instruments社の32ビットARM Cortex-M0+コアを搭載したマイコンである。<br>
しかし、イーサネット機能は内蔵していないため、TCP/IP通信を行うには、外付けのイーサネットコントローラIC (ENC28J60等) とuIPライブラリを組み合わせて使用する。<br>
最大80[MHz]で動作し、512[KB]のフラッシュROMと128[KB]のSRAMを搭載している。<br>
しかし、イーサネット機能は内蔵していないため、TCP/IP通信を行うには、外付けのイーサネットコントローラIC (ENC28J60等) とlwIPまたはuIPライブラリを組み合わせて使用する。<br>
<br>
<br>
uIPは、スウェーデンのAdam Dunkels氏が開発した軽量TCP/IPスタックで、限られたRAM容量 ([KB]) で動作するように設計されており、組み込みシステムに最適である。<br>
lwIPは、スウェーデンのAdam Dunkels氏が開発した軽量TCP/IPスタックで、uIPの後継として位置付けられている。<br>
MSPM0G3519は十分なRAM容量 (128[KB]) を持つため、lwIPの使用が推奨される。<br>
<br>
lwIPは、より高速で安定したTCP/IP通信を実現し、多数の同時接続に対応できる。<br>
<br>
<br>
主な構成は以下の通りである。<br>
主な構成は以下の通りである。<br>
* MSP430F5529 (マイコン)
* MSPM0G3519 (マイコン)
* ENC28J60 (イーサネットコントローラ)
* ENC28J60 (イーサネットコントローラ)
* SPIインターフェース
* lwIPライブラリ (TCP/IPスタック)
*: <u>推奨</u>
* uIPライブラリ (TCP/IPスタック)
* uIPライブラリ (TCP/IPスタック)
* SPIインターフェース
*: 軽量版 : 非推奨 (メンテナンスされていない)
<br>
この構成により、MSPM0G3519を使用したWebサーバ、HTTPクライアント、Telnetサーバ、UDPアプリケーション、DHCPクライアント、DNSクライアント等の構築が可能となる。<br>
<br><br>
 
== ENC28J60 と W5500 ==
イーサネット通信を実現するには、大きく分けて2つの階層がある。
* 上位層
*: ネットワーク層以上で、TCP/IPプロトコルの処理を行う。
* 下位層
*: <u>物理層</u> および <u>データリンク層</u> で、イーサネットフレームの送受信を担当する。
<br>
ENC28J60は下位層のみを持つICである。<br>
イーサネットフレームの送受信はできるが、TCP/IPプロトコルの処理機能は持たない。<br>
これを <u>イーサネットMAC/PHYコントローラ</u> と呼ぶ。<br>
<br>
一方、W5500は、上位層および下位層の両方をIC内に持つため、TCP/IPの複雑な処理も含めて全てW5500が自動的に処理する。<br>
これを <u>ハードウェアTCP/IPスタック内蔵型</u> と呼ぶ。<br>
<br><br>
 
== lwIPライブラリの特徴 ==
lwIP (Lightweight IP) は、スウェーデンのAdam Dunkels氏が開発したTCP/IPプロトコルスタックで、uIPの後継として位置付けられている。<br>
lwIPは、uIPよりも高機能であり、より多くのメモリを使用するが、その分、高いスループットと多数の同時接続に対応できる設計となっている。<br>
<br>
lwIPの特徴を以下に示す。<br>
<br>
* 高機能な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言語で記述されており、様々なマイコンに移植可能
*: ハードウェア依存部分が明確に分離されている
<br>
<u>※MSPM0G3519でのlwIP使用について</u><br>
<u>MSPM0G3519は、512[KB]のフラッシュと128[KB]のSRAMを持つため、lwIPを標準構成で十分に使用できる。</u><br>
<u>これにより、以下に示すメリットが得られる。</u><br>
* メリット
*: より多くの同時接続に対応 (通常10〜50接続以上)
*: 高速なデータ転送が可能
*: DHCP、DNS等の上位機能を標準サポート
*: より安定したTCP実装
*: パケットの分割送信と再送処理が効率的
*: ゼロコピー機能によるメモリ効率の向上
*: <br>
* デメリット
*: uIPと比較して、初期設定がやや複雑
*: コードサイズが大きい (ただし、MSPM0G3519のフラッシュ容量で十分に収まる)
<br>
<br>
この構成により、MSP430F5529を使用したWebサーバ、HTTPクライアント、Telnetサーバ、UDPアプリケーション等の実装が可能となる。<br>
MSPM0G3519では、lwIPの使用を推奨する。<br>
RAMとフラッシュROMに十分な余裕があるため、lwIPの高機能性を最大限活用できる。<br>
軽量なアプリケーションにはuIPも使用可能であるが、将来的な機能拡張を考慮すると、lwIPの採用がより適切である。<br>
<br><br>
<br><br>


== uIPライブラリの特徴 ==
== uIPライブラリの特徴 ==
<u>uIPは既にメンテナンスされていないため、使用は非推奨である。</u><br>
<br>
uIPは、組み込みシステム向けに最適化された軽量TCP/IPプロトコルスタックである。<br>
uIPは、組み込みシステム向けに最適化された軽量TCP/IPプロトコルスタックである。<br>
<br>
<br>
35行目: 101行目:
<u>同時接続数が限られており (通常1〜10接続程度)、スループットよりも省メモリを優先した設計となっている。</u><br>
<u>同時接続数が限られており (通常1〜10接続程度)、スループットよりも省メモリを優先した設計となっている。</u><br>
<u>高速通信や多数の同時接続が必要な場合は、lwIP等のより高機能なスタックの使用を検討する必要がある。</u><br>
<u>高速通信や多数の同時接続が必要な場合は、lwIP等のより高機能なスタックの使用を検討する必要がある。</u><br>
<br>
<u>MSPM0G3519は128[KB]の十分なRAMを持つため、特別な理由がない限りlwIPの使用を推奨する。</u><br>
<br><br>
<br><br>


== ENC28J60イーサネットコントローラ ==
== ENC28J60イーサネットコントローラ ==
MSP430F5529はイーサネットコントローラを内蔵していないため、外付けのイーサネットコントローラチップが必要となる。<br>
MSPM0G3519はイーサネットコントローラを内蔵していないため、外付けのイーサネットコントローラチップが必要となる。<br>
<br>
<br>
Microchip社のENC28J60は、SPI接続のイーサネットコントローラで、MSP430との組み合わせで最も広く使用されている。<br>
Microchip社のENC28J60は、SPI接続のイーサネットコントローラで、組み込みシステムで広く使用されている。<br>
<br>
<br>
* IEEE 802.3準拠の10BASE-T Ethernetコントローラ
* IEEE 802.3準拠の10BASE-T Ethernetコントローラ
* SPIインターフェース (最大20MHz)
* SPIインターフェース (最大20[MHz])
* 内蔵8KBのバッファRAM
* 内蔵8KBのバッファRAM
* MACおよびPHY機能を統合
* MACおよびPHY機能を統合
55行目: 123行目:
* 入手性が良好で、価格も比較的安価
* 入手性が良好で、価格も比較的安価
* 豊富なサンプルコードとドキュメント
* 豊富なサンプルコードとドキュメント
* MSP430F5529のUSART (SPI)で容易に接続可能
* MSPM0G3519のSPIペリフェラルで容易に接続可能
<br>
<br>
<u>デメリット</u><br>
<u>デメリット</u><br>
* 10Mbpsのみの対応 (100Mbps非対応)
* 10[Mbps]のみの対応 (100[Mbps]非対応)
* SPIのオーバーヘッドによりスループットが制限される。
* SPIのオーバーヘッドによりスループットが制限される。
<br>
<br>
65行目: 133行目:
<br><br>
<br><br>


== MSP430F5529の特徴 ==
== MSPM0G3519の特徴 ==
MSP430F5529はの16ビットRISCマイコンで、USB機能を内蔵した上位モデルである。<br>
MSPM0G3519は、Texas Instruments社の32ビットARM Cortex-M0+コアを搭載したマイコンで、MSPM0ファミリの上位モデルである。<br>
<br>
<br>
MSP430F5529の特徴を以下に示す。<br>
MSPM0G3519の主な特徴を以下に示す。<br>
<br>
<br>
* 128[KB] フラッシュROM
* コア
* 8[KB] RAM
*: ARM 32ビット Cortex-M0+ CPU (最大80[MHz])
* 最大25[MHz]動作
*: メモリ保護ユニット (MPU) 搭載
* USB 2.0フルスピード (12Mbps) 対応
* メモリ
* USCI (UART、SPI、I2C対応)×4モジュール
*: 512[KB] フラッシュROM (デュアルバンク、ECC付き)
* 12ビットADC
*: 128[KB] SRAM (合計)
* タイマA/B
**: SRAM バンク0: 64[KB] (ECC保護またはハードウェアパリティ、スタンバイモードまで保持)
* 80ピン (LQFP)
**: SRAM バンク1: 64[KB] (STOPモードまで保持)
* DMAコントローラ
*: 16[KB] データフラッシュバンク (ECC保護)
* 低消費電力設計
* 高性能アナログペリフェラル
* 内蔵LDO (USB用3.3V電源回路)
*: 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[℃]
<br>
<br>
uIP実装に必要なリソース要件は以下の通りである。<br>
lwIP実装に必要なリソース要件は以下の通りである。<br>
* フラッシュROM
* フラッシュROM
*: 約15〜25[KB] (uIP本体+アプリケーション)
*: 約50〜80[KB] (lwIP本体+ドライバ+アプリケーション)
* RAM
* RAM
*: 約4〜8[KB] (バッファ+スタック変数)
*: 約20〜40[KB] (バッファ+メモリプール+スタック変数)
<br>
<br>
MSP430F5529のリソースは、これらの要件を十分に満たしているため、uIPの実装に適している。<br>
MSPM0G3519のリソースは、これらの要件を十分に満たしているため、lwIPの実装に適している。<br>
MSP430F5529と比較して、以下の点で大幅に優位性がある。<br>
* RAMが16倍 (8[KB] → 128[KB]) で、多数の同時接続とバッファを確保可能
* フラッシュが4倍 (128[KB] → 512[KB]) で、より多くの機能を実装可能
* 32ビットアーキテクチャにより、TCP/IP処理が高速
* 高速クロック (80[MHz]) により、SPIスループットが向上
* 12チャネルDMAコントローラにより、CPUを介さずにSPIデータ転送が可能
* CAN-FD対応により、車載やFA分野での応用が可能
<br><br>
<br><br>


== ハードウェア接続 ==
== ハードウェア接続 ==
MSP430F5529とENC28J60は、SPIインターフェースで接続する。<br>
MSPM0G3519とENC28J60は、SPIインターフェースで接続する。<br>
<br>
<br>
下表にMSP430F5529のUSCI_B0をSPIモードで使用する接続例を示す。<br>
下表にMSPM0G3519のSPI0モジュールを使用する接続例を示す。<br>
<br>
<br>
<center>
<center>
{| class="wikitable"
{| class="wikitable"
|+ ピン接続表 (ENC28J60)
|+ ピン接続表 (ENC28J60)
! MSP430F5529 !! ENC28J60 !! 機能 !! 説明
! MSPM0G3519 !! ENC28J60 !! 機能 !! 説明
|-
|-
| style="text-align:center;" | P3.0 || style="text-align:center;" | MOSI (SI) || SPI || マスター出力/スレーブ入力
| style="text-align:center;" | PB8 (SPI0_PICO) || style="text-align:center;" | MOSI (SI) || SPI || コントローラ出力/ペリフェラル入力
|-
|-
| style="text-align:center;" | P3.1 || style="text-align:center;" | MISO (SO) || SPI || マスター入力/スレーブ出力
| style="text-align:center;" | PB7 (SPI0_POCI) || style="text-align:center;" | MISO (SO) || SPI || コントローラ入力/ペリフェラル出力
|-
|-
| style="text-align:center;" | P3.2 || style="text-align:center;" | SCLK (SCK) || SPI || シリアルクロック
| style="text-align:center;" | PB9 (SPI0_SCLK) || style="text-align:center;" | SCLK (SCK) || SPI || シリアルクロック
|-
|-
| style="text-align:center;" | P2.7 || style="text-align:center;" | CS# || 制御信号 || チップセレクト<br> (アクティブLow)
| style="text-align:center;" | PB6 (GPIO) || style="text-align:center;" | CS# || 制御信号 || チップセレクト<br> (アクティブLow)
|-
|-
| style="text-align:center;" | P2.6 || style="text-align:center;" | RESET# || 制御信号 || リセット信号<br> (アクティブLow)
| style="text-align:center;" | PA18 (GPIO) || style="text-align:center;" | RESET# || 制御信号 || リセット信号<br> (アクティブLow)
|-
|-
| style="text-align:center;" | P2.0 || style="text-align:center;" | INT || 割り込み || 割り込み信号<br> (アクティブLow)
| style="text-align:center;" | PA17 (GPIO) || style="text-align:center;" | INT || 割り込み || 割り込み信号<br> (アクティブLow)
|-
|-
| style="text-align:center;" | VCC || style="text-align:center;" | VCC || 電源 || 3.3V
| style="text-align:center;" | VDD || style="text-align:center;" | VCC || 電源 || 3.3V
|-
|-
| style="text-align:center;" | GND || style="text-align:center;" | VSS || GND || グランド
| style="text-align:center;" | VSS || style="text-align:center;" | VSS || GND || グランド
|}
|}
</center>
</center>
128行目: 229行目:
* LEDインジケータ
* LEDインジケータ
*: LEDA (ピン15)、LEDB (ピン14) に接続可能
*: LEDA (ピン15)、LEDB (ピン14) に接続可能
* ピン配置
*: 上記のピン配置は一例であり、MSPM0G3519の他のSPIペリフェラルやGPIOピンも使用可能
*: 使用するパッケージに応じて適切なピンを選択する
*: MSPM0G3519は最大100ピンのパッケージがあり、ピン配置の自由度が高い
<br>
<br>
SPIインターフェースは、MSP430F5529のP3ポートに配置されているUSCI_B0モジュールを使用する。<br>
SPIインターフェースは、MSPM0G3519のSPI0ペリフェラルを使用する。<br>
このモジュールはハードウェアSPIをサポートしており、効率的な通信が可能である。<br>
このペリフェラルはハードウェアSPIをサポートしており、DMAと組み合わせることで効率的な通信が可能である。<br>
MSPM0G3519のSPI0は最大32[Mbit/s]に対応しているが、ENC28J60の最大速度は20[MHz]であるため、実際の動作速度はENC28J60の制限となる。<br>
<br>
<br>
制御信号 (CS#、RESET#) および 割り込み信号 (INT) は任意のGPIOピンに接続できるが、割り込み機能を使用する場合は、割り込み対応ピンを選択する必要がある。<br>
制御信号 (CS#、RESET#) および 割り込み信号 (INT) は任意のGPIOピンに接続できるが、割り込み機能を使用する場合は、割り込み対応ピンを選択する必要がある。<br>
<br>
<br>
ENC28J60の電源は3.3[V]であり、MSP430F5529と同じ電圧で動作するため、レベル変換回路は不要である。<br>
ENC28J60の電源は3.3[V]であり、MSPM0G3519と同じ電圧で動作するため、レベル変換回路は不要である。<br>
ただし、電源の安定性を確保するため、各電源ピン近くに適切なデカップリングコンデンサを配置することが重要である。<br>
ただし、電源の安定性を確保するため、各電源ピン近くに適切なデカップリングコンデンサを配置することが重要である。<br>
<br><br>
== lwIPライブラリの構成 ==
lwIPライブラリは、以下に示すような階層構造を持っている。<br>
<br>
* アプリケーション層
*: ユーザアプリケーション (Webサーバ、HTTPクライアント等)
* API層
*: Sequential API (netconn API) : ※RTOS使用時
*: Raw API (コールバックベース、低レベルAPI) : ※NO_SYS=1の時
* lwIPコア層
*: TCP、UDP、IP、ICMP、ARPプロトコル実装
* ネットワークインターフェース層
*: イーサネットコントローラドライバ (ENC28J60)
* メモリ管理層
*: メモリプール、バッファ管理 (pbuf)
<br>
lwIPの主要なソースファイルを以下に示す。<br>
* 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ドライバ (移植先マイコン依存)
<br>
この階層構造により、lwIPの中核部分とハードウェア依存部分が明確に分離されている。<br>
MSPM0G3519では、主にアーキテクチャ依存部分 (arch/)、ネットワークインターフェース層 (enc28j60_lwip.c)、設定ファイル (lwipopts.h) を実装または修正する必要がある。<br>
<br>
MSPM0G3519では、一般的にRaw APIを使用して実装する。(NO_SYS=1時)<br>
Raw APIは、コールバック関数ベースの低レベルAPIで、RTOSを必要とせずに動作する。<br>
Sequential APIは、よりシンプルな記述が可能であるが、RTOSが必要となる。<br>
<br>
MSPM0G3519のリソースは十分であるため、将来的にRTOSの導入を検討する場合は、Sequential APIへの移行も容易である。<br>
<br>
* lwIPの公式Webサイト
*: https://savannah.nongnu.org/projects/lwip/
* lwIPのGithub
*: https://github.com/lwip-tcpip/lwip
* lwIP Documentation
*: http://www.nongnu.org/lwip/2_1_x/index.html
<br><br>
<br><br>


== uIPライブラリの構成 ==
== uIPライブラリの構成 ==
uIPライブラリは、以下のような階層構造を持っている。<br>
uIPライブラリは、以下に示すような階層構造を持っている。<br>
<br>
<br>
* アプリケーション層
* アプリケーション層
173行目: 334行目:
<br>
<br>
この階層構造により、uIPの中核部分とハードウェア依存部分が明確に分離されている。<br>
この階層構造により、uIPの中核部分とハードウェア依存部分が明確に分離されている。<br>
MSP430F5529への移植では、主にドライバ層 (enc28j60.c)、タイマ管理 (clock-arch.c)、設定ファイル (uip-conf.h) を追加または修正する必要がある。<br>
MSPM0G3519への移植では、主にドライバ層 (enc28j60.c)、タイマ管理 (clock-arch.c)、設定ファイル (uip-conf.h) を追加または修正する必要がある。<br>
また、多くの場合、uIPコア層はそのまま使用できる。<br>
また、多くの場合、uIPコア層はそのまま使用できる。<br>
<br>
* uIPの公式Webサイト
*: 閉鎖
* uIPのGithub
*: https://github.com/adamdunkels/uip
<br><br>
== lwIP : サンプルコード ==
以下の例では、ENC28J60およびlwIPを使用して、簡単なWebサーバを構築してHTTPリクエストに応答している。<br>
MSPM0G3519の豊富なリソースを活用し、lwIPのRaw APIを使用している。<br>
<br>
==== lwIP設定ファイル (lwipopts.h) ====
まず、lwIPの動作パラメータを設定する。<br>
<br>
この設定ファイルでは、NO_SYS=1によりRTOSを使用しないスタンドアロン構成を指定している。<br>
MSPM0G3519の128[KB] RAMを活用し、複数の同時接続とDHCPクライアント機能を有効化している。<br>
<br>
* メモリプール設定
*: MSPM0G3519の豊富なRAM容量に応じて、メモリプールサイズを最適化している。
*: pbufプールやヒープサイズは、アプリケーションの要件に応じて柔軟に変更可能である。
* TCP接続数
*: 16接続に設定しているが、これはアプリケーションの要件に応じて調整できる。
*: MSPM0G3519では、20〜30接続以上も十分に対応可能である。
* DHCP機能
*: DHCPクライアント機能を有効化することで、IPアドレスの自動取得が可能となる。
<br>
<syntaxhighlight lang="c">
#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__ */
</syntaxhighlight>
<br>
==== ENC28J60ドライバ (enc28j60.c) ====
ENC28J60との通信を行うドライバ制御 (SPIを使用したレジスタアクセスやパケット送受信等) を記述する。<br>
<br>
このドライバでは、MSPM0G3519のSPIペリフェラルを使用してENC28J60を制御する。<br>
MSPM0G3519のSPI通信速度は最大32[Mbit/s]であり、ENC28J60の最大速度である20[MHz]に近い動作が可能である。<br>
<br>
以下の例では、基本的なレジスタアクセスとパケット送受信機能を提供している。<br>
必要に応じて、DMAを使用した高速データ転送や、割り込み駆動の受信処理を追加することも可能である。<br>
<br>
<syntaxhighlight lang="c">
// 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;
}
</syntaxhighlight>
<br>
==== lwIPネットワークインターフェース (netif_enc28j60.c) ====
lwIPとENC28J60ドライバを接続するネットワークインターフェース層を実装する。<br>
この層は、lwIPのpbuf構造体とENC28J60のパケットバッファ間でデータを変換する役割を持つ。<br>
<br>
<syntaxhighlight lang="c">
// 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);
      }
    }
}
</syntaxhighlight>
<br>
==== メイン処理 ====
メイン処理では、MSPM0G3519の初期化、lwIPの初期化、HTTPサーバの起動を記述する。<br>
<br>
<syntaxhighlight lang="c">
// 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();
    }
}
</syntaxhighlight>
<br>
==== 簡易HTTPサーバの構築 ====
lwIPには、HTTPサーバ実装 (httpd.c) が含まれているため、これを利用することで簡単にWebサーバを構築できる。<br>
以下の例では、カスタムCGIハンドラを追加して、動的なコンテンツを生成している。<br>
<br>
<syntaxhighlight lang="c">
// 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));
}
</syntaxhighlight>
<br>
lwIPのHTTPサーバは、ファイルシステムを持たないため、Webページのコンテンツはプログラムに埋め込む必要がある。<br>
lwIPには、makefsdataツールが付属しており、HTMLファイルをC言語のヘッダファイルに変換することができる。<br>
<br>
<syntaxhighlight lang="html">
<!-- 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>
</syntaxhighlight>
<br><br>
<br><br>


== サンプルコード ==
== その他の注意事項 ==
以下の例では、ENC28J60およびuIPを使用して、簡単なWebサーバを構築してHTTPリクエストに応答している。<br>
lwIPを使用したTCP/IP通信を実装する場合の注意事項を以下に示す。<br>
<br>
==== タイマ管理 ====
lwIPは、sys_check_timeouts関数を定期的に呼び出す必要がある。<br>
メインループ内で適切な間隔 (推奨 : 数[ms]〜数十[ms]) で呼び出すことを推奨する。<br>
<br>
タイマの精度は、TCP通信の品質に影響するため、正確なタイマ実装が重要である。<br>
<br>
==== メモリ管理 ====
lwIPは、メモリプール方式を採用しているため、lwipopts.hで適切なプールサイズを設定する必要がある。<br>
メモリ不足が発生すると、パケットのドロップや接続の失敗が生じる可能性がある。<br>
<br>
MSPM0G3519は128[KB]のRAMを持つため、メモリプールサイズは柔軟に調整できる。<br>
<br>
==== pbuf処理 ====
pbuf構造体は、参照カウント方式でメモリ管理されている。<br>
pbuf_free関数を適切に呼び出さないと、メモリリークが発生する。<br>
<br>
特に、パケット受信処理やエラー処理時には注意が必要である。<br>
<br>
==== 割り込みコンテキスト ====
lwIPの関数は、基本的に割り込みコンテキストから呼び出すべきではない。<br>
パケット受信処理は、メインループで行うことを推奨する。<br>
<br>
ENC28J60の割り込み信号を使用する場合は、割り込みハンドラ内ではフラグを立てるのみとし、実際の処理はメインループで行う。<br>
<br>
==== DHCPの使用 ====
DHCPクライアントを使用する場合、IPアドレスの取得に時間がかかる場合がある。<br>
netif_set_link_callback関数を使用して、リンクアップ時にDHCPを起動することを推奨する。<br>
<br>
DHCPによるIPアドレス取得中は、TCP/UDP通信ができないため、アプリケーションの実装時に考慮する必要がある。<br>
<br>
==== エンディアン変換 ====
ネットワークバイトオーダー (ビッグエンディアン) とホストバイトオーダーの変換に注意する。<br>
<br>
lwIPは、htons()、ntohs()、htonl()、ntohl()等のマクロを提供している。<br>
<br>
ARM Cortex-M0+はリトルエンディアンであるため、適切な変換が必要である。<br>
<br><br>
 
== トラブルシューティング ==
==== パケットが受信できない ====
* ENC28J60の初期化、MACアドレス設定、ネットワークインターフェースの設定を確認する。
* netif_set_up()が呼ばれているか、NETIF_FLAG_LINK_UPが設定されているかを確認する。
* SPIの通信速度が高すぎる場合、通信が不安定になる可能性があるため、速度を下げてみる。
<br>
==== ARPが応答しない ====
*: MACアドレスが正しく設定されているか、etharp_output()が正しく設定されているかを確認する。
* ARP_TABLE_SIZEが適切に設定されているかを確認する。
<br>
==== TCP接続が確立しない ====
* ファイアウォールやルータの設定を確認する。
* sys_check_timeouts()が定期的に呼ばれているかを確認する。
* TCPの3ウェイハンドシェイクが正常に行われているか、パケットキャプチャで確認する。
<br>
==== メモリ不足エラー ====
* lwipopts.hのメモリプールサイズを増やす。
* LWIP_STATS=1を有効化して、メモリ使用状況を確認する。
* MSPM0G3519は128[KB]のRAMを持つため、十分なメモリを割り当てることができる。
<br>
==== HTTPサーバが応答しない ====
* httpd_init関数が呼ばれているか、または、ポート80がリッスンされているかを確認する。
* fsdata.cが正しく生成されているかを確認する。
* makefsdata.pyを使用して、HTMLファイルを正しく変換する。
<br><br>
 
== uIP : サンプルコード ==
以下の例では、ENC28J60およびuIPを使用して、Webサーバを構築してHTTPリクエストに応答している。<br>
uIPは軽量であるため、MSPM0G3519の豊富なリソースを考慮すると軽量すぎる可能性があるが、簡単な実装や学習目的には適している。<br>
<br>
<br>
==== uIP設定ファイル (uip-conf.h) ====
==== uIP設定ファイル (uip-conf.h) ====
185行目: 1,080行目:
この設定ファイルでは、デバイスのネットワーク設定 (IPアドレス、ネットマスク、ゲートウェイ) を定義している。<br>
この設定ファイルでは、デバイスのネットワーク設定 (IPアドレス、ネットマスク、ゲートウェイ) を定義している。<br>
これらの値は、使用するネットワーク環境に合わせて変更する必要がある。<br>
これらの値は、使用するネットワーク環境に合わせて変更する必要がある。<br>
MSPM0G3519は128[KB]のRAMを持つため、バッファサイズや接続数を比較的大きく設定できる。<br>
<br>
<br>
* バッファサイズ
* バッファサイズ
*: 標準的なイーサネットフレームのMTU (Maximum Transmission Unit) である1500バイトに設定している。
*: 標準的なイーサネットフレームのMTU (Maximum Transmission Unit) である1500バイトに設定している。
* TCP接続数
* TCP接続数
*: 4に設定しているが、これはRAM容量と必要な同時接続数に応じて調整できる。
*: 8に設定しているが、これはRAM容量と必要な同時接続数に応じて調整できる。
*: MSPM0G3519の豊富なRAMを活かし、MSP430F5529の設定 (4接続) より多めに設定している。
*: 接続数を増やすと、より多くのクライアントを同時に処理できるが、RAM使用量も増加する点に注意が必要である。
*: 接続数を増やすと、より多くのクライアントを同時に処理できるが、RAM使用量も増加する点に注意が必要である。
<br>
<br>
224行目: 1,121行目:
  #define UIP_CONF_RECEIVE_WINDOW  1460  // TCP受信ウィンドウサイズ
  #define UIP_CONF_RECEIVE_WINDOW  1460  // TCP受信ウィンドウサイズ
   
   
  // TCP/UDP接続数
  // TCP/UDP接続数 (MSPM0G3519の豊富なRAMを活用)
  #define UIP_CONF_MAX_CONNECTIONS 4     // 最大TCP接続数
  #define UIP_CONF_MAX_CONNECTIONS 8     // 最大TCP接続数 (MSP430より多め)
  #define UIP_CONF_MAX_LISTENPORTS 4     // 最大TCP待受ポート数
  #define UIP_CONF_MAX_LISTENPORTS 8     // 最大TCP待受ポート数
  #define UIP_CONF_UDP_CONNS      2     // 最大UDP接続数
  #define UIP_CONF_UDP_CONNS      4     // 最大UDP接続数
   
   
  // プロトコル有効化
  // プロトコル有効化
234行目: 1,131行目:
  #define UIP_CONF_BROADCAST      1    // ブロードキャストを有効化
  #define UIP_CONF_BROADCAST      1    // ブロードキャストを有効化
   
   
  // 統計情報
  // 統計情報 (開発時は有効化、製品版では無効化してRAM節約)
  #define UIP_CONF_STATISTICS      0    // 統計情報を無効化 (RAM節約)
  #define UIP_CONF_STATISTICS      0    // 統計情報を無効化
   
   
  // アプリケーション名
  // アプリケーション名
255行目: 1,152行目:
バンク選択は、ECON1レジスタの下位2ビットで制御される。<br>
バンク選択は、ECON1レジスタの下位2ビットで制御される。<br>
ドライバでは、現在のバンクを追跡し、必要な場合のみバンク切り替えを行うことにより、不要なSPI通信を削減している。<br>
ドライバでは、現在のバンクを追跡し、必要な場合のみバンク切り替えを行うことにより、不要なSPI通信を削減している。<br>
<br>
MSPM0G3519のSPIペリフェラルは、DL_SPI関数を使用して制御する。<br>
最大80[MHz]の高速動作により、SPIクロックも高速に設定できるが、ENC28J60の最大速度 (20[MHz]) を超えないように注意する必要がある。<br>
<br>
<br>
  <syntaxhighlight lang="c">
  <syntaxhighlight lang="c">
  #include <msp430f5529.h>
// enc28j60.c : ENC28J60ドライバ (MSPM0G3519 uIP用)
  #include "ti_msp_dl_config.h"  // MSPM0G3519 SDK設定
  #include "enc28j60.h"
  #include "enc28j60.h"
  #include <stdint.h>
  #include <stdint.h>
302行目: 1,204行目:
   
   
  // MACON1 : MACコントロールレジスタ1
  // MACON1 : MACコントロールレジスタ1
  #define MACON1    0x00
  #define MACON1    0x00 | 0x40
  #define MARXEN    0x01
  #define MARXEN    0x01
  #define TXPAUS    0x08
  #define TXPAUS    0x08
308行目: 1,210行目:
   
   
  // MACON3 : MACコントロールレジスタ3
  // MACON3 : MACコントロールレジスタ3
  #define MACON3    0x02
  #define MACON3    0x02 | 0x40
  #define PADCFG0  0x20
  #define PADCFG0  0x20
  #define TXCRCEN  0x10
  #define TXCRCEN  0x10
315行目: 1,217行目:
   
   
  // MAMXFLL/MAMXFLH : 最大フレーム長レジスタ
  // MAMXFLL/MAMXFLH : 最大フレーム長レジスタ
  #define MAMXFLL  0x0A
  #define MAMXFLL  0x0A | 0x40
  #define MAMXFLH  0x0B
  #define MAMXFLH  0x0B | 0x40
   
   
  // MABBIPG : バックツーバックパケット間隙レジスタ
  // MABBIPG : バックツーバックパケット間隙レジスタ
  #define MABBIPG  0x04
  #define MABBIPG  0x04 | 0x40
   
   
  // MAIPGL/MAIPGH : 非バックツーバックパケット間隙レジスタ
  // MAIPGL/MAIPGH : 非バックツーバックパケット間隙レジスタ
  #define MAIPGL    0x06
  #define MAIPGL    0x06 | 0x40
  #define MAIPGH    0x07
  #define MAIPGH    0x07 | 0x40
   
   
  // SPIコマンド定義
  // SPIコマンド定義
340行目: 1,242行目:
  #define BANK3  0x03
  #define BANK3  0x03
   
   
  // チップセレクト制御
  // チップセレクト制御 (実際のピン定義に応じて変更)
  #define ENC28J60_CS_LOW()  (P2OUT &= ~BIT7)
  #define ENC28J60_CS_LOW()  DL_GPIO_clearPins(GPIO_ENC_CS_PORT, GPIO_ENC_CS_PIN)
  #define ENC28J60_CS_HIGH()  (P2OUT |= BIT7)
  #define ENC28J60_CS_HIGH()  DL_GPIO_setPins(GPIO_ENC_CS_PORT, GPIO_ENC_CS_PIN)
   
   
  static uint8_t current_bank = 0;
  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バイト送受信
  // SPIバイト送受信
  static uint8_t spi_transfer(uint8_t data)
  static uint8_t spi_transfer(uint8_t data)
  {
  {
     while (!(UCB0IFG & UCTXIFG));  // 送信バッファが空になるまで待機
     // SPIにデータ送信
     UCB0TXBUF = data;              // データ送信
    DL_SPI_transmitData8(SPI_0_INST, data);
     while (!(UCB0IFG & UCRXIFG));  // 受信完了まで待機
   
     return UCB0RXBUF;             // 受信データを返す
     // 送信完了待ち
     while (DL_SPI_isBusy(SPI_0_INST));
   
    // 受信データ取得
     return DL_SPI_receiveData8(SPI_0_INST);
  }
  }
   
   
443行目: 1,358行目:
     ENC28J60_CS_HIGH();
     ENC28J60_CS_HIGH();
   
   
     __delay_cycles(50000);  // 1ms待機 (50[MHz]クロック想定)
     delay_us(1000);  // 1[ms]待機
   
   
     // クロックが安定するまで待機
     // クロックが安定するまで待機
462行目: 1,377行目:
     enc28j60_write(ETXNDH, 0x1F);
     enc28j60_write(ETXNDH, 0x1F);
   
   
     // MAC初期化 (バンク2)
     // MAC初期化
     enc28j60_write(MACON1, MARXEN); // MAC受信有効
     enc28j60_write(MACON1, MARXEN);                       // MAC受信有効
     enc28j60_write(MACON3, PADCFG0 | TXCRCEN | FRMLNEN);  // パディング、CRC、フレーム長チェック有効
     enc28j60_write(MACON3, PADCFG0 | TXCRCEN | FRMLNEN);  // パディング、CRC、フレーム長チェック有効
     enc28j60_write(MAMXFLL, 0xEE);   // 最大フレーム長1518バイト
     enc28j60_write(MAMXFLL, 0xEE);                       // 最大フレーム長1518バイト
     enc28j60_write(MAMXFLH, 0x05);
     enc28j60_write(MAMXFLH, 0x05);
     enc28j60_write(MABBIPG, 0x12);   // バックツーバック間隔
     enc28j60_write(MABBIPG, 0x12);                       // バックツーバック間隔
     enc28j60_write(MAIPGL, 0x12);   // 非バックツーバック間隔
     enc28j60_write(MAIPGL, 0x12);                         // 非バックツーバック間隔
     enc28j60_write(MAIPGH, 0x0C);
     enc28j60_write(MAIPGH, 0x0C);
   
   
     // MACアドレス設定 (バンク3)
     // MACアドレス設定
     enc28j60_write(0x00 | 0x60, mac_address[0]);  // MAADR1
     enc28j60_write(0x00 | 0x60, mac_address[0]);  // MAADR1
     enc28j60_write(0x01 | 0x60, mac_address[1]);  // MAADR2
     enc28j60_write(0x01 | 0x60, mac_address[1]);  // MAADR2
505行目: 1,420行目:
     // 送信開始
     // 送信開始
     enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON1, TXRTS);
     enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON1, TXRTS);
    // 送信完了待ち (オプション)
    while (enc28j60_read(ECON1) & TXRTS);
  }
  }
   
   
543行目: 1,461行目:
<br>
<br>
==== メイン処理 ====
==== メイン処理 ====
メイン処理では、MSP430F5529の初期化、uIPの初期化等を記述する。<br>
メイン処理では、MSPM0G3519の初期化、uIPの初期化等を記述する。<br>
<br>
MSPM0G3519のタイマペリフェラルを使用して、uIPに必要な周期的なタイマイベントを生成する。<br>
uIPは、TCPの再送タイマやARPのタイムアウト管理のために、定期的なタイマ処理を必要とする。<br>
一般的には、TCPタイマは500[ms]周期、ARPタイマは10秒周期で処理する。<br>
<br>
MSPM0G3519の80MHz動作により、uIPのTCP/IP処理は非常に高速に実行できる。<br>
また、128[KB]のRAMにより、uIPのバッファやスタック変数に十分なメモリを割り当てることができる。<br>
<br>
<br>
  <syntaxhighlight lang="c">
  <syntaxhighlight lang="c">
  #include <msp430f5529.h>
// main.c : メイン処理 (MSPM0G3519 uIP)
  #include "ti_msp_dl_config.h"
  #include "uip.h"
  #include "uip.h"
  #include "uip_arp.h"
  #include "uip_arp.h"
560行目: 1,487行目:
  #define MAC_ADDR5  0x01
  #define MAC_ADDR5  0x01
   
   
  // グローバルバッファ
  // グローバルバッファ (uIP用)
  uint8_t uip_buf[UIP_BUFSIZE + 2]; // uIP用バッファ
// MSPM0G3519は128KB RAMを持つため、余裕を持ったバッファサイズを確保可能
  uint8_t uip_buf[UIP_BUFSIZE + 2];
   
   
  // タイマ管理用
  // タイマ管理用
567行目: 1,495行目:
  volatile uint16_t arp_timer = 0;
  volatile uint16_t arp_timer = 0;
   
   
  // SPI初期化 (USCI_B0をSPIモードで使用)
  // タイマ割り込みハンドラ (10ms周期想定)
  void spi_init(void)
  void TIMER_0_INST_IRQHandler(void)
{
    // ポート設定
    P3SEL |= BIT0 | BIT1 | BIT2;  // P3.0 = MOSI, P3.1 = MISO, P3.2 = SCLK
    P2DIR |= BIT7;                // P2.7 = CS (出力)
    P2OUT |= BIT7;                // CS初期値をHigh
    // USCI_B0をSPIマスターモードで初期化
    UCB0CTL1 |= UCSWRST;                        // ソフトウェアリセット有効
    UCB0CTL0 = UCMST | UCSYNC | UCCKPH | UCMSB;  // マスター、同期、位相、MSBファースト
    UCB0CTL1 = UCSSEL_2 | UCSWRST;              // SMCLKを使用
    UCB0BR0 = 2;                                // クロック分周 (25[MHz] / 2 = 12.5[MHz])
    UCB0BR1 = 0;
    UCB0CTL1 &= ~UCSWRST;                        // ソフトウェアリセット解除
}
// クロック初期化 (25[MHz])
void clock_init(void)
{
    UCSCTL3 = SELREF_2;            // FLLリファレンス = REFO
    UCSCTL4 |= SELA_2;            // ACLK = REFO
    __bis_SR_register(SCG0);      // FLLを無効化
    UCSCTL0 = 0x0000;              // DCO、MOD初期化
    UCSCTL1 = DCORSEL_6;          // DCO周波数範囲選択
    UCSCTL2 = FLLD_0 + 762;        // 25[MHz]設定 (762 + 1)× 32768[Hz]
    __bic_SR_register(SCG0);      // FLLを有効化
    __delay_cycles(250000);        // 安定化待機
}
// Timer A0初期化 (10[ms]周期)
void timer_init(void)
{
    TA0CCR0 = 25000 - 1;              // 10[ms]周期 (25[MHz] / 100 = 250000、分周8で25000)
    TA0CTL = TASSEL_2 | ID_3 | MC_1;  // SMCLK、分周8、アップモード
    TA0CCTL0 = CCIE;                  // 割り込み有効
}
// Timer A0割り込みハンドラ (10[ms]周期)
#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer_A0_ISR(void)
  {
  {
     periodic_timer++;
     periodic_timer++;
659行目: 1,546行目:
     uint8_t mac_address[6] = {MAC_ADDR0, MAC_ADDR1, MAC_ADDR2, MAC_ADDR3, MAC_ADDR4, MAC_ADDR5};
     uint8_t mac_address[6] = {MAC_ADDR0, MAC_ADDR1, MAC_ADDR2, MAC_ADDR3, MAC_ADDR4, MAC_ADDR5};
   
   
     WDTCTL = WDTPW | WDTHOLD;  // ウォッチドッグ停止
     // MSPM0G3519ペリフェラル初期化
    SYSCFG_DL_init();
   
   
     // システム初期化
     // タイマ割り込み有効化
     clock_init();             // クロック設定 (25[MHz])
     NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN);
    spi_init();                // SPI初期化
    timer_init();              // タイマー初期化
   
   
     // ENC28J60初期化
     // ENC28J60初期化
690行目: 1,576行目:
     uip_listen(HTONS(80));
     uip_listen(HTONS(80));
   
   
     __enable_interrupt();     // グローバル割り込み有効
     // グローバル割り込み有効化
    __enable_irq();
   
   
    // メインループ
     while (1) {
     while (1) {
       // パケット受信チェック
       // パケット受信チェック
720行目: 1,608行目:
       }
       }
   
   
       // 周期処理
       // 周期処理 (TCP再送、ARPタイムアウト等)
       uip_periodic_handler();
       uip_periodic_handler();
     }
     }
726行目: 1,614行目:
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
初期化フェーズでは、マイコンのクロック設定、SPI通信の設定、タイマの設定、ENC28J60およびuIPの初期化を実行する。<br>
初期化フェーズでは、MSPM0G3519のペリフェラル設定、SPI通信の設定、タイマの設定、ENC28J60およびuIPの初期化を実行する。<br>
SYSCFG_DL_init関数は、TI MSPM0 SDKによって自動生成される設定関数であり、クロック、GPIO、SPI、タイマ等のペリフェラルを一括で初期化する。<br>
<br>
<br>
メイン処理では、受信パケットの確認と周期処理を繰り返し実行する。<br>
メイン処理では、受信パケットの確認と周期処理を繰り返し実行する。<br>
732行目: 1,621行目:
* 受信パケットの確認
* 受信パケットの確認
** IPパケットの場合
** IPパケットの場合
**: uIPスタックに渡して、TCP / UDP処理を行う。
**: uIPスタックに渡して、TCP/UDP処理を行う。
**: MSPM0G3519の80MHz動作により、TCP/IP処理は非常に高速に実行される。
** ARPパケットの場合
** ARPパケットの場合
**: ARPテーブルの更新や応答を行う。
**: ARPテーブルの更新や応答を行う。
741行目: 1,631行目:
*: これらのタイミングは、uIPの仕様に基づいて設定されており、変更する場合は慎重に検討する必要がある。
*: これらのタイミングは、uIPの仕様に基づいて設定されており、変更する場合は慎重に検討する必要がある。
<br>
<br>
==== 簡易HTTPサーバの構築 ====
==== HTTPサーバの構築 ====
HTTPリクエストに応答する簡単なWebサーバを構築する。<br>
HTTPリクエストに応答する簡単なWebサーバを構築する。<br>
<br>
<br>
uIPはイベント駆動型のアーキテクチャを採用しており、アプリケーション関数 (httpd_appcall関数) は、様々なイベントが発生したときに呼び出される。<br>
uIPはイベント駆動型のアーキテクチャを採用しており、アプリケーション関数 (httpd_appcall関数) は、様々なイベントが発生したときに呼び出される。<br>
主なイベントは以下の通りである。<br>
* 新規接続が確立された場合
* 新規接続が確立された場合
*: uip_connected
*: uip_connected
758行目: 1,649行目:
<br>
<br>
以下の例では、GETリクエストを受信した時、予め用意されたHTMLページを送信する。<br>
以下の例では、GETリクエストを受信した時、予め用意されたHTMLページを送信する。<br>
実務では、URLに応じて異なるコンテンツを返したり、センサデータを含む動的なページを生成することも可能である。<br>
MSPM0G3519の豊富なフラッシュメモリ (512[KB]) により、複数のHTMLページや画像データを格納することも可能である。<br>
実務では、URLに応じて異なるコンテンツを返したり、ADCで取得したセンサデータを含む動的なページを生成することも可能である。<br>
<br>
<br>
ただし、uIPは限られたRAMで動作するため、大きなコンテンツを扱う場合は、分割送信やフラッシュROMからの直接読み出す等の工夫が必要となる。<br>
uIPは限られたRAMで動作するように設計されているが、MSPM0G3519の128[KB] RAMにより、比較的大きなコンテンツも扱うことができる。<br>
ただし、非常に大きなコンテンツを扱う場合は、分割送信やフラッシュROMからの直接読み出し等の工夫が推奨される。<br>
<br>
<br>
  <syntaxhighlight lang="c">
  <syntaxhighlight lang="c">
// httpd.c : HTTPサーバ実装
  #include "uip.h"
  #include "uip.h"
  #include <string.h>
  #include <string.h>
   
   
  // HTTPレスポンス (HTMLページ)
  // HTTPレスポンス (HTMLページ)
// MSPM0G3519のフラッシュROM (512[KB]) により、複数のページを格納可能
  const char http_response[] =  
  const char http_response[] =  
     "HTTP/1.0 200 OK\r\n"
     "HTTP/1.0 200 OK\r\n"
774行目: 1,670行目:
     "<!DOCTYPE html>"
     "<!DOCTYPE html>"
     "<html>"
     "<html>"
     "<head><title>MSP430F5529 uIP Server</title></head>"
     "<head>"
    "  <title>MSPM0G3519 uIP Server</title>"
    "  <meta charset=\"UTF-8\">"
    "  <style>"
    "    body { font-family: Arial, sans-serif; margin: 40px; background-color: #f5f5f5; }"
    "    .container { max-width: 800px; margin: 0 auto; background-color: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }"
    "    h1 { color: #c00; border-bottom: 2px solid #c00; padding-bottom: 10px; }"
    "    .specs { background-color: #f9f9f9; padding: 15px; border-left: 4px solid #c00; margin: 20px 0; }"
    "    .specs h2 { margin-top: 0; color: #333; }"
    "    ul { line-height: 1.8; }"
    "    .footer { margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd; text-align: center; color: #666; font-size: 0.9em; }"
    "  </style>"
    "</head>"
     "<body>"
     "<body>"
     "<h1>Welcome to MSP430F5529!</h1>"
     " <div class=\"container\">"
     "<p>This is a simple web server running on MSP430F5529 with uIP.</p>"
    "    <h1>Welcome to MSPM0G3519 uIP Server!</h1>"
     "<p>ENC28J60 Ethernet Controller</p>"
     "   <div class=\"specs\">"
    "      <h2>System Specifications</h2>"
    "      <ul>"
    "        <li><strong>MCU:</strong> MSPM0G3519 (ARM Cortex-M0+)</li>"
    "        <li><strong>Clock Speed:</strong> 80 MHz</li>"
    "        <li><strong>Flash Memory:</strong> 512 KB (Dual-bank with ECC)</li>"
    "        <li><strong>SRAM:</strong> 128 KB</li>"
    "        <li><strong>Ethernet Controller:</strong> ENC28J60 (10 Mbps)</li>"
    "        <li><strong>TCP/IP Stack:</strong> uIP (Lightweight Implementation)</li>"
    "      </ul>"
    "    </div>"
    "    <p>This embedded web server demonstrates the power of the MSPM0G3519 microcontroller combined with the uIP TCP/IP stack.</p>"
    "    <p>The MSPM0G3519 offers excellent performance with its 80 MHz ARM Cortex-M0+ core and generous memory resources, making it ideal for IoT and embedded networking applications.</p>"
     "   <div class=\"footer\">"
    "      <p>&copy; 2025 MSPM0G3519 Embedded Web Server</p>"
    "    </div>"
    "  </div>"
     "</body>"
     "</body>"
     "</html>";
     "</html>";
787行目: 1,711行目:
     // 新規接続時
     // 新規接続時
     if (uip_connected()) {
     if (uip_connected()) {
        // 特に処理なし (GETリクエスト待ち)
        // 特に処理なし (GETリクエスト待ち)
     }
     }
   
     // データ受信時
     // データ受信時
     if (uip_newdata()) {
     if (uip_newdata()) {
        // 簡易的にGETリクエストをチェック
        // 簡易的にGETリクエストをチェック
        if (strncmp((char *)uip_appdata, "GET ", 4) == 0) {
        // より高度な実装では、HTTPヘッダの完全なパースを行う
            // HTTPレスポンス送信
        if (strncmp((char *)uip_appdata, "GET ", 4) == 0) {
            uip_send((void *)http_response, sizeof(http_response) - 1);
          // HTTPレスポンス送信
        }
          uip_send((void *)http_response, sizeof(http_response) - 1);
        }
     }
     }
   
     // 再送要求時
     // 再送要求時
     if (uip_rexmit()) {
     if (uip_rexmit()) {
        uip_send((void *)http_response, sizeof(http_response) - 1);
        uip_send((void *)http_response, sizeof(http_response) - 1);
     }
     }
   
     // 接続クローズ時
     // 接続クローズ時
     if (uip_closed() || uip_aborted() || uip_timedout()) {
     if (uip_closed() || uip_aborted() || uip_timedout()) {
        // 特に処理なし
        // 特に処理なし
        // 必要に応じてクリーンアップ処理を追加
     }
     }
  }
  }
  </syntaxhighlight>
  </syntaxhighlight>
<br>
MSPM0G3519の豊富なリソースを活かすことにより、高度な機能を追加することも可能である。<br>
例えば、以下のような拡張が考えられる。<br>
* URLルーティング
*: リクエストされたURLに応じて、異なるHTMLページを返す。
*: 複数のページを用意し、ナビゲーション機能を実装する。
*: <br>
* 動的コンテンツ生成
*: ADCで取得したセンサデータをリアルタイムでWebページに表示する。
*: GPIO状態を読み取り、LEDの点灯状態等をWebページに反映する。
*: <br>
* フォーム処理
*: POSTリクエストを処理し、Webブラウザから設定値を変更する。
*: LEDの制御、PWMのデューティ比設定等をWebインターフェースから行う。
*: <br>
* JSON API
*: REST APIを実装し、JSON形式でデータを送受信する。
*: IoTデバイスとしての機能を強化する。
<br><br>
<br><br>


== その他の注意事項 ==
== SPI通信の速度 ==
uIPを使用したTCP/IP通信を使用する場合の注意事項を以下に示す。<br>
MSPM0G3519のSPI0は最大32[Mbit/s]に対応しているが、ENC28J60の最大速度は20[MHz]である。<br>
そのため、実際の動作速度はENC28J60の制限となる。<br>
<br>
<br>
* タイミング管理
SPIクロック周波数は、MSPM0G3519のクロック設定により調整可能である。<br>
*: uIPは周期的な処理が必要である。
通信が不安定な場合は、SPIクロック周波数を下げて (例: 10[MHz]、5[MHz]) 通信の安定性を確認することを推奨する。<br>
*: TCPタイマは通常500[ms]周期、ARPタイマは10秒周期で呼び出す。
配線長やノイズの影響により、実際の動作速度は制限される場合がある。<br>
*: これらのタイミングがずれると、接続の確立や維持に問題が生じる可能性がある。
<br><br>
* バッファ管理
 
*: uIPは単一のグローバルバッファ (uip_buf) を使用する。
== DMAの活用 ==
*: 送受信の処理中は、このバッファの内容が上書きされないように注意する必要がある。
MSPM0G3519の12チャネルDMAコントローラを使用することで、CPUの負荷を軽減しつつ、高速なデータ転送が可能となる。<br>
* スタックサイズ
特に、大きなパケットを連続的に送受信する場合、DMAの活用により大幅なパフォーマンス向上が期待できる。<br>
*: uIPは比較的小さなスタックで動作するが、再帰的な処理や大きなローカル変数を避ける必要がある。
*: MSP430ではスタックオーバーフローに特に注意する。
* 割り込みコンテキスト
*: イーサネットコントローラの割り込みハンドラ内では、最小限の処理のみを行い、実際のパケット処理はメイン処理で行うことを推奨する。
* デバッグ
*: uIPにはデバッグ用の統計情報機能があるが、RAM使用量が増加する。
*: 開発時は有効化し、製品版では無効化することを検討する。
* クロック精度
*: タイマ処理の精度は、TCP通信の品質に影響する。
*: MSP430F5529のDCOは温度変動の影響を受けやすいため、可能であれば外部クリスタルの使用を検討する。
<br>
<br>
<u>※注意</u><br>
DMAを使用する場合は、以下の点に注意する必要がある。<br>
<u>よくあるトラブルシューティングを以下に示す。</u><br>
* DMAチャネルの割り当て
* パケットが受信できない
*: 他のペリフェラルとDMAチャネルが競合しないように設定する。
*: ENC28J60の受信フィルタ設定、MACアドレス設定、バッファポインタ設定を確認する。
*: MSPM0G3519は12チャネルのDMAを持つため、柔軟に割り当てが可能である。
* ARPが応答しない
* バッファアライメント
*: MACアドレスが正しく設定されているか、uip_arp_init()が呼ばれているかを確認する。
*: DMA転送用のバッファは、適切なアライメント (4バイト境界等) に配置する。
* TCP接続が確立しない
* 割り込みハンドリング
*: uip_listen関数等でポートが開いているか、周期処理が正しく動作しているかを確認する。
*: DMA転送完了割り込みを適切に処理する。
* データが送信できない
*: DMA転送完了後に、次の処理 (パケット受信処理等) を行う。
*: 送信バッファの設定、TXRTSビットのクリア条件、uip_send関数等の呼び出しタイミングを確認する。
<br><br>
<br>
 
==== SPI通信の速度 ====
== CAN-FDインターフェースの活用 ==
一般的な問題として、SPIクロック速度の設定ミスがある。<br>
MSPM0G3519は2個のCAN-FDインターフェースを搭載しているため、イーサネット通信とCAN通信を組み合わせたゲートウェイ応用が可能である。<br>
ENC28J60は最大20[MHz]のSPIクロックに対応しているが、MSP430F5529のクロック設定や配線の状態によっては、より低い周波数での動作が必要になる場合がある。<br>
例えば、以下のような応用が考えられる。<br>
そのため、通信が不安定な場合は、まずSPIクロック周波数を下げて通信できるかどうかを推奨する。<br>
* CANバスとイーサネットを接続するゲートウェイ
<br>
*: 車載ネットワークやFA機器のデータをイーサネット経由でクラウドに送信
==== ENC28J60の初期化 ====
* CANデータのロギングとWeb表示
ENC28J60の初期化は、データシートに記載されている手順に従う必要がある。<br>
*: CANバスのデータをリアルタイムでWebブラウザに表示
特に、ソフトウェアリセット後のクロック安定化待機、バンク切り替えのタイミングは重要である。<br>
* イーサネット経由でのCAN制御
初期化が正しく行われていない場合、パケットの送受信が正常に動作しない可能性がある。<br>
*: Webブラウザから車載機器やFA機器を制御
<br><br>
<br><br>


== 参考資料 ==
== 参考資料 ==
* MSP430F5529データシート
* MSPM0G3519データシート
*: https://www.ti.com/product/ja-jp/MSP430F5529
*: https://www.ti.com/product/ja-jp/MSPM0G3519
* MSPM0 SDK
*: https://www.ti.com/tool/MSPM0-SDK
* MSPM0 G-Series 80-MHz Microcontrollers Technical Reference Manual
*: https://www.ti.com/lit/pdf/SLAU846
* ENC28J60データシート
* ENC28J60データシート
*: https://www.microchip.com/en-us/product/ENC28J60
*: https://www.microchip.com/en-us/product/ENC28J60
* uIP公式ドキュメント
* lwIP公式ドキュメント
*: Adam Dunkels, "uIP - A Free Small TCP/IP Implementation"
*: http://www.nongnu.org/lwip/2_1_x/index.html
*: https://github.com/adamdunkels/uip
* lwIP Github
* RFC 793 : Transmission Control Protocol (TCP)
*: https://github.com/lwip-tcpip/lwip
* RFC 826 : An Ethernet Address Resolution Protocol (ARP)
* [https://www.rfc-editor.org/rfc/rfc793 RFC 793]
*: Transmission Control Protocol (TCP)
* [https://www.rfc-editor.org/rfc/rfc826 RFC 826]
*: An Ethernet Address Resolution Protocol (ARP)
* [https://www.rfc-editor.org/rfc/rfc2131 RFC 2131]
*: Dynamic Host Configuration Protocol (DHCP)
<br><br>
<br><br>




{{#seo:
{{#seo:
|title={{PAGENAME}} : MSP430F5529 uIP TCP/IP Implementation | MochiuWiki
|title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki
|keywords=MochiuWiki,MSP430,MSP430F5529,uIP,TCP/IP,Ethernet,ENC28J60,Embedded,Network,SPI,C,TI,Texas Instruments
|keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,電気回路,電子回路,基板,プリント基板
|description={{PAGENAME}} - MSP430F5529マイコンにおけるuIPライブラリを使用したTCP/IP通信の実装方法
|description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux
|image=/resources/assets/MochiuLogo_Single_Blue.png
}}
}}


__FORCETOC__
__FORCETOC__
[[カテゴリ:MSP430]]
[[カテゴリ:MSP430]]

2025年12月22日 (月) 16:44時点における最新版

概要

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ファイルを正しく変換する。



uIP : サンプルコード

以下の例では、ENC28J60およびuIPを使用して、Webサーバを構築してHTTPリクエストに応答している。
uIPは軽量であるため、MSPM0G3519の豊富なリソースを考慮すると軽量すぎる可能性があるが、簡単な実装や学習目的には適している。

uIP設定ファイル (uip-conf.h)

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

この設定ファイルでは、デバイスのネットワーク設定 (IPアドレス、ネットマスク、ゲートウェイ) を定義している。
これらの値は、使用するネットワーク環境に合わせて変更する必要がある。
MSPM0G3519は128[KB]のRAMを持つため、バッファサイズや接続数を比較的大きく設定できる。

  • バッファサイズ
    標準的なイーサネットフレームのMTU (Maximum Transmission Unit) である1500バイトに設定している。
  • TCP接続数
    8に設定しているが、これはRAM容量と必要な同時接続数に応じて調整できる。
    MSPM0G3519の豊富なRAMを活かし、MSP430F5529の設定 (4接続) より多めに設定している。
    接続数を増やすと、より多くのクライアントを同時に処理できるが、RAM使用量も増加する点に注意が必要である。


 #ifndef __UIP_CONF_H__
 #define __UIP_CONF_H__
 
 #include <stdint.h>
 
 // uIP設定パラメータ
 typedef uint8_t u8_t;        // 8ビット符号なし整数型
 typedef uint16_t u16_t;      // 16ビット符号なし整数型
 
 // IPアドレス設定
 #define UIP_IPADDR0   192    // IPアドレス 192.168.1.100
 #define UIP_IPADDR1   168
 #define UIP_IPADDR2   1
 #define UIP_IPADDR3   100
 
 // ネットマスク設定
 #define UIP_NETMASK0  255    // ネットマスク 255.255.255.0
 #define UIP_NETMASK1  255
 #define UIP_NETMASK2  255
 #define UIP_NETMASK3  0
 
 // デフォルトゲートウェイ
 #define UIP_DRIPADDR0 192    // ゲートウェイ 192.168.1.1
 #define UIP_DRIPADDR1 168
 #define UIP_DRIPADDR2 1
 #define UIP_DRIPADDR3 1
 
 // uIPバッファ設定
 #define UIP_CONF_BUFFER_SIZE     1500  // イーサネットMTUサイズ
 #define UIP_CONF_RECEIVE_WINDOW  1460  // TCP受信ウィンドウサイズ
 
 // TCP/UDP接続数 (MSPM0G3519の豊富なRAMを活用)
 #define UIP_CONF_MAX_CONNECTIONS 8     // 最大TCP接続数 (MSP430より多め)
 #define UIP_CONF_MAX_LISTENPORTS 8     // 最大TCP待受ポート数
 #define UIP_CONF_UDP_CONNS       4     // 最大UDP接続数
 
 // プロトコル有効化
 #define UIP_CONF_UDP             1     // UDPを有効化
 #define UIP_CONF_UDP_CHECKSUMS   1     // UDPチェックサムを有効化
 #define UIP_CONF_BROADCAST       1     // ブロードキャストを有効化
 
 // 統計情報 (開発時は有効化、製品版では無効化してRAM節約)
 #define UIP_CONF_STATISTICS      0     // 統計情報を無効化
 
 // アプリケーション名
 #define UIP_APPCALL   httpd_appcall    // HTTPサーバーアプリケーション
 
 #endif /* __UIP_CONF_H__ */


ENC28J60ドライバ (enc28j60.c)

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

このドライバでは、ENC28J60の内部構造が重要である。
ENC28J60は、8[KB]の内部RAMを持ち、これを受信バッファと送信バッファに分割して使用する。

以下の例では、受信バッファを0x0000から0x19FFまで (約6.5[KB])、送信バッファを0x1A00から0x1FFFまで (約1.5[KB]) に設定している。
この分割比率は、アプリケーションの特性に応じて調整可能である。

また、ENC28J60のレジスタは4つのバンク (Bank0〜Bank3) に分かれており、アクセスする前に適切なバンクを選択する必要がある。
バンク選択は、ECON1レジスタの下位2ビットで制御される。
ドライバでは、現在のバンクを追跡し、必要な場合のみバンク切り替えを行うことにより、不要なSPI通信を削減している。

MSPM0G3519のSPIペリフェラルは、DL_SPI関数を使用して制御する。
最大80[MHz]の高速動作により、SPIクロックも高速に設定できるが、ENC28J60の最大速度 (20[MHz]) を超えないように注意する必要がある。

 // enc28j60.c : ENC28J60ドライバ (MSPM0G3519 uIP用)
 
 #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
 
 // EIE : イーサネット割り込み有効レジスタ
 #define EIE       0x1B
 #define INTIE     0x80
 #define PKTIE     0x40
 
 // EIR : イーサネット割り込み要求レジスタ
 #define EIR       0x1C
 #define PKTIF     0x40
 #define TXIF      0x08
 
 // ECON1 : イーサネット制御レジスタ1
 #define ECON1     0x1F
 #define TXRTS     0x08
 #define RXEN      0x04
 
 // ECON2 : イーサネット制御レジスタ2
 #define ECON2     0x1E
 #define AUTOINC   0x80
 #define PKTDEC    0x40
 
 // ESTAT : イーサネットステータスレジスタ
 #define ESTAT     0x1D
 #define CLKRDY    0x01
 
 // MACON1 : MACコントロールレジスタ1
 #define MACON1    0x00 | 0x40
 #define MARXEN    0x01
 #define TXPAUS    0x08
 #define RXPAUS    0x04
 
 // MACON3 : MACコントロールレジスタ3
 #define MACON3    0x02 | 0x40
 #define PADCFG0   0x20
 #define TXCRCEN   0x10
 #define FRMLNEN   0x02
 #define FULDPX    0x01
 
 // MAMXFLL/MAMXFLH : 最大フレーム長レジスタ
 #define MAMXFLL   0x0A | 0x40
 #define MAMXFLH   0x0B | 0x40
 
 // MABBIPG : バックツーバックパケット間隙レジスタ
 #define MABBIPG   0x04 | 0x40
 
 // MAIPGL/MAIPGH : 非バックツーバックパケット間隙レジスタ
 #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 BANK0  0x00
 #define BANK1  0x01
 #define BANK2  0x02
 #define BANK3  0x03
 
 // チップセレクト制御 (実際のピン定義に応じて変更)
 #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) {
       // EIE、EIR、ESTAT、ECON1、ECON2は全バンク共通
       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();
 }
 
 // ビット操作 (OR)
 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);  // 1[ms]待機
 
    // クロックが安定するまで待機
    while (!(enc28j60_read(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, MARXEN);                       // MAC受信有効
    enc28j60_write(MACON3, PADCFG0 | TXCRCEN | FRMLNEN);  // パディング、CRC、フレーム長チェック有効
    enc28j60_write(MAMXFLL, 0xEE);                        // 最大フレーム長1518バイト
    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, INTIE | PKTIE);
 
    // 受信有効化
    enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON1, RXEN);
 }
 
 // パケット送信
 void enc28j60_packet_send(const uint8_t *buffer, uint16_t len)
 {
    // 送信バッファポインタ設定
    enc28j60_write(EWRPTL, 0x00);
    enc28j60_write(EWRPTH, 0x1A);
 
    // 制御バイト書き込み (0x00 = デフォルト設定)
    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, TXRTS);
 
    // 送信完了待ち (オプション)
    while (enc28j60_read(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, PKTDEC);
 
    return (len <= max_len) ? len : 0;
 }


メイン処理

メイン処理では、MSPM0G3519の初期化、uIPの初期化等を記述する。

MSPM0G3519のタイマペリフェラルを使用して、uIPに必要な周期的なタイマイベントを生成する。
uIPは、TCPの再送タイマやARPのタイムアウト管理のために、定期的なタイマ処理を必要とする。
一般的には、TCPタイマは500[ms]周期、ARPタイマは10秒周期で処理する。

MSPM0G3519の80MHz動作により、uIPのTCP/IP処理は非常に高速に実行できる。
また、128[KB]のRAMにより、uIPのバッファやスタック変数に十分なメモリを割り当てることができる。

 // main.c : メイン処理 (MSPM0G3519 uIP)
 
 #include "ti_msp_dl_config.h"
 #include "uip.h"
 #include "uip_arp.h"
 #include "enc28j60.h"
 #include <stdint.h>
 
 // MACアドレス定義
 #define MAC_ADDR0  0x02
 #define MAC_ADDR1  0x00
 #define MAC_ADDR2  0x00
 #define MAC_ADDR3  0x00
 #define MAC_ADDR4  0x00
 #define MAC_ADDR5  0x01
 
 // グローバルバッファ (uIP用)
 // MSPM0G3519は128KB RAMを持つため、余裕を持ったバッファサイズを確保可能
 uint8_t uip_buf[UIP_BUFSIZE + 2];
 
 // タイマ管理用
 volatile uint16_t periodic_timer = 0;
 volatile uint16_t arp_timer = 0;
 
 // タイマ割り込みハンドラ (10ms周期想定)
 void TIMER_0_INST_IRQHandler(void)
 {
    periodic_timer++;
    arp_timer++;
 }
 
 // uIP周期処理
 void uip_periodic_handler(void)
 {
    int i;
 
    // TCP接続の周期処理 (500[ms] = 50 × 10[ms])
    if (periodic_timer >= 50) {
       periodic_timer = 0;
 
       for (i = 0; i < UIP_CONNS; i++) {
          uip_periodic(i);
 
          if (uip_len > 0) {
             uip_arp_out();  // ARPテーブル参照してイーサネットヘッダ追加
             enc28j60_packet_send(uip_buf, uip_len);
          }
       }
 
       // UDP接続の周期処理
 #if UIP_UDP
       for (i = 0; i < UIP_UDP_CONNS; i++) {
          uip_udp_periodic(i);
 
          if (uip_len > 0) {
             uip_arp_out();
             enc28j60_packet_send(uip_buf, uip_len);
          }
       }
 #endif
    }
 
    // ARPタイマー処理 (10秒 = 1000 × 10ms)
    if (arp_timer >= 1000) {
       arp_timer = 0;
       uip_arp_timer();
    }
 }
 
 // メイン関数
 int main(void)
 {
    uip_ipaddr_t ipaddr;
    uint8_t mac_address[6] = {MAC_ADDR0, MAC_ADDR1, MAC_ADDR2, MAC_ADDR3, MAC_ADDR4, MAC_ADDR5};
 
    // MSPM0G3519ペリフェラル初期化
    SYSCFG_DL_init();
 
    // タイマ割り込み有効化
    NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN);
 
    // ENC28J60初期化
    enc28j60_init(mac_address);
 
    // uIP初期化
    uip_init();
 
    // IPアドレス設定
    uip_ipaddr(ipaddr, UIP_IPADDR0, UIP_IPADDR1, UIP_IPADDR2, UIP_IPADDR3);
    uip_sethostaddr(ipaddr);
 
    // ネットマスク設定
    uip_ipaddr(ipaddr, UIP_NETMASK0, UIP_NETMASK1, UIP_NETMASK2, UIP_NETMASK3);
    uip_setnetmask(ipaddr);
 
    // デフォルトゲートウェイ設定
    uip_ipaddr(ipaddr, UIP_DRIPADDR0, UIP_DRIPADDR1, UIP_DRIPADDR2, UIP_DRIPADDR3);
    uip_setdraddr(ipaddr);
 
    // ARPテーブル初期化
    uip_arp_init();
 
    // HTTPサーバ起動 (ポート80)
    uip_listen(HTONS(80));
 
    // グローバル割り込み有効化
    __enable_irq();
 
    // メインループ
    while (1) {
       // パケット受信チェック
       uip_len = enc28j60_packet_receive(uip_buf, UIP_BUFSIZE);
 
       if (uip_len > 0) {
          // イーサネットヘッダ解析
          struct uip_eth_hdr *eth_hdr = (struct uip_eth_hdr *)&uip_buf[0];
 
          if (eth_hdr->type == htons(UIP_ETHTYPE_IP)) {
             // IPパケット処理
             uip_arp_ipin();    // ARPテーブル更新
             uip_input();       // uIPへ入力
 
             if (uip_len > 0) {
                uip_arp_out(); // ARPテーブル参照
                enc28j60_packet_send(uip_buf, uip_len);
             }
          }
          else if (eth_hdr->type == htons(UIP_ETHTYPE_ARP)) {
             // ARPパケット処理
             uip_arp_arpin();
 
             if (uip_len > 0) {
                enc28j60_packet_send(uip_buf, uip_len);
             }
          }
       }
 
       // 周期処理 (TCP再送、ARPタイムアウト等)
       uip_periodic_handler();
    }
 }


初期化フェーズでは、MSPM0G3519のペリフェラル設定、SPI通信の設定、タイマの設定、ENC28J60およびuIPの初期化を実行する。
SYSCFG_DL_init関数は、TI MSPM0 SDKによって自動生成される設定関数であり、クロック、GPIO、SPI、タイマ等のペリフェラルを一括で初期化する。

メイン処理では、受信パケットの確認と周期処理を繰り返し実行する。
受信パケットがある場合、イーサネットヘッダを解析してIPパケットかARPパケットかを判断し、それぞれに適した処理を行う。

  • 受信パケットの確認
    • IPパケットの場合
      uIPスタックに渡して、TCP/UDP処理を行う。
      MSPM0G3519の80MHz動作により、TCP/IP処理は非常に高速に実行される。
    • ARPパケットの場合
      ARPテーブルの更新や応答を行う。

  • 周期処理
    TCPの再送タイマやARPエントリのタイムアウト管理を行う。(必須)
    TCP接続は500[ms]周期、ARPタイマは10秒周期で処理する。
    これらのタイミングは、uIPの仕様に基づいて設定されており、変更する場合は慎重に検討する必要がある。


HTTPサーバの構築

HTTPリクエストに応答する簡単なWebサーバを構築する。

uIPはイベント駆動型のアーキテクチャを採用しており、アプリケーション関数 (httpd_appcall関数) は、様々なイベントが発生したときに呼び出される。
主なイベントは以下の通りである。

  • 新規接続が確立された場合
    uip_connected
  • クライアントからデータを受信した場合
    uip_newdata
  • データの再送が必要な場合
    uip_rexmit
  • 接続がクローズされた場合
    uip_closed
    uip_aborted
    uip_timedout
    その他


以下の例では、GETリクエストを受信した時、予め用意されたHTMLページを送信する。
MSPM0G3519の豊富なフラッシュメモリ (512[KB]) により、複数のHTMLページや画像データを格納することも可能である。
実務では、URLに応じて異なるコンテンツを返したり、ADCで取得したセンサデータを含む動的なページを生成することも可能である。

uIPは限られたRAMで動作するように設計されているが、MSPM0G3519の128[KB] RAMにより、比較的大きなコンテンツも扱うことができる。
ただし、非常に大きなコンテンツを扱う場合は、分割送信やフラッシュROMからの直接読み出し等の工夫が推奨される。

 // httpd.c : HTTPサーバ実装
 
 #include "uip.h"
 #include <string.h>
 
 // HTTPレスポンス (HTMLページ)
 // MSPM0G3519のフラッシュROM (512[KB]) により、複数のページを格納可能
 const char http_response[] = 
     "HTTP/1.0 200 OK\r\n"
     "Content-Type: text/html\r\n"
     "Connection: close\r\n"
     "\r\n"
     "<!DOCTYPE html>"
     "<html>"
     "<head>"
     "  <title>MSPM0G3519 uIP Server</title>"
     "  <meta charset=\"UTF-8\">"
     "  <style>"
     "    body { font-family: Arial, sans-serif; margin: 40px; background-color: #f5f5f5; }"
     "    .container { max-width: 800px; margin: 0 auto; background-color: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }"
     "    h1 { color: #c00; border-bottom: 2px solid #c00; padding-bottom: 10px; }"
     "    .specs { background-color: #f9f9f9; padding: 15px; border-left: 4px solid #c00; margin: 20px 0; }"
     "    .specs h2 { margin-top: 0; color: #333; }"
     "    ul { line-height: 1.8; }"
     "    .footer { margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd; text-align: center; color: #666; font-size: 0.9em; }"
     "  </style>"
     "</head>"
     "<body>"
     "  <div class=\"container\">"
     "    <h1>Welcome to MSPM0G3519 uIP Server!</h1>"
     "    <div class=\"specs\">"
     "      <h2>System Specifications</h2>"
     "      <ul>"
     "        <li><strong>MCU:</strong> MSPM0G3519 (ARM Cortex-M0+)</li>"
     "        <li><strong>Clock Speed:</strong> 80 MHz</li>"
     "        <li><strong>Flash Memory:</strong> 512 KB (Dual-bank with ECC)</li>"
     "        <li><strong>SRAM:</strong> 128 KB</li>"
     "        <li><strong>Ethernet Controller:</strong> ENC28J60 (10 Mbps)</li>"
     "        <li><strong>TCP/IP Stack:</strong> uIP (Lightweight Implementation)</li>"
     "      </ul>"
     "    </div>"
     "    <p>This embedded web server demonstrates the power of the MSPM0G3519 microcontroller combined with the uIP TCP/IP stack.</p>"
     "    <p>The MSPM0G3519 offers excellent performance with its 80 MHz ARM Cortex-M0+ core and generous memory resources, making it ideal for IoT and embedded networking applications.</p>"
     "    <div class=\"footer\">"
     "      <p>&copy; 2025 MSPM0G3519 Embedded Web Server</p>"
     "    </div>"
     "  </div>"
     "</body>"
     "</html>";
 
 // HTTPアプリケーション処理
 void httpd_appcall(void)
 {
     // 新規接続時
     if (uip_connected()) {
        // 特に処理なし (GETリクエスト待ち)
     }
 
     // データ受信時
     if (uip_newdata()) {
        // 簡易的にGETリクエストをチェック
        // より高度な実装では、HTTPヘッダの完全なパースを行う
        if (strncmp((char *)uip_appdata, "GET ", 4) == 0) {
           // HTTPレスポンス送信
           uip_send((void *)http_response, sizeof(http_response) - 1);
        }
     }
 
     // 再送要求時
     if (uip_rexmit()) {
        uip_send((void *)http_response, sizeof(http_response) - 1);
     }
 
     // 接続クローズ時
     if (uip_closed() || uip_aborted() || uip_timedout()) {
        // 特に処理なし
        // 必要に応じてクリーンアップ処理を追加
     }
 }


MSPM0G3519の豊富なリソースを活かすことにより、高度な機能を追加することも可能である。
例えば、以下のような拡張が考えられる。

  • URLルーティング
    リクエストされたURLに応じて、異なるHTMLページを返す。
    複数のページを用意し、ナビゲーション機能を実装する。

  • 動的コンテンツ生成
    ADCで取得したセンサデータをリアルタイムでWebページに表示する。
    GPIO状態を読み取り、LEDの点灯状態等をWebページに反映する。

  • フォーム処理
    POSTリクエストを処理し、Webブラウザから設定値を変更する。
    LEDの制御、PWMのデューティ比設定等をWebインターフェースから行う。

  • JSON API
    REST APIを実装し、JSON形式でデータを送受信する。
    IoTデバイスとしての機能を強化する。



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機器を制御



参考資料