LWIP移植文件介紹


在介紹文件之前首先介紹一下DMA描述符

stm32以太網模塊接收/發送FIFO和內存之間的以太網傳輸是通過以太網DMA使用DMA描述符完成的,一共有兩個描述符列表:一個用於接收,一個用於發送, 兩個列表的基址分別寫入ETH_DMARDLAR 寄存器和 ETH_DMATDLAR 寄存器中。 

 1 typedef struct {
 2 __IO uint32_t Status; //狀態
 3 uint32_t ControlBufferSize; //控制和 buffer1, buffer2 的長度
 4 uint32_t Buffer1Addr; //buffer1 地址
 5 uint32_t Buffer2NextDescAddr; //buffer2 地址或下一個描述符地址
 6 uint32_t ExtendedStatus; //增強描述符狀態
 7 uint32_t Reserved1; //保留
 8 uint32_t TimeStampLow; //時間戳低位
 9 uint32_t TimeStampHigh; //時間戳高位
10 } ETH_DMADESCTypeDef;

根據DMA描述符的內容可以組成兩種結構,而stm32以太網庫提供的就是鏈接結構。即Buffer1Addr存放數據,Buffer2NextDescAddr指向下一個描述符的首地址

uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE];      
uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE]
上面就是在初始化DMA描述符鏈表使能用到,相當於二維數組,第一個代表描述符的數量,此時定義的是5,第二個代表描述符 
Buffer1Addr 的分配空間的大小,此時定義1500

 

1.lwipopts.h 

用戶自己定義使用的配置文件 可以對宏定義的開關選擇打開或者關閉某些功能。系統提供的配置文件opt.h有大量的條件編譯,所以還是不要動系統的默認配置。

配置信息有:LWIP_UDP/LWIP_TCP 、系統、lwip的接口編程方式,接收緩沖區的大小,是否使用socket,是否需要打印debug消息等等。

2.網卡驅動的編寫:                                         LWIP協議棧只能通過pbuf和網卡通訊

lwip的網卡驅動的移植其實就是對以下幾個函數函數的編寫與封裝。lwip內核已經寫好了框架在ethnetif.c中,該文件默認是沒有編譯進去的(指的是LWIP官網下載的安裝包),我們需要自己填充這個文件

1 static void low_level_init(struct netif *netif)
2 static err_t low_level_output(struct netif *netif, struct pbuf *p)
3 static struct pbuf * low_level_input(struct netif *netif)
4 void ethernetif_input(struct netif *netif)
5 err_t ethernetif_init(struct netif *netif)

(1).網卡初始化函數,它主要包括mcu的片內外設

   ① eth的初始化(自協商模式是否開啟,PHY的地址,網卡的MAC地址,接收模式是中斷還是輪訓,stm32官網開發文檔寫着中斷模式只能用於系統,不帶系統只能使用輪訓模式,但是正點原子使用的demo是中斷模式,我此次使用的是poll,是否開啟硬件校驗和PHY的通訊模式(MII/RMII))

           ② 將DMA發送和接收描述符初始化成鏈表結構 ,設置協議棧網絡接口管理netif中國與網卡屬性相關的特性,如設置MAC硬件地址,mac地址長度,最大傳輸單元,使能DMA發送和接收

(2)網卡數據包發送函數 將內核數據結構體pbuf描述的數據包發送出去

(3)網卡數據包接收函數,為了讓內核更好的處理接收過來的數據,我們需要將接收到的數據封裝成pbuf的形式

(4)主要是調用low_level_input()實現從網卡讀取一個數據包,及解析數據包的類型(ARP,IP)並提交給應用層,這個函數可直接被應用程序調用

(5)在管理網絡接口netif會調用,只要是對netif某些字段進行初始化,並調用low_level_init完成相關的初始化。

 

下面分別講解一下這幾個函數:.

 void low_level_init(struct netif *netif)

