CUBE配置STM32H750、Lan8720、FreeRTOS、lwip、掉线重连、KeepAlive移植

CUBE配置STM32H750、Lan8720、FreeRTOS、lwip、掉线重连、KeepAlive移植

2020年10月6日

1.CubeMX配置

很早就想用H750带一下Lan8720A,但lwip看起来有点复杂,这次趁着国庆节有时间,进行一下全面的调试。目的很简单,就是保证H750、Lan8720、FreeRTOS、lwip正常工作,潜在坑可能有点多,试之前也不知道可不可行,只能强上。

1.1常规配置

项目Value
High Speed ClockCrystal/Ceramic Resonator
Low Speed ClockDisable
Timebase SourceTIM1
DebugSerial Wire
CPU ICacheEnabled
CPU DCacheEnabled----必须启用,不然Lwip不让用

在这里插入图片描述

1.2 FreeRTOS配置

使用lwip迟早要上rtos,一并搞定。FRERTOS都用默认设置,建立一个任务即可,Interface=CMSIS_V1
在这里插入图片描述

1.3 Lwip配置

这里使用静态IP地址,注意一定要把LWIP_NETIF_LINK_CALLBACK选上,不然连接状态改变不能进入拔下或者插入网线回调函数,里面做一点自己的事情。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.H750 Cache配置

2.1 锁定cache位置

如果使用stm32F4+lwip的方法,是调不通的。

顺便提一下,最近编程环境从keil转向了vscode+gcc-arm-none-eabi+gdbserver,为什么要说这个。。。因为配置方法不同。

Lwip使用DMA传递信息,对应的DMA内存定义在sram中。H7的sram分为好几段,高速段为cpu独享,通俗点说就是这一段允许用户编写的程序使用,但是不允许DMA使用。所以为DMA定义的内存或者数组要避开这一段。另外Lwip使用DMA时存在交互存取问题,避开这一段后,也不能让cpu像使用普通cache那样乱序使用,否则将可能出现严重问题。很多人用F7、H7和Lwip协议栈都出现ping不通的现象,都是内存管理问题。怎样管理??需要使用内存守护单元MPU。

协议栈的DMA已经在ethernetif.c已经做了编译设定(如下代码)。条件编译结果是Keil和IAR中直接给了sram地址,从地址可以看出,已经避开了高速段,而gcc只给定了段,需要加一个手动指定地址的步骤。

ethernetif.c

#if defined ( __ICCARM__ ) /*!< IAR Compiler */

#pragma location=0x30040000
ETH_DMADescTypeDef  DMARxDscrTab[ETH_RX_DESC_CNT]; /* Ethernet Rx DMA Descriptors */
#pragma location=0x30040060
ETH_DMADescTypeDef  DMATxDscrTab[ETH_TX_DESC_CNT]; /* Ethernet Tx DMA Descriptors */
#pragma location=0x30040200
uint8_t Rx_Buff[ETH_RX_DESC_CNT][ETH_RX_BUFFER_SIZE]; /* Ethernet Receive Buffers */

#elif defined ( __CC_ARM )  /* MDK ARM Compiler */

__attribute__((at(0x30040000))) ETH_DMADescTypeDef  DMARxDscrTab[ETH_RX_DESC_CNT]; /* Ethernet Rx DMA Descriptors */
__attribute__((at(0x30040060))) ETH_DMADescTypeDef  DMATxDscrTab[ETH_TX_DESC_CNT]; /* Ethernet Tx DMA Descriptors */
__attribute__((at(0x30040200))) uint8_t Rx_Buff[ETH_RX_DESC_CNT][ETH_RX_BUFFER_SIZE]; /* Ethernet Receive Buffer */

