STM32で始めるFreeRTOSとLwIP(5) -LwIPの処理まとめ

下記の記事で、STM32でFreeRTOS＋LwIPのサンプルソースコードを見てきました。

1. [CubeIDEで行うFreeRTOS+LwIPハンズオン](https://qiita.com/tom_S/items/b152bfc8cf572c1a6f04)
1. [サンプルソースにおけるLwIPの初期化処理の解説](https://qiita.com/tom_S/items/c1b1bfc030a67fe7977a)
1. [サンプルソースにおけるLwIPのDHCP Clientの解説](https://qiita.com/tom_S/items/f4552357336bdc1d8fc0)
1. [サンプルソースにおけるLHTTP Serverの解説](https://qiita.com/tom_S/items/9d0343c07acbe107c628)

上記の記事ではLwIPの使い方だけを記載しているわけではないので、この記事ではLwIPの使い方に関してまとめます。
なお、FreeRTOSを使う前提としているので、OSレスでLwIPを使う方法は想定していません。

# LwIPの初期化処理

初期化時に行う処理としては、下記となります。
- TCPIPのプロトコルスタックの初期化
- ネットワークインターフェースの作成
- Ethernetドライバの送信関数、受信関数、接続に変化があった際に呼ばれるコールバック関数を、ネットワークインターフェースに登録

実際に呼び出すLwIPは下記のようになります。

1. tcpipの初期化
    - [tcpip_init()](https://www.nongnu.org/lwip/2_1_x/group__lwip__os.html#ga1f3a88b8df6ba3b9ed1c00e0a305e3db)呼び出し
2. IPアドレスを設定
    - [ip_addr_set_zero_ip4](https://www.nongnu.org/lwip/2_1_x/ip__addr_8h.html#aecb93d575e65f835f1045a4e87e70ed9), [IP_ADDR4](https://www.nongnu.org/lwip/2_1_x/group__ip4addr.html#gadd2203c6cbfb6de8c2d9ce5bbfb2bd2a)等の呼び出し
3. ネットワークインターフェースを作成
    - [netif_add()](https://www.nongnu.org/lwip/2_1_x/group__netif.html#gac90f290edd005238d62aa94c4ac9dea3)呼び出し。第六引数に登録する関数は、Ethernetドライバの受信と送信を行う関数をネットワークインターフェースに登録する処理を実装しておく
4. 作成したネットワークインターフェースをデフォルトで使用するよう設定
    - [netif_set_default()](https://www.nongnu.org/lwip/2_1_x/group__netif.html#gac90f290edd005238d62aa94c4ac9dea3)呼び出し
5. ethernetが接続された際のコールバックを登録
    - [netif_set_link_callback()](https://www.nongnu.org/lwip/2_1_x/group__netif.html#ga94a4c24c988afc0a577387730f303b19)呼び出し

下記に初期化のサンプルを示します。

```C
#include "lwip/netif.h"
#include "lwip/tcpip.h"

void lwIPInitSample()
{
     ip_addr_t ipaddr;
     ip_addr_t netmask;
     ip_addr_t gw;
     struct netif gnetif; /* network interface structure */
    
    
    /* TCP/IPのプロトコルスタックの初期化 */
     tcpip_init(NULL, NULL);
    
    #if LWIP_DHCP
    /* DHCP clientに対応する場合 */
    /* IPアドレス、ネットマスク、ゲートウェイを全て0で初期化 */
     ip_addr_set_zero_ip4(&ipaddr);
     ip_addr_set_zero_ip4(&netmask);
     ip_addr_set_zero_ip4(&gw);
    #else
    /* DHCP clientに対応しない場合 */
    /* ユーザーのIPアドレス、ネットマスク、ゲートウェイを設定 */
     IP_ADDR4(&ipaddr,IP_ADDR0,IP_ADDR1,IP_ADDR2,IP_ADDR3);
     IP_ADDR4(&netmask,NETMASK_ADDR0,NETMASK_ADDR1,NETMASK_ADDR2,NETMASK_ADDR3);
     IP_ADDR4(&gw,GW_ADDR0,GW_ADDR1,GW_ADDR2,GW_ADDR3);
    #endif /* LWIP_DHCP */
    
     /*設定したIPアドレス、ネットマスク、ゲートウェイのネットワークインターフェースを作成 */
     /* ethernet_initはイーサネットドライバで実装したもの */
     netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &tcpip_input);
     /*  作成したネットワークインターフェースをデフォルトで使用するように設定 */
     netif_set_default(&gnetif);
    /* 接続状況に変化があった際のコールバックを登録 */
    /* ethernet_link_status_upodatedはイーサネットドライバで実装したもの */
     netif_set_link_callback(&gnetif, ethernet_link_status_updated);
}
```

# LwIPのDHCP Client処理

主に行うのは、DHCP ClientのAPIを呼び出すのみです。IPアドレスを取得下かどうかに関しては、APIを呼び出して確認します。

1. Ethernetケーブルが接続されている状態で、IPアドレスの設定を要求するパケット(DHCP Discover)を送信する
    - [dhcp_start()](https://www.nongnu.org/lwip/2_1_x/group__dhcp4.html#ga0c50968d9811aa2aa67fadc0885d744f  )を呼び出す
2. DHCPでIPアドレスを取得できたかどうかを確認する。
    - [dhcp_supplied_address()](https://www.nongnu.org/lwip/2_1_x/dhcp_8c.html#ae24a2529372218327ab9cb6592041c85 )を呼び出す。返り値がtrueの場合はIPアドレスが取得できた状態となる

DHCP Offer、DHCP Request、DHCP ACKの処理はLwIP内で行っているため、アプリケーション側から何か別のAPIを呼び出す必要はありません。
下記にDHCP Clientのサンプルを示します。

```C
void DHCP_Thread( struct netif *netif)
{
        /* DHCP Discoverパケットを送信(受信処理はLwIPの内部処理で行う) */
       dhcp_start(netif);
 
       while (!dhcp_supplied_address(netif))
       {
           osDelay(500);
       }
}
```

# LwIPのTCP通信処理

Client側とサーバー側で振る舞いが違うので分けて示します。

## Server型処理

行うこととしては、下記となります。
- 制御用の構造体の作成
- 通信するIPアドレスとポート番号を、制御用構造体と紐づける
- TCPコネクション確立要求待ち
- TCPコネクションの確立
- データ通信

実際のフローとしては下記の通りとなります。

1. TCPの通信制御用の構造体の作成
    - [netconn_new](https://www.nongnu.org/lwip/2_1_x/group__netconn__common.html#gab2bd1d5e633e14e428cfc0ca908d5e0c )を、引数に「NETCONN_TCP」を指定して呼び出す
2. 通信制御用の構造体と、IPアドレスとポート番号を紐づける
    - [netconn_bind](https://www.nongnu.org/lwip/2_1_x/group__netconn__common.html#gaaba260d28d105fb4bce9185fd0300d91 )を、第一引数をnetconn_new()で生成した構造体を指定し、第二引数で通信に使うIPアドレス、第三引数で通信に使うポート番号を指定して呼び出す
3. TCPのコネクションの確立要求を待つ
    - [netconn_listen](https://www.nongnu.org/lwip/2_1_x/group__netconn__tcp.html#ga43e3f4c2169dec0f666c502e490416b7 )を、引数にnetconn_new()で生成した構造体を指定して呼び出す
4. コネクションの確立要求があったデバイスを承認して接続する
    - [netconn_accept](https://www.nongnu.org/lwip/2_1_x/group__netconn__tcp.html#ga13593148f60f7bbc6a505b567f175d69 )を、第一引数をnetconn_new()で生成した構造体を指定し、第二引数で空のnetconn型構造体を指定する。接続先の情報は第二引数で指定したnetconn型構造体に格納される
5. データの送受信を行う
    - [netconn_recv](https://www.nongnu.org/lwip/2_1_x/group__netconn__common.html#ga50490bab058f4e740798beffcf48cabf )と[netconn_write](https://www.nongnu.org/lwip/2_1_x/group__netconn__tcp.html#gafea5e9b90770a7c6cd0762ceb65e5b46 )を使ってデータの送受信を行う
      - netconn_recvは第一引数をnetconn_new()で生成した構造体を指定し、第二引数でnetbuf型のポインタのポインタを指定する。受信データは第二引数で指定したnetbuf型のポインタのポインタが指し示すインスタンスに格納される
      -  netconn_writeは第一引数をnetconn_new()で生成した構造体を指定し、第二引数に送信データのポインタ、第三引数に送信データのサイズを指定する。第四引数では、送信時のオプション設定を指定する。
6. 通信を終了し、TCPのコネクションを切断する(削除は行わない)
    - [netconn_close](https://www.nongnu.org/lwip/2_1_x/group__netconn__tcp.html#ga25bb1c1c9928f91f53149a026e2e2624 )を、引数にnetconn_accept()の第二引数に格納された構造体を指定して呼び出す
7. 現在通信を行っている接続状態を完全に削除する
    - [netconn_delete](https://www.nongnu.org/lwip/2_1_x/group__netconn__common.html#gab411221f912a6d9e4c82ac3963989403 )を、引数にnetconn_accept()の第二引数に格納された構造体を指定して呼び出す

下記にサンプルを示します。

```C
static void server_netconn_thread(void *arg)
{
    struct netconn *conn, *newconn;
    err_t err, accept_err;
    struct netbuf *inbuf;
    err_t recv_err;
    char* buf;
    u16_t buflen;
    
    /* 制御用構造体の作成 */
    conn = netconn_new(NETCONN_TCP);
    if (conn!= NULL)
    {
        /* ポート番号とIPアドレスを紐づけ(ここでは例としてポート番号80のみ指定) */
        err = netconn_bind(conn, NULL, 80);
        if (err == ERR_OK)
        {
            /* TCPコネクション確立要求待ち状態にする */
            netconn_listen(conn);
            while(1)
            {
                /* TCPコネクション確立 */
                accept_err = netconn_accept(conn, &newconn);
                if(accept_err == ERR_OK)
                {
                     /* データ通信(サンプルなので受信データをそのまま返す) */
                     recv_err = netconn_recv(conn, &inbuf);
                     if (netconn_err(conn) == ERR_OK)
                     {
                         netbuf_data(inbuf, (void**)&buf, &buflen);
                     }
                     netconn_write(conn, (const unsigned char*)buf, (size_t)buflen, NETCONN_NOCOPY);
                
                     /* TCPコネクション切断 */
                     netconn_delete(newconn);
                }
            }
        }
    }
}
```



## Client型処理

行うこととしては、下記となります。
- 制御用の構造体の作成
- 通信するIPアドレスとポート番号を、制御用構造体と紐づける
- TCPコネクション確立要求送信
- データ通信

実際のフローとしては下記の通りとなります。

1. 1. TCPの通信制御用の構造体の作成
    - [netconn_new](https://www.nongnu.org/lwip/2_1_x/group__netconn__common.html#gab2bd1d5e633e14e428cfc0ca908d5e0c )を、引数に「NETCONN_TCP」を指定して呼び出す
2. コネクションの確立要求を指定したIPアドレスとポート番号に送る
    - [netconn_connect](https://www.nongnu.org/lwip/2_1_x/group__netconn__common.html#gacba7f74d973872ad0b88b9a96098cf77  )を、第一引数をnetconn_new()で生成した構造体を指定し、第二引数と第三引数で接続先のIPアドレスとポート番号を指定する
3. データの送受信を行う
    - [netconn_recv](https://www.nongnu.org/lwip/2_1_x/group__netconn__common.html#ga50490bab058f4e740798beffcf48cabf )と[netconn_write](https://www.nongnu.org/lwip/2_1_x/group__netconn__tcp.html#gafea5e9b90770a7c6cd0762ceb65e5b46 )を使う
      - netconn_recvは第一引数をnetconn_new()で生成した構造体を指定し、第二引数でnetbuf型のポインタのポインタを指定する。受信データは第二引数で指定したnetbuf型のポインタのポインタが指し示すインスタンスに格納される
      - netconn_writeは第一引数をnetconn_new()で生成した構造体を指定し、第二引数に送信データのポインタ、第三引数に送信データのサイズを指定する。第四引数では、送信時のオプション設定を指定する
4. 通信を終了し、TCPのコネクションを切断する(削除は行わない)
    - [netconn_close](https://www.nongnu.org/lwip/2_1_x/group__netconn__tcp.html#ga25bb1c1c9928f91f53149a026e2e2624 )を、引数にnetconn_accept()の第二引数に格納された構造体を指定して呼び出す
5. 現在通信を行っている接続状態を完全に削除する
    - [netconn_delete](https://www.nongnu.org/lwip/2_1_x/group__netconn__common.html#gab411221f912a6d9e4c82ac3963989403 )を、引数にnetconn_accept()の第二引数に格納された構造体を指定して呼び出す

下記にサンプルを示します。
```C
static void client_netconn_thread(void *arg)
{
    struct netif *netif = (struct netif *) arg;   /* 引数としてnetifをもらう前提 */
    struct netconn *conn;
    err_t err;
    
    
    /* Create a new TCP connection handle */
    conn = netconn_new(NETCONN_TCP);
    
    
    /* DHCPでIPアドレスが設定されるのを待つ */
    while(!dhcp_supplied_address(netif))
    {
        osDelay(100);
    }
    
    if (conn != NULL)
    {
        ip_addr_t targetIpaddr;
        IP_ADDR4(&targetIpaddr,192,168,11,195); /* 例としてIPアドレスを192.168.11.195としている */
        /* create connection */
        while(1)
        {
            err = netconn_connect(conn,&targetIpaddr,1024);
            if (err == ERR_OK)
            {
                break;
            }
            /* wait 500 ms */
            osDelay(500);
        }
        /* send */
        char buf[] = "test";
        netconn_write(conn, (const unsigned char*)(buf), strlen(buf), NETCONN_NOCOPY);
        /* Close the connection (server closes in HTTP) */
        netconn_close(conn);
        /* delete connection */
        netconn_delete(conn);
    }
}
```