初始化了netif的hwaddr_len、hwaddr、mtu、flags,其他的上面已經很詳細的介紹,這里不再贅敘

 1 static void low_level_init(struct netif *netif)
 2 { 
 3   uint32_t regvalue = 0;
 4   HAL_StatusTypeDef hal_eth_init_status;
 5   
 6 /* Init ETH */
 7 
 8    uint8_t MACAddr[6] ;
 9   heth.Instance = ETH;
10   heth.Init.AutoNegotiation = ETH_AUTONEGOTIATION_ENABLE;
11   heth.Init.PhyAddress = LAN8742A_PHY_ADDRESS;
12   MACAddr[0] = 0x02;
13   MACAddr[1] = 0x00;
14   MACAddr[2] = 0x00;
15   MACAddr[3] = 0x00;
16   MACAddr[4] = 0x00;
17   MACAddr[5] = 0x00;
18   heth.Init.MACAddr = &MACAddr[0];
19   heth.Init.RxMode = ETH_RXPOLLING_MODE;
20   heth.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE;
21   heth.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII;
22 
23   /* USER CODE BEGIN MACADDRESS */
24     
25   /* USER CODE END MACADDRESS */
26 
27   hal_eth_init_status = HAL_ETH_Init(&heth);
28 
29   if (hal_eth_init_status == HAL_OK)
30   {
31     /* Set netif link flag */  
32     netif->flags |= NETIF_FLAG_LINK_UP;
33   }
34   /* Initialize Tx Descriptors list: Chain Mode */
35   HAL_ETH_DMATxDescListInit(&heth, DMATxDscrTab, &Tx_Buff[0][0], ETH_TXBUFNB);
36      
37   /* Initialize Rx Descriptors list: Chain Mode  */
38   HAL_ETH_DMARxDescListInit(&heth, DMARxDscrTab, &Rx_Buff[0][0], ETH_RXBUFNB);
39  
40 #if LWIP_ARP || LWIP_ETHERNET 
41 
42   /* set MAC hardware address length */
43   netif->hwaddr_len = ETH_HWADDR_LEN;
44   
45   /* set MAC hardware address */
46   netif->hwaddr[0] =  heth.Init.MACAddr[0];
47   netif->hwaddr[1] =  heth.Init.MACAddr[1];
48   netif->hwaddr[2] =  heth.Init.MACAddr[2];
49   netif->hwaddr[3] =  heth.Init.MACAddr[3];
50   netif->hwaddr[4] =  heth.Init.MACAddr[4];
51   netif->hwaddr[5] =  heth.Init.MACAddr[5];
52   
53   /* maximum transfer unit */
54   netif->mtu = 1500;
55   
56   /* Accept broadcast address and ARP traffic */
57   /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
58   #if LWIP_ARP
59     netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;
60   #else 
61     netif->flags |= NETIF_FLAG_BROADCAST;
62   #endif /* LWIP_ARP */
63   
64   /* Enable MAC and DMA transmission and reception */
65   HAL_ETH_Start(&heth);
66 
67 /* USER CODE BEGIN PHY_PRE_CONFIG */ 
68     
69 /* USER CODE END PHY_PRE_CONFIG */
70   
71 
72   /* Read Register Configuration */
73   HAL_ETH_ReadPHYRegister(&heth, PHY_ISFR, &regvalue);
74   regvalue |= (PHY_ISFR_INT4);
75 
76   /* Enable Interrupt on change of link status */ 
77   HAL_ETH_WritePHYRegister(&heth, PHY_ISFR , regvalue );
78   
79   /* Read Register Configuration */
80   HAL_ETH_ReadPHYRegister(&heth, PHY_ISFR , &regvalue);
81 
82 
83 #endif /* LWIP_ARP || LWIP_ETHERNET */
84 
85 
86 }
low_level_init

 

low_level_output(struct netif *netif, struct pbuf *p)