#elif defined ( __GNUC__ ) /* GNU Compiler */

ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT] __attribute__((section(".RxDecripSection"))); /* Ethernet Rx DMA Descriptors */
ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT] __attribute__((section(".TxDecripSection")));   /* Ethernet Tx DMA Descriptors */
uint8_t Rx_Buff[ETH_RX_DESC_CNT][ETH_RX_BUFFER_SIZE] __attribute__((section(".RxArraySection"))); /* Ethernet Receive Buffers */

#endif

修改链接文件,地址指定成和keil一样即可。链接文件中手动增加一段(再次提醒:使用keil不用管这里)。
(链接文件是链接器的配置文件,位置在工程的根目录,扩展名是ld,规定了elf/bin/map文件的基本结构,这个gcc就比较好,一个链接文件说明分布问题,相比之下keil就比较隐晦,不利于理解。)

  .lwip_sec (NOLOAD) : 
  {
    . = ABSOLUTE(0x30040000);
    *(.RxDecripSection) 
    
    . = ABSOLUTE(0x30040060);
    *(.TxDecripSection)
    
    . = ABSOLUTE(0x30040200);
    *(.RxArraySection) 
  } >RAM_D2 AT> FLASH

使用CubeMX配置MPU,最多可以管理16段。为lwip配置,管理两段即可。
在这里插入图片描述


2.2 MPU设定总结(非操作步骤)

总结一下这样做的原因与目的:
(1)Lwip不被允许使用cpu专用的高速L1缓存(DTCM),只能用D2 Sram区域;
(2)cpu可以无序访问cache,为防止这种情况,Lwip的DMA段必须是device类型或者Strongly-ordered类型,保证有序;
(3)通过MPU配置这段cache,其中一段允许share、允许buffer,长度为256Byte,放TXRX交互存取头;另外一段不share,不buffer,不cache;长度32k。
这里面有很多名词比如share/buffer/cache,如果需要详细了解,可以参考另外一篇博文:
STM32H7的Cache与Buffer

以下两篇对于这个问题原理与配置过程有详细描述:
https://community.st.com/s/article/FAQ-Ethernet-not-working-on-STM32H7x3
https://community.st.com/s/article/How-to-create-project-for-STM32H7-with-Ethernet-and-LwIP-stack-working
在这里插入图片描述

2.3 MPU选项含义(非操作步骤)

cubeMX里面配置TEX、C、B,三者搭配。

https://www.stmicroelectronics.com.cn/resource/en/application_note/DM00272912-.pdf
在这里插入图片描述

Access permission被定义为3,即Full access。
在这里插入图片描述

经过上面的配置,编译下载,不出意外不用写任何代码就可以ping通了。

好了,可以进行下一步!

3. TCP/IP连接

3.1 自动重连实现

本例使用H750做client,电脑做server,利用netconn api进行tcpip通讯,其实知道原理后改用socket或者rawapi也是一样的,lwip更推荐用netconn。

ping通后只做TcpIP连接是非常容易的。但是实际应用场合,仅在开机阶段发起连接是不够的,单片机还需要具备检测连接状态的能力,比如网线被拔或服务器端down掉等异常。类似需求很常见,墙内外找个遍,没有一个公开的好的解决方案。

比较官方的解释是为了维持lwip的轻量级,没做restart功能,也就是说一旦启动了connect操作,即使失败也不能利用原来的资源进行第二次连接,有点坑爹。

跟踪了一下,原因在于连接失败后会进入错误回调函数,netconn结构体成员tcp会被销毁掉。想重连只能是魔改协议栈或者重新new一个netconn,再次走tcpip连接流程。lwip这种复杂协议,魔改很不靠谱,那么只剩一种选择。

有一点需要注意:回调函数内部只销毁tcp资源,用户函数内的netconn结构体实例需要手动销毁,不然连续4-6次connect不成功就会内存申请失败,程序会死在一个循环里面。

从逻辑上讲,没连上就应该定时重连,连接失败后进入回调函数销毁连接,看起来总有些粗暴,但是没办法,就是这种设定。。。

3.2 Lwip协议栈TCP保活(KeepAlive)设定

万事具备了么?no!

以上只解决了异常自动重连的问题,并不等于协议栈具备检测异常的能力。即H750必须知道网线是在什么时候被拔掉。

有很多博客都提到KeepAlive的开启方法,但都是详细说明怎样打开,打开了该怎么用就没说。。。

由于tcp是可靠连接,有数据往来的时候能够检测异常,需要解决的是无数据检测。这就要用到TCP协议的KeepAlive功能,原理就是在空闲的时候,以一定的频率发空数据包给服务器,服务器收到后答复一个数据包,说白了又把空闲段给变成有数据往复状态了。TCP协议栈包含KeepAlive,lwip协议栈这部分也没少,启用几个宏即可自动进行收发。设定方法为:在lwipopts.h后面加入

#define  LWIP_TCP_KEEPALIVE       1             //激活keepalive
#define  TCP_KEEPIDLE_DEFAULT     2000UL        //2秒内连接双方都无数据,则发起保活探测
#define  TCP_KEEPINTVL_DEFAULT    1000UL        //1秒发送一次保活探测
#define  TCP_KEEPCNT_DEFAULT      3UL           //3次保活探测无数据则进入错误回调函数

tcpecho.c中实例化netconn后,为其tcp成员加入SPF_KEEPALIVE属性。

xNetConn->pcb.tcp->so_options |= SOF_KEEPALIVE; //保活设定,实际上是tcp的一个属性选项

这样就能实时检测连接状态了。使用wireshark抓取数据是下图的样子,里面可以看到空闲时段交互的keepalive数据包。
电脑IP :192.168.0.99
单片机IP :192.168.0.80

在这里插入图片描述

自动化流程为:
拔掉网线------进入回调函数销毁tcp资源------主程序while循环连接出错------主程序销毁netconn资源------主程序实例化新的netconn资源------再次连接
如此往复…

这一部分内容就是要使tcp客户端成为一只打不死的小强。

4. 代码实现

4.1 Freertos.c

/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void const * argument)
{
  /* init code for LWIP */
  MX_LWIP_Init();
  /* USER CODE BEGIN StartDefaultTask */
  tcpecho_init();
  /* Infinite loop */
  for(;;)
  {
    osDelay(1000);
  }
  
  /* USER CODE END StartDefaultTask */
}

4.2 tcpecho.h/c

#ifndef LWIP_TCPECHO_H
#define LWIP_TCPECHO_H

void tcpecho_init(void);

#endif /* LWIP_TCPECHO_H */

tcpecho.c
#include "tcpecho.h"
#include "lwip/opt.h"
#include "lwip/sys.h"
#include "lwip/api.h"
#include "ip_addr.h"
#include "netbuf.h"
#include "tcp.h"

