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

提供: MochiuWiki : SUSE, EC, PCB

Wiki がページ「MSP430F5529 - イーサネット (ENC28J60)」を「MSPM0G3519 - イーサネット (ENC28J60)」に、リダイレクトを残さずに移動しました
編集の要約なし
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>
<br>
この構成により、MSP430F5529を使用したWebサーバ、HTTPクライアント、Telnetサーバ、UDPアプリケーション等の実装が可能となる。<br>
この構成により、MSPM0G3519を使用したWebサーバ、HTTPクライアント、Telnetサーバ、UDPアプリケーション、DHCPクライアント、DNSクライアント等の構築が可能となる。<br>
<br><br>
<br><br>


46行目: 53行目:
* マルチスレッド対応
* マルチスレッド対応
*: RTOSと組み合わせた使用が可能
*: RTOSと組み合わせた使用が可能
*: ただし、MSP430F5529では通常はシングルスレッド構成で使用する
*: ただし、MSPM0G3519では通常はシングルスレッド構成 (NO_SYS=1) で使用する
* 移植性
* 移植性
*: C言語で記述されており、様々なマイコンに移植可能
*: C言語で記述されており、様々なマイコンに移植可能
*: ハードウェア依存部分が明確に分離されている
*: ハードウェア依存部分が明確に分離されている
<br>
<br>
<u>※注意</u><br>
<u>※MSPM0G3519でのlwIP使用について</u><br>
<u>lwIPは、uIPと比較して以下に示すメリット / デメリットがある。</u><br>
<u>MSPM0G3519は、512[KB]のフラッシュと128[KB]のSRAMを持つため、lwIPを標準構成で十分に使用できる。</u><br>
<u>これにより、以下に示すメリットが得られる。</u><br>
* メリット
* メリット
*: より多くの同時接続に対応 (通常10〜50接続以上)
*: より多くの同時接続に対応 (通常10〜50接続以上)
58行目: 66行目:
*: DHCP、DNS等の上位機能を標準サポート
*: DHCP、DNS等の上位機能を標準サポート
*: より安定したTCP実装
*: より安定したTCP実装
*: パケットの分割送信と再送処理が効率的
*: ゼロコピー機能によるメモリ効率の向上
*: <br>
*: <br>
* デメリット
* デメリット
*: 多くのRAMとROMを必要とする。
*: uIPと比較して、初期設定がやや複雑
*: MSP430F5529 (8[KB] RAM) では、軽量化した構成でのみ使用可能
*: コードサイズが大きい (ただし、MSPM0G3519のフラッシュ容量で十分に収まる)
*: 設定が複雑で、初期設定に時間が掛かる。
<br>
<br>
MSP430F5529では、RAMが8[KB]と限られているため、lwIPを使用する場合は、以下に示す工夫が必要である。<br>
MSPM0G3519では、lwIPの使用を推奨する。<br>
* メモリプールサイズの最小化
RAMとフラッシュROMに十分な余裕があるため、lwIPの高機能性を最大限活用できる。<br>
* 同時接続数の制限 (2〜4接続程度)
軽量なアプリケーションにはuIPも使用可能であるが、将来的な機能拡張を考慮すると、lwIPの採用がより適切である。<br>
* 不要な機能の無効化 (DHCP、DNS等)
* バッファサイズの最適化
<br>
これらの制約を考慮すると、MSP430F5529では、軽量なアプリケーションにはuIPを、より高機能な通信が必要な場合にはlwIPを使用することを推奨する。<br>
<br><br>
<br><br>