首先介紹一下pubuf結構體
struct pbuf {
struct pbuf *next; //指向下一個 pbuf 結構體,可以構成鏈表
void *payload; //指向該 pbuf 真正的數據區
u16_t tot_len; //當前 pbuf 和鏈表中后面所有 pbuf 的數據長度, 它們屬於一個數據包
u16_t len; //當前 pbuf 的數據長度
u8_t type; //當前 pbuf 的類型   pbuf_ram / pbuf_pool
u8_t flags; //狀態為,保留
u16_t ref; //該 pbuf 被引用的次數
};

PS:offset的值因為當前的pbuf處於不同的協議層而不同,如raw時 offset 為0 開啟TCP時offset 為54

 

 

本質:

一個pbuf可能無法將所有的數據都發出去,所以一般將pbuf組成一個鏈表,這樣數據就會在pbuf鏈表的payload區域,遍歷pbuf鏈表將數據全部memcpy到eth的DMA發送描述符鏈表中,要注意三個地方,一個是offset區域,第二個是發送時要確保DMA發送描述符被ETH所有,不是被LWIP所有,第三個是當要發送數據大於DMA描述符鏈表所能發送的最大字節.

具體代碼實現如下:

static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
err_t errval;
struct pbuf *q;
uint8_t *buffer=(uint8_t *)(ETH_Handler.TxDesc->Buffer1Addr);
__IO ETH_DMADescTypeDef *DmaTxDesc;
uint32_t framelength = 0;
uint32_t bufferoffset = 0;
uint32_t byteslefttocopy = 0;
uint32_t payloadoffset = 0;
DmaTxDesc = ETH_Handler.TxDesc;
bufferoffset = 0;
//從 pbuf 中拷貝要發送的數據
for(q=p;q!=NULL;q=q->next)
{
//判斷此發送描述符是否有效,即判斷此發送描述符是否歸以太網 DMA 所有
if((DmaTxDesc->Status&ETH_DMATXDESC_OWN)!=(uint32_t)RESET)
{
errval=ERR_USE;
goto error; //發送描述符無效,不可用
}
byteslefttocopy=q->len; //要發送的數據長度
payloadoffset=0;
//將 pbuf 中要發送的數據寫入到以太網發送描述符中,有時候我們要發送的
//數據可能大於一個以太網描述符的 Tx Buffer,因此我們需要分多次將數據
//拷貝到多個發送描述符中
while((byteslefttocopy+bufferoffset)>ETH_TX_BUF_SIZE )
{
//將數據拷貝到以太網發送描述符的 Tx Buffer 中
memcpy((uint8_t*)((uint8_t*)buffer+bufferoffset),(uint8_t*)((uint8_t*)q->payload\
+payloadoffset),(ETH_TX_BUF_SIZE-bufferoffset));
//DmaTxDsc 指向下一個發送描述符
DmaTxDesc=(ETH_DMADescTypeDef *)(DmaTxDesc->Buffer2NextDescAddr);
//檢查新的發送描述符是否有效
if((DmaTxDesc->Status&ETH_DMATXDESC_OWN)!=(uint32_t)RESET)
{
errval = ERR_USE;
goto error; //發送描述符無效,不可用
}
buffer=(uint8_t *)(DmaTxDesc->Buffer1Addr); //更新 buffer 地址,指向新
//的發送描述符的 Tx Buffer
byteslefttocopy=byteslefttocopy-(ETH_TX_BUF_SIZE-bufferoffset);
payloadoffset=payloadoffset+(ETH_TX_BUF_SIZE-bufferoffset);
framelength=framelength+(ETH_TX_BUF_SIZE-bufferoffset);
bufferoffset=0;
}
//拷貝剩余的數據
memcpy( (uint8_t*)((uint8_t*)buffer+bufferoffset),(uint8_t*)((uint8_t*)q->payload\
+payloadoffset),byteslefttocopy );
bufferoffset=bufferoffset+byteslefttocopy;
framelength=framelength+byteslefttocopy;
}
HAL_ETH_TransmitFrame(&ETH_Handler,framelength);
errval = ERR_OK;
error:
//發送緩沖區發生下溢,一旦發送緩沖區發生下溢 TxDMA 會進入掛起狀態
if((ETH_Handler.Instance->DMASR&ETH_DMASR_TUS)!=(uint32_t)RESET)
{
//清除下溢標志
ETH_Handler.Instance->DMASR = ETH_DMASR_TUS;
//當發送幀中出現下溢錯誤的時候 TxDMA 會掛起,這時候需要向 DMATPDR 寄存器
//隨便寫入一個值來將其喚醒,此處我們寫 0
ETH_Handler.Instance->DMATPDR=0;
}
return errval;
}
low_level_output_code
struct pbuf * low_level_input(struct netif *netif)
本質:
 將網卡eth的DMA描述符中的內容復制到pbuf鏈表中,供LWIP使用,過程是判斷DMA描述符中是否有數據,獲取數據長度len,為pbuf分配(offset+len)長度的空間,然后遍歷DMA描述符鏈表進行復制,
