LwIP は、メインループ モード ("NO_SYS") (ターゲット システムで OS/RTOS が実行されていない) と OS モード (TCPIP スレッド) (ターゲット システムで OS が実行されている) の 2 つの基本モードで使用できます。mainloop モードでは、raw API のみを使用できます。OSモードでは、raw APIとSequential APIを使用できます。
OS モードでは、lwip スタックとアプリケーションは別々のタスクで実行されます。このアプリケーションは、プロセス間通信のためにRTOSメールボックスメカニズムを訴えるシーケンシャルAPI呼び出しを介してLwIPスタックと通信します。
この投稿では、MCUXpresso SDKのシーケンシャルAPIを使用してOSモードでLwIPアプリケーションを設計する方法に焦点を当てています。LwIP初心者向けです。コード snipperts は MCUXpresso SDK2.6 からのものです。
raw APIを使用してメインループモード(ベアメタルモード)でLwIPアプリケーションを設計する方法については、以下のリンクを参照してください。
Raw API を使用した LwIP アプリケーションの開発
一般に、LwIP アプリケーションには、ネットワーク インターフェイスの設定、LwIP スタックの初期化、LwIP API の使用、および構成が含まれます。
1. ネットワークインターフェースの起動
新しいネットワークインターフェースを作成するには、ユーザーは新しい 構造体netif にスペースを割り当て(ただし、その一部は初期化しません)、 netifapi_netif_addを呼び出します。
IP4_ADDR(&fsl_netif0_ipaddr, configIP_ADDR0, configIP_ADDR1, configIP_ADDR2, configIP_ADDR3);
IP4_ADDR(&fsl_netif0_netmask, configNET_MASK0, configNET_MASK1, configNET_MASK2, configNET_MASK3);
IP4_ADDR(&fsl_netif0_gw, configGW_ADDR0, configGW_ADDR1, configGW_ADDR2, configGW_ADDR3);
netifapi_netif_add(&fsl_netif0, &fsl_netif0_ipaddr, &fsl_netif0_netmask, &fsl_netif0_gw, &fsl_enet_config0,
ethernetif0_init, tcpip_input);
tcpip_input API を netif_add API に入力コールバック関数として渡します。このコールバック関数は、入力パケットをプロトコル層スタックに渡します
次に、インターフェースを有効にする必要があります
「アップ」状態のインターフェースはアプリケーションで入力と出力に使用でき、「ダウン」状態はその反対の状態です。したがって、インターフェースを使用する前に、起動する必要があります。これは、インターフェースがIPアドレスを取得する方法に応じて達成できます。静的IPアドレスまたはDHCPを使用することができます。
ネットワーク・インターフェースをデフォルトのネットワーク・インターフェースとして設定します
netifapi_netif_set_default(&fsl_netif0);
インターフェースを起動し、処理が可能な状態にします
netifapi_netif_set_up(&fsl_netif0);
2. LwIPスタックの初期化
tcpip_threadを作成するために tcpip_init を呼び出すと、このスレッドはLwIPコア関数に排他的にアクセスできます。他のスレッドは、メッセージ ボックスを使用してこのスレッドと通信します。また、すべてのタイマーを開始して、適切なスレッドコンテキストで実行されていることを確認します。
tcpip_init(NULL, NULL);
void tcpip_init(tcpip_init_done_fn initfunc, void *arg)
{
lwip_init();
tcpip_init_done = initfunc;
tcpip_init_done_arg = arg;
if (sys_mbox_new(&tcpip_mbox, TCPIP_MBOX_SIZE) != ERR_OK) {
LWIP_ASSERT ("mbox の作成に失敗しましたtcpip_thread", 0);
}
#if LWIP_TCPIP_CORE_LOCKING
if (sys_mutex_new(&lock_tcpip_core) != ERR_OK) {
LWIP_ASSERT ("lock_tcpip_coreの作成に失敗しました", 0);
}
#endif /* LWIP_TCPIP_CORE_LOCKING */
sys_thread_new(TCPIP_THREAD_NAME, tcpip_thread, NULL, TCPIP_THREAD_STACKSIZE, TCPIP_THREAD_PRIO);
}
ユーザータスクの優先度は、tcpip_threadの優先度を超えてはなりません
- lwipopts.h では、tcpip_threadの優先順位
#define TCPIP_THREAD_PRIO 2
3. シーケンシャルAPIの利用
次の図に示すように、クライアント側で TCP 接続を確立する手順は次のとおりです。
サーバー側でTCP接続を確立するための手順は次のとおりです。
ミドルウェア/lwip/contrib/appa/tcpecho/tcpecho.c
スタティックボイド
tcpecho_thread(void *arg)
{
struct netconn *conn, *newconn;
err_t err;
LWIP_UNUSED_ARG(引数);
/* 新しい接続識別子を作成します。
/* 既知のポート番号 7 への接続をバインドします。 */
#if LWIP_IPV6
conn = netconn_new(NETCONN_TCP_IPV6);
netconn_bind(コネチカット州、IP6_ADDR_ANY、7);
#else /* LWIP_IPV6 */
conn = netconn_new(NETCONN_TCP);
netconn_bind(コネチカット州、IP_ADDR_ANY、7);
#endif /* LWIP_IPV6 */
LWIP_ERROR("tcpecho: invalid conn", (conn != NULL), return;);
/* 接続にリスニングモードに入るように指示します。
netconn_listen(conn);
(1) {
/* 新しい接続を取得します。
err = netconn_accept(conn, &newconn);
/*printf("新しい接続 %p\nを受け入れました", newconn);*/
/* 新しい接続を処理します。 */
if (err == ERR_OK) {
構造体 netbuf *buf;
void *data;
u16_t len;
while ((err = netconn_recv(Newconn, &buf)) == ERR_OK) {
/*printf("受信済み\n");*/
do {
netbuf_data(buf, &data, &len);
err = netconn_write(newconn, data, len, NETCONN_COPY);
#if 0
if (err != ERR_OK) {
printf("tcpecho: netconn_write: エラー \"%s\"\n", lwip_strerr(エラー));
}
#endif
} while (netbuf_next(buf) >= 0);
netbuf_delete(buf);
}
/*printf("EOF を取得しました。ループしています\n");*/
/* 接続を閉じ、接続識別子を破棄します。
netconn_close(ニューコン);
netconn_delete(ニューコン);
}
}
}
tcpechoスレッドから、
まず、API netconn_newによってパラメータ NETCONN_TCP を使用して 1 つの新しい TCP 接続が呼び出されました。
#define netconn_new(t) netconn_new_with_proto_and_callback(t, 0, NULL)
構造体 netconn *
netconn_new_with_proto_and_callback(列挙型 netconn_type t、u8_t proto、netconn_callback コールバック)
{
構造体 netconn *conn;
API_MSG_VAR_DECLARE(msg);
API_MSG_VAR_ALLOC_RETURN_NULL(msg);
conn = netconn_alloc(t, コールバック);
if (conn != NULL) {
err_t err;
API_MSG_VAR_REF(msg).msg.n.proto = proto;
API_MSG_VAR_REF(msg).conn = conn;
err = netconn_apimsg(lwip_netconn_do_newconn, &API_MSG_VAR_REF(msg));
if (err != ERR_OK) {
LWIP_ASSERT ("pcb を解放せずに conn を解放", conn->pcb.tcp == NULL);
LWIP_ASSERT("conn には recvmbox がありません", sys_mbox_valid(&conn->recvmbox));
#if LWIP_TCP
LWIP_ASSERT("conn->acceptmbox は存在すべきではありません", !sys_mbox_valid(&conn->acceptmbox));
#endif /* LWIP_TCP */
#if !LWIP_NETCONN_SEM_PER_THREAD
LWIP_ASSERT ("conn にはop_completedがありません", sys_sem_valid(&conn->op_completed));
sys_sem_free(&conn->op_completed);
#endif /* !LWIP_NETCONN_SEM_PER_THREAD */
sys_mbox_free(&conn->recvmbox);
memp_free(MEMP_NETCONN, conn);
API_MSG_VAR_FREE(msg);
return NULL;
}
}
API_MSG_VAR_FREE(msg);
return conn;
}
次に、新しく作成された接続は、API 関数 netconn_bind を呼び出すことによってポート 7 (エコー プロトコル) にバインドされます。
次に、アプリケーションは API 関数 netconn_listen を呼び出して、接続のリッスン プロセスを開始します。
無限の while(1) ループでは、アプリケーションは API 関数 netconn_accept を呼び出して新しい接続を待ちます。この API は、着信接続がない場合にアプリケーション タスクをブロックします。
受信接続がある場合、アプリケーションは API 関数 netconn_recv を呼び出してデータの受信を開始できます。着信データは netbuf で受信されます。
アプリケーションは、netbuf API 関数netbuf_dataを呼び出すことで、受信したデータを取得できます。
err_t
netbuf_data(struct netbuf *buf, void **dataptr, u16_t *len)
{
LWIP_ERROR ("netbuf_data: 無効な buf", (buf != NULL), return ERR_ARG;);
LWIP_ERROR("netbuf_data: invalid dataptr", (dataptr != NULL), return ERR_ARG;);
LWIP_ERROR("netbuf_data: invalid len", (len != NULL), return ERR_ARG;);
if (buf->ptr == NULL) {
return ERR_BUF;
}
*dataptr = buf->ptr->ペイロード;
*len = buf->ptr->len;
return ERR_OK;
}
受信したデータは、API 関数 netconn_write を呼び出すことによって、リモート TCP クライアントに送り返されます (エコーされます)。
Netconn_close と netconn_delete は、それぞれ netconn 接続を閉じて削除するために使用されます
4. LwIPの設定
lwipopts.h は、lwIP とそのすべてのモジュールを完全に設定するために使用できるユーザーファイルです。lwIP が提供するすべてのオプションを定義する必要はありません。オプションを定義しない場合は、デフォルト値が使用されます。したがって、あなたのlwipopts.hlwIP の動作の多くをオーバーライドする方法を提供します。
マルチヘッドモードでは、.0 に #define NO_SYS する必要があります。
evkbimxrt1050_lwip_tcpecho_freertos\source\lwipopts.hを参照してください。
…
#if USE_RTOS
/**
* SYS_LIGHTWEIGHT_PROT==1: 特定のタスク 間保護が必要な場合
* バッファの割り当て、割り当て解除、およびメモリ中の重要な領域
* 割り当てと割り当て解除。
*/
#define SYS_LIGHTWEIGHT_PROT 1
/**
* NO_SYS==0: RTOSを使用
*/
#define NO_SYS 0
/**
* LWIP_NETCONN==1: Netconn API を有効にする (api_lib.c の使用が必要)
*/
#define LWIP_NETCONN 1
/**
* LWIP_SOCKET==1: ソケットAPIを有効にする(sockets.cの使用が必要)
*/
#define LWIP_SOCKET 1
/**
* LWIP_SO_RCVTIMEO==1: ソケット/ネットコンの受信 タイムアウトを有効にし、
※SO_RCVTIMEO加工中です。
*/
#define LWIP_SO_RCVTIMEO 1
…
Daniel,
netif API呼び出しを行う前に、ネットワーク接続がアクティブである必要はありませんか?netif_dhcp_start() を呼び出すときにネットワーク ケーブルが接続されていないのに、後で接続すると、dhcp 状態変数が DHCP_STATE_BOUND になるのが見えません。だから、リンクがアップまたはダウンしたときに知らせるコールバックまたはステータス変数があると思いますが、MCUXpresso SDKまたはLwIPでそれを見つけることができないようです。リンクステータスはどのように検出および報告されますか?
ありがとうございます!