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

提供: MochiuWiki : SUSE, EC, PCB

ページの作成:「== 概要 == MSP430F5529は、USB機能を内蔵した16ビットRISCマイコンである。<br> しかし、イーサネット機能は内蔵していないため、TCP/IP通信を行うには、外付けのイーサネットコントローラIC (ENC28J60等) とuIPライブラリを組み合わせて使用する。<br> <br> uIPは、スウェーデンのAdam Dunkels氏が開発した軽量TCP/IPスタックで、限られたRAM容量 (数[KB]) で動作するよ…」
 
864行目: 864行目:
*: Adam Dunkels, "uIP - A Free Small TCP/IP Implementation"
*: Adam Dunkels, "uIP - A Free Small TCP/IP Implementation"
*: https://github.com/adamdunkels/uip
*: https://github.com/adamdunkels/uip
* RFC 793 : Transmission Control Protocol (TCP)
* RFC 793
* RFC 826 : An Ethernet Address Resolution Protocol (ARP)
*: Transmission Control Protocol (TCP)
* RFC 826
*: An Ethernet Address Resolution Protocol (ARP)
<br><br>
<br><br>



2025年12月21日 (日) 16:37時点における版

概要

MSP430F5529は、USB機能を内蔵した16ビットRISCマイコンである。
しかし、イーサネット機能は内蔵していないため、TCP/IP通信を行うには、外付けのイーサネットコントローラIC (ENC28J60等) とuIPライブラリを組み合わせて使用する。

uIPは、スウェーデンのAdam Dunkels氏が開発した軽量TCP/IPスタックで、限られたRAM容量 (数[KB]) で動作するように設計されており、組み込みシステムに最適である。

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

  • MSP430F5529 (マイコン)
  • ENC28J60 (イーサネットコントローラ)
  • uIPライブラリ (TCP/IPスタック)
  • SPIインターフェース


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


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等のより高機能なスタックの使用を検討する必要がある。


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

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

Microchip社のENC28J60は、SPI接続のイーサネットコントローラで、MSP430との組み合わせで最も広く使用されている。

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


メリット

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


デメリット

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


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


MSP430F5529の特徴

MSP430F5529はの16ビットRISCマイコンで、USB機能を内蔵した上位モデルである。

MSP430F5529の特徴を以下に示す。

  • 128[KB] フラッシュROM
  • 8[KB] RAM
  • 最大25[MHz]動作
  • USB 2.0フルスピード (12Mbps) 対応
  • USCI (UART、SPI、I2C対応)×4モジュール
  • 12ビットADC
  • タイマA/B
  • 80ピン (LQFP)
  • DMAコントローラ
  • 低消費電力設計
  • 内蔵LDO (USB用3.3V電源回路)


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

  • フラッシュROM
    約15〜25[KB] (uIP本体+アプリケーション)
  • RAM
    約4〜8[KB] (バッファ+スタック変数)


MSP430F5529のリソースは、これらの要件を十分に満たしているため、uIPの実装に適している。


ハードウェア接続

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

下表にMSP430F5529のUSCI_B0をSPIモードで使用する接続例を示す。

ピン接続表 (ENC28J60)
MSP430F5529 ENC28J60 機能 説明
P3.0 MOSI (SI) SPI マスター出力/スレーブ入力
P3.1 MISO (SO) SPI マスター入力/スレーブ出力
P3.2 SCLK (SCK) SPI シリアルクロック
P2.7 CS# 制御信号 チップセレクト
(アクティブLow)
P2.6 RESET# 制御信号 リセット信号
(アクティブLow)
P2.0 INT 割り込み 割り込み信号
(アクティブLow)
VCC VCC 電源 3.3V
GND VSS GND グランド


※注意

  • 水晶振動子
    ENC28J60には25MHzの水晶振動子が必要 (ピン23、24に接続)
  • デカップリングコンデンサ
    各電源ピン近くに0.1[uF]を配置
  • RJ45コネクタ
    トランス内蔵型を使用 (Pulse社 J0011D21BNL等)
  • LEDインジケータ
    LEDA (ピン15)、LEDB (ピン14) に接続可能


SPIインターフェースは、MSP430F5529のP3ポートに配置されているUSCI_B0モジュールを使用する。
このモジュールはハードウェアSPIをサポートしており、効率的な通信が可能である。

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

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


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


サンプルコード

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

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

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