最后復制完成將DMA描述符鏈表清空,最后將DMA描述符還給網卡,標記后即可接受新的數據。
具體代碼實現如下:
 1 static struct pbuf * low_level_input(struct netif *netif)
 2 {
 3 struct pbuf *p = NULL;
 4 struct pbuf *q;
 5 uint16_t len;
 6 uint8_t *buffer;
 7 __IO ETH_DMADescTypeDef *dmarxdesc;
 8 uint32_t bufferoffset=0;
 9 uint32_t payloadoffset=0;
10 uint32_t byteslefttocopy=0;
11 uint32_t i=0;
12 if(HAL_ETH_GetReceivedFrame(&ETH_Handler)!=HAL_OK) //判斷是否接收到數據
13 return NULL;
14 len=ETH_Handler.RxFrameInfos.length; //獲取接收到的以太網幀長度
15 buffer=(uint8_t *)ETH_Handler.RxFrameInfos.buffer; //獲取接收到的以太網幀的數據 buffer
16 if(len>0) p=pbuf_alloc(PBUF_RAW,len,PBUF_POOL); //申請 pbuf
17 if(p!=NULL) //pbuf 申請成功
18 {
19 dmarxdesc=ETH_Handler.RxFrameInfos.FSRxDesc; //獲取接收描述符鏈表中
20 //的第一個描述符
21 bufferoffset = 0;
22 for(q=p;q!=NULL;q=q->next)
23 {
24 byteslefttocopy=q->len;
25 payloadoffset=0;
26 //將接收描述符中 Rx Buffer 的數據拷貝到 pbuf 中
27 while((byteslefttocopy+bufferoffset)>ETH_RX_BUF_SIZE )
28 {
29 //將數據拷貝到 pbuf 中
30 memcpy((uint8_t*)((uint8_t*)q->payload+payloadoffset),\
31 (uint8_t*)((uint8_t*)buffer+bufferoffset),(ETH_RX_BUF_SIZE-bufferoffset));
32 //dmarxdesc 指向下一個接收描述符
33 dmarxdesc=(ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
34 //更新 buffer 地址,指向新的接收描述符的 Rx Buffer
35 buffer=(uint8_t *)(dmarxdesc->Buffer1Addr);
36 byteslefttocopy=byteslefttocopy-(ETH_RX_BUF_SIZE-bufferoffset);
37 payloadoffset=payloadoffset+(ETH_RX_BUF_SIZE-bufferoffset);
38 bufferoffset=0;
39 }
40 //拷貝剩余的數據
41 memcpy((uint8_t*)((uint8_t*)q->payload+payloadoffset),\
42 (uint8_t*)((uint8_t*)buffer+bufferoffset),byteslefttocopy);
43 bufferoffset=bufferoffset+byteslefttocopy;
44 }
45 }
46 //釋放 DMA 描述符
47 dmarxdesc=ETH_Handler.RxFrameInfos.FSRxDesc;
48 for(i=0;i<ETH_Handler.RxFrameInfos.SegCount; i++)
49 {
50 dmarxdesc->Status|=ETH_DMARXDESC_OWN; //標記描述符歸 DMA 所有
51 dmarxdesc=(ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr);
52 }
53 ETH_Handler.RxFrameInfos.SegCount =0; //清除段計數器
54 //接收緩沖區不可用
55 if((ETH_Handler.Instance->DMASR&ETH_DMASR_RBUS)!=(uint32_t)RESET)
56 {
57 //清除接收緩沖區不可用標志
58 ETH_Handler.Instance->DMASR = ETH_DMASR_RBUS;
59 //當接收緩沖區不可用的時候 RxDMA 會進去掛起狀態,通過向 DMARPDR
60 //寫入任意一個值來喚醒 Rx DMA
61 ETH_Handler.Instance->DMARPDR=0;
62 }
63 return p;
64 }
low_level_input_code
 
        
void ethernetif_input(struct netif *netif)
主要是對low_level_inpu()的封裝,然后傳入指定的網卡結構中
 1 err_t ethernetif_input(struct netif *netif)
 2 {
 3 err_t err;
 4 struct pbuf *p;
 5 p=low_level_input(netif); //調用 low_level_input 函數接收數據
 6 if(p==NULL) return ERR_MEM;
 7 err=netif->input(p, netif); //調用 netif 結構體中的 input 字段(一個函數)來處理數據包
 8 if(err!=ERR_OK)
 9 {
10 LWIP_DEBUGF(NETIF_DEBUG,("ethernetif_input: IP input error\n"));
11 pbuf_free(p);
12 p = NULL;
13 }
14 return err;
15 }
ethernetif_input
err_t ethernetif_init(struct netif *netif)
主要是對low_level_init()的封裝,初始化了netif的相關字段,注冊IP層發送函數
 1 err_t ethernetif_init(struct netif *netif)
 2 {
 3 LWIP_ASSERT("netif!=NULL",(netif!=NULL));
 4 #if LWIP_NETIF_HOSTNAME //LWIP_NETIF_HOSTNAME
 5 netif->hostname="lwip"; //初始化名稱
 6 #endif
 7 netif->name[0]=IFNAME0; //初始化變量 netif 的 name 字段
 8 netif->name[1]=IFNAME1; //在文件外定義這里不用關心具體值
 9 netif->output=etharp_output;//IP 層發送數據包函數
10 netif->linkoutput=low_level_output;//ARP 模塊發送數據包函數
11 low_level_init(netif); //底層硬件初始化函數
12 return ERR_OK;
13 }
ethernetif_init

 

3.系統時鍾

 系統通過調用函數sys_check_timerouts來處理內核的各種定時時間比如ARP、TCP等,該函數要求移植者實現一個函數sys_now來返回當前的系統時間,通過差值判斷是否時間到達,從而調用相關的函數處理定時事件。在本次移植中,用的hal庫自帶的tick(1ms)來實現,也可以用全局變量和定時器來實現

1 u32_t sys_now(void)
2 {
3   return HAL_GetTick();
4 }
 

4.LWIP初始化過程

       lwip_init()      初始化LWIP內核
            ↓
     IP4_ADDR()         將ip/netmask/gw數值整合成一個ip4_addr_t變量
            ↓
    netif_add()         此函數主要設置ip/netmask/gw 和調用ethernetif_init函數,當有消息來的時候調用ethernet_input。這兩個函數會在后面文章介紹,一種網卡設備添加一次
            ↓
netif_set_default()     將此網卡設置為默認網口
            ↓
    netif_set_up()      開啟網口,在添加網口設備后就會將NETIF_IS_LINK_UP_FLAG 置1,后面會詳細講解 netif_set_link_up和netif_set_up的區別

 

 因為此次是用的是poll模式,所以要在主循環調用ethnetif_input()和sys_sys_check_timerouts(); 確保內核是工作的。完成這一步,板子就能夠ping通了

 

************************************************************************************************

參考文章:

正點原子的《STM32F7 LWIP開發手冊》

《嵌入式網絡那些事:LwIP協議棧深度剖析與演練》

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM