標籤:
0.前言 去年(2013年)的整理了LwIP相關代碼,並在STM32上“裸奔”成功。一直沒有時間深入整理,在這裡借博文整理總結。LwIP的移植過程細節很多,博文也不可能一一詳細解釋個別部分僅僅能點到為止。 【本文要點】 【1】不帶作業系統的LwIP移植,LwIP版本號碼為1.4.1。
【2】MCU為STM32F103VE,網卡為ENC28J60。
【3】移植過程重點描寫敘述ethernetif.c和LwIP宏配置等。 【4】一個簡單的TCP echo範例。
【5】力求簡單。沒有DHCP功能,甚至沒實用到網卡中斷。
【代碼倉庫】 代碼倉庫位於Bitbucket(要源碼請點擊這裡)。博文中不能把每一個細節描寫敘述清楚。很多其它內容請參考代碼倉庫中的詳細代碼。
【硬體說明】 測試平台使用奮鬥版,原理圖請參考代碼倉庫中的DOC目錄。
【參考博文】 學習嵌入式網路是一個循序漸進的過程,從淺入深從簡單到複雜。 【1】ENC28J60學習筆記——學習網卡 【2】STM32NET學習筆記——索引——理解TCPIP協議棧 【3】uIP學習筆記——初次應用協議棧 【4】Yeelink平台使用——遠端控制 RT Thread + LwIP+ STM32——更加實用的做法
1.ethernetif.c的相關改動 儘管LwIP移植過程比較複雜,可是僅僅要結合網卡詳細功能,耐心改動ethernetif.c就可以。ethernetif.c重點實現網卡的三個功能,初始化,發送和接收。
為了更好的配合lwIP,改動了ENC28J60學習筆記中部分驅動函數。
(換句話說,想要從0開始移植LwIP必須對操作網卡很熟悉) 【1】初始化
static voidlow_level_init(struct netif *netif){ struct ethernetif *ethernetif = netif->state; /* set MAC hardware address length */ netif->hwaddr_len = ETHARP_HWADDR_LEN; /* set MAC hardware address */ netif->hwaddr[0] = ‘A‘; netif->hwaddr[1] = ‘R‘; netif->hwaddr[2] = ‘M‘; netif->hwaddr[3] = ‘N‘; netif->hwaddr[4] = ‘E‘; netif->hwaddr[5] = ‘T‘; /* maximum transfer unit */ netif->mtu = 1500; /* device capabilities */ /* don‘t set NETIF_FLAG_ETHARP if this device is not an ethernet one */ netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP; /* Do whatever else is needed to initialize interface. */ enc28j60_init(netif->hwaddr); // 【1】} 【說明】 【1】 enc28j60_init(netif->hwaddr); low_level_init中指定了enc28j60中的網卡地址。
【2】發送
static err_tlow_level_output(struct netif *netif, struct pbuf *p){ struct ethernetif *ethernetif = netif->state; struct pbuf *q; enc28j60_init_send(p->tot_len); //【1】initiate transfer(); #if ETH_PAD_SIZE pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */#endif for(q = p; q != NULL; q = q->next) { /* Send the data from the pbuf to the interface, one pbuf at a time. The size of the data in each pbuf is kept in the ->len variable. */ enc28j60_writebuf( q->payload, q->len ); //【2】send data from(q->payload, q->len); } enc28j60_start_send(); //【3】signal that packet should be sent();#if ETH_PAD_SIZE pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */#endif LINK_STATS_INC(link.xmit); return ERR_OK;} 【說明】 【1】enc28j60_init_send(p->tot_len); 初始化發送緩衝區大小, pbuf結構為一個鏈表,第一個pbuf結構體中的tot_len欄位代表整個乙太網路資料包的大小。
【2】enc28j60_writebuf( q->payload, q->len ); 通過遍曆鏈表把內容填入ENC28J60的緩衝區中。 【3】enc28j60_start_send();啟動網卡發送。
【3】接收
static struct pbuf *low_level_input(struct netif *netif){ struct ethernetif *ethernetif = netif->state; struct pbuf *p, *q; u16_t len; len = enc28j60_packet_getlen(); // 【1】#if ETH_PAD_SIZE len += ETH_PAD_SIZE; /* allow room for Ethernet padding */#endif /* We allocate a pbuf chain of pbufs from the pool. */ p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL); if (p != NULL) {#if ETH_PAD_SIZE pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */#endif for(q = p; q != NULL; q = q->next) { enc28j60_readbuf (q->payload, q->len ); //【2】read data into(q->payload, q->len); } enc28j60_finish_receive(); //【3】acknowledge that packet has been read();#if ETH_PAD_SIZE pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */#endif LINK_STATS_INC(link.recv); } else { enc28j60_finish_receive(); //【4】drop packet(); LINK_STATS_INC(link.memerr); LINK_STATS_INC(link.drop); } return p;} 【說明】 【1】len = enc28j60_packet_getlen(); 獲得網卡中資料包的長度。 【2】enc28j60_readbuf (q->payload, q->len);把網卡中的內容拷貝到記憶體池中。 【3】enc28j60_finish_receive();接收完畢,移動網卡中緩衝區指標。
【4】應用 【1】LwIP網卡硬體初始化調用ethernetif_init就可以,該函數中調用了low_level_init,並指定了網卡輸出函數low_level_output。
【2】一旦網卡有資料進入。應馬上代用ethernetif_input函數。
能夠使用中斷方法或查詢方法。
2.lwipopt.h配置簡述 lwip中的配置選項很的多。瞭解全部的配置很不easy。本博文參考STM32官方的兩個範例總結得到。
#ifndef __LWIPOPTS_H__#define __LWIPOPTS_H__#define SYS_LIGHTWEIGHT_PROT 0#define NO_SYS 1#define NO_SYS_NO_TIMERS 1/* ---------- Memory options ---------- *//* MEM_ALIGNMENT: should be set to the alignment of the CPU for which lwIP is compiled. 4 byte alignment -> define MEM_ALIGNMENT to 4, 2 byte alignment -> define MEM_ALIGNMENT to 2. */#define MEM_ALIGNMENT 4/* MEM_SIZE: the size of the heap memory. If the application will senda lot of data that needs to be copied, this should be set high. */#define MEM_SIZE (5*1024)/* MEMP_NUM_PBUF: the number of memp struct pbufs. If the application sends a lot of data out of ROM (or other static memory), this should be set high. */#define MEMP_NUM_PBUF 10/* MEMP_NUM_UDP_PCB: the number of UDP protocol control blocks. One per active UDP "connection". */#define MEMP_NUM_UDP_PCB 6/* MEMP_NUM_TCP_PCB: the number of simulatenously active TCP connections. */#define MEMP_NUM_TCP_PCB 10/* MEMP_NUM_TCP_PCB_LISTEN: the number of listening TCP connections. */#define MEMP_NUM_TCP_PCB_LISTEN 6/* MEMP_NUM_TCP_SEG: the number of simultaneously queued TCP segments. */#define MEMP_NUM_TCP_SEG 12/* MEMP_NUM_SYS_TIMEOUT: the number of simulateously active timeouts. */#define MEMP_NUM_SYS_TIMEOUT 3/* ---------- Pbuf options ---------- *//* PBUF_POOL_SIZE: the number of buffers in the pbuf pool. */#define PBUF_POOL_SIZE 10/* PBUF_POOL_BUFSIZE: the size of each pbuf in the pbuf pool. */#define PBUF_POOL_BUFSIZE 1500/* ---------- TCP options ---------- */#define LWIP_TCP 1#define TCP_TTL 255/* Controls if TCP should queue segments that arrive out of order. Define to 0 if your device is low on memory. */#define TCP_QUEUE_OOSEQ 0/* TCP Maximum segment size. */#define TCP_MSS (1500 - 40) /* TCP_MSS = (Ethernet MTU - IP header size - TCP header size) *//* TCP sender buffer space (bytes). */#define TCP_SND_BUF (2*TCP_MSS)/* TCP sender buffer space (pbufs). This must be at least = 2 * TCP_SND_BUF/TCP_MSS for things to work. */#define TCP_SND_QUEUELEN (6 * TCP_SND_BUF)/TCP_MSS/* TCP receive window. */#define TCP_WND (2*TCP_MSS)/* ---------- ICMP options ---------- */#define LWIP_ICMP 1/* ---------- DHCP options ---------- *//* Define LWIP_DHCP to 1 if you want DHCP configuration of interfaces. DHCP is not implemented in lwIP 0.5.1, however, so turning this on does currently not work. */#define LWIP_DHCP 0/* ---------- UDP options ---------- */#define LWIP_UDP 1#define UDP_TTL 255/* ---------- Statistics options ---------- */#define LWIP_STATS 0#define LWIP_PROVIDE_ERRNO 1/** * LWIP_NETCONN==1: Enable Netconn API (require to use api_lib.c) */#define LWIP_NETCONN 0/** * LWIP_SOCKET==1: Enable Socket API (require to use sockets.c) */#define LWIP_SOCKET 0#endif /* __LWIPOPTS_H__ */
【詳細說明和改動】 【1】未使用作業系統。全部NO_SYS定義為1。LWIP_NETCONN定義為0(表示不使用),LWIP_SOCKET定義為0(表示不使用)。
【2】NO_SYS_NO_TIMERS定義為1,該定義為LwIP1.4.0以上版本號碼添加。詳細可參考LwIP改動文檔。 【3】LWIP_DHCP被定義為0。關閉了DHCP功能以簡化代碼。
【4】相比STM32官方範例。去除了校正碼相關配置全部使用軟體校正。STM32官方案例中使用了代碼EMAC功能的MCU,該系列MCU中包含硬體校正功能,可是ENC28J60並沒有此功能,所以僅僅能開啟LwIP中的軟體校正功能。
3.LwIP相關初始化
void LwIP_Config (void){ struct ip_addr ipaddr; struct ip_addr netmask; struct ip_addr gw; // 調用LWIP初始化函數 lwip_init(); IP4_ADDR(&ipaddr, 192, 168, 1, 16); // 設定網路介面的ip地址 IP4_ADDR(&netmask, 255, 255, 255, 0); // 子網路遮罩 IP4_ADDR(&gw, 192, 168, 1, 1); // 網關 // 初始化enc28j60與LWIP的介面。參數為網路介面結構體、ip地址、 // 子網路遮罩、網關、網卡資訊指標、初始化函數、輸入函數 netif_add(&enc28j60, &ipaddr, &netmask, &gw, NULL, eernetif_init, eernet_input); // 把enc28j60設定為預設網卡 netif_set_default(&enc28j60); netif_set_up(&enc28j60);} 【說明】 【1】通過netif_add初始化網卡IP地址,子網路遮罩和網關地址。此處使用靜態IP地址。
【2】netif_add須要傳入兩個函數指標,各自是網卡初始化函數和接收內容處理函數。ethernetif_init位於ethernetif.c而ethernet_input並不位於ethernetif.c,此處也不能使用ethernetif_input,事實上ethernet_input在函數ethernetif_input被調用,可是ethernet_input變了一個樣子: netif->input(p, netif)!=ERR_OK 【3】在帶作業系統的移植中。最後一個參數使用tcpip_input。
4.while(1)部分
timer_typedef tcp_timer, arp_timer; /* 設定查詢定時器 ARP定時器 */ timer_set(&tcp_timer, CLOCK_SECOND / 10); // tcp處理定時器 100ms timer_set(&arp_timer, CLOCK_SECOND * 5); // arp處理定時器 5s while (1) { if (enc28j60_packet_getcount() != 0) { ethernetif_input(&enc28j60); } // TCP 定時處理 if (timer_expired(&tcp_timer)) { timer_set(&tcp_timer, CLOCK_SECOND / 4); tcp_tmr(); } // ARP 定時處理 if (timer_expired(&arp_timer)) { timer_set(&arp_timer, CLOCK_SECOND * 5); etharp_tmr(); } } 【說明】 while(1)迴圈包含3個主要功能 【1】一旦接受到資料包,立馬調用 ethernetif_input。此處使用查詢法而不是中斷法(中斷法效果類似) 【2】定期處理TCP連結。定時時間為100ms。可依據情況適當縮小時間間隔。 【3】定期更新ARP緩衝,可依據情況適當擴大時間間隔。 【4】此處的timer通過systick實現。詳細實現請參考代碼倉庫。
4.基本測試 【1】ping實驗 此時網卡的靜態IP地址為192.168.1.16,通過ping指令發送16個資料包 ping 192.168.1.16 -n 16
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveHVrYWk4NzExMDU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" /> 圖1 ping實驗 【2】TCP Echo範例 LwIP提供很多示範範例。TCP Echo示範範例位於contrib-1.4.1的apps目錄中,目錄名為tcpecho_raw)。改動TCP偵聽port為10086。
err = tcp_bind(echo_pcb, IP_ADDR_ANY, 10086);
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveHVrYWk4NzExMDU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" />
圖2 TCP Echo範例
5.總結 【1】移植和應用LwIP一定要耐心仔細。 【2】一旦網卡接收到資料,應調用ethernetif_input函數。調用該函數讓資料進入LwIP協議棧。 【3】 netif_add(&enc28j60, &ipaddr, &netmask, &gw, NULL, ðernetif_init, ðernet_input);最後一個參數為ethernet_input,千萬必要寫成ethernetif_input。
6.參考資料 很多其它細節內容請參考圖書資料 【1】《嵌入式網路系統設計——基於Atmel ARM7系列》 【2】《STM32嵌入式系統開發實戰指南——FreeRTOS與LwIP移植》
著作權聲明:本文博主原創文章,部落格,未經同意,不得轉載。
LwIP學習筆記——STM32 ENC28J60移植與入門