== uIPライブラリの特徴 ==
== uIPライブラリの特徴 ==
<u>uIPは既にメンテナンスされていないため、使用は非推奨である。</u><br>
<br>
uIPは、組み込みシステム向けに最適化された軽量TCP/IPプロトコルスタックである。<br>
uIPは、組み込みシステム向けに最適化された軽量TCP/IPプロトコルスタックである。<br>
<br>
<br>
94行目: 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機能を統合
114行目: 123行目:
* 入手性が良好で、価格も比較的安価
* 入手性が良好で、価格も比較的安価
* 豊富なサンプルコードとドキュメント
* 豊富なサンプルコードとドキュメント
* MSP430F5529のUSART (SPI)で容易に接続可能
* MSPM0G3519のSPIペリフェラルで容易に接続可能
<br>
<br>
<u>デメリット</u><br>
<u>デメリット</u><br>
* 10Mbpsのみの対応 (100Mbps非対応)
* 10[Mbps]のみの対応 (100[Mbps]非対応)
* SPIのオーバーヘッドによりスループットが制限される。
* SPIのオーバーヘッドによりスループットが制限される。
<br>
<br>
124行目: 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>
187行目: 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>
<br><br>
203行目: 250行目:
*: ユーザアプリケーション (Webサーバ、HTTPクライアント等)
*: ユーザアプリケーション (Webサーバ、HTTPクライアント等)
* API層
* API層
*: Sequential API (netconn API)
*: Sequential API (netconn API) : ※RTOS使用時
*: Raw API (コールバックベース、低レベルAPI)
*: Raw API (コールバックベース、低レベルAPI) : ※NO_SYS=1の時
* lwIPコア層
* lwIPコア層
*: TCP、UDP、IP、ICMP、ARPプロトコル実装
*: TCP、UDP、IP、ICMP、ARPプロトコル実装
217行目: 264行目:
*: tcp.c、udp.c、ip.c、icmp.c、arp.c等
*: tcp.c、udp.c、ip.c、icmp.c、arp.c等
* lwip/src/api/
* lwip/src/api/
*: Sequential API実装
*: Sequential API実装 (RTOS使用時)
*: netconn.c、api_lib.c、api_msg.c等
*: netconn.c、api_lib.c、api_msg.c等
* lwip/src/netif/
* lwip/src/netif/
224行目: 271行目:
* lwip/src/include/lwip/
* lwip/src/include/lwip/
*: lwIPヘッダファイル群
*: lwIPヘッダファイル群
* lwip/src/apps/
*: アプリケーション層実装
*: httpd.c、mqtt.c、snmp.c等
* arch/
* arch/
*: アーキテクチャ依存部分 (移植先マイコン依存)
*: アーキテクチャ依存部分 (移植先マイコン依存)
233行目: 283行目:
<br>
<br>
この階層構造により、lwIPの中核部分とハードウェア依存部分が明確に分離されている。<br>
この階層構造により、lwIPの中核部分とハードウェア依存部分が明確に分離されている。<br>
MSP430F5529では、主にアーキテクチャ依存部分 (arch/)、ネットワークインターフェース層 (enc28j60_lwip.c)、設定ファイル (lwipopts.h) を使用または修正する必要がある。<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>
<br>
lwIPは、一般的にRaw APIを使用して実装する。<br>
MSPM0G3519のリソースは十分であるため、将来的にRTOSの導入を検討する場合は、Sequential APIへの移行も容易である。<br>
Raw APIは、コールバック関数ベースの低レベルAPIで、メモリ使用量が少なく、MSP430F5529のような限られたリソースのマイコンに適している。<br>
Sequential APIは、よりシンプルな記述が可能であるが、RTOSが必要となるため、MSP430F5529では通常使用しない。<br>
<br>
<br>
* lwIPの公式Webサイト
* lwIPの公式Webサイト
243行目: 295行目:
* lwIPのGithub
* lwIPのGithub
*: https://github.com/lwip-tcpip/lwip
*: https://github.com/lwip-tcpip/lwip
* lwIP Documentation
*: http://www.nongnu.org/lwip/2_1_x/index.html
<br><br>
<br><br>


280行目: 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>
<br>
289行目: 343行目:
<br><br>
<br><br>


== uIP : サンプルコード ==
== lwIP : サンプルコード ==
以下の例では、ENC28J60およびuIPを使用して、簡単なWebサーバを構築してHTTPリクエストに応答している。<br>
以下の例では、ENC28J60およびlwIPを使用して、簡単なWebサーバを構築してHTTPリクエストに応答している。<br>
MSPM0G3519の豊富なリソースを活用し、lwIPのRaw APIを使用している。<br>
<br>
<br>
==== uIP設定ファイル (uip-conf.h) ====
==== lwIP設定ファイル (lwipopts.h) ====
まず、uIPの動作パラメータを設定する。<br>
まず、lwIPの動作パラメータを設定する。<br>
<br>
<br>
この設定ファイルでは、デバイスのネットワーク設定 (IPアドレス、ネットマスク、ゲートウェイ) を定義している。<br>
この設定ファイルでは、NO_SYS=1によりRTOSを使用しないスタンドアロン構成を指定している。<br>
これらの値は、使用するネットワーク環境に合わせて変更する必要がある。<br>
MSPM0G3519の128[KB] RAMを活用し、複数の同時接続とDHCPクライアント機能を有効化している。<br>
<br>
<br>
* バッファサイズ
* メモリプール設定
*: 標準的なイーサネットフレームのMTU (Maximum Transmission Unit) である1500バイトに設定している。
*: MSPM0G3519の豊富なRAM容量に応じて、メモリプールサイズを最適化している。
*: pbufプールやヒープサイズは、アプリケーションの要件に応じて柔軟に変更可能である。
* TCP接続数
* TCP接続数
*: 4に設定しているが、これはRAM容量と必要な同時接続数に応じて調整できる。
*: 16接続に設定しているが、これはアプリケーションの要件に応じて調整できる。
*: 接続数を増やすと、より多くのクライアントを同時に処理できるが、RAM使用量も増加する点に注意が必要である。
*: MSPM0G3519では、20〜30接続以上も十分に対応可能である。
* DHCP機能
*: DHCPクライアント機能を有効化することで、IPアドレスの自動取得が可能となる。
<br>
<br>
  <syntaxhighlight lang="c">
  <syntaxhighlight lang="c">
  #ifndef __UIP_CONF_H__
  #ifndef __LWIPOPTS_H__
  #define __UIP_CONF_H__
  #define __LWIPOPTS_H__
   
   
  #include <stdint.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
   
   
  // uIP設定パラメータ
  // DHCP設定
  typedef uint8_t u8_t;        // 8ビット符号なし整数型
  #define LWIP_DHCP              1
  typedef uint16_t u16_t;      // 16ビット符号なし整数型
  #define LWIP_NETIF_HOSTNAME    1
   
   
  // IPアドレス設定
  // DNS設定
  #define UIP_IPADDR0  192    // IPアドレス 192.168.1.100
  #define LWIP_DNS                1        // DNS有効化
  #define UIP_IPADDR1  168
  #define DNS_TABLE_SIZE          4