uint8_t data[2000];                               //TCP客户端接收数据缓冲区
static void tcpecho_thread(void *arg)
{
  struct netconn *xNetConn = NULL;
  struct netbuf *buf;

  ip_addr_t local_ip = IPADDR4_INIT(IPADDR_ANY);
  ip_addr_t remote_ip = IPADDR4_INIT(IPADDR_ANY);

  err_t err,err_rev;
  
  IP4_ADDR( &local_ip, 192, 168, 0, 80);  
  IP4_ADDR( &remote_ip, 192, 168, 0, 100);

  xNetConn = netconn_new ( NETCONN_TCP );

  while(1)
  {
    err = netconn_connect ( xNetConn, &remote_ip, 7); //正常连接这里只会执行一次,阻塞在下面的netconn_recv
    /*

      保活设定:保活的含义是让单片机能够知道网络断了,进入错误处理程序。

      1.在tcpecho.h中进行如下设定
      #define  LWIP_TCP_KEEPALIVE       1             //激活keepalive
      #define  TCP_KEEPIDLE_DEFAULT     2000UL        //2秒内连接双方都无数据
      #define  TCP_KEEPINTVL_DEFAULT    1000UL        //1秒发送一次保活探测
      #define  TCP_KEEPCNT_DEFAULT      3UL           //3次保活探测无数据则进入错误回调函数
      
      2.为tcp的so_options加上SOF_KEEPALIVE选项
    */

    xNetConn->pcb.tcp->so_options |= SOF_KEEPALIVE;    //保活设定,实际上是tcp的一个选项

    if (err==ERR_OK)
    {
      while ((err_rev = netconn_recv(xNetConn, &buf)) == ERR_OK) //阻塞在这里
      {
        do {
              netbuf_data(buf, data, sizeof(data));
              //err = netconn_write(xNetConn, data, len, NETCONN_COPY);
        } while (netbuf_next(buf) >= 0);
        netbuf_delete(buf);
      }
    }
    else
    {
      netconn_close(xNetConn);                          //一定要关闭
      netconn_delete(xNetConn);                         //一定要删除,否则在malloc xNetConn时候,4~6次就申请不到导致直接error退出
      xNetConn = netconn_new ( NETCONN_TCP );
      vTaskDelay(2000);
    }
  }

}

void
tcpecho_init(void)
{
  sys_thread_new("tcpecho_thread", tcpecho_thread, NULL, (configMINIMAL_STACK_SIZE*2), osPriorityAboveNormal);
}

4.3 lwipopts.h

//省略CubeMX自动生成的内容
#define  LWIP_TCP_KEEPALIVE       1             //激活keepalive
#define  TCP_KEEPIDLE_DEFAULT     2000UL        //2秒内连接双方都无数据,则发起保活探测
#define  TCP_KEEPINTVL_DEFAULT    1000UL        //1秒发送一次保活探测
#define  TCP_KEEPCNT_DEFAULT      3UL           //3次保活探测无数据则进入错误回调函数

3.4 tcpecho.ld(gcc编译器才用这个)

/* Specify the memory areas */
MEMORY
{
DTCMRAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 128K
RAM_D1 (xrw)      : ORIGIN = 0x24000000, LENGTH = 512K
RAM_D2 (xrw)      : ORIGIN = 0x30000000, LENGTH = 288K
RAM_D3 (xrw)      : ORIGIN = 0x38000000, LENGTH = 64K
ITCMRAM (xrw)      : ORIGIN = 0x00000000, LENGTH = 64K
FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 128K
}
//-----省略部分内容
//-----省略部分内容
//-----省略部分内容
 /* User_heap_stack section, used to check that there is enough RAM left */
  ._user_heap_stack :
  {
    . = ALIGN(8);
    PROVIDE ( end = . );
    PROVIDE ( _end = . );
    . = . + _Min_Heap_Size;
    . = . + _Min_Stack_Size;
    . = ALIGN(8);
  } >DTCMRAM
//在这里新加一段
//新加段开始
  .lwip_sec (NOLOAD) : 
  {
    . = ABSOLUTE(0x30040000);
    *(.RxDecripSection) 
    
    . = ABSOLUTE(0x30040060);
    *(.TxDecripSection)
    
    . = ABSOLUTE(0x30040200);
    *(.RxArraySection) 
  } >RAM_D2 AT> FLASH
//新加段结束

  /* Remove information from the standard libraries */
  /DISCARD/ :
  {
    libc.a ( * )
    libm.a ( * )
    libgcc.a ( * )
  }

5.Tcp速度测试

测试环境:H750做Client,网络调试助手做Server。调试助手发送数据,H750接包后直接丢弃。

实测23M单向传输速度,比预想快很多,现在的速度瓶颈应该是网络助手,H750实际的网络速度能够达到多少仍然未知。
在这里插入图片描述
上面已经把关键步骤说得很清楚了,如果还是没搞定,下面是本文配置成功的工程文件下载地址:
https://download.csdn.net/download/monei3525/12913098

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值