STM32 LWIP数据丢包分析

转载自:https://blog.csdn.net/lu_embedded/article/details/76285666

 

1. 测试环境说明

硬件平台:NUCLEO-F767ZI 开发板(STM32F7,Cortex-M7,216MHz,2MB Flash,512KB SRAM) 
操作系统:FreeRTOS v9.0.0(CMSIS-RTOS v1.02) 
TCP/IP协议栈:LwIP v2.0.0
  这里所描述的网络丢包问题的测试程序,是使用 STM32CubeMX 工具(库版本为 STM32Cube_FW_F7_V1.7.0 ),基于 FreeRTOS 和 LwIP 实现的一个以太网 Demo 程序。协议栈已实现 ICMP 包的 echo 功能(即可以通过其他以太网设备 ping 开发板),此外,我们在该程序框架之上添加了 tcp_echoserver 和 udp_echoserver 功能,进行 TCP 和 UDP 网络性能测试,均采用 RAW API 方式实现。 
   
  相关的几个测试工具:

Ping 测试工具(ATKKPING): http://download.csdn.net/detail/luckydarcy/9914445
TCP&UDP测试工具 V1.02: http://download.csdn.net/detail/luckydarcy/9895750
TCP/UDP Socket调试工具V2.3: http://download.csdn.net/detail/luckydarcy/9895746
2. 以太网控制器

  要分析网络丢包问题,我们先来看一下 STM32F7 的以太网控制器提供了哪些资源。如下图所示,我们可以把 STM32F7 的以太网控制器划分为三部分,从左到右分别是:DMA 控制器、MAC 控制器、PHY 接口。NUCLEO-F767ZI 开发板上的 PHY 芯片是 8742A,通过 RMII 与 MAC 控制器相连。


  需要注意的是,这里的 DMA 是以太网外设专用的,它包含两个 FIFO 缓冲区,分别用于以太网数据帧的接收和发送,大小均为 2K 字节。 
  以太网数据接收模式设置为中断模式,当产生中断或由发送需求时,对于任务来说,以太网数据的接收和发送是通过操作描述符列表和数据缓冲区来完成的。

3. 数据的接收流程

  下面我们来看一下以太网数据的接收流程: 
  中断向量表“__vector_table”中定义了以太网外设中断处理函数 ETH_IRQHandler,对于 Rx 中断来说,实际上调用的是回调函数 HAL_ETH_RxCpltCallback,而该函数只做了一件事,那就是释放信号量:

/**
  * @brief  Ethernet Rx Transfer completed callback
  * @param  heth: ETH handle
  * @retval None
  */
void HAL_ETH_RxCpltCallback(ETH_HandleTypeDef *heth)
{
  osSemaphoreRelease(s_xSemaphore);
}
1
2
3
4
5
6
7
8
9
  该信号量在 low_level_init 里面被定义为二值信号量,也就是互斥量:

  osSemaphoreDef(SEM);
  s_xSemaphore = osSemaphoreCreate(osSemaphore(SEM) , 1 );
1
2
  而等待该信号量的是一个称为“ethernetif_input”的线程,我们可以称它为中断服务程序 ISR,它作为整个 LwIP 协议栈的入口,它在 low_level_init 中被定义为 realtime 优先级别的线程:

osThreadDef(EthIf, ethernetif_input, osPriorityRealtime, 0, INTERFACE_THREAD_STACK_SIZE);
1
  线程 ethernetif_input 所做的工作也就是等待该信号量,当接收到以太网数据帧,中断处理函数就会释放信号量,从而使得 ethernetif_input 线程进入就绪态,之后便等待操作系统调度,进而进行数据帧解析。

4. 数据的发送流程

  数据的发送流程根据 LwIP 的配置有所区别,但最终都会调用 low_level_output 函数。该函数也是通过操作描述符列表和数据缓冲区来完成发送动作。 
  和接收流程不同的是,发送的动作没有使用信号量进行同步,而是根据应用程序的需求进行调用。也就是说,只要应用程序有发送数据的需求都可以直接调用相应的API进行发送。那么,这样就会造成资源冲突,所以在更底层的 HAL_ETH_TransmitFrame 函数中,使用了网卡句柄的锁进行保护:

__HAL_LOCK(heth);
  ......
