lwIP は open source の TCP/IP プロトコルスタックで、組み込みシステム用に設計され広く使われている。 lwIP の概要は Wikipedia に任せることにして、ここでは bare-metal で M16C に実装するための作業の概要を述べる。
lwIP-1.4.1 および contrib-1.4.1 はhttp://download.savannah.gnu.org/releases/lwip/ からダウンロードできます。
contrib-1.4.1 は実際には使いませんが、lwIP を実装するにあたってはこの中に含まれる lwip-contrib/ports/unix/proj/minimal のプログラムを参考にしています。lwIP-1.4.1 は HEW のプロジェクトワークスペース app.hws が存在するディレクトリに lwip-1.4.1 ディレクトリ以下をそのまま配置します。ディレクトリ構造は以下のようになります。
Workspaces | app.hws |
app (directory) | |
lwip-1.4.1 (directory) |
lwip-1.4.1 以下のファイルを、ソースコードポータビリティを保ったままインクルードするため HEW の環境を設定します。(これを行わないと lwip-1.4.1 以下のインクルードファイルのほとんどを手で変更しなくてはならなくなります) HEW の Build メニューから KPIT GNU Toolchain を選択します。 C/C++ タブで Category Source を選択し、Show entiries for: で Include file directories を選択します。すでに KPIT GNU Toolchain のシステムインクルードパスが設定されているはずなので、 Add... ボタンを押し以下のディレクトリを追加します。
${WORKSPACEDIR}\lwip-1.4.1\src\include
${WORKSPACEDIR}\lwip-1.4.1\src\include\ipv4
${WORKSPACEDIR}\app
HEW プロジェクトワークスペースに対して lwip-1.4.1/src 以下のソースファイルを追加します。実際に追加するファイルは
..\lwip-1.4.1\src\core\ipv4\*.c
..\lwip-1.4.1\src\core\*.c
..\lwip-1.4.1\src\api\*.c
..\lwip-1.4.1\src\netif\*.c
lwip の実際の機能やパラメータは lwipopts.h によって制御します。このヘッダファイルは app ディレクトリに置きます。実際の app/lwipopts.h ファイルは以下のようになります。
/**
* NO_SYS==1: Provides VERY minimal functionality. Otherwise,
* use lwIP facilities.
*/
#define NO_SYS 1
#define MEM_ALIGNMENT 2
#define MEM_SIZE 15000
#define MEMP_NUM_PBUF 30
... 以下省略 .. See lwip-contrib/ports/unix/proj/minimal/lwipopts.h as reference.
今回は UDP のみ使用しますので、LWIP_UDP=1 の指定に加えて RFC1122 勧告に従って ICMP プロトコルと ARP プロトコルを合わせて実装する (LWIP_ICMP=1, LWIP_ARP=1)とます。ICMP プロトコルは IP 標準で実装が義務付けられているのみならず、ping コマンドによる初期のファームウエア作動確認、応答遅延の監視などに効果を発揮します。
ethif.c として仮想 netif を作成し、lwip が定義する netif 構造体に対して ethif.c で定義されているハンドラを設定することにより lwip は ethernet (SMSC9210) ハードウエアの実態を呼び出すことができる。
M16C は、アドレッシングが 16bit のプロセッサであるが、ROM 領域が 20bit 長確保されてえいる。 今回使用している gcc 4.5 M32C ターゲットコンパイラでは関数ポインタが 16bit 長でしか宣言できないため、コードサイズが 64kb を超える場合呼び出し先が不定となる恐れがある。
今回、ファームウエアに実装されているネットワークインタフェースは SMSC9210 一つだけであるので、関数ポインタによる呼び出しではなく直接 ethif.c で定義した関数を呼び出すよう lwip 内部を変更した。この制限は、gcc 4.7 ベースの M32C ターゲットコンパイラでは解決済みである。
実際に使用した ethif.c, smsc9210.c およびヘッダファイルは本 Wiki の File Galleries に配置してあるので参照されたい。
bare-metal (OS なし)での組み込みシステムであるため Barkley socket ではなく lwip が定義した軽量の api を用い必要な数の UDP ソケットを生成する。今回は接続の開始にファームウエア側で UDP PORT=7000 へのブロードキャストを使用するため、実際の lwip api コードは以下のようになる。
#include "lwip/udp.h"
extern struct udp_pdb * udp;
if ( udp = udp_new() ) {
udp_bind( udp, IP_ADDR_ANY, 7000 );
ip_set_option( udp, SOF_BROADCAST );
udp_recv( udp, app_udp_recv, 0 );
}
この呼び出しにより、UDP PORT=7000 への受信データは app_udp_recv 関数に引き渡される。
while (1) { int i; for ( i = 0; i < NUM_PERIODIC_PROCESS; ++i ) { struct periodic_process * proc = &periodic[ i ]; while ( proc->nque && proc->process ) { proc->nque--; proc->process(); } } struct pbuf * pbuf = reactor_dispatch( reactor_pop(), &__lifecycle ); if ( pbuf ) { udp_sendto( udp, pbuf, &addr_cli, 8000 ); pbuf_free( pbuf ); } ethif_poll( eth0 ); }
上記が実際のメインループのコードである。最初の for() ループは、アプリケーションが指定した周期起動呼び出し要求を処理している。セマフォが使用できないため、割り込み処理よって受信されるUDP データは lwip のメモリ管理機構を用いてキャッシュされ reactor_dispatch によってこのメインループ内で処理を行う。これにより複雑な同期管理を回避している。
今回、実際の周期起動処理は 5 つ定義しており以下の様に初期化している。
app_install_periodic_process( 0, 100, &sys_check_timeouts ); // for lwIP aoo_install_periodic_process( 1, 100, &app_command_periodic_voltage_update ); app_install_periodic_process( 2, 250, &app_command_periodic_voltage_readout ); app_install_periodic_process( 3, 5000, &app_periodic_hartbeat ); // hart beat app_install_periodic_process( 4, 1000, &app_periodic_ad_conversion ); // hart beat enable_timer_a0();