LWIP是一款開源的嵌入式網絡協議棧,支持的功能很多,而且能在多任務環境下和單任務裸機環境下跑,今天說說他的移植過程,芯片為STM32,網卡為ENC28J60,無操作系統
首先下載LWIP的源代碼,我下載的是1.4.1的源碼,下載后解壓,文件結構如圖
將這四個目錄中的文件全部拷貝到工程中,API是一些socket通訊的接口,需要在多任務的環境下實現,core里面存放的內核源碼,我們主要使用IPV4,include目錄下是需要包含的目錄,lwip只要求我們包含include目錄,里面的內層目錄會自動找到,最后建立的工程目錄如下
好了,此時源碼已經做好,還有需要做的,在include目錄下新建一個文件夾,必須叫arch,里面存放這幾個文件,自己新建
文件的具體內容如下
cc.h
/* * Copyright (c) 2001-2003 Swedish Institute of Computer Science. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * This file is part of the lwIP TCP/IP stack. * * Author: Adam Dunkels <adam@sics.se> * */ #ifndef __CC_H__ #define __CC_H__ #include "cpu.h" //編譯器無關的數據類型定義 typedef unsigned char u8_t; typedef signed char s8_t; typedef unsigned short u16_t; typedef signed short s16_t; typedef unsigned long u32_t; typedef signed long s32_t; typedef u32_t mem_ptr_t; typedef int sys_prot_t; //lwip調試的時候數據類型定義 #define U16_F "hu" #define S16_F "d" #define X16_F "hx" #define U32_F "u" #define S32_F "d" #define X32_F "x" #define SZT_F "uz" //根據不同的編譯器的符號定義 #if defined (__ICCARM__) #define PACK_STRUCT_BEGIN #define PACK_STRUCT_STRUCT #define PACK_STRUCT_END #define PACK_STRUCT_FIELD(x) x #define PACK_STRUCT_USE_INCLUDES #elif defined (__CC_ARM) #define PACK_STRUCT_BEGIN __packed #define PACK_STRUCT_STRUCT #define PACK_STRUCT_END #define PACK_STRUCT_FIELD(x) x #elif defined (__GNUC__) #define PACK_STRUCT_BEGIN #define PACK_STRUCT_STRUCT __attribute__ ((__packed__)) #define PACK_STRUCT_END #define PACK_STRUCT_FIELD(x) x #elif defined (__TASKING__) #define PACK_STRUCT_BEGIN #define PACK_STRUCT_STRUCT #define PACK_STRUCT_END #define PACK_STRUCT_FIELD(x) x #endif #define LWIP_PLATFORM_ASSERT(x) //do { if(!(x)) while(1); } while(0) #endif /* __CC_H__ */
cpu.h
/* * Copyright (c) 2001-2003 Swedish Institute of Computer Science. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. * * This file is part of the lwIP TCP/IP stack. * * Author: Adam Dunkels <adam@sics.se> * */ #ifndef __CPU_H__ #define __CPU_H__ //定義cpu的數據模式,大端小端 #define BYTE_ORDER LITTLE_ENDIAN #endif /* __CPU_H__ */
perf.h
#ifndef __PERF_H__ #define __PERF_H__ //用於lwip內置的統計功能 //不使能定義為空就可以了 #define PERF_START /* null definition */ #define PERF_STOP(x) /* null definition */ #endif /* __PERF_H__ */
sys_arch.h
#ifndef __SYS_RTXC_H__ #define __SYS_RTXC_H__ void init_lwip_timer(void); //初始化LWIP定時器 u8_t timer_expired(u32_t *last_time,u32_t tmr_interval); //定時器超時判斷 #endif /* __SYS_RTXC_H__ */
sya_arch.c--注意該文件要加入源文件列表中,這是c文件哦
#include "lwip/debug.h" #include "lwip/def.h" #include "lwip/sys.h" #include "lwip/mem.h" #include "timerx.h" //初始化LWIP定時器 void init_lwip_timer(void) { TIM6_Int_Init(1000,719);//100Khz計數頻率,計數到100為10ms } //為LWIP提供計時 extern u32_t lwip_timer;//lwip 計時器,每10ms增加1. u32_t sys_now(void) { return lwip_timer; } //定時器超時判斷 //last_time:最近時間 //tmr_interval:定時器溢出周期 u8_t timer_expired(u32_t *last_time,u32_t tmr_interval) { u32_t time; time = *last_time; if((lwip_timer-time)>=tmr_interval){ *last_time = lwip_timer; return 1; } return 0; }
可以看到我們定義了定時器,那么就要修改相關的定時器文件,文件如下
timerx.c
#include "timerx.h" u32 lwip_timer=0;//lwip 計時器,每10ms增加1. //定時器6中斷服務程序 void TIM6_IRQHandler(void) { if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) //檢查指定的TIM中斷發生與否:TIM 中斷源 { TIM_ClearITPendingBit(TIM6, TIM_IT_Update ); //清除TIMx的中斷待處理位:TIM 中斷源 lwip_timer++;//lwip計時器增加1 } } //基本定時器6中斷初始化 //這里時鍾選擇為APB1的2倍,而APB1為36M //arr:自動重裝值。 //psc:時鍾預分頻數 //這里使用的是定時器3! void TIM6_Int_Init(u16 arr,u16 psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); //時鍾使能 TIM_TimeBaseStructure.TIM_Period = arr; //設置在下一個更新事件裝入活動的自動重裝載寄存器周期的值 計數到5000為500ms TIM_TimeBaseStructure.TIM_Prescaler =psc; //設置用來作為TIMx時鍾頻率除數的預分頻值 10Khz的計數頻率 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //設置時鍾分割:TDTS = Tck_tim TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上計數模式 TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure); //根據TIM_TimeBaseInitStruct中指定的參數初始化TIMx的時間基數單位 TIM_ITConfig( TIM6,TIM_IT_Update|TIM_IT_Trigger,ENABLE);//使能定時器6更新觸發中斷 TIM_Cmd(TIM6, ENABLE); //使能TIMx外設 NVIC_InitStructure.NVIC_IRQChannel = TIM6_IRQn; //TIM3中斷 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占優先級0級 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //從優先級3級 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能 NVIC_Init(&NVIC_InitStructure); //根據NVIC_InitStruct中指定的參數初始化外設NVIC寄存器 }
timerx.h
#ifndef __TIMER_H_ #define __TIMER_H_ #include "sys.h" void TIM6_Int_Init(u16 arr,u16 psc); #endif
好了,這個時候移植就基本完成了,但是編譯是過不了的,因為我們缺少一個配置文件.h,這個文件放哪都可以,只要工程包含這個文件的目錄,另外,該文件名稱不能改動
lwipopts.h
#ifndef __LWIPOPTS_H__ #define __LWIPOPTS_H__ //回環模式 //#define LWIP_HAVE_LOOPIF 1 /* Prevent having to link sys_arch.c (we don't test the API layers in unit tests) */ #define NO_SYS 1 //是否使用操作系統 #define LWIP_NETCONN 0 #define LWIP_SOCKET 0 #define LWIP_DHCP 1 //使能DHCP模塊 #define MEM_ALIGNMENT 4 //必須4字節對齊 曾出現在memset的時候hardfault #define LWIP_DNS 1 #define MEM_SIZE 16000 //使用多大一塊內存做lwip內存 #define TCP_SND_QUEUELEN 40 //tcp發送序列長度 #define MEMP_NUM_TCP_SEG TCP_SND_QUEUELEN #define TCP_MSS 1460 #define TCP_WND (4 * TCP_MSS) #define TCP_SND_BUF (8 * TCP_MSS) #define ETHARP_SUPPORT_STATIC_ENTRIES 1 #endif /* __LWIPOPTS_H__ */
可以看到,我們沒有使用操作系統並且使能了dhcp功能
這時候就可以編譯通過了,但是還有一個必須做的工作,移植網卡的驅動到lwip中,
也就是netif文件夾中的ethernetif.c文件,該文件修改為如下內容
//網卡驅動層文件 #if 1 /* don't build, this is only a skeleton, see previous comment */ #include "lwip/def.h" #include "lwip/mem.h" #include "lwip/pbuf.h" #include <lwip/stats.h> #include <lwip/snmp.h> #include "netif/etharp.h" #include "netif/ppp_oe.h" #include "enc28j60.h" #include "netif/ethernetif.h" #include "string.h" #include "delay.h" /* Define those to better describe your network interface. */ #define IFNAME0 'e' #define IFNAME1 'n' //MAC地址 const u8 mymac[6]={0x99,0x02,0x35,0x04,0x45,0x61}; //MAC地址 //定義發送接受緩沖區 u8 lwip_buf[1500*2]; //返回網卡地址 struct ethernetif { struct eth_addr *ethaddr; /* Add whatever per-interface state that is needed here. */ }; //網卡的初始化 static err_t low_level_init(struct netif *netif) { //mac地址 netif->hwaddr_len = ETHARP_HWADDR_LEN; /* set MAC hardware address */ netif->hwaddr[0] = mymac[0]; netif->hwaddr[1] = mymac[1]; netif->hwaddr[2] = mymac[2]; netif->hwaddr[3] = mymac[3]; netif->hwaddr[4] = mymac[4]; netif->hwaddr[5] = mymac[5]; //最大傳輸單元 netif->mtu = MAX_FRAMELEN; if(ENC28J60_Init((u8*)mymac)) //初始化ENC28J60 { return ERR_IF; //底層網絡接口錯誤 } //指示燈狀態:0x476 is PHLCON LEDA(綠)=links status, LEDB(紅)=receive/transmit //PHLCON:PHY 模塊LED 控制寄存器 ENC28J60_PHY_Write(PHLCON,0x0476); netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP; return ERR_OK; } //從指定網卡輸出一部分數據 static err_t low_level_output(struct netif *netif, struct pbuf *p) { struct pbuf *q; int send_len=0; #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. */ //send data from(q->payload, q->len); memcpy((u8_t*)&lwip_buf[send_len], (u8_t*)q->payload, q->len); send_len +=q->len; } // signal that packet should be sent(); ENC28J60_Packet_Send(send_len,lwip_buf); #if ETH_PAD_SIZE pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */ #endif LINK_STATS_INC(link.xmit); return ERR_OK; } //從指定網卡讀取一幀數據回來 static struct pbuf *low_level_input(struct netif *netif) { // struct ethernetif *ethernetif = netif->state; struct pbuf *p, *q; u16_t len; int rev_len=0; /* Obtain the size of the packet and put it into the "len" variable. */ len = ENC28J60_Packet_Receive(MAX_FRAMELEN,lwip_buf); #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 /* We iterate over the pbuf chain until we have read the entire * packet into the pbuf. */ for(q = p; q != NULL; q = q->next) { /* Read enough bytes to fill this pbuf in the chain. The * available data in the pbuf is given by the q->len * variable. * This does not necessarily have to be a memcpy, you can also preallocate * pbufs for a DMA-enabled MAC and after receiving truncate it to the * actually received size. In this case, ensure the tot_len member of the * pbuf is the sum of the chained pbuf len members. */ //read data into(q->payload, q->len); memcpy((u8_t*)q->payload, (u8_t*)&lwip_buf[rev_len],q->len); rev_len +=q->len; } // 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 { //drop packet(); LINK_STATS_INC(link.memerr); LINK_STATS_INC(link.drop); } return p; } /** * This function should be called when a packet is ready to be read * from the interface. It uses the function low_level_input() that * should handle the actual reception of bytes from the network * interface. Then the type of the received packet is determined and * the appropriate input function is called. * * @param netif the lwip network interface structure for this ethernetif */ void ethernetif_input(struct netif *netif) { // struct ethernetif *ethernetif; struct eth_hdr *ethhdr; struct pbuf *p; // ethernetif = netif->state; /* move received packet into a new pbuf */ p = low_level_input(netif); /* no packet could be read, silently ignore this */ if (p == NULL) return; /* points to packet payload, which starts with an Ethernet header */ ethhdr = p->payload; switch (htons(ethhdr->type)) { /* IP or ARP packet? */ case ETHTYPE_IP: case ETHTYPE_ARP: #if PPPOE_SUPPORT /* PPPoE packet? */ case ETHTYPE_PPPOEDISC: case ETHTYPE_PPPOE: #endif /* PPPOE_SUPPORT */ /* full packet send to tcpip_thread to process */ if (netif->input(p, netif)!=ERR_OK) { LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n")); pbuf_free(p); p = NULL; } break; default: pbuf_free(p); p = NULL; break; } } /** * Should be called at the beginning of the program to set up the * network interface. It calls the function low_level_init() to do the * actual setup of the hardware. * * This function should be passed as a parameter to netif_add(). * * @param netif the lwip network interface structure for this ethernetif * @return ERR_OK if the loopif is initialized * ERR_MEM if private data couldn't be allocated * any other err_t on error */ err_t ethernetif_init(struct netif *netif) { struct ethernetif *ethernetif; LWIP_ASSERT("netif != NULL", (netif != NULL)); ethernetif = mem_malloc(sizeof(struct ethernetif)); if (ethernetif == NULL) { LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_init: out of memory\n")); return ERR_MEM; } #if LWIP_NETIF_HOSTNAME /* Initialize interface hostname */ netif->hostname = "lwip"; #endif /* LWIP_NETIF_HOSTNAME */ /* * Initialize the snmp variables and counters inside the struct netif. * The last argument should be replaced with your link speed, in units * of bits per second. */ NETIF_INIT_SNMP(netif, snmp_ifType_ethernet_csmacd, LINK_SPEED_OF_YOUR_NETIF_IN_BPS); netif->state = ethernetif; netif->name[0] = IFNAME0; netif->name[1] = IFNAME1; /* We directly use etharp_output() here to save a function call. * You can instead declare your own function an call etharp_output() * from it if you have to do some checks before sending (e.g. if link * is available...) */ netif->output = etharp_output; netif->linkoutput = low_level_output; ethernetif->ethaddr = (struct eth_addr *)&(netif->hwaddr[0]); /* initialize the hardware */ return low_level_init(netif); } #endif
主要是三個功能,網卡數據輸入,網卡數據輸入,網卡初始化,而且我們可以看到,lwip的配置是可以支持多個網卡滴
要是沒有網卡驅動的可以看我之前的例程,里面有
這時候我們算是完整完成了lwip的移植,現在到了使用階段了
這是我們的main函數
int main(void) { NVIC_Group_Init();//系統默認中斷分組 Debug_Serial_Init(115200); Delay_Init(); Led_Init(); Key_Exti_Init(); LCD_Init(); LCD_Clear(LCD_BLACK); ipaddr.addr = 0; netmask.addr = 0; gw.addr = 0; init_lwip_timer(); //初始化LWIP定時器 //初始化LWIP協議棧,執行檢查用戶所有可配置的值,初始化所有的模塊 lwip_init(); //添加網絡接口 while((netif_add(&enc28j60_netif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, ðernet_input)==NULL)) { LCD_ShowString(0,0,240,320,(u8*)"ENC28J60 Init Failed ",LCD_BLACK); Delay_Ms(200); LCD_ShowString(0,0,240,320,(u8*)" ",LCD_BLACK); Delay_Ms(200); } LCD_ShowString(0,0,240,320,(u8*)"ENC28J60 Init OK ",LCD_BLACK); //注冊默認的網絡接口 netif_set_default(&enc28j60_netif); //建立網絡接口用於處理通信 netif_set_up(&enc28j60_netif); dhcp_start(&enc28j60_netif); //為網卡創建一個新的DHCP客戶端 while(1) { LWIP_Polling(); Led_Set(0,0); Delay_Ms(100); Led_Set(0,1); Delay_Ms(500); } }
初始化定時器,初始化協議棧,添加網絡接口(該函數同時完成網卡的初始化,mac地址在之前的文件中),注冊網絡接口,建立網絡通訊接口,因為是dhcp,所以完全不用管網絡的地址是多少,直接給0,后面他會自己獲得到的
接下來啟動dhcp服務,因為是裸機程序,沒有多任務再跑,所以我們必須輪詢系統事件並處理,也就是LWIP_Polling(),該函數如下
#define CLOCKTICKS_PER_MS 10 //定義時鍾節拍 static ip_addr_t ipaddr, netmask, gw; //定義IP地址 struct netif enc28j60_netif; //定義網絡接口 u32_t input_time; u32_t last_arp_time; u32_t last_tcp_time; u32_t last_ipreass_time; u32_t last_dhcp_fine_time; u32_t last_dhcp_coarse_time; u32 dhcp_ip=0; //LWIP查詢 void LWIP_Polling(void) { if(timer_expired(&input_time,5)) //接收包,周期處理函數 { ethernetif_input(&enc28j60_netif); } if(timer_expired(&last_tcp_time,TCP_TMR_INTERVAL/CLOCKTICKS_PER_MS))//TCP處理定時器處理函數 { tcp_tmr(); } if(timer_expired(&last_arp_time,ARP_TMR_INTERVAL/CLOCKTICKS_PER_MS))//ARP處理定時器 { etharp_tmr(); } if(timer_expired(&last_ipreass_time,IP_TMR_INTERVAL/CLOCKTICKS_PER_MS))//IP重新組裝定時器 { ip_reass_tmr(); } if(timer_expired(&last_dhcp_fine_time,DHCP_FINE_TIMER_MSECS/CLOCKTICKS_PER_MS))//dhcp服務 { dhcp_fine_tmr(); } if(timer_expired(&last_dhcp_coarse_time,DHCP_COARSE_TIMER_MSECS/CLOCKTICKS_PER_MS))//dhcp服務 { dhcp_coarse_tmr(); } }
在循環中處理好相應的事件,因為我們沒建立tcp鏈接所以tcp其實可以不要,另外dhcp服務使用的是udp,端口使用的是udp的57,58
到這里編譯連接,系統啟動就能查看到路由器自動分配的IP了
具體工程在下面下載
http://download.csdn.net/detail/dengrengong/8548851