__HAL_UNLOCK(heth);
1
2
3
  这样确实可以解决资源冲突的问题,但是这个 Demo 程序的问题在于,当前运行的任务无论有没有拿到锁(网卡资源),low_level_output 都返回 OK。这样就会造成丢包现象。

5. 验证测试

  经过上面的分析,我们可以确认,在这个 Demo 程序中,以太网数据的接收和发送机制是存在缺陷的。针对这些缺陷,我们采用 Ping 和 UDP echo 进行测试,并在关键位置添加统计信息。 
  一个是 ICMP 的 echo 计数(ping_times),我们在程序认为发送成功时才计数:

/* send an ICMP packet */
ret = ip4_output_if(p, src, LWIP_IP_HDRINCL,ICMP_TTL, 0, IP_PROTO_ICMP, inp);
if (ret != ERR_OK) {
  // ......
  }else{
    ping_times++;
  }
1
2
3
4
5
6
7
另一个是 UDP 的 echo 计数(udp_times),同样,我们在程序认为发送成功时才计数:

static void udp_echoserver_recv_callback(......)
{
if(ERR_OK == udp_sendto(upcb, p, addr, port))udp_times++;
pbuf_free(p);
}
1
2
3
4
5
  另外,我们在 low_level_output 的各个出错部分添加出错统计(output_err),比如:

/* Prepare transmit descriptors to give to DMA */ 
if(HAL_OK != HAL_ETH_TransmitFrame(&heth, framelength)) {
    output_err++;
}
1
2
3
4
5.1 PC直接连接STM32F7

测试一、UDP echo测试(间隔100ms,长度1000字节)

  PC 机计数显示成功发送 18005 个数据包,成功接收 17999 个数据包。也即丢失 6 个,丢包率为 0.0333%。

  STM32F7 打印数据显示,程序“认为”成功发送 18005 个 UDP 数据包,但实际上有 6 个没有发送成功。与 PC 端统计数据相符。 
(串口打印数据只需关注最后三个数字,分别表示 ping_times、udp_times、output_err)

测试二、UDP echo测试(间隔10ms,长度1000字节)

  PC 机计数显示成功发送 59255 个数据包,成功接收 59233 个数据包。也即丢失 22 个,丢包率为 0.0371%。

  STM32F7 打印数据显示,程序“认为”成功发送 59255 个 UDP 数据包,但实际上有 22 个没有发送成功。与 PC 端统计数据相符。

测试三、UDP echo测试(间隔1ms,长度1000字节)

  PC 机计数显示成功发送 1920341 个数据包,成功接收 1919724 个数据包。也即丢失 617 个,丢包率为 0.0321%。

  STM32F7 打印数据显示,程序“认为”成功发送 1920341 个 UDP 数据包,但实际上有 617 个没有发送成功。与 PC 端统计数据相符。

测试四、Ping测试(以最快速度Ping,长度32字节)

  PC 机计数显示成功发送 15409 个 ICMP 包,超时 8 个,丢包率为 0.05%。

  STM32F7 打印数据显示,程序“认为”成功发送 15409 个 ICMP 包,但实际上有 1 个没有发送成功。与 PC 端显示丢失 8 个不相符。

测试五、Ping测试(以最快速度Ping,长度1472字节)

  PC 机计数显示成功发送 15402 个 ICMP 包,超时 30 个,丢包率为 0.19%。

  STM32F7 打印数据显示,程序“认为”成功发送 15402 个 ICMP 包,但实际上有 2 个没有发送成功。与 PC 端显示丢失 30 个不相符。

5.2 PC通过交换机连接STM32F7(交换机连接外网)

测试一、UDP echo测试(间隔10ms,长度1000字节)

  PC 机计数显示成功发送 61240 个数据包,成功接收 61209 个数据包。也即丢失 31 个,丢包率为 0.0506%。

  STM32F7 打印数据显示,程序“认为”成功发送 61240 个 UDP 数据包,但实际上有 12 个没有发送成功。与 PC 端显示丢失 31 个不相符。

测试二、Ping测试(以最快速度Ping,长度1472字节)

  PC 机计数显示成功发送 21827 个 ICMP 包,超时 161 个,丢包率为 0.74%。

  STM32F7 打印数据显示,程序“认为”成功发送 21827 个 ICMP 包,但实际上有 3 个没有发送成功。与 PC 端显示丢失 161 个不相符。

6. 总结

  根据以上测试,我们可以判断,不管是 UDP 测试还是 ICMP 测试,STM32F7 接收到的数据包都与 PC 端统计的成功发送数量完全相同。也就是说,对于 STM32F7 来说并没有丢包。但对于 PC 来说,确实是丢包了。情况如下: 
  (1)在 UDP 的 echo 测试中,我们发现在与 PC 直连的环境下,STM32F7 的发送失败统计数据与 PC 端统计的丢包数据完全吻合。而在交换机环境下,STM32F7 的发送失败统计数据却小于 PC 端的丢包数据。 
  (2)在 Ping 测试中,不管直连还是存在交换机,STM32F7 接收到的包与 PC 端发送的一致,但 STM32F7 的发送失败统计数据却总小于 PC 端的丢包数据。但 Ping 存在超时时间等条件限制,建议以 UDP 测试数据为准。

  经分析认为,STM32F7 出现网络丢包的主要原因不在于 LwIP 协议栈、网卡驱动以及中断响应,而是该 Demo 程序所实现的以太网数据收发机制存在缺陷所致。即: 
  (1)对于接收部分,采用二值信号量来同步以太网外设中断和中断服务程序 ISR 是个不错的主意,在上述测试中也确实没出现接收端丢包的情况。但是如果网络数据过于频繁,系统任务繁重,使得中断服务程序还没执行完就出现了下一个中断,也可能会导致丢包。 
  (2)对于发送部分,采用网卡句柄的锁来保护共享资源的做法有待商榷,Demo 中的这种机制会直接导致资源冲突的时候丢包。

  此外,根据测试情况,Demo 程序中应该还存在其他导致数据包发送时丢失的路径。所以,建议相关开发工程师不要过分依赖 Demo 程序,如果确定使用 STM32 + FreeRTOS + LwIP 的设计方案,应该明确项目需求,在硬件资源有限的条件下,结合 FreeRTOS 和 LwIP 协议栈的特性设计出更合理的程序。
--------------------- 
作者:阿基米东 
来源:CSDN 
原文:https://blog.csdn.net/lu_embedded/article/details/76285666 
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STM32中使用LWIP库发送UDP数据,可以通过以下步骤进行: 1. 配置LWIP库:首先,需要在STM32的工程中添加LWIP库,并在代码中进行初始化和配置。可以使用STM32CubeMX工具生成基本的LWIP代码框架,并根据需求进行配置,如启用UDP功能。 2. 创建UDP套接字:使用LWIP库提供的API函数,可以创建一个UDP套接字,该套接字将用于发送数据。套接字是网络通信的端点,可以通过设置IP地址和端口号进行通信。 3. 准备数据:将待发送的数据准备为16进制格式。可以通过编程方式将数据转换为16进制格式,或者手动设置为16进制字符串。 4. 发送数据:使用LWIP库提供的UDP发送函数,将准备好的数据发送出去。调用发送函数时,需要指定UDP套接字、目标IP地址和端口号。发送成功后,会返回发送的字节数。 下面是一个简单的代码示例,用于在STM32中使用LWIP库发送16进制数据的UDP包: ```c #include "lwip/udp.h" #define DEST_IP_ADDR "192.168.0.100" // 目标IP地址 #define DEST_PORT 1234 // 目标端口号 void udp_send_data(void) { struct udp_pcb *udp_pcb; err_t err; // 创建UDP套接字 udp_pcb = udp_new(); if (udp_pcb == NULL) { // 套接字创建失败 return; } // 设置目标IP地址和端口号 ip_addr_t ip_addr; err = ipaddr_aton(DEST_IP_ADDR, &ip_addr); if (err != ERR_OK) { // IP地址转换失败 return; } udp_connect(udp_pcb, &ip_addr, DEST_PORT); // 准备数据 const char* data_str = "Hello World"; // 待发送的数据字符串 size_t data_len = strlen(data_str); // 数据长度 uint8_t data[data_len]; // 数据缓冲区 for (int i = 0; i < data_len; i++) { sscanf(data_str + 2*i, "%2hhx", &data[i]); // 将字符串解析为16进制数据 } // 发送数据 err = udp_send(udp_pcb, data, data_len); if (err != ERR_OK) { // 发送失败 return; } // 关闭套接字 udp_remove(udp_pcb); } ``` 以上代码简单地演示了如何使用LWIP库在STM32中发送16进制数据的基本过程。根据实际需求,你可以根据自己的需求进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值