#define UIP_IPADDR2  1
  #define DNS_MAX_SERVERS        2
  #define UIP_IPADDR3  100
   
   
  // ネットマスク設定
  // ARP設定
  #define UIP_NETMASK0  255    // ネットマスク 255.255.255.0
  #define LWIP_ARP                1
#define UIP_NETMASK1  255
  #define ARP_TABLE_SIZE          10
  #define UIP_NETMASK2  255
  #define ARP_QUEUEING            1
  #define UIP_NETMASK3  0
   
   
  // デフォルトゲートウェイ
  // IP設定
  #define UIP_DRIPADDR0 192    // ゲートウェイ 192.168.1.1
  #define LWIP_IPV4              1
  #define UIP_DRIPADDR1 168
  #define LWIP_IPV6              0
  #define UIP_DRIPADDR2 1
  #define IP_REASSEMBLY          1         // IPフラグメント再構築を有効化
  #define UIP_DRIPADDR3 1
  #define IP_FRAG                1         // IPフラグメント分割を有効化
   
   
  // uIPバッファ設定
  // チェックサム設定
  #define UIP_CONF_BUFFER_SIZE    1500 // イーサネットMTUサイズ
  #define CHECKSUM_GEN_IP        1
  #define UIP_CONF_RECEIVE_WINDOW 1460 // TCP受信ウィンドウサイズ
  #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
   
   
  // TCP/UDP接続数
  // 統計情報 (開発時は有効化、製品版では無効化してRAM節約)
  #define UIP_CONF_MAX_CONNECTIONS 4    // 最大TCP接続数
  #define LWIP_STATS              1
  #define UIP_CONF_MAX_LISTENPORTS 4    // 最大TCP待受ポート数
  #define LWIP_STATS_DISPLAY      1
#define UIP_CONF_UDP_CONNS      2    // 最大UDP接続数
   
   
  // プロトコル有効化
  // デバッグ設定 (開発時は有効化可能)
  #define UIP_CONF_UDP            1    // UDPを有効化
  #define LWIP_DEBUG              0
#define UIP_CONF_UDP_CHECKSUMS  1    // UDPチェックサムを有効化
#define UIP_CONF_BROADCAST      1    // ブロードキャストを有効化
   
   
  // 統計情報
  // その他
  #define UIP_CONF_STATISTICS      0     // 統計情報を無効化 (RAM節約)
  #define LWIP_NETCONN            0 // Raw API使用のため無効化
#define LWIP_SOCKET            0  // ソケットAPI不使用
#define LWIP_NETIF_LINK_CALLBACK 1
#define LWIP_NETIF_STATUS_CALLBACK 1
   
   
  // アプリケーション名
  // プラットフォーム依存の型定義
  #define UIP_APPCALL  httpd_appcall    // HTTPサーバーアプリケーション
  #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 /* __UIP_CONF_H__ */
  #endif /* __LWIPOPTS_H__ */
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
358行目: 451行目:
ENC28J60との通信を行うドライバ制御 (SPIを使用したレジスタアクセスやパケット送受信等) を記述する。<br>
ENC28J60との通信を行うドライバ制御 (SPIを使用したレジスタアクセスやパケット送受信等) を記述する。<br>
<br>
<br>
このドライバでは、ENC28J60の内部構造が重要である。<br>
このドライバでは、MSPM0G3519のSPIペリフェラルを使用してENC28J60を制御する。<br>
ENC28J60は、8[KB]の内部RAMを持ち、これを受信バッファと送信バッファに分割して使用する。<br>
MSPM0G3519のSPI通信速度は最大32[Mbit/s]であり、ENC28J60の最大速度である20[MHz]に近い動作が可能である。<br>
<br>
以下の例では、受信バッファを0x0000から0x19FFまで (約6.5[KB])、送信バッファを0x1A00から0x1FFFまで (約1.5[KB]) に設定している。<br>
この分割比率は、アプリケーションの特性に応じて調整可能である。<br>
<br>
<br>
また、ENC28J60のレジスタは4つのバンク (Bank0〜Bank3) に分かれており、アクセスする前に適切なバンクを選択する必要がある。<br>
以下の例では、基本的なレジスタアクセスとパケット送受信機能を提供している。<br>
バンク選択は、ECON1レジスタの下位2ビットで制御される。<br>
必要に応じて、DMAを使用した高速データ転送や、割り込み駆動の受信処理を追加することも可能である。<br>
ドライバでは、現在のバンクを追跡し、必要な場合のみバンク切り替えを行うことにより、不要なSPI通信を削減している。<br>
<br>
<br>
  <syntaxhighlight lang="c">
  <syntaxhighlight lang="c">
  #include <msp430f5529.h>