この設定ファイルでは、デバイスのネットワーク設定 (IPアドレス、ネットマスク、ゲートウェイ) を定義している。
これらの値は、使用するネットワーク環境に合わせて変更する必要がある。

  • バッファサイズ
    標準的なイーサネットフレームのMTU (Maximum Transmission Unit) である1500バイトに設定している。
  • TCP接続数
    4に設定しているが、これはRAM容量と必要な同時接続数に応じて調整できる。
    接続数を増やすと、より多くのクライアントを同時に処理できるが、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接続数
 #define UIP_CONF_MAX_CONNECTIONS 4     // 最大TCP接続数
 #define UIP_CONF_MAX_LISTENPORTS 4     // 最大TCP待受ポート数
 #define UIP_CONF_UDP_CONNS       2     // 最大UDP接続数
 
 // プロトコル有効化
 #define UIP_CONF_UDP             1     // UDPを有効化
 #define UIP_CONF_UDP_CHECKSUMS   1     // UDPチェックサムを有効化
 #define UIP_CONF_BROADCAST       1     // ブロードキャストを有効化
 
 // 統計情報
 #define UIP_CONF_STATISTICS      0     // 統計情報を無効化 (RAM節約)
 
 // アプリケーション名
 #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通信を削減している。

 #include <msp430f5529.h>
 #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
 #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 : 非バックツーバックパケット間隙レジスタ
 #define MAIPGL    0x06
 #define MAIPGH    0x07
 
 // 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()   (P2OUT &= ~BIT7)
 #define ENC28J60_CS_HIGH()  (P2OUT |= BIT7)
 
 static uint8_t current_bank = 0;
 
 // SPIバイト送受信
 static uint8_t spi_transfer(uint8_t data)
 {
    while (!(UCB0IFG & UCTXIFG));  // 送信バッファが空になるまで待機
    UCB0TXBUF = data;              // データ送信
    while (!(UCB0IFG & UCRXIFG));  // 受信完了まで待機
    return UCB0RXBUF;              // 受信データを返す
 }
 
 // バンク選択
 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_cycles(50000);  // 1ms待機 (50[MHz]クロック想定)
 
    // クロックが安定するまで待機
    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初期化 (バンク2)
    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アドレス設定 (バンク3)
    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);
 }
 
 // パケット受信
 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;
 }


メイン処理

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

 #include <msp430f5529.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
 
 // グローバルバッファ
 uint8_t uip_buf[UIP_BUFSIZE + 2];  // uIP用バッファ
 
 // タイマ管理用
 volatile uint16_t periodic_timer = 0;
 volatile uint16_t arp_timer = 0;
 
 // SPI初期化 (USCI_B0をSPIモードで使用)
 void spi_init(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++;
    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};
 
    WDTCTL = WDTPW | WDTHOLD;  // ウォッチドッグ停止
 
    // システム初期化
    clock_init();              // クロック設定 (25[MHz])
    spi_init();                // SPI初期化
    timer_init();              // タイマー初期化
 
    // 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_interrupt();      // グローバル割り込み有効
 
    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);
             }
          }
       }
 
       // 周期処理
       uip_periodic_handler();
    }
 }


初期化フェーズでは、マイコンのクロック設定、SPI通信の設定、タイマの設定、ENC28J60およびuIPの初期化を実行する。

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

  • 受信パケットの確認
    • IPパケットの場合
      uIPスタックに渡して、TCP / UDP処理を行う。
    • 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ページを送信する。
実務では、URLに応じて異なるコンテンツを返したり、センサデータを含む動的なページを生成することも可能である。

ただし、uIPは限られたRAMで動作するため、大きなコンテンツを扱う場合は、分割送信やフラッシュROMからの直接読み出す等の工夫が必要となる。

 #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()) {
         // 特に処理なし
     }
 }



その他の注意事項

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

  • タイミング管理
    uIPは周期的な処理が必要である。
    TCPタイマは通常500[ms]周期、ARPタイマは10秒周期で呼び出す。
    これらのタイミングがずれると、接続の確立や維持に問題が生じる可能性がある。
  • バッファ管理
    uIPは単一のグローバルバッファ (uip_buf) を使用する。
    送受信の処理中は、このバッファの内容が上書きされないように注意する必要がある。
  • スタックサイズ
    uIPは比較的小さなスタックで動作するが、再帰的な処理や大きなローカル変数を避ける必要がある。
    MSP430ではスタックオーバーフローに特に注意する。
  • 割り込みコンテキスト
    イーサネットコントローラの割り込みハンドラ内では、最小限の処理のみを行い、実際のパケット処理はメイン処理で行うことを推奨する。
  • デバッグ
    uIPにはデバッグ用の統計情報機能があるが、RAM使用量が増加する。
    開発時は有効化し、製品版では無効化することを検討する。
  • クロック精度
    タイマ処理の精度は、TCP通信の品質に影響する。
    MSP430F5529のDCOは温度変動の影響を受けやすいため、可能であれば外部クリスタルの使用を検討する。


※注意
よくあるトラブルシューティングを以下に示す。

  • パケットが受信できない
    ENC28J60の受信フィルタ設定、MACアドレス設定、バッファポインタ設定を確認する。
  • ARPが応答しない
    MACアドレスが正しく設定されているか、uip_arp_init()が呼ばれているかを確認する。
  • TCP接続が確立しない
    uip_listen関数等でポートが開いているか、周期処理が正しく動作しているかを確認する。
  • データが送信できない
    送信バッファの設定、TXRTSビットのクリア条件、uip_send関数等の呼び出しタイミングを確認する。


SPI通信の速度

一般的な問題として、SPIクロック速度の設定ミスがある。
ENC28J60は最大20[MHz]のSPIクロックに対応しているが、MSP430F5529のクロック設定や配線の状態によっては、より低い周波数での動作が必要になる場合がある。
そのため、通信が不安定な場合は、まずSPIクロック周波数を下げて通信できるかどうかを推奨する。

ENC28J60の初期化

ENC28J60の初期化は、データシートに記載されている手順に従う必要がある。
特に、ソフトウェアリセット後のクロック安定化待機、バンク切り替えのタイミングは重要である。
初期化が正しく行われていない場合、パケットの送受信が正常に動作しない可能性がある。


参考資料