打印
[应用相关]

STM32F4+DP83848以太网通信指南系列(八):收包流程

[复制链接]
909|10
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主

本章为系列指南的第八章,讲述如何使用STM32F407芯片配合DP83848进行以太网数据的收包流程,将监听到的网络包数据通过UART传给PC,同时辅以WireShark监听对比验证。

关于UART,也就是串口通信的使用,这里不做赘述,我们这里预设两个函数分别为UART6Init()和UART6Send(),实现的功能是串口6的初始化和发送。


使用特权

评论回复
沙发
keaibukelian|  楼主 | 2019-7-4 09:39 | 只看该作者
以太网中断

在《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,要远远低于以太网速率,如果给串口的优先级过高,会影响以太网的使用。


使用特权

评论回复
板凳
keaibukelian|  楼主 | 2019-7-4 09:40 | 只看该作者

配置了中断后,我们还需要知道中断的入口函数,这个函数名是固定死的,不能乱写,我们去找找。

在《STM32F4+DP83848以太网通信指南第五章:MAC+DMA配置》我提到,中断配置代码中的ETH_IRQn变量,我们可以在stm32f4xx.h文件中找到定义,是在一个枚举结构中。那与之对应的中断入口名称该怎么找呢,原来所有的中断入口的定义,都用汇编入口的方式定义在启动文件中,这份启动文件我们之前一直没有关注过,现在打开看一看,在startup_stm32f40_41xxx.s中148行有其定义,截图如下。


使用特权

评论回复
地板
keaibukelian|  楼主 | 2019-7-4 09:40 | 只看该作者
中断中的数据处理

配置好了以太网中断,也知道了中断入口函数的名称,下面我们就来编写以太网中断函数。打开工程中的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);



  • }



使用特权

评论回复
5
keaibukelian|  楼主 | 2019-7-4 09:40 | 只看该作者

上面的代码配上英文的注释也很好理解,以太网中断中首先检查是否接受到以太网的数据包,如果是,就调用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了。


使用特权

评论回复
6
keaibukelian|  楼主 | 2019-7-4 09:41 | 只看该作者

果不其然,在ethernetif_input函数中,我们看到了这个预料中的函数调用,截图如下:


使用特权

评论回复
7
keaibukelian|  楼主 | 2019-7-4 09:41 | 只看该作者

我这里把路径为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;



  • }



使用特权

评论回复
8
keaibukelian|  楼主 | 2019-7-4 09:42 | 只看该作者
同样的,配合注释应该也容易理解。22行的frame变量取到了以太网数据包,ETH_Get_Received_Frame函数,不用LWIP也是有的,在之前我们提到过的stm32f4x7_eth.c文件中,len和buffer两个变量一个是包长度,一个是包内容头指针。下面/* copy received frame to pbuf chain */那一段是用链表遍历的方式,将以太网包数据放入LWIP处理数据的pbuf链表中,方便LWIP上层逻辑获取数据,这里我们不使用LWIP,这一段可忽略。接下来所有的操作都是针对DMA进行的,将DMA复位,因此我们需要保留,否则会一直产生重复的中断。

使用特权

评论回复
9
keaibukelian|  楼主 | 2019-7-4 09:42 | 只看该作者

通过以上的分析,我们可以轻松写出自己的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);



  •         }



  •     }







  •     /* 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;



  •     }



  • }



使用特权

评论回复
10
keaibukelian|  楼主 | 2019-7-4 09:42 | 只看该作者
验证和总结

上述函数,配合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,整个实验顺利完成。


使用特权

评论回复
11
keaibukelian|  楼主 | 2019-7-4 09:43 | 只看该作者
总结一下,本章我们依旧分析到了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协议的处理过程,我们就能在此基础上扩展更多其他的协议,即使遇到工业以太网跑在链路层的各个协议,我们也能捡其重点,按照自己的意愿,随心所欲的搭建了。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

77

主题

4146

帖子

5

粉丝