「MSPM0G3519 - イーサネット (ENC28J60)」の版間の差分
| 182行目: | 182行目: | ||
<br><br> | <br><br> | ||
== サンプルコード == | == uIP : サンプルコード == | ||
以下の例では、ENC28J60およびuIPを使用して、簡単なWebサーバを構築してHTTPリクエストに応答している。<br> | 以下の例では、ENC28J60およびuIPを使用して、簡単なWebサーバを構築してHTTPリクエストに応答している。<br> | ||
<br> | <br> | ||
2025年12月21日 (日) 16:53時点における版
概要
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モードで使用する接続例を示す。
| 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コア層はそのまま使用できる。
- uIPの公式Webサイト
- 閉鎖
- uIPのGithub
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テーブルの更新や応答を行う。
- IPパケットの場合
- 周期処理
- 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の初期化は、データシートに記載されている手順に従う必要がある。
特に、ソフトウェアリセット後のクロック安定化待機、バンク切り替えのタイミングは重要である。
初期化が正しく行われていない場合、パケットの送受信が正常に動作しない可能性がある。
参考資料
- MSP430F5529データシート
- ENC28J60データシート
- uIP公式ドキュメント
- Adam Dunkels, "uIP - A Free Small TCP/IP Implementation"
- https://github.com/adamdunkels/uip
- RFC 793
- Transmission Control Protocol (TCP)
- RFC 826
- An Ethernet Address Resolution Protocol (ARP)