// enc28j60.c : ENC28J60ドライバ
  #include "ti_msp_dl_config.h"  // MSPM0G3519 SDK設定
  #include "enc28j60.h"
  #include "enc28j60.h"
  #include <stdint.h>
  #include <stdint.h>
388行目: 479行目:
  #define ERXRDPTL  0x0C
  #define ERXRDPTL  0x0C
  #define ERXRDPTH  0x0D
  #define ERXRDPTH  0x0D
// EIE : イーサネット割り込み有効レジスタ
  #define EIE      0x1B
  #define EIE      0x1B
#define INTIE    0x80
#define PKTIE    0x40
// EIR : イーサネット割り込み要求レジスタ
  #define EIR      0x1C
  #define EIR      0x1C
  #define PKTIF     0x40
  #define ESTAT     0x1D
  #define TXIF      0x08
  #define ECON2    0x1E
// ECON1 : イーサネット制御レジスタ1
  #define ECON1    0x1F
  #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
#define MARXEN    0x01
#define TXPAUS    0x08
#define RXPAUS    0x04
// MACON3 : MACコントロールレジスタ3
#define MACON3    0x02
#define PADCFG0  0x20
#define TXCRCEN  0x10
#define FRMLNEN  0x02
#define FULDPX    0x01
// MAMXFLL/MAMXFLH : 最大フレーム長レジスタ
#define MAMXFLL  0x0A
#define MAMXFLH  0x0B
// MABBIPG : バックツーバックパケット間隙レジスタ
#define MABBIPG  0x04
   
   
  // MAIPGL/MAIPGH : 非バックツーバックパケット間隙レジスタ
  // MACレジスタ
  #define MAIPGL    0x06
#define MACON1    0x00 | 0x40
  #define MAIPGH    0x07
#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コマンド定義
  // SPIコマンド定義
446行目: 503行目:
  #define ENC28J60_SOFT_RESET    0xFF
  #define ENC28J60_SOFT_RESET    0xFF
   
   
  // バンク選択
  // ビット定義
  #define BANK0 0x00
  #define ECON1_TXRTS 0x08
  #define BANK1 0x01
  #define ECON1_RXEN  0x04
  #define BANK2 0x02
#define ECON2_AUTOINC 0x80
  #define BANK3 0x03
  #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()  (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);
  }
  }
   
   
473行目: 548行目:
   
   
     if (bank != current_bank) {
     if (bank != current_bank) {
      // EIE、EIR、ESTAT、ECON1、ECON2は全バンク共通
       enc28j60_write_op(ENC28J60_BIT_FIELD_CLR, ECON1, 0x03);
       enc28j60_write_op(ENC28J60_BIT_FIELD_CLR, ECON1, 0x03);
       enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON1, bank);
       enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON1, bank);
512行目: 586行目:
  }
  }
   
   
  // ビット操作 (OR)
  // ビット操作
  void enc28j60_write_op(uint8_t op, uint8_t address, uint8_t data)
  void enc28j60_write_op(uint8_t op, uint8_t address, uint8_t data)
  {
  {
555行目: 629行目:
     ENC28J60_CS_HIGH();
     ENC28J60_CS_HIGH();
   
   
     __delay_cycles(50000);  // 1ms待機 (50[MHz]クロック想定)
     delay_us(1000);  // 1ms待機
   
   
     // クロックが安定するまで待機
     // クロックが安定するまで待機
     while (!(enc28j60_read(ESTAT) & CLKRDY));
     while (!(enc28j60_read(ESTAT) & ESTAT_CLKRDY));
   
   
     // 受信バッファ設定 (0x0000 - 0x19FF)
     // 受信バッファ設定 (0x0000 - 0x19FF)
574行目: 648行目:
     enc28j60_write(ETXNDH, 0x1F);
     enc28j60_write(ETXNDH, 0x1F);
   
   
     // MAC初期化 (バンク2)
     // MAC初期化
     enc28j60_write(MACON1, MARXEN); // MAC受信有効
     enc28j60_write(MACON1, MACON1_MARXEN);
     enc28j60_write(MACON3, PADCFG0 | TXCRCEN | FRMLNEN); // パディング、CRC、フレーム長チェック有効
     enc28j60_write(MACON3, MACON3_PADCFG0 | MACON3_TXCRCEN | MACON3_FRMLNEN);
     enc28j60_write(MAMXFLL, 0xEE);   // 最大フレーム長1518バイト
     enc28j60_write(MAMXFLL, 0xEE);
     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
592行目: 666行目:
   
   
     // 割り込み有効化
     // 割り込み有効化
     enc28j60_write(EIE, INTIE | PKTIE);
     enc28j60_write(EIE, 0xC0);  // INTIE | PKTIE
   
   
     // 受信有効化
     // 受信有効化
     enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON1, RXEN);
     enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);
  }
  }
   
   
