「MSPM0G3519 - イーサネット (ENC28J60)」の版間の差分
編集の要約なし |
|||
| 1,069行目: | 1,069行目: | ||
* fsdata.cが正しく生成されているかを確認する。 | * fsdata.cが正しく生成されているかを確認する。 | ||
* makefsdata.pyを使用して、HTMLファイルを正しく変換する。 | * makefsdata.pyを使用して、HTMLファイルを正しく変換する。 | ||
<br><br> | |||
== uIP : サンプルコード == | |||
以下の例では、ENC28J60およびuIPを使用して、Webサーバを構築してHTTPリクエストに応答している。<br> | |||
uIPは軽量であるため、MSPM0G3519の豊富なリソースを考慮すると軽量すぎる可能性があるが、簡単な実装や学習目的には適している。<br> | |||
<br> | |||
==== uIP設定ファイル (uip-conf.h) ==== | |||
まず、uIPの動作パラメータを設定する。<br> | |||
<br> | |||
この設定ファイルでは、デバイスのネットワーク設定 (IPアドレス、ネットマスク、ゲートウェイ) を定義している。<br> | |||
これらの値は、使用するネットワーク環境に合わせて変更する必要がある。<br> | |||
MSPM0G3519は128[KB]のRAMを持つため、バッファサイズや接続数を比較的大きく設定できる。<br> | |||
<br> | |||
* バッファサイズ | |||
*: 標準的なイーサネットフレームのMTU (Maximum Transmission Unit) である1500バイトに設定している。 | |||
* TCP接続数 | |||
*: 8に設定しているが、これはRAM容量と必要な同時接続数に応じて調整できる。 | |||
*: MSPM0G3519の豊富なRAMを活かし、MSP430F5529の設定 (4接続) より多めに設定している。 | |||
*: 接続数を増やすと、より多くのクライアントを同時に処理できるが、RAM使用量も増加する点に注意が必要である。 | |||
<br> | |||
<syntaxhighlight lang="c"> | |||
#ifndef __UIP_CONF_H__ | |||
#define __UIP_CONF_H__ | |||
#include <stdint.h> | |||
// uIP設定パラメータ | |||
typedef uint8_t u8_t; // 8ビット符号なし整数型 | |||
typedef uint16_t u16_t; // 16ビット符号なし整数型 | |||
// IPアドレス設定 | |||
#define UIP_IPADDR0 192 // IPアドレス 192.168.1.100 | |||
#define UIP_IPADDR1 168 | |||
#define UIP_IPADDR2 1 | |||
#define UIP_IPADDR3 100 | |||
// ネットマスク設定 | |||
#define UIP_NETMASK0 255 // ネットマスク 255.255.255.0 | |||
#define UIP_NETMASK1 255 | |||
#define UIP_NETMASK2 255 | |||
#define UIP_NETMASK3 0 | |||
// デフォルトゲートウェイ | |||
#define UIP_DRIPADDR0 192 // ゲートウェイ 192.168.1.1 | |||
#define UIP_DRIPADDR1 168 | |||
#define UIP_DRIPADDR2 1 | |||
#define UIP_DRIPADDR3 1 | |||
// uIPバッファ設定 | |||
#define UIP_CONF_BUFFER_SIZE 1500 // イーサネットMTUサイズ | |||
#define UIP_CONF_RECEIVE_WINDOW 1460 // TCP受信ウィンドウサイズ | |||
// TCP/UDP接続数 (MSPM0G3519の豊富なRAMを活用) | |||
#define UIP_CONF_MAX_CONNECTIONS 8 // 最大TCP接続数 (MSP430より多め) | |||
#define UIP_CONF_MAX_LISTENPORTS 8 // 最大TCP待受ポート数 | |||
#define UIP_CONF_UDP_CONNS 4 // 最大UDP接続数 | |||
// プロトコル有効化 | |||
#define UIP_CONF_UDP 1 // UDPを有効化 | |||
#define UIP_CONF_UDP_CHECKSUMS 1 // UDPチェックサムを有効化 | |||
#define UIP_CONF_BROADCAST 1 // ブロードキャストを有効化 | |||
// 統計情報 (開発時は有効化、製品版では無効化してRAM節約) | |||
#define UIP_CONF_STATISTICS 0 // 統計情報を無効化 | |||
// アプリケーション名 | |||
#define UIP_APPCALL httpd_appcall // HTTPサーバーアプリケーション | |||
#endif /* __UIP_CONF_H__ */ | |||
</syntaxhighlight> | |||
<br> | |||
==== ENC28J60ドライバ (enc28j60.c) ==== | |||
ENC28J60との通信を行うドライバ制御 (SPIを使用したレジスタアクセスやパケット送受信等) を記述する。<br> | |||
<br> | |||
このドライバでは、ENC28J60の内部構造が重要である。<br> | |||
ENC28J60は、8[KB]の内部RAMを持ち、これを受信バッファと送信バッファに分割して使用する。<br> | |||
<br> | |||
以下の例では、受信バッファを0x0000から0x19FFまで (約6.5[KB])、送信バッファを0x1A00から0x1FFFまで (約1.5[KB]) に設定している。<br> | |||
この分割比率は、アプリケーションの特性に応じて調整可能である。<br> | |||
<br> | |||
また、ENC28J60のレジスタは4つのバンク (Bank0〜Bank3) に分かれており、アクセスする前に適切なバンクを選択する必要がある。<br> | |||
バンク選択は、ECON1レジスタの下位2ビットで制御される。<br> | |||
ドライバでは、現在のバンクを追跡し、必要な場合のみバンク切り替えを行うことにより、不要なSPI通信を削減している。<br> | |||
<br> | |||
MSPM0G3519のSPIペリフェラルは、DL_SPI関数を使用して制御する。<br> | |||
最大80[MHz]の高速動作により、SPIクロックも高速に設定できるが、ENC28J60の最大速度 (20[MHz]) を超えないように注意する必要がある。<br> | |||
<br> | |||
<syntaxhighlight lang="c"> | |||
// enc28j60.c : ENC28J60ドライバ (MSPM0G3519 uIP用) | |||
#include "ti_msp_dl_config.h" // MSPM0G3519 SDK設定 | |||
#include "enc28j60.h" | |||
#include <stdint.h> | |||
// ENC28J60レジスタアドレス定義 | |||
#define ERDPTL 0x00 | |||
#define ERDPTH 0x01 | |||
#define EWRPTL 0x02 | |||
#define EWRPTH 0x03 | |||
#define ETXSTL 0x04 | |||
#define ETXSTH 0x05 | |||
#define ETXNDL 0x06 | |||
#define ETXNDH 0x07 | |||
#define ERXSTL 0x08 | |||
#define ERXSTH 0x09 | |||
#define ERXNDL 0x0A | |||
#define ERXNDH 0x0B | |||
#define ERXRDPTL 0x0C | |||
#define ERXRDPTH 0x0D | |||
// EIE : イーサネット割り込み有効レジスタ | |||
#define EIE 0x1B | |||
#define INTIE 0x80 | |||
#define PKTIE 0x40 | |||
// EIR : イーサネット割り込み要求レジスタ | |||
#define EIR 0x1C | |||
#define PKTIF 0x40 | |||
#define TXIF 0x08 | |||
// ECON1 : イーサネット制御レジスタ1 | |||
#define ECON1 0x1F | |||
#define TXRTS 0x08 | |||
#define RXEN 0x04 | |||
// ECON2 : イーサネット制御レジスタ2 | |||
#define ECON2 0x1E | |||
#define AUTOINC 0x80 | |||
#define PKTDEC 0x40 | |||
// ESTAT : イーサネットステータスレジスタ | |||
#define ESTAT 0x1D | |||
#define CLKRDY 0x01 | |||
// MACON1 : MACコントロールレジスタ1 | |||
#define MACON1 0x00 | 0x40 | |||
#define MARXEN 0x01 | |||
#define TXPAUS 0x08 | |||
#define RXPAUS 0x04 | |||
// MACON3 : MACコントロールレジスタ3 | |||
#define MACON3 0x02 | 0x40 | |||
#define PADCFG0 0x20 | |||
#define TXCRCEN 0x10 | |||
#define FRMLNEN 0x02 | |||
#define FULDPX 0x01 | |||
// MAMXFLL/MAMXFLH : 最大フレーム長レジスタ | |||
#define MAMXFLL 0x0A | 0x40 | |||
#define MAMXFLH 0x0B | 0x40 | |||
// MABBIPG : バックツーバックパケット間隙レジスタ | |||
#define MABBIPG 0x04 | 0x40 | |||
// MAIPGL/MAIPGH : 非バックツーバックパケット間隙レジスタ | |||
#define MAIPGL 0x06 | 0x40 | |||
#define MAIPGH 0x07 | 0x40 | |||
// SPIコマンド定義 | |||
#define ENC28J60_READ_CTRL_REG 0x00 | |||
#define ENC28J60_WRITE_CTRL_REG 0x40 | |||
#define ENC28J60_BIT_FIELD_SET 0x80 | |||
#define ENC28J60_BIT_FIELD_CLR 0xA0 | |||
#define ENC28J60_READ_BUF_MEM 0x3A | |||
#define ENC28J60_WRITE_BUF_MEM 0x7A | |||
#define ENC28J60_SOFT_RESET 0xFF | |||
// バンク選択 | |||
#define BANK0 0x00 | |||
#define BANK1 0x01 | |||
#define BANK2 0x02 | |||
#define BANK3 0x03 | |||
// チップセレクト制御 (実際のピン定義に応じて変更) | |||
#define ENC28J60_CS_LOW() DL_GPIO_clearPins(GPIO_ENC_CS_PORT, GPIO_ENC_CS_PIN) | |||
#define ENC28J60_CS_HIGH() DL_GPIO_setPins(GPIO_ENC_CS_PORT, GPIO_ENC_CS_PIN) | |||
static uint8_t current_bank = 0; | |||
// 遅延関数 (80[MHz]駆動の場合) | |||
static void delay_us(uint32_t us) | |||
{ | |||
// 80[MHz] = 80 cycles/us | |||
// 簡易的な遅延実装のため、精度が必要な場合はタイマの割り込みを使用する | |||
volatile uint32_t cycles = us * 80; | |||
while (cycles--); | |||
} | |||
// SPIバイト送受信 | |||
static uint8_t spi_transfer(uint8_t data) | |||
{ | |||
// SPIにデータ送信 | |||
DL_SPI_transmitData8(SPI_0_INST, data); | |||
// 送信完了待ち | |||
while (DL_SPI_isBusy(SPI_0_INST)); | |||
// 受信データ取得 | |||
return DL_SPI_receiveData8(SPI_0_INST); | |||
} | |||
// バンク選択 | |||
static void enc28j60_set_bank(uint8_t address) | |||
{ | |||
uint8_t bank = (address & 0x60) >> 5; | |||
if (bank != current_bank) { | |||
// EIE、EIR、ESTAT、ECON1、ECON2は全バンク共通 | |||
enc28j60_write_op(ENC28J60_BIT_FIELD_CLR, ECON1, 0x03); | |||
enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON1, bank); | |||
current_bank = bank; | |||
} | |||
} | |||
// レジスタ読み出し | |||
uint8_t enc28j60_read(uint8_t address) | |||
{ | |||
uint8_t data; | |||
enc28j60_set_bank(address); | |||
ENC28J60_CS_LOW(); | |||
spi_transfer(ENC28J60_READ_CTRL_REG | (address & 0x1F)); | |||
// MACおよびMIIレジスタの場合、ダミーバイトを読む | |||
if (address & 0x80) { | |||
spi_transfer(0x00); | |||
} | |||
data = spi_transfer(0x00); | |||
ENC28J60_CS_HIGH(); | |||
return data; | |||
} | |||
// レジスタ書き込み | |||
void enc28j60_write(uint8_t address, uint8_t data) | |||
{ | |||
enc28j60_set_bank(address); | |||
ENC28J60_CS_LOW(); | |||
spi_transfer(ENC28J60_WRITE_CTRL_REG | (address & 0x1F)); | |||
spi_transfer(data); | |||
ENC28J60_CS_HIGH(); | |||
} | |||
// ビット操作 (OR) | |||
void enc28j60_write_op(uint8_t op, uint8_t address, uint8_t data) | |||
{ | |||
ENC28J60_CS_LOW(); | |||
spi_transfer(op | (address & 0x1F)); | |||
spi_transfer(data); | |||
ENC28J60_CS_HIGH(); | |||
} | |||
// バッファメモリ読み出し | |||
void enc28j60_read_buffer(uint8_t *buffer, uint16_t len) | |||
{ | |||
ENC28J60_CS_LOW(); | |||
spi_transfer(ENC28J60_READ_BUF_MEM); | |||
while (len--) { | |||
*buffer++ = spi_transfer(0x00); | |||
} | |||
ENC28J60_CS_HIGH(); | |||
} | |||
// バッファメモリ書き込み | |||
void enc28j60_write_buffer(const uint8_t *buffer, uint16_t len) | |||
{ | |||
ENC28J60_CS_LOW(); | |||
spi_transfer(ENC28J60_WRITE_BUF_MEM); | |||
while (len--) { | |||
spi_transfer(*buffer++); | |||
} | |||
ENC28J60_CS_HIGH(); | |||
} | |||
// ENC28J60初期化 | |||
void enc28j60_init(const uint8_t *mac_address) | |||
{ | |||
// ソフトウェアリセット | |||
ENC28J60_CS_LOW(); | |||
spi_transfer(ENC28J60_SOFT_RESET); | |||
ENC28J60_CS_HIGH(); | |||
delay_us(1000); // 1[ms]待機 | |||
// クロックが安定するまで待機 | |||
while (!(enc28j60_read(ESTAT) & CLKRDY)); | |||
// 受信バッファ設定 (0x0000 - 0x19FF) | |||
enc28j60_write(ERXSTL, 0x00); | |||
enc28j60_write(ERXSTH, 0x00); | |||
enc28j60_write(ERXNDL, 0xFF); | |||
enc28j60_write(ERXNDH, 0x19); | |||
enc28j60_write(ERXRDPTL, 0x00); | |||
enc28j60_write(ERXRDPTH, 0x00); | |||
// 送信バッファ設定 (0x1A00 - 0x1FFF) | |||
enc28j60_write(ETXSTL, 0x00); | |||
enc28j60_write(ETXSTH, 0x1A); | |||
enc28j60_write(ETXNDL, 0xFF); | |||
enc28j60_write(ETXNDH, 0x1F); | |||
// MAC初期化 | |||
enc28j60_write(MACON1, MARXEN); // MAC受信有効 | |||
enc28j60_write(MACON3, PADCFG0 | TXCRCEN | FRMLNEN); // パディング、CRC、フレーム長チェック有効 | |||
enc28j60_write(MAMXFLL, 0xEE); // 最大フレーム長1518バイト | |||
enc28j60_write(MAMXFLH, 0x05); | |||
enc28j60_write(MABBIPG, 0x12); // バックツーバック間隔 | |||
enc28j60_write(MAIPGL, 0x12); // 非バックツーバック間隔 | |||
enc28j60_write(MAIPGH, 0x0C); | |||
// MACアドレス設定 | |||
enc28j60_write(0x00 | 0x60, mac_address[0]); // MAADR1 | |||
enc28j60_write(0x01 | 0x60, mac_address[1]); // MAADR2 | |||
enc28j60_write(0x02 | 0x60, mac_address[2]); // MAADR3 | |||
enc28j60_write(0x03 | 0x60, mac_address[3]); // MAADR4 | |||
enc28j60_write(0x04 | 0x60, mac_address[4]); // MAADR5 | |||
enc28j60_write(0x05 | 0x60, mac_address[5]); // MAADR6 | |||
// 割り込み有効化 | |||
enc28j60_write(EIE, INTIE | PKTIE); | |||
// 受信有効化 | |||
enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON1, RXEN); | |||
} | |||
// パケット送信 | |||
void enc28j60_packet_send(const uint8_t *buffer, uint16_t len) | |||
{ | |||
// 送信バッファポインタ設定 | |||
enc28j60_write(EWRPTL, 0x00); | |||
enc28j60_write(EWRPTH, 0x1A); | |||
// 制御バイト書き込み (0x00 = デフォルト設定) | |||
enc28j60_write_buffer((uint8_t[]){0x00}, 1); | |||
// パケットデータ書き込み | |||
enc28j60_write_buffer(buffer, len); | |||
// 送信終了ポインタ設定 | |||
enc28j60_write(ETXNDL, (0x1A00 + len) & 0xFF); | |||
enc28j60_write(ETXNDH, ((0x1A00 + len) >> 8) & 0xFF); | |||
// 送信開始 | |||
enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON1, TXRTS); | |||
// 送信完了待ち (オプション) | |||
while (enc28j60_read(ECON1) & TXRTS); | |||
} | |||
// パケット受信 | |||
uint16_t enc28j60_packet_receive(uint8_t *buffer, uint16_t max_len) | |||
{ | |||
uint16_t len = 0; | |||
uint8_t header[6]; | |||
// 受信パケットがあるかどうかを確認 | |||
if (enc28j60_read(0x19) == 0) { // EPKTCNT | |||
return 0; | |||
} | |||
// ヘッダ読み出し (次のパケットポインタ、ステータス、長さ) | |||
enc28j60_read_buffer(header, 6); | |||
// パケット長取得 (ヘッダ内の長さフィールド) | |||
len = (header[3] << 8) | header[2]; | |||
len -= 4; // CRCを除く | |||
// バッファに収まる場合のみデータを読み出し | |||
if (len <= max_len) { | |||
enc28j60_read_buffer(buffer, len); | |||
} | |||
// 次のパケットポインタ更新 | |||
uint16_t next_packet = (header[1] << 8) | header[0]; | |||
enc28j60_write(ERXRDPTL, next_packet & 0xFF); | |||
enc28j60_write(ERXRDPTH, (next_packet >> 8) & 0xFF); | |||
// パケットデクリメント | |||
enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON2, PKTDEC); | |||
return (len <= max_len) ? len : 0; | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
==== メイン処理 ==== | |||
メイン処理では、MSPM0G3519の初期化、uIPの初期化等を記述する。<br> | |||
<br> | |||
MSPM0G3519のタイマペリフェラルを使用して、uIPに必要な周期的なタイマイベントを生成する。<br> | |||
uIPは、TCPの再送タイマやARPのタイムアウト管理のために、定期的なタイマ処理を必要とする。<br> | |||
一般的には、TCPタイマは500[ms]周期、ARPタイマは10秒周期で処理する。<br> | |||
<br> | |||
MSPM0G3519の80MHz動作により、uIPのTCP/IP処理は非常に高速に実行できる。<br> | |||
また、128[KB]のRAMにより、uIPのバッファやスタック変数に十分なメモリを割り当てることができる。<br> | |||
<br> | |||
<syntaxhighlight lang="c"> | |||
// main.c : メイン処理 (MSPM0G3519 uIP) | |||
#include "ti_msp_dl_config.h" | |||
#include "uip.h" | |||
#include "uip_arp.h" | |||
#include "enc28j60.h" | |||
#include <stdint.h> | |||
// MACアドレス定義 | |||
#define MAC_ADDR0 0x02 | |||
#define MAC_ADDR1 0x00 | |||
#define MAC_ADDR2 0x00 | |||
#define MAC_ADDR3 0x00 | |||
#define MAC_ADDR4 0x00 | |||
#define MAC_ADDR5 0x01 | |||
// グローバルバッファ (uIP用) | |||
// MSPM0G3519は128KB RAMを持つため、余裕を持ったバッファサイズを確保可能 | |||
uint8_t uip_buf[UIP_BUFSIZE + 2]; | |||
// タイマ管理用 | |||
volatile uint16_t periodic_timer = 0; | |||
volatile uint16_t arp_timer = 0; | |||
// タイマ割り込みハンドラ (10ms周期想定) | |||
void TIMER_0_INST_IRQHandler(void) | |||
{ | |||
periodic_timer++; | |||
arp_timer++; | |||
} | |||
// uIP周期処理 | |||
void uip_periodic_handler(void) | |||
{ | |||
int i; | |||
// TCP接続の周期処理 (500[ms] = 50 × 10[ms]) | |||
if (periodic_timer >= 50) { | |||
periodic_timer = 0; | |||
for (i = 0; i < UIP_CONNS; i++) { | |||
uip_periodic(i); | |||
if (uip_len > 0) { | |||
uip_arp_out(); // ARPテーブル参照してイーサネットヘッダ追加 | |||
enc28j60_packet_send(uip_buf, uip_len); | |||
} | |||
} | |||
// UDP接続の周期処理 | |||
#if UIP_UDP | |||
for (i = 0; i < UIP_UDP_CONNS; i++) { | |||
uip_udp_periodic(i); | |||
if (uip_len > 0) { | |||
uip_arp_out(); | |||
enc28j60_packet_send(uip_buf, uip_len); | |||
} | |||
} | |||
#endif | |||
} | |||
// ARPタイマー処理 (10秒 = 1000 × 10ms) | |||
if (arp_timer >= 1000) { | |||
arp_timer = 0; | |||
uip_arp_timer(); | |||
} | |||
} | |||
// メイン関数 | |||
int main(void) | |||
{ | |||
uip_ipaddr_t ipaddr; | |||
uint8_t mac_address[6] = {MAC_ADDR0, MAC_ADDR1, MAC_ADDR2, MAC_ADDR3, MAC_ADDR4, MAC_ADDR5}; | |||
// MSPM0G3519ペリフェラル初期化 | |||
SYSCFG_DL_init(); | |||
// タイマ割り込み有効化 | |||
NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN); | |||
// ENC28J60初期化 | |||
enc28j60_init(mac_address); | |||
// uIP初期化 | |||
uip_init(); | |||
// IPアドレス設定 | |||
uip_ipaddr(ipaddr, UIP_IPADDR0, UIP_IPADDR1, UIP_IPADDR2, UIP_IPADDR3); | |||
uip_sethostaddr(ipaddr); | |||
// ネットマスク設定 | |||
uip_ipaddr(ipaddr, UIP_NETMASK0, UIP_NETMASK1, UIP_NETMASK2, UIP_NETMASK3); | |||
uip_setnetmask(ipaddr); | |||
// デフォルトゲートウェイ設定 | |||
uip_ipaddr(ipaddr, UIP_DRIPADDR0, UIP_DRIPADDR1, UIP_DRIPADDR2, UIP_DRIPADDR3); | |||
uip_setdraddr(ipaddr); | |||
// ARPテーブル初期化 | |||
uip_arp_init(); | |||
// HTTPサーバ起動 (ポート80) | |||
uip_listen(HTONS(80)); | |||
// グローバル割り込み有効化 | |||
__enable_irq(); | |||
// メインループ | |||
while (1) { | |||
// パケット受信チェック | |||
uip_len = enc28j60_packet_receive(uip_buf, UIP_BUFSIZE); | |||
if (uip_len > 0) { | |||
// イーサネットヘッダ解析 | |||
struct uip_eth_hdr *eth_hdr = (struct uip_eth_hdr *)&uip_buf[0]; | |||
if (eth_hdr->type == htons(UIP_ETHTYPE_IP)) { | |||
// IPパケット処理 | |||
uip_arp_ipin(); // ARPテーブル更新 | |||
uip_input(); // uIPへ入力 | |||
if (uip_len > 0) { | |||
uip_arp_out(); // ARPテーブル参照 | |||
enc28j60_packet_send(uip_buf, uip_len); | |||
} | |||
} | |||
else if (eth_hdr->type == htons(UIP_ETHTYPE_ARP)) { | |||
// ARPパケット処理 | |||
uip_arp_arpin(); | |||
if (uip_len > 0) { | |||
enc28j60_packet_send(uip_buf, uip_len); | |||
} | |||
} | |||
} | |||
// 周期処理 (TCP再送、ARPタイムアウト等) | |||
uip_periodic_handler(); | |||
} | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
初期化フェーズでは、MSPM0G3519のペリフェラル設定、SPI通信の設定、タイマの設定、ENC28J60およびuIPの初期化を実行する。<br> | |||
SYSCFG_DL_init関数は、TI MSPM0 SDKによって自動生成される設定関数であり、クロック、GPIO、SPI、タイマ等のペリフェラルを一括で初期化する。<br> | |||
<br> | |||
メイン処理では、受信パケットの確認と周期処理を繰り返し実行する。<br> | |||
受信パケットがある場合、イーサネットヘッダを解析してIPパケットかARPパケットかを判断し、それぞれに適した処理を行う。<br> | |||
* 受信パケットの確認 | |||
** IPパケットの場合 | |||
**: uIPスタックに渡して、TCP/UDP処理を行う。 | |||
**: MSPM0G3519の80MHz動作により、TCP/IP処理は非常に高速に実行される。 | |||
** ARPパケットの場合 | |||
**: ARPテーブルの更新や応答を行う。 | |||
*: <br> | |||
* 周期処理 | |||
*: TCPの再送タイマやARPエントリのタイムアウト管理を行う。(必須) | |||
*: TCP接続は500[ms]周期、ARPタイマは10秒周期で処理する。 | |||
*: これらのタイミングは、uIPの仕様に基づいて設定されており、変更する場合は慎重に検討する必要がある。 | |||
<br> | |||
==== HTTPサーバの構築 ==== | |||
HTTPリクエストに応答する簡単なWebサーバを構築する。<br> | |||
<br> | |||
uIPはイベント駆動型のアーキテクチャを採用しており、アプリケーション関数 (httpd_appcall関数) は、様々なイベントが発生したときに呼び出される。<br> | |||
主なイベントは以下の通りである。<br> | |||
* 新規接続が確立された場合 | |||
*: uip_connected | |||
* クライアントからデータを受信した場合 | |||
*: uip_newdata | |||
* データの再送が必要な場合 | |||
*: uip_rexmit | |||
* 接続がクローズされた場合 | |||
*: uip_closed | |||
*: uip_aborted | |||
*: uip_timedout | |||
*: その他 | |||
<br> | |||
以下の例では、GETリクエストを受信した時、予め用意されたHTMLページを送信する。<br> | |||
MSPM0G3519の豊富なフラッシュメモリ (512[KB]) により、複数のHTMLページや画像データを格納することも可能である。<br> | |||
実務では、URLに応じて異なるコンテンツを返したり、ADCで取得したセンサデータを含む動的なページを生成することも可能である。<br> | |||
<br> | |||
uIPは限られたRAMで動作するように設計されているが、MSPM0G3519の128[KB] RAMにより、比較的大きなコンテンツも扱うことができる。<br> | |||
ただし、非常に大きなコンテンツを扱う場合は、分割送信やフラッシュROMからの直接読み出し等の工夫が推奨される。<br> | |||
<br> | |||
<syntaxhighlight lang="c"> | |||
// httpd.c : HTTPサーバ実装 | |||
#include "uip.h" | |||
#include <string.h> | |||
// HTTPレスポンス (HTMLページ) | |||
// MSPM0G3519のフラッシュROM (512[KB]) により、複数のページを格納可能 | |||
const char http_response[] = | |||
"HTTP/1.0 200 OK\r\n" | |||
"Content-Type: text/html\r\n" | |||
"Connection: close\r\n" | |||
"\r\n" | |||
"<!DOCTYPE html>" | |||
"<html>" | |||
"<head>" | |||
" <title>MSPM0G3519 uIP Server</title>" | |||
" <meta charset=\"UTF-8\">" | |||
" <style>" | |||
" body { font-family: Arial, sans-serif; margin: 40px; background-color: #f5f5f5; }" | |||
" .container { max-width: 800px; margin: 0 auto; background-color: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }" | |||
" h1 { color: #c00; border-bottom: 2px solid #c00; padding-bottom: 10px; }" | |||
" .specs { background-color: #f9f9f9; padding: 15px; border-left: 4px solid #c00; margin: 20px 0; }" | |||
" .specs h2 { margin-top: 0; color: #333; }" | |||
" ul { line-height: 1.8; }" | |||
" .footer { margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd; text-align: center; color: #666; font-size: 0.9em; }" | |||
" </style>" | |||
"</head>" | |||
"<body>" | |||
" <div class=\"container\">" | |||
" <h1>Welcome to MSPM0G3519 uIP Server!</h1>" | |||
" <div class=\"specs\">" | |||
" <h2>System Specifications</h2>" | |||
" <ul>" | |||
" <li><strong>MCU:</strong> MSPM0G3519 (ARM Cortex-M0+)</li>" | |||
" <li><strong>Clock Speed:</strong> 80 MHz</li>" | |||
" <li><strong>Flash Memory:</strong> 512 KB (Dual-bank with ECC)</li>" | |||
" <li><strong>SRAM:</strong> 128 KB</li>" | |||
" <li><strong>Ethernet Controller:</strong> ENC28J60 (10 Mbps)</li>" | |||
" <li><strong>TCP/IP Stack:</strong> uIP (Lightweight Implementation)</li>" | |||
" </ul>" | |||
" </div>" | |||
" <p>This embedded web server demonstrates the power of the MSPM0G3519 microcontroller combined with the uIP TCP/IP stack.</p>" | |||
" <p>The MSPM0G3519 offers excellent performance with its 80 MHz ARM Cortex-M0+ core and generous memory resources, making it ideal for IoT and embedded networking applications.</p>" | |||
" <div class=\"footer\">" | |||
" <p>© 2025 MSPM0G3519 Embedded Web Server</p>" | |||
" </div>" | |||
" </div>" | |||
"</body>" | |||
"</html>"; | |||
// HTTPアプリケーション処理 | |||
void httpd_appcall(void) | |||
{ | |||
// 新規接続時 | |||
if (uip_connected()) { | |||
// 特に処理なし (GETリクエスト待ち) | |||
} | |||
// データ受信時 | |||
if (uip_newdata()) { | |||
// 簡易的にGETリクエストをチェック | |||
// より高度な実装では、HTTPヘッダの完全なパースを行う | |||
if (strncmp((char *)uip_appdata, "GET ", 4) == 0) { | |||
// HTTPレスポンス送信 | |||
uip_send((void *)http_response, sizeof(http_response) - 1); | |||
} | |||
} | |||
// 再送要求時 | |||
if (uip_rexmit()) { | |||
uip_send((void *)http_response, sizeof(http_response) - 1); | |||
} | |||
// 接続クローズ時 | |||
if (uip_closed() || uip_aborted() || uip_timedout()) { | |||
// 特に処理なし | |||
// 必要に応じてクリーンアップ処理を追加 | |||
} | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
MSPM0G3519の豊富なリソースを活かすことにより、高度な機能を追加することも可能である。<br> | |||
例えば、以下のような拡張が考えられる。<br> | |||
* URLルーティング | |||
*: リクエストされたURLに応じて、異なるHTMLページを返す。 | |||
*: 複数のページを用意し、ナビゲーション機能を実装する。 | |||
*: <br> | |||
* 動的コンテンツ生成 | |||
*: ADCで取得したセンサデータをリアルタイムでWebページに表示する。 | |||
*: GPIO状態を読み取り、LEDの点灯状態等をWebページに反映する。 | |||
*: <br> | |||
* フォーム処理 | |||
*: POSTリクエストを処理し、Webブラウザから設定値を変更する。 | |||
*: LEDの制御、PWMのデューティ比設定等をWebインターフェースから行う。 | |||
*: <br> | |||
* JSON API | |||
*: REST APIを実装し、JSON形式でデータを送受信する。 | |||
*: IoTデバイスとしての機能を強化する。 | |||
<br><br> | <br><br> | ||
2025年12月22日 (月) 16:44時点における最新版
概要
MSPM0G3519は、Texas Instruments社の32ビットARM Cortex-M0+コアを搭載したマイコンである。
最大80[MHz]で動作し、512[KB]のフラッシュROMと128[KB]のSRAMを搭載している。
しかし、イーサネット機能は内蔵していないため、TCP/IP通信を行うには、外付けのイーサネットコントローラIC (ENC28J60等) とlwIPまたはuIPライブラリを組み合わせて使用する。
lwIPは、スウェーデンのAdam Dunkels氏が開発した軽量TCP/IPスタックで、uIPの後継として位置付けられている。
MSPM0G3519は十分なRAM容量 (128[KB]) を持つため、lwIPの使用が推奨される。
lwIPは、より高速で安定したTCP/IP通信を実現し、多数の同時接続に対応できる。
主な構成は以下の通りである。
- MSPM0G3519 (マイコン)
- ENC28J60 (イーサネットコントローラ)
- SPIインターフェース
- lwIPライブラリ (TCP/IPスタック)
- 推奨
- uIPライブラリ (TCP/IPスタック)
- 軽量版 : 非推奨 (メンテナンスされていない)
この構成により、MSPM0G3519を使用したWebサーバ、HTTPクライアント、Telnetサーバ、UDPアプリケーション、DHCPクライアント、DNSクライアント等の構築が可能となる。
ENC28J60 と W5500
イーサネット通信を実現するには、大きく分けて2つの階層がある。
- 上位層
- ネットワーク層以上で、TCP/IPプロトコルの処理を行う。
- 下位層
- 物理層 および データリンク層 で、イーサネットフレームの送受信を担当する。
ENC28J60は下位層のみを持つICである。
イーサネットフレームの送受信はできるが、TCP/IPプロトコルの処理機能は持たない。
これを イーサネットMAC/PHYコントローラ と呼ぶ。
一方、W5500は、上位層および下位層の両方をIC内に持つため、TCP/IPの複雑な処理も含めて全てW5500が自動的に処理する。
これを ハードウェアTCP/IPスタック内蔵型 と呼ぶ。
lwIPライブラリの特徴
lwIP (Lightweight IP) は、スウェーデンのAdam Dunkels氏が開発したTCP/IPプロトコルスタックで、uIPの後継として位置付けられている。
lwIPは、uIPよりも高機能であり、より多くのメモリを使用するが、その分、高いスループットと多数の同時接続に対応できる設計となっている。
lwIPの特徴を以下に示す。
- 高機能なTCP/IP実装
- 完全なTCP/IP実装で約40〜60[KB]程度のコードサイズ
- uIPと比較して、より高速で安定した通信が可能
- 適度なRAM使用量
- 通常構成で約20〜40[KB]程度のRAM使用量
- メモリプール方式による効率的なバッファ管理
- RFC準拠
- TCP、UDP、IP、ICMP、ARP、DHCP、DNSプロトコルに対応
- HTTP、SNMP、MQTT等の上位プロトコルも実装可能
- マルチスレッド対応
- RTOSと組み合わせた使用が可能
- ただし、MSPM0G3519では通常はシングルスレッド構成 (NO_SYS=1) で使用する
- 移植性
- C言語で記述されており、様々なマイコンに移植可能
- ハードウェア依存部分が明確に分離されている
※MSPM0G3519でのlwIP使用について
MSPM0G3519は、512[KB]のフラッシュと128[KB]のSRAMを持つため、lwIPを標準構成で十分に使用できる。
これにより、以下に示すメリットが得られる。
- メリット
- より多くの同時接続に対応 (通常10〜50接続以上)
- 高速なデータ転送が可能
- DHCP、DNS等の上位機能を標準サポート
- より安定したTCP実装
- パケットの分割送信と再送処理が効率的
- ゼロコピー機能によるメモリ効率の向上
- デメリット
- uIPと比較して、初期設定がやや複雑
- コードサイズが大きい (ただし、MSPM0G3519のフラッシュ容量で十分に収まる)
MSPM0G3519では、lwIPの使用を推奨する。
RAMとフラッシュROMに十分な余裕があるため、lwIPの高機能性を最大限活用できる。
軽量なアプリケーションにはuIPも使用可能であるが、将来的な機能拡張を考慮すると、lwIPの採用がより適切である。
uIPライブラリの特徴
uIPは既にメンテナンスされていないため、使用は非推奨である。
uIPは、組み込みシステム向けに最適化された軽量TCP/IPプロトコルスタックである。
uIPの特徴を以下に示す。
- 小さなコードサイズ
- 完全なTCP/IP実装で約10〜20[KB]程度のコードサイズ
- 低RAM使用量
- 最小構成で数[KB]程度のRAM使用量
- バッファ管理が効率的に設計されている
- RFC準拠
- TCP、UDP、IP、ICMP、ARPプロトコルに対応
- HTTP 1.0サーバ実装を含む
- 移植性
- C言語で記述されており、様々なマイコンに移植可能
- ハードウェア依存部分が明確に分離されている
※注意
uIPは軽量化のため、いくつかの制約がある。
同時接続数が限られており (通常1〜10接続程度)、スループットよりも省メモリを優先した設計となっている。
高速通信や多数の同時接続が必要な場合は、lwIP等のより高機能なスタックの使用を検討する必要がある。
MSPM0G3519は128[KB]の十分なRAMを持つため、特別な理由がない限りlwIPの使用を推奨する。
ENC28J60イーサネットコントローラ
MSPM0G3519はイーサネットコントローラを内蔵していないため、外付けのイーサネットコントローラチップが必要となる。
Microchip社のENC28J60は、SPI接続のイーサネットコントローラで、組み込みシステムで広く使用されている。
- IEEE 802.3準拠の10BASE-T Ethernetコントローラ
- SPIインターフェース (最大20[MHz])
- 内蔵8KBのバッファRAM
- MACおよびPHY機能を統合
- パケットフィルタリング機能
- 低消費電力設計 (スリープモード対応)
- 3.3V単一電源動作
- 28ピンSSOP、QFNパッケージ
メリット
- SPIインターフェースのため、配線が簡単 (4本の信号線)
- 入手性が良好で、価格も比較的安価
- 豊富なサンプルコードとドキュメント
- MSPM0G3519のSPIペリフェラルで容易に接続可能
デメリット
- 10[Mbps]のみの対応 (100[Mbps]非対応)
- SPIのオーバーヘッドによりスループットが制限される。
上記のデメリットはあるものの、多くの組み込みアプリケーションでは10[Mbps]で十分であり、
配線の簡便性と入手性の良さからENC28J60は組み込みイーサネット実装において人気の高い選択肢となっている。
MSPM0G3519の特徴
MSPM0G3519は、Texas Instruments社の32ビットARM Cortex-M0+コアを搭載したマイコンで、MSPM0ファミリの上位モデルである。
MSPM0G3519の主な特徴を以下に示す。
- コア
- ARM 32ビット Cortex-M0+ CPU (最大80[MHz])
- メモリ保護ユニット (MPU) 搭載
- メモリ
- 512[KB] フラッシュROM (デュアルバンク、ECC付き)
- 128[KB] SRAM (合計)
- SRAM バンク0: 64[KB] (ECC保護またはハードウェアパリティ、スタンバイモードまで保持)
- SRAM バンク1: 64[KB] (STOPモードまで保持)
- 16[KB] データフラッシュバンク (ECC保護)
- 高性能アナログペリフェラル
- 2個の同時サンプリング12ビット4[Msps] ADC (最大27外部チャネル)
- 1個の12ビット1Msps DAC (出力バッファ内蔵)
- 3個の高速コンパレータ (8ビットリファレンスDAC内蔵)
- 内部共有電圧リファレンス (1.4[V] または 2.5[V])
- 温度センサ内蔵
- 通信インターフェース
- 7個のUARTインターフェース
- 2個はLIN、IrDA、DALI、スマートカード、マンチェスターをサポート
- 3個はスタンバイモードでの低消費電力動作をサポート
- 3個のI2Cインターフェース (FM+ 1[Mbit/s]対応、STOP モードからのウェークアップ対応)
- 3個のSPIインターフェース (1個は最大32[Mbit/s])
- 2個のCAN-FDインターフェース (CAN 2.0 A/B、CAN-FD対応)
- タイマ / DMA
- 9個のタイマ (最大28個のPWMチャネル対応)
- 12チャネルDMAコントローラ
- セキュリティ機能
- AES-128/256アクセラレータ (GCM/GMAC、CCM/CBC-MAC、CBC、CTR対応)
- セキュアキーストレージ (最大4個のAESキー)
- 真性乱数生成器 (TRNG)
- フレキシブルなファイアウォール
- その他
- 最大94個のGPIO
- RTC (アラームおよびカレンダーモード付き)
- ウォッチドッグタイマ
- 低消費電力モード対応
- 動作電圧: 1.62[V]〜3.6[V]
- 動作温度範囲: -40[℃]〜125[℃]
lwIP実装に必要なリソース要件は以下の通りである。
- フラッシュROM
- 約50〜80[KB] (lwIP本体+ドライバ+アプリケーション)
- RAM
- 約20〜40[KB] (バッファ+メモリプール+スタック変数)
MSPM0G3519のリソースは、これらの要件を十分に満たしているため、lwIPの実装に適している。
MSP430F5529と比較して、以下の点で大幅に優位性がある。
- RAMが16倍 (8[KB] → 128[KB]) で、多数の同時接続とバッファを確保可能
- フラッシュが4倍 (128[KB] → 512[KB]) で、より多くの機能を実装可能
- 32ビットアーキテクチャにより、TCP/IP処理が高速
- 高速クロック (80[MHz]) により、SPIスループットが向上
- 12チャネルDMAコントローラにより、CPUを介さずにSPIデータ転送が可能
- CAN-FD対応により、車載やFA分野での応用が可能
ハードウェア接続
MSPM0G3519とENC28J60は、SPIインターフェースで接続する。
下表にMSPM0G3519のSPI0モジュールを使用する接続例を示す。
| 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への移行も容易である。
- lwIPの公式Webサイト
- lwIPのGithub
- lwIP Documentation
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コア層はそのまま使用できる。
- uIPの公式Webサイト
- 閉鎖
- uIPのGithub
lwIP : サンプルコード
以下の例では、ENC28J60およびlwIPを使用して、簡単なWebサーバを構築してHTTPリクエストに応答している。
MSPM0G3519の豊富なリソースを活用し、lwIPのRaw APIを使用している。
lwIP設定ファイル (lwipopts.h)
まず、lwIPの動作パラメータを設定する。
この設定ファイルでは、NO_SYS=1によりRTOSを使用しないスタンドアロン構成を指定している。
MSPM0G3519の128[KB] RAMを活用し、複数の同時接続とDHCPクライアント機能を有効化している。
- メモリプール設定
- MSPM0G3519の豊富なRAM容量に応じて、メモリプールサイズを最適化している。
- pbufプールやヒープサイズは、アプリケーションの要件に応じて柔軟に変更可能である。
- TCP接続数
- 16接続に設定しているが、これはアプリケーションの要件に応じて調整できる。
- MSPM0G3519では、20〜30接続以上も十分に対応可能である。
- DHCP機能
- DHCPクライアント機能を有効化することで、IPアドレスの自動取得が可能となる。
#ifndef __LWIPOPTS_H__
#define __LWIPOPTS_H__
// NO_SYS==1: RTOSを使用しないスタンドアロン構成
#define NO_SYS 1
// メモリ管理設定 (MSPM0G3519の128KB RAMを活用)
#define MEM_ALIGNMENT 4
#define MEM_SIZE (32*1024) // 32KB ヒープ (十分なRAMがあるため大きめに設定)
#define MEMP_NUM_PBUF 32
#define MEMP_NUM_TCP_PCB 16
#define MEMP_NUM_TCP_PCB_LISTEN 8
#define MEMP_NUM_TCP_SEG 32
#define MEMP_NUM_SYS_TIMEOUT 16
// pbuf設定
#define PBUF_POOL_SIZE 32
#define PBUF_POOL_BUFSIZE 512
// TCP設定
#define LWIP_TCP 1
#define TCP_MSS 1460
#define TCP_WND (8 * TCP_MSS) // 8 MSS ウィンドウサイズ
#define TCP_SND_BUF (8 * TCP_MSS) // 8 MSS 送信バッファ
#define TCP_SND_QUEUELEN (4 * TCP_SND_BUF/TCP_MSS)
// UDP設定
#define LWIP_UDP 1
#define MEMP_NUM_UDP_PCB 8
// ICMP設定
#define LWIP_ICMP 1
// DHCP設定
#define LWIP_DHCP 1
#define LWIP_NETIF_HOSTNAME 1
// DNS設定
#define LWIP_DNS 1 // DNS有効化
#define DNS_TABLE_SIZE 4
#define DNS_MAX_SERVERS 2
// ARP設定
#define LWIP_ARP 1
#define ARP_TABLE_SIZE 10
#define ARP_QUEUEING 1
// IP設定
#define LWIP_IPV4 1
#define LWIP_IPV6 0
#define IP_REASSEMBLY 1 // IPフラグメント再構築を有効化
#define IP_FRAG 1 // IPフラグメント分割を有効化
// チェックサム設定
#define CHECKSUM_GEN_IP 1
#define CHECKSUM_GEN_UDP 1
#define CHECKSUM_GEN_TCP 1
#define CHECKSUM_CHECK_IP 1
#define CHECKSUM_CHECK_UDP 1
#define CHECKSUM_CHECK_TCP 1
// 統計情報 (開発時は有効化、製品版では無効化してRAM節約)
#define LWIP_STATS 1
#define LWIP_STATS_DISPLAY 1
// デバッグ設定 (開発時は有効化可能)
#define LWIP_DEBUG 0
// その他
#define LWIP_NETCONN 0 // Raw API使用のため無効化
#define LWIP_SOCKET 0 // ソケットAPI不使用
#define LWIP_NETIF_LINK_CALLBACK 1
#define LWIP_NETIF_STATUS_CALLBACK 1
// プラットフォーム依存の型定義
#define U16_F "hu"
#define S16_F "hd"
#define X16_F "hx"
#define U32_F "u"
#define S32_F "d"
#define X32_F "x"
#endif /* __LWIPOPTS_H__ */
ENC28J60ドライバ (enc28j60.c)
ENC28J60との通信を行うドライバ制御 (SPIを使用したレジスタアクセスやパケット送受信等) を記述する。
このドライバでは、MSPM0G3519のSPIペリフェラルを使用してENC28J60を制御する。
MSPM0G3519のSPI通信速度は最大32[Mbit/s]であり、ENC28J60の最大速度である20[MHz]に近い動作が可能である。
以下の例では、基本的なレジスタアクセスとパケット送受信機能を提供している。
必要に応じて、DMAを使用した高速データ転送や、割り込み駆動の受信処理を追加することも可能である。
// enc28j60.c : ENC28J60ドライバ
#include "ti_msp_dl_config.h" // MSPM0G3519 SDK設定
#include "enc28j60.h"
#include <stdint.h>
// ENC28J60レジスタアドレス定義
#define ERDPTL 0x00
#define ERDPTH 0x01
#define EWRPTL 0x02
#define EWRPTH 0x03
#define ETXSTL 0x04
#define ETXSTH 0x05
#define ETXNDL 0x06
#define ETXNDH 0x07
#define ERXSTL 0x08
#define ERXSTH 0x09
#define ERXNDL 0x0A
#define ERXNDH 0x0B
#define ERXRDPTL 0x0C
#define ERXRDPTH 0x0D
#define EIE 0x1B
#define EIR 0x1C
#define ESTAT 0x1D
#define ECON2 0x1E
#define ECON1 0x1F
// MACレジスタ
#define MACON1 0x00 | 0x40
#define MACON3 0x02 | 0x40
#define MAMXFLL 0x0A | 0x40
#define MAMXFLH 0x0B | 0x40
#define MABBIPG 0x04 | 0x40
#define MAIPGL 0x06 | 0x40
#define MAIPGH 0x07 | 0x40
// SPIコマンド定義
#define ENC28J60_READ_CTRL_REG 0x00
#define ENC28J60_WRITE_CTRL_REG 0x40
#define ENC28J60_BIT_FIELD_SET 0x80
#define ENC28J60_BIT_FIELD_CLR 0xA0
#define ENC28J60_READ_BUF_MEM 0x3A
#define ENC28J60_WRITE_BUF_MEM 0x7A
#define ENC28J60_SOFT_RESET 0xFF
// ビット定義
#define ECON1_TXRTS 0x08
#define ECON1_RXEN 0x04
#define ECON2_AUTOINC 0x80
#define ECON2_PKTDEC 0x40
#define ESTAT_CLKRDY 0x01
#define MACON1_MARXEN 0x01
#define MACON3_PADCFG0 0x20
#define MACON3_TXCRCEN 0x10
#define MACON3_FRMLNEN 0x02
// チップセレクト制御 (実際のピン定義に応じて変更)
#define ENC28J60_CS_LOW() DL_GPIO_clearPins(GPIO_ENC_CS_PORT, GPIO_ENC_CS_PIN)
#define ENC28J60_CS_HIGH() DL_GPIO_setPins(GPIO_ENC_CS_PORT, GPIO_ENC_CS_PIN)
static uint8_t current_bank = 0;
// 遅延関数 (80[MHz]駆動時)
static void delay_us(uint32_t us)
{
// 80[MHz] = 80 cycles/us
// 簡易的な遅延実装 (正確に計測する場合はタイマを使用すること)
volatile uint32_t cycles = us * 80;
while (cycles--);
}
// SPIバイト送受信
static uint8_t spi_transfer(uint8_t data)
{
// SPIにデータ送信
DL_SPI_transmitData8(SPI_0_INST, data);
// 送信完了待ち
while (DL_SPI_isBusy(SPI_0_INST));
// 受信データ取得
return DL_SPI_receiveData8(SPI_0_INST);
}
// バンク選択
static void enc28j60_set_bank(uint8_t address)
{
uint8_t bank = (address & 0x60) >> 5;
if (bank != current_bank) {
enc28j60_write_op(ENC28J60_BIT_FIELD_CLR, ECON1, 0x03);
enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON1, bank);
current_bank = bank;
}
}
// レジスタ読み出し
uint8_t enc28j60_read(uint8_t address)
{
uint8_t data;
enc28j60_set_bank(address);
ENC28J60_CS_LOW();
spi_transfer(ENC28J60_READ_CTRL_REG | (address & 0x1F));
// MACおよびMIIレジスタの場合、ダミーバイトを読む
if (address & 0x80) {
spi_transfer(0x00);
}
data = spi_transfer(0x00);
ENC28J60_CS_HIGH();
return data;
}
// レジスタ書き込み
void enc28j60_write(uint8_t address, uint8_t data)
{
enc28j60_set_bank(address);
ENC28J60_CS_LOW();
spi_transfer(ENC28J60_WRITE_CTRL_REG | (address & 0x1F));
spi_transfer(data);
ENC28J60_CS_HIGH();
}
// ビット操作
void enc28j60_write_op(uint8_t op, uint8_t address, uint8_t data)
{
ENC28J60_CS_LOW();
spi_transfer(op | (address & 0x1F));
spi_transfer(data);
ENC28J60_CS_HIGH();
}
// バッファメモリ読み出し
void enc28j60_read_buffer(uint8_t *buffer, uint16_t len)
{
ENC28J60_CS_LOW();
spi_transfer(ENC28J60_READ_BUF_MEM);
while (len--) {
*buffer++ = spi_transfer(0x00);
}
ENC28J60_CS_HIGH();
}
// バッファメモリ書き込み
void enc28j60_write_buffer(const uint8_t *buffer, uint16_t len)
{
ENC28J60_CS_LOW();
spi_transfer(ENC28J60_WRITE_BUF_MEM);
while (len--) {
spi_transfer(*buffer++);
}
ENC28J60_CS_HIGH();
}
// ENC28J60初期化
void enc28j60_init(const uint8_t *mac_address)
{
// ソフトウェアリセット
ENC28J60_CS_LOW();
spi_transfer(ENC28J60_SOFT_RESET);
ENC28J60_CS_HIGH();
delay_us(1000); // 1ms待機
// クロックが安定するまで待機
while (!(enc28j60_read(ESTAT) & ESTAT_CLKRDY));
// 受信バッファ設定 (0x0000 - 0x19FF)
enc28j60_write(ERXSTL, 0x00);
enc28j60_write(ERXSTH, 0x00);
enc28j60_write(ERXNDL, 0xFF);
enc28j60_write(ERXNDH, 0x19);
enc28j60_write(ERXRDPTL, 0x00);
enc28j60_write(ERXRDPTH, 0x00);
// 送信バッファ設定 (0x1A00 - 0x1FFF)
enc28j60_write(ETXSTL, 0x00);
enc28j60_write(ETXSTH, 0x1A);
enc28j60_write(ETXNDL, 0xFF);
enc28j60_write(ETXNDH, 0x1F);
// MAC初期化
enc28j60_write(MACON1, MACON1_MARXEN);
enc28j60_write(MACON3, MACON3_PADCFG0 | MACON3_TXCRCEN | MACON3_FRMLNEN);
enc28j60_write(MAMXFLL, 0xEE);
enc28j60_write(MAMXFLH, 0x05);
enc28j60_write(MABBIPG, 0x12);
enc28j60_write(MAIPGL, 0x12);
enc28j60_write(MAIPGH, 0x0C);
// MACアドレス設定
enc28j60_write(0x00 | 0x60, mac_address[0]); // MAADR1
enc28j60_write(0x01 | 0x60, mac_address[1]); // MAADR2
enc28j60_write(0x02 | 0x60, mac_address[2]); // MAADR3
enc28j60_write(0x03 | 0x60, mac_address[3]); // MAADR4
enc28j60_write(0x04 | 0x60, mac_address[4]); // MAADR5
enc28j60_write(0x05 | 0x60, mac_address[5]); // MAADR6
// 割り込み有効化
enc28j60_write(EIE, 0xC0); // INTIE | PKTIE
// 受信有効化
enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);
}
// パケット送信
void enc28j60_packet_send(const uint8_t *buffer, uint16_t len)
{
// 送信バッファポインタ設定
enc28j60_write(EWRPTL, 0x00);
enc28j60_write(EWRPTH, 0x1A);
// 制御バイト書き込み
enc28j60_write_buffer((uint8_t[]){0x00}, 1);
// パケットデータ書き込み
enc28j60_write_buffer(buffer, len);
// 送信終了ポインタ設定
enc28j60_write(ETXNDL, (0x1A00 + len) & 0xFF);
enc28j60_write(ETXNDH, ((0x1A00 + len) >> 8) & 0xFF);
// 送信開始
enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS);
// 送信完了待ち (オプション: タイムアウト処理を追加することを推奨)
while (enc28j60_read(ECON1) & ECON1_TXRTS);
}
// パケット受信
uint16_t enc28j60_packet_receive(uint8_t *buffer, uint16_t max_len)
{
uint16_t len = 0;
uint8_t header[6];
// 受信パケット数確認
if (enc28j60_read(0x19) == 0) { // EPKTCNT
return 0;
}
// ヘッダ読み出し
enc28j60_read_buffer(header, 6);
// パケット長取得
len = (header[3] << 8) | header[2];
len -= 4; // CRCを除く
// バッファに収まる場合のみデータを読み出し
if (len <= max_len) {
enc28j60_read_buffer(buffer, len);
}
// 次のパケットポインタ更新
uint16_t next_packet = (header[1] << 8) | header[0];
enc28j60_write(ERXRDPTL, next_packet & 0xFF);
enc28j60_write(ERXRDPTH, (next_packet >> 8) & 0xFF);
// パケットデクリメント
enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC);
return (len <= max_len) ? len : 0;
}
lwIPネットワークインターフェース (netif_enc28j60.c)
lwIPとENC28J60ドライバを接続するネットワークインターフェース層を実装する。
この層は、lwIPのpbuf構造体とENC28J60のパケットバッファ間でデータを変換する役割を持つ。
// netif_enc28j60.c : lwIPネットワークインターフェース
#include "lwip/opt.h"
#include "lwip/def.h"
#include "lwip/mem.h"
#include "lwip/pbuf.h"
#include "lwip/sys.h"
#include "lwip/stats.h"
#include "netif/etharp.h"
#include "enc28j60.h"
#include <string.h>
#define IFNAME0 'e'
#define IFNAME1 'n'
// パケット送信
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
struct pbuf *q;
uint8_t buffer[1518];
uint16_t len = 0;
// pbufチェーンからバッファにコピー
for (q = p; q != NULL; q = q->next) {
if (len + q->len > sizeof(buffer)) {
return ERR_BUF;
}
memcpy(&buffer[len], q->payload, q->len);
len += q->len;
}
// ENC28J60で送信
enc28j60_packet_send(buffer, len);
return ERR_OK;
}
// パケット受信
static struct pbuf *low_level_input(struct netif *netif)
{
struct pbuf *p, *q;
uint8_t buffer[1518];
uint16_t len;
// ENC28J60からパケット受信
len = enc28j60_packet_receive(buffer, sizeof(buffer));
if (len == 0) {
return NULL;
}
// pbuf割り当て
p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
if (p != NULL) {
uint16_t offset = 0;
// pbufチェーンにデータをコピー
for (q = p; q != NULL; q = q->next) {
memcpy(q->payload, &buffer[offset], q->len);
offset += q->len;
}
}
return p;
}
// ネットワークインターフェース初期化
err_t netif_enc28j60_init(struct netif *netif)
{
netif->name[0] = IFNAME0;
netif->name[1] = IFNAME1;
netif->output = etharp_output;
netif->linkoutput = low_level_output;
netif->mtu = 1500;
netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;
// MACアドレス設定
netif->hwaddr_len = 6;
netif->hwaddr[0] = 0x02;
netif->hwaddr[1] = 0x00;
netif->hwaddr[2] = 0x00;
netif->hwaddr[3] = 0x00;
netif->hwaddr[4] = 0x00;
netif->hwaddr[5] = 0x01;
// ENC28J60初期化
enc28j60_init(netif->hwaddr);
return ERR_OK;
}
// パケット受信処理 (メインループから定期的に呼び出す)
void netif_enc28j60_input(struct netif *netif)
{
struct pbuf *p;
// 受信可能なパケットを全て処理
while ((p = low_level_input(netif)) != NULL) {
if (netif->input(p, netif) != ERR_OK) {
pbuf_free(p);
}
}
}
メイン処理
メイン処理では、MSPM0G3519の初期化、lwIPの初期化、HTTPサーバの起動を記述する。
// main.c : メイン処理
#include "ti_msp_dl_config.h"
#include "lwip/init.h"
#include "lwip/netif.h"
#include "lwip/timeouts.h"
#include "lwip/dhcp.h"
#include "netif/etharp.h"
#include "netif_enc28j60.h"
#include "lwip/apps/httpd.h"
// lwIP用タイマ (1[ms]周期)
volatile uint32_t lwip_timer_ms = 0;
// タイマ割り込みハンドラ (1[ms]周期)
void TIMER_0_INST_IRQHandler(void)
{
lwip_timer_ms++;
}
int main(void)
{
struct netif netif;
ip4_addr_t ipaddr, netmask, gw;
// MSPM0G3519ペリフェラル初期化
SYSCFG_DL_init();
// タイマ割り込み有効化
NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN);
// lwIP初期化
lwip_init();
// 静的IPアドレス設定 (DHCPを使用しない場合)
IP4_ADDR(&ipaddr, 192, 168, 1, 100);
IP4_ADDR(&netmask, 255, 255, 255, 0);
IP4_ADDR(&gw, 192, 168, 1, 1);
// ネットワークインターフェース追加
netif_add(&netif, &ipaddr, &netmask, &gw, NULL, netif_enc28j60_init, ethernet_input);
netif_set_default(&netif);
netif_set_up(&netif);
// DHCPクライアント起動 (オプション)
// dhcp_start(&netif);
// HTTPサーバ初期化
httpd_init();
// メインループ
while (1) {
// パケット受信処理
netif_enc28j60_input(&netif);
// lwIPタイムアウト処理 (周期的なタイマイベント処理)
sys_check_timeouts();
}
}
簡易HTTPサーバの構築
lwIPには、HTTPサーバ実装 (httpd.c) が含まれているため、これを利用することで簡単にWebサーバを構築できる。
以下の例では、カスタムCGIハンドラを追加して、動的なコンテンツを生成している。
// httpd_cgi.c - CGIハンドラ
#include "lwip/apps/httpd.h"
#include "ti_msp_dl_config.h"
#include <string.h>
// LED状態取得CGI
const char *led_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[])
{
// LEDの状態を確認
uint32_t led_state = DL_GPIO_readPins(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_1_PIN);
if (led_state) {
return "/led_on.ssi";
}
else {
return "/led_off.ssi";
}
}
// LED制御CGI
const char *led_control_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[])
{
int i;
for (i = 0; i < iNumParams; i++) {
if (strcmp(pcParam[i], "led") == 0) {
if (strcmp(pcValue[i], "on") == 0) {
DL_GPIO_setPins(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_1_PIN);
}
else if (strcmp(pcValue[i], "off") == 0) {
DL_GPIO_clearPins(GPIO_LEDS_PORT, GPIO_LEDS_USER_LED_1_PIN);
}
}
}
return "/index.html";
}
// CGIハンドラテーブル
static const tCGI cgi_handlers[] = {
{ "/led_status.cgi", led_cgi_handler },
{ "/led_control.cgi", led_control_cgi_handler }
};
// CGI初期化
void httpd_cgi_init(void)
{
http_set_cgi_handlers(cgi_handlers, sizeof(cgi_handlers) / sizeof(tCGI));
}
lwIPのHTTPサーバは、ファイルシステムを持たないため、Webページのコンテンツはプログラムに埋め込む必要がある。
lwIPには、makefsdataツールが付属しており、HTMLファイルをC言語のヘッダファイルに変換することができる。
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<title>MSPM0G3519 Web Server</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
h1 { color: #333; }
.info { background-color: #f0f0f0; padding: 20px; border-radius: 5px; }
.controls { margin-top: 20px; }
button { padding: 10px 20px; margin: 5px; }
</style>
</head>
<body>
<h1>MSPM0G3519 Web Server</h1>
<div class="info">
<p>This is running on lwIP stack with ENC28J60 Ethernet controller.</p>
<ul>
<li>MCU: MSPM0G3519 (ARM Cortex-M0+, 80MHz)</li>
<li>Flash: 512KB</li>
<li>RAM: 128KB</li>
<li>Ethernet: ENC28J60 (10Mbps)</li>
</ul>
</div>
<div class="controls">
<h2>LED Control</h2>
<form action="/led_control.cgi" method="get">
<button type="submit" name="led" value="on">LED ON</button>
<button type="submit" name="led" value="off">LED OFF</button>
</form>
</div>
</body>
</html>
その他の注意事項
lwIPを使用したTCP/IP通信を実装する場合の注意事項を以下に示す。
タイマ管理
lwIPは、sys_check_timeouts関数を定期的に呼び出す必要がある。
メインループ内で適切な間隔 (推奨 : 数[ms]〜数十[ms]) で呼び出すことを推奨する。
タイマの精度は、TCP通信の品質に影響するため、正確なタイマ実装が重要である。
メモリ管理
lwIPは、メモリプール方式を採用しているため、lwipopts.hで適切なプールサイズを設定する必要がある。
メモリ不足が発生すると、パケットのドロップや接続の失敗が生じる可能性がある。
MSPM0G3519は128[KB]のRAMを持つため、メモリプールサイズは柔軟に調整できる。
pbuf処理
pbuf構造体は、参照カウント方式でメモリ管理されている。
pbuf_free関数を適切に呼び出さないと、メモリリークが発生する。
特に、パケット受信処理やエラー処理時には注意が必要である。
割り込みコンテキスト
lwIPの関数は、基本的に割り込みコンテキストから呼び出すべきではない。
パケット受信処理は、メインループで行うことを推奨する。
ENC28J60の割り込み信号を使用する場合は、割り込みハンドラ内ではフラグを立てるのみとし、実際の処理はメインループで行う。
DHCPの使用
DHCPクライアントを使用する場合、IPアドレスの取得に時間がかかる場合がある。
netif_set_link_callback関数を使用して、リンクアップ時にDHCPを起動することを推奨する。
DHCPによるIPアドレス取得中は、TCP/UDP通信ができないため、アプリケーションの実装時に考慮する必要がある。
エンディアン変換
ネットワークバイトオーダー (ビッグエンディアン) とホストバイトオーダーの変換に注意する。
lwIPは、htons()、ntohs()、htonl()、ntohl()等のマクロを提供している。
ARM Cortex-M0+はリトルエンディアンであるため、適切な変換が必要である。
トラブルシューティング
パケットが受信できない
- ENC28J60の初期化、MACアドレス設定、ネットワークインターフェースの設定を確認する。
- netif_set_up()が呼ばれているか、NETIF_FLAG_LINK_UPが設定されているかを確認する。
- SPIの通信速度が高すぎる場合、通信が不安定になる可能性があるため、速度を下げてみる。
ARPが応答しない
- MACアドレスが正しく設定されているか、etharp_output()が正しく設定されているかを確認する。
- ARP_TABLE_SIZEが適切に設定されているかを確認する。
TCP接続が確立しない
- ファイアウォールやルータの設定を確認する。
- sys_check_timeouts()が定期的に呼ばれているかを確認する。
- TCPの3ウェイハンドシェイクが正常に行われているか、パケットキャプチャで確認する。
メモリ不足エラー
- lwipopts.hのメモリプールサイズを増やす。
- LWIP_STATS=1を有効化して、メモリ使用状況を確認する。
- MSPM0G3519は128[KB]のRAMを持つため、十分なメモリを割り当てることができる。
HTTPサーバが応答しない
- httpd_init関数が呼ばれているか、または、ポート80がリッスンされているかを確認する。
- fsdata.cが正しく生成されているかを確認する。
- makefsdata.pyを使用して、HTMLファイルを正しく変換する。
uIP : サンプルコード
以下の例では、ENC28J60およびuIPを使用して、Webサーバを構築してHTTPリクエストに応答している。
uIPは軽量であるため、MSPM0G3519の豊富なリソースを考慮すると軽量すぎる可能性があるが、簡単な実装や学習目的には適している。
uIP設定ファイル (uip-conf.h)
まず、uIPの動作パラメータを設定する。
この設定ファイルでは、デバイスのネットワーク設定 (IPアドレス、ネットマスク、ゲートウェイ) を定義している。
これらの値は、使用するネットワーク環境に合わせて変更する必要がある。
MSPM0G3519は128[KB]のRAMを持つため、バッファサイズや接続数を比較的大きく設定できる。
- バッファサイズ
- 標準的なイーサネットフレームのMTU (Maximum Transmission Unit) である1500バイトに設定している。
- TCP接続数
- 8に設定しているが、これはRAM容量と必要な同時接続数に応じて調整できる。
- MSPM0G3519の豊富なRAMを活かし、MSP430F5529の設定 (4接続) より多めに設定している。
- 接続数を増やすと、より多くのクライアントを同時に処理できるが、RAM使用量も増加する点に注意が必要である。
#ifndef __UIP_CONF_H__
#define __UIP_CONF_H__
#include <stdint.h>
// uIP設定パラメータ
typedef uint8_t u8_t; // 8ビット符号なし整数型
typedef uint16_t u16_t; // 16ビット符号なし整数型
// IPアドレス設定
#define UIP_IPADDR0 192 // IPアドレス 192.168.1.100
#define UIP_IPADDR1 168
#define UIP_IPADDR2 1
#define UIP_IPADDR3 100
// ネットマスク設定
#define UIP_NETMASK0 255 // ネットマスク 255.255.255.0
#define UIP_NETMASK1 255
#define UIP_NETMASK2 255
#define UIP_NETMASK3 0
// デフォルトゲートウェイ
#define UIP_DRIPADDR0 192 // ゲートウェイ 192.168.1.1
#define UIP_DRIPADDR1 168
#define UIP_DRIPADDR2 1
#define UIP_DRIPADDR3 1
// uIPバッファ設定
#define UIP_CONF_BUFFER_SIZE 1500 // イーサネットMTUサイズ
#define UIP_CONF_RECEIVE_WINDOW 1460 // TCP受信ウィンドウサイズ
// TCP/UDP接続数 (MSPM0G3519の豊富なRAMを活用)
#define UIP_CONF_MAX_CONNECTIONS 8 // 最大TCP接続数 (MSP430より多め)
#define UIP_CONF_MAX_LISTENPORTS 8 // 最大TCP待受ポート数
#define UIP_CONF_UDP_CONNS 4 // 最大UDP接続数
// プロトコル有効化
#define UIP_CONF_UDP 1 // UDPを有効化
#define UIP_CONF_UDP_CHECKSUMS 1 // UDPチェックサムを有効化
#define UIP_CONF_BROADCAST 1 // ブロードキャストを有効化
// 統計情報 (開発時は有効化、製品版では無効化してRAM節約)
#define UIP_CONF_STATISTICS 0 // 統計情報を無効化
// アプリケーション名
#define UIP_APPCALL httpd_appcall // HTTPサーバーアプリケーション
#endif /* __UIP_CONF_H__ */
ENC28J60ドライバ (enc28j60.c)
ENC28J60との通信を行うドライバ制御 (SPIを使用したレジスタアクセスやパケット送受信等) を記述する。
このドライバでは、ENC28J60の内部構造が重要である。
ENC28J60は、8[KB]の内部RAMを持ち、これを受信バッファと送信バッファに分割して使用する。
以下の例では、受信バッファを0x0000から0x19FFまで (約6.5[KB])、送信バッファを0x1A00から0x1FFFまで (約1.5[KB]) に設定している。
この分割比率は、アプリケーションの特性に応じて調整可能である。
また、ENC28J60のレジスタは4つのバンク (Bank0〜Bank3) に分かれており、アクセスする前に適切なバンクを選択する必要がある。
バンク選択は、ECON1レジスタの下位2ビットで制御される。
ドライバでは、現在のバンクを追跡し、必要な場合のみバンク切り替えを行うことにより、不要なSPI通信を削減している。
MSPM0G3519のSPIペリフェラルは、DL_SPI関数を使用して制御する。
最大80[MHz]の高速動作により、SPIクロックも高速に設定できるが、ENC28J60の最大速度 (20[MHz]) を超えないように注意する必要がある。
// enc28j60.c : ENC28J60ドライバ (MSPM0G3519 uIP用)
#include "ti_msp_dl_config.h" // MSPM0G3519 SDK設定
#include "enc28j60.h"
#include <stdint.h>
// ENC28J60レジスタアドレス定義
#define ERDPTL 0x00
#define ERDPTH 0x01
#define EWRPTL 0x02
#define EWRPTH 0x03
#define ETXSTL 0x04
#define ETXSTH 0x05
#define ETXNDL 0x06
#define ETXNDH 0x07
#define ERXSTL 0x08
#define ERXSTH 0x09
#define ERXNDL 0x0A
#define ERXNDH 0x0B
#define ERXRDPTL 0x0C
#define ERXRDPTH 0x0D
// EIE : イーサネット割り込み有効レジスタ
#define EIE 0x1B
#define INTIE 0x80
#define PKTIE 0x40
// EIR : イーサネット割り込み要求レジスタ
#define EIR 0x1C
#define PKTIF 0x40
#define TXIF 0x08
// ECON1 : イーサネット制御レジスタ1
#define ECON1 0x1F
#define TXRTS 0x08
#define RXEN 0x04
// ECON2 : イーサネット制御レジスタ2
#define ECON2 0x1E
#define AUTOINC 0x80
#define PKTDEC 0x40
// ESTAT : イーサネットステータスレジスタ
#define ESTAT 0x1D
#define CLKRDY 0x01
// MACON1 : MACコントロールレジスタ1
#define MACON1 0x00 | 0x40
#define MARXEN 0x01
#define TXPAUS 0x08
#define RXPAUS 0x04
// MACON3 : MACコントロールレジスタ3
#define MACON3 0x02 | 0x40
#define PADCFG0 0x20
#define TXCRCEN 0x10
#define FRMLNEN 0x02
#define FULDPX 0x01
// MAMXFLL/MAMXFLH : 最大フレーム長レジスタ
#define MAMXFLL 0x0A | 0x40
#define MAMXFLH 0x0B | 0x40
// MABBIPG : バックツーバックパケット間隙レジスタ
#define MABBIPG 0x04 | 0x40
// MAIPGL/MAIPGH : 非バックツーバックパケット間隙レジスタ
#define MAIPGL 0x06 | 0x40
#define MAIPGH 0x07 | 0x40
// SPIコマンド定義
#define ENC28J60_READ_CTRL_REG 0x00
#define ENC28J60_WRITE_CTRL_REG 0x40
#define ENC28J60_BIT_FIELD_SET 0x80
#define ENC28J60_BIT_FIELD_CLR 0xA0
#define ENC28J60_READ_BUF_MEM 0x3A
#define ENC28J60_WRITE_BUF_MEM 0x7A
#define ENC28J60_SOFT_RESET 0xFF
// バンク選択
#define BANK0 0x00
#define BANK1 0x01
#define BANK2 0x02
#define BANK3 0x03
// チップセレクト制御 (実際のピン定義に応じて変更)
#define ENC28J60_CS_LOW() DL_GPIO_clearPins(GPIO_ENC_CS_PORT, GPIO_ENC_CS_PIN)
#define ENC28J60_CS_HIGH() DL_GPIO_setPins(GPIO_ENC_CS_PORT, GPIO_ENC_CS_PIN)
static uint8_t current_bank = 0;
// 遅延関数 (80[MHz]駆動の場合)
static void delay_us(uint32_t us)
{
// 80[MHz] = 80 cycles/us
// 簡易的な遅延実装のため、精度が必要な場合はタイマの割り込みを使用する
volatile uint32_t cycles = us * 80;
while (cycles--);
}
// SPIバイト送受信
static uint8_t spi_transfer(uint8_t data)
{
// SPIにデータ送信
DL_SPI_transmitData8(SPI_0_INST, data);
// 送信完了待ち
while (DL_SPI_isBusy(SPI_0_INST));
// 受信データ取得
return DL_SPI_receiveData8(SPI_0_INST);
}
// バンク選択
static void enc28j60_set_bank(uint8_t address)
{
uint8_t bank = (address & 0x60) >> 5;
if (bank != current_bank) {
// EIE、EIR、ESTAT、ECON1、ECON2は全バンク共通
enc28j60_write_op(ENC28J60_BIT_FIELD_CLR, ECON1, 0x03);
enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON1, bank);
current_bank = bank;
}
}
// レジスタ読み出し
uint8_t enc28j60_read(uint8_t address)
{
uint8_t data;
enc28j60_set_bank(address);
ENC28J60_CS_LOW();
spi_transfer(ENC28J60_READ_CTRL_REG | (address & 0x1F));
// MACおよびMIIレジスタの場合、ダミーバイトを読む
if (address & 0x80) {
spi_transfer(0x00);
}
data = spi_transfer(0x00);
ENC28J60_CS_HIGH();
return data;
}
// レジスタ書き込み
void enc28j60_write(uint8_t address, uint8_t data)
{
enc28j60_set_bank(address);
ENC28J60_CS_LOW();
spi_transfer(ENC28J60_WRITE_CTRL_REG | (address & 0x1F));
spi_transfer(data);
ENC28J60_CS_HIGH();
}
// ビット操作 (OR)
void enc28j60_write_op(uint8_t op, uint8_t address, uint8_t data)
{
ENC28J60_CS_LOW();
spi_transfer(op | (address & 0x1F));
spi_transfer(data);
ENC28J60_CS_HIGH();
}
// バッファメモリ読み出し
void enc28j60_read_buffer(uint8_t *buffer, uint16_t len)
{
ENC28J60_CS_LOW();
spi_transfer(ENC28J60_READ_BUF_MEM);
while (len--) {
*buffer++ = spi_transfer(0x00);
}
ENC28J60_CS_HIGH();
}
// バッファメモリ書き込み
void enc28j60_write_buffer(const uint8_t *buffer, uint16_t len)
{
ENC28J60_CS_LOW();
spi_transfer(ENC28J60_WRITE_BUF_MEM);
while (len--) {
spi_transfer(*buffer++);
}
ENC28J60_CS_HIGH();
}
// ENC28J60初期化
void enc28j60_init(const uint8_t *mac_address)
{
// ソフトウェアリセット
ENC28J60_CS_LOW();
spi_transfer(ENC28J60_SOFT_RESET);
ENC28J60_CS_HIGH();
delay_us(1000); // 1[ms]待機
// クロックが安定するまで待機
while (!(enc28j60_read(ESTAT) & CLKRDY));
// 受信バッファ設定 (0x0000 - 0x19FF)
enc28j60_write(ERXSTL, 0x00);
enc28j60_write(ERXSTH, 0x00);
enc28j60_write(ERXNDL, 0xFF);
enc28j60_write(ERXNDH, 0x19);
enc28j60_write(ERXRDPTL, 0x00);
enc28j60_write(ERXRDPTH, 0x00);
// 送信バッファ設定 (0x1A00 - 0x1FFF)
enc28j60_write(ETXSTL, 0x00);
enc28j60_write(ETXSTH, 0x1A);
enc28j60_write(ETXNDL, 0xFF);
enc28j60_write(ETXNDH, 0x1F);
// MAC初期化
enc28j60_write(MACON1, MARXEN); // MAC受信有効
enc28j60_write(MACON3, PADCFG0 | TXCRCEN | FRMLNEN); // パディング、CRC、フレーム長チェック有効
enc28j60_write(MAMXFLL, 0xEE); // 最大フレーム長1518バイト
enc28j60_write(MAMXFLH, 0x05);
enc28j60_write(MABBIPG, 0x12); // バックツーバック間隔
enc28j60_write(MAIPGL, 0x12); // 非バックツーバック間隔
enc28j60_write(MAIPGH, 0x0C);
// MACアドレス設定
enc28j60_write(0x00 | 0x60, mac_address[0]); // MAADR1
enc28j60_write(0x01 | 0x60, mac_address[1]); // MAADR2
enc28j60_write(0x02 | 0x60, mac_address[2]); // MAADR3
enc28j60_write(0x03 | 0x60, mac_address[3]); // MAADR4
enc28j60_write(0x04 | 0x60, mac_address[4]); // MAADR5
enc28j60_write(0x05 | 0x60, mac_address[5]); // MAADR6
// 割り込み有効化
enc28j60_write(EIE, INTIE | PKTIE);
// 受信有効化
enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON1, RXEN);
}
// パケット送信
void enc28j60_packet_send(const uint8_t *buffer, uint16_t len)
{
// 送信バッファポインタ設定
enc28j60_write(EWRPTL, 0x00);
enc28j60_write(EWRPTH, 0x1A);
// 制御バイト書き込み (0x00 = デフォルト設定)
enc28j60_write_buffer((uint8_t[]){0x00}, 1);
// パケットデータ書き込み
enc28j60_write_buffer(buffer, len);
// 送信終了ポインタ設定
enc28j60_write(ETXNDL, (0x1A00 + len) & 0xFF);
enc28j60_write(ETXNDH, ((0x1A00 + len) >> 8) & 0xFF);
// 送信開始
enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON1, TXRTS);
// 送信完了待ち (オプション)
while (enc28j60_read(ECON1) & TXRTS);
}
// パケット受信
uint16_t enc28j60_packet_receive(uint8_t *buffer, uint16_t max_len)
{
uint16_t len = 0;
uint8_t header[6];
// 受信パケットがあるかどうかを確認
if (enc28j60_read(0x19) == 0) { // EPKTCNT
return 0;
}
// ヘッダ読み出し (次のパケットポインタ、ステータス、長さ)
enc28j60_read_buffer(header, 6);
// パケット長取得 (ヘッダ内の長さフィールド)
len = (header[3] << 8) | header[2];
len -= 4; // CRCを除く
// バッファに収まる場合のみデータを読み出し
if (len <= max_len) {
enc28j60_read_buffer(buffer, len);
}
// 次のパケットポインタ更新
uint16_t next_packet = (header[1] << 8) | header[0];
enc28j60_write(ERXRDPTL, next_packet & 0xFF);
enc28j60_write(ERXRDPTH, (next_packet >> 8) & 0xFF);
// パケットデクリメント
enc28j60_write_op(ENC28J60_BIT_FIELD_SET, ECON2, PKTDEC);
return (len <= max_len) ? len : 0;
}
メイン処理
メイン処理では、MSPM0G3519の初期化、uIPの初期化等を記述する。
MSPM0G3519のタイマペリフェラルを使用して、uIPに必要な周期的なタイマイベントを生成する。
uIPは、TCPの再送タイマやARPのタイムアウト管理のために、定期的なタイマ処理を必要とする。
一般的には、TCPタイマは500[ms]周期、ARPタイマは10秒周期で処理する。
MSPM0G3519の80MHz動作により、uIPのTCP/IP処理は非常に高速に実行できる。
また、128[KB]のRAMにより、uIPのバッファやスタック変数に十分なメモリを割り当てることができる。
// main.c : メイン処理 (MSPM0G3519 uIP)
#include "ti_msp_dl_config.h"
#include "uip.h"
#include "uip_arp.h"
#include "enc28j60.h"
#include <stdint.h>
// MACアドレス定義
#define MAC_ADDR0 0x02
#define MAC_ADDR1 0x00
#define MAC_ADDR2 0x00
#define MAC_ADDR3 0x00
#define MAC_ADDR4 0x00
#define MAC_ADDR5 0x01
// グローバルバッファ (uIP用)
// MSPM0G3519は128KB RAMを持つため、余裕を持ったバッファサイズを確保可能
uint8_t uip_buf[UIP_BUFSIZE + 2];
// タイマ管理用
volatile uint16_t periodic_timer = 0;
volatile uint16_t arp_timer = 0;
// タイマ割り込みハンドラ (10ms周期想定)
void TIMER_0_INST_IRQHandler(void)
{
periodic_timer++;
arp_timer++;
}
// uIP周期処理
void uip_periodic_handler(void)
{
int i;
// TCP接続の周期処理 (500[ms] = 50 × 10[ms])
if (periodic_timer >= 50) {
periodic_timer = 0;
for (i = 0; i < UIP_CONNS; i++) {
uip_periodic(i);
if (uip_len > 0) {
uip_arp_out(); // ARPテーブル参照してイーサネットヘッダ追加
enc28j60_packet_send(uip_buf, uip_len);
}
}
// UDP接続の周期処理
#if UIP_UDP
for (i = 0; i < UIP_UDP_CONNS; i++) {
uip_udp_periodic(i);
if (uip_len > 0) {
uip_arp_out();
enc28j60_packet_send(uip_buf, uip_len);
}
}
#endif
}
// ARPタイマー処理 (10秒 = 1000 × 10ms)
if (arp_timer >= 1000) {
arp_timer = 0;
uip_arp_timer();
}
}
// メイン関数
int main(void)
{
uip_ipaddr_t ipaddr;
uint8_t mac_address[6] = {MAC_ADDR0, MAC_ADDR1, MAC_ADDR2, MAC_ADDR3, MAC_ADDR4, MAC_ADDR5};
// MSPM0G3519ペリフェラル初期化
SYSCFG_DL_init();
// タイマ割り込み有効化
NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN);
// ENC28J60初期化
enc28j60_init(mac_address);
// uIP初期化
uip_init();
// IPアドレス設定
uip_ipaddr(ipaddr, UIP_IPADDR0, UIP_IPADDR1, UIP_IPADDR2, UIP_IPADDR3);
uip_sethostaddr(ipaddr);
// ネットマスク設定
uip_ipaddr(ipaddr, UIP_NETMASK0, UIP_NETMASK1, UIP_NETMASK2, UIP_NETMASK3);
uip_setnetmask(ipaddr);
// デフォルトゲートウェイ設定
uip_ipaddr(ipaddr, UIP_DRIPADDR0, UIP_DRIPADDR1, UIP_DRIPADDR2, UIP_DRIPADDR3);
uip_setdraddr(ipaddr);
// ARPテーブル初期化
uip_arp_init();
// HTTPサーバ起動 (ポート80)
uip_listen(HTONS(80));
// グローバル割り込み有効化
__enable_irq();
// メインループ
while (1) {
// パケット受信チェック
uip_len = enc28j60_packet_receive(uip_buf, UIP_BUFSIZE);
if (uip_len > 0) {
// イーサネットヘッダ解析
struct uip_eth_hdr *eth_hdr = (struct uip_eth_hdr *)&uip_buf[0];
if (eth_hdr->type == htons(UIP_ETHTYPE_IP)) {
// IPパケット処理
uip_arp_ipin(); // ARPテーブル更新
uip_input(); // uIPへ入力
if (uip_len > 0) {
uip_arp_out(); // ARPテーブル参照
enc28j60_packet_send(uip_buf, uip_len);
}
}
else if (eth_hdr->type == htons(UIP_ETHTYPE_ARP)) {
// ARPパケット処理
uip_arp_arpin();
if (uip_len > 0) {
enc28j60_packet_send(uip_buf, uip_len);
}
}
}
// 周期処理 (TCP再送、ARPタイムアウト等)
uip_periodic_handler();
}
}
初期化フェーズでは、MSPM0G3519のペリフェラル設定、SPI通信の設定、タイマの設定、ENC28J60およびuIPの初期化を実行する。
SYSCFG_DL_init関数は、TI MSPM0 SDKによって自動生成される設定関数であり、クロック、GPIO、SPI、タイマ等のペリフェラルを一括で初期化する。
メイン処理では、受信パケットの確認と周期処理を繰り返し実行する。
受信パケットがある場合、イーサネットヘッダを解析してIPパケットかARPパケットかを判断し、それぞれに適した処理を行う。
- 受信パケットの確認
- IPパケットの場合
- uIPスタックに渡して、TCP/UDP処理を行う。
- MSPM0G3519の80MHz動作により、TCP/IP処理は非常に高速に実行される。
- ARPパケットの場合
- ARPテーブルの更新や応答を行う。
- 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ページを送信する。
MSPM0G3519の豊富なフラッシュメモリ (512[KB]) により、複数のHTMLページや画像データを格納することも可能である。
実務では、URLに応じて異なるコンテンツを返したり、ADCで取得したセンサデータを含む動的なページを生成することも可能である。
uIPは限られたRAMで動作するように設計されているが、MSPM0G3519の128[KB] RAMにより、比較的大きなコンテンツも扱うことができる。
ただし、非常に大きなコンテンツを扱う場合は、分割送信やフラッシュROMからの直接読み出し等の工夫が推奨される。
// httpd.c : HTTPサーバ実装
#include "uip.h"
#include <string.h>
// HTTPレスポンス (HTMLページ)
// MSPM0G3519のフラッシュROM (512[KB]) により、複数のページを格納可能
const char http_response[] =
"HTTP/1.0 200 OK\r\n"
"Content-Type: text/html\r\n"
"Connection: close\r\n"
"\r\n"
"<!DOCTYPE html>"
"<html>"
"<head>"
" <title>MSPM0G3519 uIP Server</title>"
" <meta charset=\"UTF-8\">"
" <style>"
" body { font-family: Arial, sans-serif; margin: 40px; background-color: #f5f5f5; }"
" .container { max-width: 800px; margin: 0 auto; background-color: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }"
" h1 { color: #c00; border-bottom: 2px solid #c00; padding-bottom: 10px; }"
" .specs { background-color: #f9f9f9; padding: 15px; border-left: 4px solid #c00; margin: 20px 0; }"
" .specs h2 { margin-top: 0; color: #333; }"
" ul { line-height: 1.8; }"
" .footer { margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd; text-align: center; color: #666; font-size: 0.9em; }"
" </style>"
"</head>"
"<body>"
" <div class=\"container\">"
" <h1>Welcome to MSPM0G3519 uIP Server!</h1>"
" <div class=\"specs\">"
" <h2>System Specifications</h2>"
" <ul>"
" <li><strong>MCU:</strong> MSPM0G3519 (ARM Cortex-M0+)</li>"
" <li><strong>Clock Speed:</strong> 80 MHz</li>"
" <li><strong>Flash Memory:</strong> 512 KB (Dual-bank with ECC)</li>"
" <li><strong>SRAM:</strong> 128 KB</li>"
" <li><strong>Ethernet Controller:</strong> ENC28J60 (10 Mbps)</li>"
" <li><strong>TCP/IP Stack:</strong> uIP (Lightweight Implementation)</li>"
" </ul>"
" </div>"
" <p>This embedded web server demonstrates the power of the MSPM0G3519 microcontroller combined with the uIP TCP/IP stack.</p>"
" <p>The MSPM0G3519 offers excellent performance with its 80 MHz ARM Cortex-M0+ core and generous memory resources, making it ideal for IoT and embedded networking applications.</p>"
" <div class=\"footer\">"
" <p>© 2025 MSPM0G3519 Embedded Web Server</p>"
" </div>"
" </div>"
"</body>"
"</html>";
// HTTPアプリケーション処理
void httpd_appcall(void)
{
// 新規接続時
if (uip_connected()) {
// 特に処理なし (GETリクエスト待ち)
}
// データ受信時
if (uip_newdata()) {
// 簡易的にGETリクエストをチェック
// より高度な実装では、HTTPヘッダの完全なパースを行う
if (strncmp((char *)uip_appdata, "GET ", 4) == 0) {
// HTTPレスポンス送信
uip_send((void *)http_response, sizeof(http_response) - 1);
}
}
// 再送要求時
if (uip_rexmit()) {
uip_send((void *)http_response, sizeof(http_response) - 1);
}
// 接続クローズ時
if (uip_closed() || uip_aborted() || uip_timedout()) {
// 特に処理なし
// 必要に応じてクリーンアップ処理を追加
}
}
MSPM0G3519の豊富なリソースを活かすことにより、高度な機能を追加することも可能である。
例えば、以下のような拡張が考えられる。
- URLルーティング
- リクエストされたURLに応じて、異なるHTMLページを返す。
- 複数のページを用意し、ナビゲーション機能を実装する。
- 動的コンテンツ生成
- ADCで取得したセンサデータをリアルタイムでWebページに表示する。
- GPIO状態を読み取り、LEDの点灯状態等をWebページに反映する。
- フォーム処理
- POSTリクエストを処理し、Webブラウザから設定値を変更する。
- LEDの制御、PWMのデューティ比設定等をWebインターフェースから行う。
- JSON API
- REST APIを実装し、JSON形式でデータを送受信する。
- IoTデバイスとしての機能を強化する。
SPI通信の速度
MSPM0G3519のSPI0は最大32[Mbit/s]に対応しているが、ENC28J60の最大速度は20[MHz]である。
そのため、実際の動作速度はENC28J60の制限となる。
SPIクロック周波数は、MSPM0G3519のクロック設定により調整可能である。
通信が不安定な場合は、SPIクロック周波数を下げて (例: 10[MHz]、5[MHz]) 通信の安定性を確認することを推奨する。
配線長やノイズの影響により、実際の動作速度は制限される場合がある。
DMAの活用
MSPM0G3519の12チャネルDMAコントローラを使用することで、CPUの負荷を軽減しつつ、高速なデータ転送が可能となる。
特に、大きなパケットを連続的に送受信する場合、DMAの活用により大幅なパフォーマンス向上が期待できる。
DMAを使用する場合は、以下の点に注意する必要がある。
- DMAチャネルの割り当て
- 他のペリフェラルとDMAチャネルが競合しないように設定する。
- MSPM0G3519は12チャネルのDMAを持つため、柔軟に割り当てが可能である。
- バッファアライメント
- DMA転送用のバッファは、適切なアライメント (4バイト境界等) に配置する。
- 割り込みハンドリング
- DMA転送完了割り込みを適切に処理する。
- DMA転送完了後に、次の処理 (パケット受信処理等) を行う。
CAN-FDインターフェースの活用
MSPM0G3519は2個のCAN-FDインターフェースを搭載しているため、イーサネット通信とCAN通信を組み合わせたゲートウェイ応用が可能である。
例えば、以下のような応用が考えられる。
- CANバスとイーサネットを接続するゲートウェイ
- 車載ネットワークやFA機器のデータをイーサネット経由でクラウドに送信
- CANデータのロギングとWeb表示
- CANバスのデータをリアルタイムでWebブラウザに表示
- イーサネット経由でのCAN制御
- Webブラウザから車載機器やFA機器を制御
参考資料
- MSPM0G3519データシート
- MSPM0 SDK
- MSPM0 G-Series 80-MHz Microcontrollers Technical Reference Manual
- ENC28J60データシート
- lwIP公式ドキュメント
- lwIP Github
- RFC 793
- Transmission Control Protocol (TCP)
- RFC 826
- An Ethernet Address Resolution Protocol (ARP)
- RFC 2131
- Dynamic Host Configuration Protocol (DHCP)