STM32開發筆記54:STM32F4+DP83848乙太網通訊指南系列(八):收包流程
本章為系列指南的第八章,講述如何使用STM32F407晶片配合DP83848進行乙太網資料的收包流程,將監聽到的網路包資料通過UART傳給PC,同時輔以WireShark監聽對比驗證。
關於UART,也就是串列埠通訊的使用,這裡不做贅述,我們這裡預設兩個函式分別為UART6Init()和UART6Send(),實現的功能是串列埠6的初始化和傳送。
乙太網中斷
在《STM32F4+DP83848乙太網通訊指南第五章:MAC+DMA配置》中,我們已經添加了乙太網中斷,其思路就是想讓每次乙太網上有收到包都能觸發中斷,我們可以在中斷中將DMA中的資料包取出來進行分析,然後復位,讓晶片下一次繼續響應中斷。
配置中斷的程式碼非常簡單,跟其他任何中斷都一樣,這裡再複習一次:
void ETH_NVIC_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; /* Enable the Ethernet global Interrupt */ NVIC_InitStructure.NVIC_IRQChannel = ETH_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }
整個工程的優先順序組別選用的NVIC_PriorityGroup_4,有4位搶佔位,0位響應位,也就是可以分配16個可以互相巢狀的中斷等級。這裡乙太網中斷的主優先順序為1,相當於第二高,前面預留了個優先順序為0的,用來分配給系統計時器,畢竟不能因為乙太網資料的響應影響系統走時。
在UART6Init()串列埠初始化函式中,給串列埠的中斷等級是2,低於乙太網中斷,因為串列埠的波特率是9600,要遠遠低於乙太網速率,如果給串列埠的優先順序過高,會影響乙太網的使用。
配置了中斷後,我們還需要知道中斷的入口函式,這個函式名是固定死的,不能亂寫,我們去找找。
在《STM32F4+DP83848乙太網通訊指南第五章:MAC+DMA配置》我提到,中斷配置程式碼中的ETH_IRQn變數,我們可以在stm32f4xx.h檔案中找到定義,是在一個列舉結構中。那與之對應的中斷入口名稱該怎麼找呢,原來所有的中斷入口的定義,都用匯編入口的方式定義在啟動檔案中,這份啟動檔案我們之前一直沒有關注過,現在開啟看一看,在startup_stm32f40_41xxx.s中148行有其定義,截圖如下。
中斷中的資料處理
配置好了乙太網中斷,也知道了中斷入口函式的名稱,下面我們就來編寫乙太網中斷函式。開啟工程中的stm32f4xx_it.c檔案,一般每一個使用了中斷的STM32工程都會有這麼一個檔案,用來集中管理中斷入口。追加以下程式碼:
/**
* @brief This function handles ethernet DMA interrupt request.
* @param None
* @retval None
*/
void ETH_IRQHandler(void)
{
/* Handles all the received frames */
/* check if any packet received */
while(ETH_CheckFrameReceived()){
/* process received ethernet packet */
Pkt_Handle();
}
/* Clear the Eth DMA Rx IT pending bits */
ETH_DMAClearITPendingBit(ETH_DMA_IT_R);
ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS);
}
上面的程式碼配上英文的註釋也很好理解,乙太網中斷中首先檢查是否接受到乙太網的資料包,如果是,就呼叫Pkt_Handle()函式進行下一層的分析和處理,最後兩行Clear復位中斷標記,讓下一次中斷能夠產生。那麼問題就集中到Pkt_Handle()函式上來了。kt_Handle()函式是我自己命名的,這個函式的原型取自LWIP中的LWIP_Pkt_Handle(),我們先來觀察一下LWIP中的包分析函式怎麼寫的:在STM32F4x7_ETH_LwIP_V1.1.1/Project/Standalone/udp_echo_client/src/netconf.c中,有如下程式碼:
/**
* @brief Called when a frame is received
* @param None
* @retval None
*/
void LwIP_Pkt_Handle(void)
{
/* Read a received packet from the Ethernet buffers and send it to the lwIP for handling */
ethernetif_input(&gnetif);
}
可以看到LWIP繼續呼叫了下層的ethernetif_input(),繼續追蹤到我們之前提到的最底層檔案ethernetif.c,是不是有種似曾相識的感覺,這個檔案前幾章我們不止一次遇到過,分別為我們提供了low_level_init、low_level_output多個重要函式,我們現在又一次遇到它了,看上去它這次要為我們的乙太網監聽提供low_level_input了。
果不其然,在ethernetif_input函式中,我們看到了這個預料中的函式呼叫,截圖如下:
我這裡把路徑為STM32F4x7_ETH_LwIP_V1.1.1/Utilities/Third_Party/lwip-1.4.1/port/STM32F4x7/Standalone/ethernetif.c的原版low_level_input()的所有程式碼都貼出來,感興趣的朋友可以仔細研讀:
/**
* Should allocate a pbuf and transfer the bytes of the incoming
* packet from the interface into the pbuf.
*
* @param netif the lwip network interface structure for this ethernetif
* @return a pbuf filled with the received packet (including MAC header)
* NULL on memory error
*/
static struct pbuf * low_level_input(struct netif *netif)
{
struct pbuf *p, *q;
u16_t len;
int l =0;
FrameTypeDef frame;
u8 *buffer;
uint32_t i=0;
__IO ETH_DMADESCTypeDef *DMARxNextDesc;
p = NULL;
/* get received frame */
frame = ETH_Get_Received_Frame();
/* Obtain the size of the packet and put it into the "len" variable. */
len = frame.length;
buffer = (u8 *)frame.buffer;
/* We allocate a pbuf chain of pbufs from the Lwip buffer pool */
p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
/* copy received frame to pbuf chain */
if (p != NULL)
{
for (q = p; q != NULL; q = q->next)
{
memcpy((u8_t*)q->payload, (u8_t*)&buffer[l], q->len);
l = l + q->len;
}
}
/* Release descriptors to DMA */
/* Check if frame with multiple DMA buffer segments */
if (DMA_RX_FRAME_infos->Seg_Count > 1)
{
DMARxNextDesc = DMA_RX_FRAME_infos->FS_Rx_Desc;
}
else
{
DMARxNextDesc = frame.descriptor;
}
/* Set Own bit in Rx descriptors: gives the buffers back to DMA */
for (i=0; i<DMA_RX_FRAME_infos->Seg_Count; i++)
{
DMARxNextDesc->Status = ETH_DMARxDesc_OWN;
DMARxNextDesc = (ETH_DMADESCTypeDef *)(DMARxNextDesc->Buffer2NextDescAddr);
}
/* Clear Segment_Count */
DMA_RX_FRAME_infos->Seg_Count =0;
/* When Rx Buffer unavailable flag is set: clear it and resume reception */
if ((ETH->DMASR & ETH_DMASR_RBUS) != (u32)RESET)
{
/* Clear RBUS ETHERNET DMA flag */
ETH->DMASR = ETH_DMASR_RBUS;
/* Resume DMA reception */
ETH->DMARPDR = 0;
}
return p;
}
同樣的,配合註釋應該也容易理解。22行的frame變數取到了乙太網資料包,ETH_Get_Received_Frame函式,不用LWIP也是有的,在之前我們提到過的stm32f4x7_eth.c檔案中,len和buffer兩個變數一個是包長度,一個是包內容頭指標。下面/* copy received frame to pbuf chain */那一段是用連結串列遍歷的方式,將乙太網包資料放入LWIP處理資料的pbuf連結串列中,方便LWIP上層邏輯獲取資料,這裡我們不使用LWIP,這一段可忽略。接下來所有的操作都是針對DMA進行的,將DMA復位,因此我們需要保留,否則會一直產生重複的中斷。
通過以上的分析,我們可以輕鬆寫出自己的Pkg_Handle()函數了:
void Pkt_Handle(void) {
FrameTypeDef frame;
/* get received frame */
frame = ETH_Get_Received_Frame();
/* Obtain the size of the packet and put it into the "len" variable. */
receiveLen = frame.length;
receiveBuffer = (u8 *)frame.buffer;
printf("0011%d0022\n", receiveLen); //將每一個的包長度發往串列埠
if(receiveBuffer[41] == 201){ //如果第42位元組是十進位制201,則將整個包內容發往串列埠
for (i = 0; i < receiveLen; i++) {
printf("%c", receiveBuffer[i]);
}
}
/* Check if frame with multiple DMA buffer segments */
if (DMA_RX_FRAME_infos->Seg_Count > 1) {
DMARxNextDesc = DMA_RX_FRAME_infos->FS_Rx_Desc;
} else {
DMARxNextDesc = frame.descriptor;
}
/* Set Own bit in Rx descriptors: gives the buffers back to DMA */
for (i = 0; i < DMA_RX_FRAME_infos->Seg_Count; i++) {
DMARxNextDesc->Status = ETH_DMARxDesc_OWN;
DMARxNextDesc = (ETH_DMADESCTypeDef *)(DMARxNextDesc->Buffer2NextDescAddr);
}
/* Clear Segment_Count */
DMA_RX_FRAME_infos->Seg_Count = 0;
/* When Rx Buffer unavailable flag is set: clear it and resume reception */
if ((ETH->DMASR & ETH_DMASR_RBUS) != (u32)RESET) {
/* Clear RBUS ETHERNET DMA flag */
ETH->DMASR = ETH_DMASR_RBUS;
/* Resume DMA reception */
ETH->DMARPDR = 0;
}
}
驗證和總結
上述函式,配合ETH_IRQHandler中斷中的呼叫,完成了乙太網的收包,並且將接受的包的長度使用0011%d0022通過printf函式通過UART發往了PC端,因為如果將整個包內容發往PC的話,串列埠資料會非常多。同時如果為了驗證buffer中的內容能正確獲取,我們寫了一個if判斷,判斷如果資料包中的第42個位元組為201,則將包內容轉發到串列埠中去。
我們用JLink燒錄進STM32F4,將PC的有線網絡卡與STM32直接,開啟WireShark和串列埠通訊助手進行觀察和驗證,截圖如下。
紅色框框部分是關注重點,我們在CMD命令視窗ping 192.168.1.201,可以觸發一個ARP包,這個包中的第42個位元組就是201,因此可以觸發STM32中的if判斷,將包內容通過串列埠轉發給PC,而其他普通包,STM32則使用0011%d0022的格式將包長度發給了PC,整個實驗順利完成。
總結一下,本章我們依舊分析到了ethernetif.c檔案,這次是觀察的它的low_level_input()函式,藉助這個函式,我們編寫了我們自己的處理包的邏輯函式Pkg_Handle(),並通過乙太網中斷入口函式ETH_IRQHandler呼叫它,最後我們成功的使用WireShark配合串列埠進行了收包的驗證。
我們可以發現這個系列教程的後半段幾乎都在不停地圍繞LWIP庫中的ethernetif.c檔案進行分析,到目前為止,它的幾個重要底層函式low_level_init、low_level_output、low_level_input已經分別為我們的乙太網初始化、乙太網發包、乙太網收包等程式碼提供了重要的核心邏輯。
下一章,是我們這個系列的最後一章,我們將在STM32F4上,利用之前實驗過的各個功能,自己構建一個能響應ARP協議的程式。有了ARP協議的處理過程,我們就能在此基礎上擴充套件更多其他的協議,即使遇到工業乙太網跑在鏈路層的各個協議,我們也能撿其重點,按照自己的意願,隨心所欲的搭建了。