605行目: 679行目:
     enc28j60_write(EWRPTH, 0x1A);
     enc28j60_write(EWRPTH, 0x1A);
   
   
     // 制御バイト書き込み (0x00 = デフォルト設定)
     // 制御バイト書き込み
     enc28j60_write_buffer((uint8_t[]){0x00}, 1);
     enc28j60_write_buffer((uint8_t[]){0x00}, 1);
   
   
616行目: 690行目:
   
   
     // 送信開始
     // 送信開始
     enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON1, TXRTS);
     enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS);
    // 送信完了待ち (オプション: タイムアウト処理を追加することを推奨)
    while (enc28j60_read(ECON1) & ECON1_TXRTS);
  }
  }
   
   
625行目: 702行目:
     uint8_t header[6];
     uint8_t header[6];
   
   
     // 受信パケットがあるかどうかを確認
     // 受信パケット数確認
     if (enc28j60_read(0x19) == 0) {  // EPKTCNT
     if (enc28j60_read(0x19) == 0) {  // EPKTCNT
       return 0;
       return 0;
     }
     }
   
   
     // ヘッダ読み出し (次のパケットポインタ、ステータス、長さ)
     // ヘッダ読み出し
     enc28j60_read_buffer(header, 6);
     enc28j60_read_buffer(header, 6);
   
   
     // パケット長取得 (ヘッダ内の長さフィールド)
     // パケット長取得
     len = (header[3] << 8) | header[2];
     len = (header[3] << 8) | header[2];
     len -= 4;  // CRCを除く
     len -= 4;  // CRCを除く
648行目: 725行目:
   
   
     // パケットデクリメント
     // パケットデクリメント
     enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON2, PKTDEC);
     enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC);
   
   
     return (len <= max_len) ? len : 0;
     return (len <= max_len) ? len : 0;
654行目: 731行目:
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
==== メイン処理 ====
==== lwIPネットワークインターフェース (netif_enc28j60.c) ====
メイン処理では、MSP430F5529の初期化、uIPの初期化等を記述する。<br>
lwIPとENC28J60ドライバを接続するネットワークインターフェース層を実装する。<br>
この層は、lwIPのpbuf構造体とENC28J60のパケットバッファ間でデータを変換する役割を持つ。<br>
<br>
<br>
  <syntaxhighlight lang="c">
  <syntaxhighlight lang="c">
  #include <msp430f5529.h>
// netif_enc28j60.c : lwIPネットワークインターフェース
  #include "uip.h"
  #include "uip_arp.h"
#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 "enc28j60.h"
  #include <stdint.h>
  #include <string.h>
   
   
// MACアドレス定義
  #define IFNAME0 'e'
#define MAC_ADDR0  0x02
  #define IFNAME1 'n'
#define MAC_ADDR1  0x00
#define MAC_ADDR2  0x00
#define MAC_ADDR3  0x00
  #define MAC_ADDR4  0x00
  #define MAC_ADDR5  0x01
   
   
  // グローバルバッファ
  // パケット送信
  uint8_t uip_buf[UIP_BUFSIZE + 2]; // uIP用バッファ
  static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
    struct pbuf *q;
    uint8_t buffer[1518];
    uint16_t len = 0;
   
   
// タイマ管理用
    // pbufチェーンからバッファにコピー
volatile uint16_t periodic_timer = 0;
    for (q = p; q != NULL; q = q->next) {
volatile uint16_t arp_timer = 0;
      if (len + q->len > sizeof(buffer)) {
          return ERR_BUF;
      }
      memcpy(&buffer[len], q->payload, q->len);
      len += q->len;
    }
   
   
// SPI初期化 (USCI_B0をSPIモードで使用)
     // ENC28J60で送信
void spi_init(void)
     enc28j60_packet_send(buffer, len);
{
     // ポート設定
     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マスターモードで初期化
     return ERR_OK;
    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)
  static struct pbuf *low_level_input(struct netif *netif)
  {
  {
     UCSCTL3 = SELREF_2;           // FLLリファレンス = REFO
     struct pbuf *p, *q;
     UCSCTL4 |= SELA_2;             // ACLK = REFO
    uint8_t buffer[1518];
    uint16_t len;
    // ENC28J60からパケット受信
    len = enc28j60_packet_receive(buffer, sizeof(buffer));
     if (len == 0) {
      return NULL;
    }
   
   
     __bis_SR_register(SCG0);      // FLLを無効化
     // pbuf割り当て
    UCSCTL0 = 0x0000;              // DCO、MOD初期化
     p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
    UCSCTL1 = DCORSEL_6;          // DCO周波数範囲選択
     UCSCTL2 = FLLD_0 + 762;        // 25[MHz]設定 (762 + 1)× 32768[Hz]
    __bic_SR_register(SCG0);       // FLLを有効化
   
   
     __delay_cycles(250000);        // 安定化待機
     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;
      }
    }
   
   
// Timer A0初期化 (10[ms]周期)
     return p;
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
  err_t netif_enc28j60_init(struct netif *netif)
  __interrupt void Timer_A0_ISR(void)
  {
  {
     periodic_timer++;
     netif->name[0] = IFNAME0;
     arp_timer++;
     netif->name[1] = IFNAME1;
}
    netif->output = etharp_output;
    netif->linkoutput = low_level_output;
   
   
// uIP周期処理
    netif->mtu = 1500;
void uip_periodic_handler(void)
     netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;
{
     int i;
   
   
     // TCP接続の周期処理 (500[ms] = 50 × 10[ms])
     // MACアドレス設定
     if (periodic_timer >= 50) {
    netif->hwaddr_len = 6;
      periodic_timer = 0;
    netif->hwaddr[0] = 0x02;
    netif->hwaddr[1] = 0x00;
     netif->hwaddr[2] = 0x00;
    netif->hwaddr[3] = 0x00;
    netif->hwaddr[4] = 0x00;
    netif->hwaddr[5] = 0x01;
   
   
      for (i = 0; i < UIP_CONNS; i++) {
    // ENC28J60初期化
          uip_periodic(i);
    enc28j60_init(netif->hwaddr);
   
   
          if (uip_len > 0) {
    return ERR_OK;
            uip_arp_out(); // ARPテーブル参照してイーサネットヘッダ追加
  }
            enc28j60_packet_send(uip_buf, uip_len);
          }
      }
   
   
      // UDP接続の周期処理
// パケット受信処理 (メインループから定期的に呼び出す)
  #if UIP_UDP
  void netif_enc28j60_input(struct netif *netif)
      for (i = 0; i < UIP_UDP_CONNS; i++) {
{
          uip_udp_periodic(i);
    struct pbuf *p;
   
   
          if (uip_len > 0) {
    // 受信可能なパケットを全て処理
            uip_arp_out();
    while ((p = low_level_input(netif)) != NULL) {
            enc28j60_packet_send(uip_buf, uip_len);
      if (netif->input(p, netif) != ERR_OK) {
          }
          pbuf_free(p);
       }
       }
#endif
     }
     }
}
</syntaxhighlight>
<br>
==== メイン処理 ====
メイン処理では、MSPM0G3519の初期化、lwIPの初期化、HTTPサーバの起動を記述する。<br>
<br>
<syntaxhighlight lang="c">
// main.c : メイン処理
   
   
    // ARPタイマー処理 (10秒 = 1000 × 10ms)
#include "ti_msp_dl_config.h"
    if (arp_timer >= 1000) {
#include "lwip/init.h"
      arp_timer = 0;
#include "lwip/netif.h"
      uip_arp_timer();
#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)
  int main(void)
  {
  {
     uip_ipaddr_t ipaddr;
     struct netif netif;
     uint8_t mac_address[6] = {MAC_ADDR0, MAC_ADDR1, MAC_ADDR2, MAC_ADDR3, MAC_ADDR4, MAC_ADDR5};
     ip4_addr_t ipaddr, netmask, gw;
   
   
     WDTCTL = WDTPW | WDTHOLD;  // ウォッチドッグ停止
     // MSPM0G3519ペリフェラル初期化
    SYSCFG_DL_init();
   
   
     // システム初期化
     // タイマ割り込み有効化
     clock_init();             // クロック設定 (25[MHz])
     NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN);
    spi_init();                // SPI初期化
    timer_init();              // タイマー初期化
   
   
     // ENC28J60初期化
     // lwIP初期化
     enc28j60_init(mac_address);
     lwip_init();
   
   
     // uIP初期化
     // 静的IPアドレス設定 (DHCPを使用しない場合)
     uip_init();
    IP4_ADDR(&ipaddr, 192, 168, 1, 100);
    IP4_ADDR(&netmask, 255, 255, 255, 0);
     IP4_ADDR(&gw, 192, 168, 1, 1);
   
   
     // IPアドレス設定
     // ネットワークインターフェース追加
     uip_ipaddr(ipaddr, UIP_IPADDR0, UIP_IPADDR1, UIP_IPADDR2, UIP_IPADDR3);
     netif_add(&netif, &ipaddr, &netmask, &gw, NULL, netif_enc28j60_init, ethernet_input);
     uip_sethostaddr(ipaddr);
    netif_set_default(&netif);
     netif_set_up(&netif);
   
   
     // ネットマスク設定
     // DHCPクライアント起動 (オプション)
    uip_ipaddr(ipaddr, UIP_NETMASK0, UIP_NETMASK1, UIP_NETMASK2, UIP_NETMASK3);
     // dhcp_start(&netif);
     uip_setnetmask(ipaddr);
   
   
     // デフォルトゲートウェイ設定
     // HTTPサーバ初期化
     uip_ipaddr(ipaddr, UIP_DRIPADDR0, UIP_DRIPADDR1, UIP_DRIPADDR2, UIP_DRIPADDR3);
     httpd_init();
    uip_setdraddr(ipaddr);
   
   
     // ARPテーブル初期化
     // メインループ
     uip_arp_init();
     while (1) {
      // パケット受信処理
      netif_enc28j60_input(&netif);
   
   
    // HTTPサーバ起動 (ポート80)
      // lwIPタイムアウト処理 (周期的なタイマイベント処理)
     uip_listen(HTONS(80));
      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>
   
   
     __enable_interrupt();     // グローバル割り込み有効
// 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);
   
   
     while (1) {
     if (led_state) {
       // パケット受信チェック
       return "/led_on.ssi";
       uip_len = enc28j60_packet_receive(uip_buf, UIP_BUFSIZE);
    }
    else {
       return "/led_off.ssi";
    }
}
   
   
      if (uip_len > 0) {
// LED制御CGI
          // イーサネットヘッダ解析
const char *led_control_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[])
          struct uip_eth_hdr *eth_hdr = (struct uip_eth_hdr *)&uip_buf[0];
  {
   
    int i;
          if (eth_hdr->type == htons(UIP_ETHTYPE_IP)) {
            // IPパケット処理
            uip_arp_ipin();   // ARPテーブル更新
            uip_input();      // uIPへ入力
   
   
            if (uip_len > 0) {
    for (i = 0; i < iNumParams; i++) {
                uip_arp_out(); // ARPテーブル参照
      if (strcmp(pcParam[i], "led") == 0) {
                enc28j60_packet_send(uip_buf, uip_len);
          if (strcmp(pcValue[i], "on") == 0) {
            }
            DL_GPIO_setPins(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_1_PIN);
           }
           }
           else if (eth_hdr->type == htons(UIP_ETHTYPE_ARP)) {
           else if (strcmp(pcValue[i], "off") == 0) {
             // ARPパケット処理
             DL_GPIO_clearPins(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_1_PIN);
            uip_arp_arpin();
            if (uip_len > 0) {
                enc28j60_packet_send(uip_buf, uip_len);
            }
           }
           }
       }
       }
    }
    return "/index.html";
}
// CGIハンドラテーブル
static const tCGI cgi_handlers[] = {
    { "/led_status.cgi", led_cgi_handler },
    { "/led_control.cgi", led_control_cgi_handler }
};
   
   
      // 周期処理
// CGI初期化
      uip_periodic_handler();
void httpd_cgi_init(void)
     }
{
     http_set_cgi_handlers(cgi_handlers, sizeof(cgi_handlers) / sizeof(tCGI));
  }
  }
  </syntaxhighlight>
  </syntaxhighlight>
<br>
<br>
初期化フェーズでは、マイコンのクロック設定、SPI通信の設定、タイマの設定、ENC28J60およびuIPの初期化を実行する。<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>
 
== その他の注意事項 ==
lwIPを使用したTCP/IP通信を実装する場合の注意事項を以下に示す。<br>
<br>
==== タイマ管理 ====
lwIPは、sys_check_timeouts関数を定期的に呼び出す必要がある。<br>
メインループ内で適切な間隔 (推奨 : 数[ms]〜数十[ms]) で呼び出すことを推奨する。<br>
<br>
タイマの精度は、TCP通信の品質に影響するため、正確なタイマ実装が重要である。<br>
<br>
==== メモリ管理 ====
lwIPは、メモリプール方式を採用しているため、lwipopts.hで適切なプールサイズを設定する必要がある。<br>
メモリ不足が発生すると、パケットのドロップや接続の失敗が生じる可能性がある。<br>
<br>
<br>
メイン処理では、受信パケットの確認と周期処理を繰り返し実行する。<br>
MSPM0G3519は128[KB]のRAMを持つため、メモリプールサイズは柔軟に調整できる。<br>
受信パケットがある場合、イーサネットヘッダを解析してIPパケットかARPパケットかを判断し、それぞれに適した処理を行う。<br>
<br>
* 受信パケットの確認
==== pbuf処理 ====
** IPパケットの場合
pbuf構造体は、参照カウント方式でメモリ管理されている。<br>
**: uIPスタックに渡して、TCP / UDP処理を行う。
pbuf_free関数を適切に呼び出さないと、メモリリークが発生する。<br>
** ARPパケットの場合
<br>
**: ARPテーブルの更新や応答を行う。
特に、パケット受信処理やエラー処理時には注意が必要である。<br>
*: <br>
<br>
* 周期処理
==== 割り込みコンテキスト ====
*: TCPの再送タイマやARPエントリのタイムアウト管理を行う。(必須)
lwIPの関数は、基本的に割り込みコンテキストから呼び出すべきではない。<br>
*: TCP接続は500[ms]周期、ARPタイマは10秒周期で処理する。
パケット受信処理は、メインループで行うことを推奨する。<br>
*: これらのタイミングは、uIPの仕様に基づいて設定されており、変更する場合は慎重に検討する必要がある。
<br>
ENC28J60の割り込み信号を使用する場合は、割り込みハンドラ内ではフラグを立てるのみとし、実際の処理はメインループで行う。<br>
<br>
<br>
==== 簡易HTTPサーバの構築 ====
==== DHCPの使用 ====
HTTPリクエストに応答する簡単なWebサーバを構築する。<br>
DHCPクライアントを使用する場合、IPアドレスの取得に時間がかかる場合がある。<br>
netif_set_link_callback関数を使用して、リンクアップ時にDHCPを起動することを推奨する。<br>
<br>
<br>
uIPはイベント駆動型のアーキテクチャを採用しており、アプリケーション関数 (httpd_appcall関数) は、様々なイベントが発生したときに呼び出される。<br>
DHCPによるIPアドレス取得中は、TCP/UDP通信ができないため、アプリケーションの実装時に考慮する必要がある。<br>
* 新規接続が確立された場合
*: uip_connected
* クライアントからデータを受信した場合
*: uip_newdata
* データの再送が必要な場合
*: uip_rexmit
* 接続がクローズされた場合
*: uip_closed
*: uip_aborted
*: uip_timedout
*: その他
<br>
<br>
以下の例では、GETリクエストを受信した時、予め用意されたHTMLページを送信する。<br>
==== エンディアン変換 ====
実務では、URLに応じて異なるコンテンツを返したり、センサデータを含む動的なページを生成することも可能である。<br>
ネットワークバイトオーダー (ビッグエンディアン) とホストバイトオーダーの変換に注意する。<br>
<br>
<br>
ただし、uIPは限られたRAMで動作するため、大きなコンテンツを扱う場合は、分割送信やフラッシュROMからの直接読み出す等の工夫が必要となる。<br>
lwIPは、htons()、ntohs()、htonl()、ntohl()等のマクロを提供している。<br>
<br>
<br>
<syntaxhighlight lang="c">
ARM Cortex-M0+はリトルエンディアンであるため、適切な変換が必要である。<br>
#include "uip.h"
#include <string.h>
// HTTPレスポンス (HTMLページ)
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>MSP430F5529 uIP Server</title></head>"
    "<body>"
    "<h1>Welcome to MSP430F5529!</h1>"
    "<p>This is a simple web server running on MSP430F5529 with uIP.</p>"
    "<p>ENC28J60 Ethernet Controller</p>"
    "</body>"
    "</html>";
// HTTPアプリケーション処理
void httpd_appcall(void)
{
    // 新規接続時
    if (uip_connected()) {
        // 特に処理なし (GETリクエスト待ち)
    }
   
    // データ受信時
    if (uip_newdata()) {
        // 簡易的にGETリクエストをチェック
        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()) {
        // 特に処理なし
    }
}
</syntaxhighlight>
<br><br>
<br><br>


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

概要

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

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

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

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

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


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


ENC28J60 と W5500

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

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


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

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


lwIPライブラリの特徴

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

lwIPの特徴を以下に示す。

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


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

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

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


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


uIPライブラリの特徴

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

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

uIPの特徴を以下に示す。

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


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

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


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

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

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

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


メリット

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


デメリット

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


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


MSPM0G3519の特徴

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

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

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


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

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


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

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



ハードウェア接続

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

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

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


※注意

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


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

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

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


lwIPライブラリの構成

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

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


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

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


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

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

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



uIPライブラリの構成

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

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


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

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


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



lwIP : サンプルコード

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

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

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

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

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


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


ENC28J60ドライバ (enc28j60.c)

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

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

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

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


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

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

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


メイン処理

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

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


簡易HTTPサーバの構築

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

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


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

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



その他の注意事項

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

タイマ管理

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

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

メモリ管理

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

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

pbuf処理

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

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

割り込みコンテキスト

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

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

DHCPの使用

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

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

エンディアン変換

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

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

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


トラブルシューティング

パケットが受信できない

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


ARPが応答しない

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


TCP接続が確立しない

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


メモリ不足エラー

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


HTTPサーバが応答しない

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



SPI通信の速度

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

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


DMAの活用

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

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

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



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

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

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



参考資料