lwIP+FreeRTOS+stm32f407

目录

tcp echo server 应用代码验证通过

问题

DMABMR->SR 始终为 1,无法自动复位

问题描述:

解决方法:

扩展:LAN8720A 和 LAN8742 这两种PHY芯片驱动可以通用吗?

官方例程注意事项


问题0:进入硬中断

平台环境:lwip2.1.2 + freertos10.3.x + stm32f407 + cubemx + tepecho server

问题描述:根据CubeMX的lwipopts.h的默认配置参数运行,必须等待系统系统运行后,才能插入RJ45水晶头,否则在系统启动前就插入水晶头,系统进入HardFault,且系统及其不稳定。必须把lwipopts参数配置的超级大,几乎快把stm32f407ve的RAM耗尽,120多字节的RAM占用,才能带着RJ45插入的前提下正常启动。调整后,可以做到90多k.

解决方案:增加两项配置参数,对lwip core的内存堆和内存池进行溢出检测,即增加:

#define MEMP_OVERFLOW_CHECK             1
#define MEM_OVERFLOW_CHECK               1

结果:测试 系统 OK 稳定,可以带着RJ45插入,重启,正常运行。

原因分析:未知,还不知道为什么会这样。头疼。

配置前的参数:

ETH_RX_BUFFER_CNT 必须大于42 否则死机HardFault

MEMP_NUM_UDP_PCB (Number of UDP Connections) 40

MEMP_NUM_TCP_PCB (Number of TCP Connections) 50

TCP_SND_QUEUELEN (Number of Packet Buffers Allowed for TCP Sender) 100

配置后的参数:

重大更新:

2024年11月10日17:10:52

原因找到了,直接扩大 defaultTask的栈区空间,就可以保证lwip协议栈的顺利初始化。不会再进入硬中断!所以,原因就是 初始化lwip协议栈所在的任务defaultTask任务的栈区空间被耗尽,导致无法顺利完成lwip且进入硬中断。

也不需要给lwipopts.h中的关键参数分配设计较大的空间。

tcp echo server 应用代码验证通过

实验平台:CubeMX v6.12.0 stm32f407vet6 FreeRTOS v10.3.1 / CMSIS V2.00/ lwIP v2.1.2/ PHY  LAN8720A

如果不知道怎么设置网络IP、掩码、网关。那么必须先补一下相关知识。否则没办法进行。。。

 CubeMX 自带的 PHY驱动 LAN8742A 完全适用于 LAN8720A ,无需改动。

问题1:DMABMR->SR 始终为 1,无法自动复位

问题描述:

使用cubemx自动生成初始化代码,原始代码未作任何修改的情况下,代码卡在:

DMABMR->SR 在被置位后,无法自动复位。DMABMR->SR 寄存器的值始终是 1。

导致 return HAL_ERROR;进入死循环。

解决方法:

1. 检查 MAC 的时钟是否配置正常:三个时钟:

在 HAL_ETH_MspInit 函数中需要执行语句: __HAL_RCC_ETH_CLK_ENABLE();

这个是 cubemx 生成的,是没问题的。

2. 检查 PHY 模块(比如LAN8720A)是否正常与stm32f4074按照RMII正确的物理连接,且IO引脚被正确配置。

这不是知道是哪个版本的代码,我使用的没有这句注释,所以保证PHY或其IO的正确配置。

3. 模糊场景:一直在向错误的寄存器中写入数据

4.模糊场景:缺失一些引脚复用功能的配置操作。

STM32F207 DMSBMR->SR always TRUE - STMicroelectronics Community

我的原因是,没有接线(接PHY),只要没有正确接线,stm32f407 以太网MAC控制器的 DMABMR->SR 无法自动复位,下图是我接线以后的调试现场

扩展:LAN8720A 和 LAN8742 这两种PHY芯片驱动可以通用吗?

网络答案,可以。应该是可以的,芯片信号太近,可能功能都一致,也可能只是物理电气特性不同,上文提及多了一条唤醒功能。

今天验证了,证明 :LAN8742 与 LAN8720A 驱动可以通用。(cebumx)2024年10月24日16:17:25

官方例程注意事项

stm32f407 设置为 http server 网络服务器 FreeRTOS lwIP

关键字 Connectivity, LwIP, Ethernet, HTTP Server, Netconn, TCP/IP, FreeRTOS, DHCP

1. ethernet link 的中断优先级要比 HAL tick 的中断优先级要低
2. 若要在外设中断中使用 HAL_Delay() ,则需要保证 HAL timer的硬件时钟优先级要
   高于该外设中断的优先级,否则此ISR就被锁住了。
3. 应用程序需要保证 HAL timer 的时基时钟为 1ms 不变。
4. 更多信息,参考 《UM1713 STM32Cube interfacing with LwIP and applications》

问题2: tcpip echo server, 创建应用线程,出现硬中断

使用官方contrib包tcpip echo server, 创建应用线程,在连接RJ45水晶头后,开启stm32f407出现硬中断;在不连接RJ45水晶头的前提下,开启stm32f407,等稳定2s后,再连接RJ45水晶头,程序正常运行,可以成功验证tcpip echo server。

问题就出现在 

#include "tcpecho.h"

#include "lwip/opt.h"

#if LWIP_NETCONN

#include "lwip/sys.h"
#include "lwip/api.h"
/*-----------------------------------------------------------------------------------*/
static void
tcpecho_thread(void *arg)
{
  struct netconn *conn, *newconn;
  err_t err;
  LWIP_UNUSED_ARG(arg);

  /* Create a new connection identifier. */
  /* Bind connection to well known port number 7. */
#if LWIP_IPV6
  conn = netconn_new(NETCONN_TCP_IPV6);
  netconn_bind(conn, IP6_ADDR_ANY, 7);
#else /* LWIP_IPV6 */
  conn = netconn_new(NETCONN_TCP);
  netconn_bind(conn, IP_ADDR_ANY, 7); //prot: 7
#endif /* LWIP_IPV6 */
  LWIP_ERROR("tcpecho: invalid conn", (conn != NULL), return;);

  /* Tell connection to go into listening mode. */
  netconn_listen(conn);

  while (1) {

    /* Grab new connection. */
    err = netconn_accept(conn, &newconn);
    /*printf("accepted new connection %p\n", newconn);*/
    /* Process the new connection. */
    if (err == ERR_OK) {
      struct netbuf *buf;
      void *data;
      u16_t len;

      while ((err = netconn_recv(newconn, &buf)) == ERR_OK) {
        /*printf("Recved\n");*/
        do {
             netbuf_data(buf, &data, &len);
             err = netconn_write(newconn, data, len, NETCONN_COPY);
#if 1
            if (err != ERR_OK) {
              printf("tcpecho: netconn_write: error \"%s\"\n", lwip_strerr(err));
            }
#endif
        } while (netbuf_next(buf) >= 0);
        netbuf_delete(buf);
      }
      /*printf("Got EOF, looping\n");*/
      /* Close connection and discard connection identifier. */
      netconn_close(newconn);
      netconn_delete(newconn);
    }
  }
}

void
tcpecho_init(void)
{
  sys_thread_new("tcpecho_thread", tcpecho_thread, NULL, DEFAULT_THREAD_STACKSIZE, DEFAULT_THREAD_PRIO);
}

这到底怎么造成stm32f407的hardfault,我也十分疑惑。目前总网络上找到的解决方案:

问题3:有无操作系统的 lwIP core thread 做的内容是一样吗?

是的,无操作系统和有操作系统情况下,lwIP core 执行的核心逻辑内容是相同的,只是实现方式上有所不同:

1. 核心逻辑的相似性

  • 在两种情况下,lwIP core 都需要执行网络协议栈的核心功能,例如数据接收与发送、协议处理(如 TCP/IP)、连接管理、定时器处理等。
  • 在有操作系统的情况下,这些操作通常在一个专门的线程(tcpip_thread)中完成,而在裸机情况下,需要通过应用程序的 while(1) 主循环来主动调用对应的处理函数。

2. 无操作系统(裸机)情况

  • 主循环:在裸机中,应用程序负责执行 while(1) 主循环,并在循环中调用 lwIP 内核的核心函数。
  • 定时调用:通过定时调用核心处理函数(如 sys_check_timeouts() 或者 tcp_tmr()),应用程序可以驱动 lwIP 的定时器事件,例如处理 TCP 重传、连接超时等。

3. 有操作系统(OS)情况

  • 独立线程:在有 OS 的情况下,lwIP 通过 tcpip_thread 来完成所有网络事件的处理,不需要主循环干预。操作系统的线程调度会自动管理这个线程的执行。
  • 定时器和中断处理:系统线程会自动调用定时器和中断响应函数,无需应用层进行轮询。

4. 总结

  • 执行内容相同:无论有无操作系统,lwIP core 的执行逻辑和处理步骤是一致的。
  • 实现方式不同:在裸机环境下,应用程序必须在主循环中调用 lwIP 内核函数;而在有 OS 的情况下,这些操作由系统线程自动完成。

lwip的内存数据结构

1. 内存堆和内存池的数据模型

---------------------------------------------------
/* 第一 lwip 的堆 */
/* 堆大小 字节对齐*/
#define MEM_SIZE                        1600                //默认值,用户可自行配置
#define MEM_SIZE_ALIGNED     LWIP_MEM_ALIGN_SIZE(MEM_SIZE)  //转换成,字节对齐后的堆大小 MEM_SIZE_ALIGNED

/* 堆地址:使用宏代码,分配一个静态数组:应用于lwip的堆和池。 arch.h */
#define LWIP_DECLARE_MEMORY_ALIGNED(variable_name, size) u8_t variable_name[LWIP_MEM_ALIGN_BUFFER(size)] //等级0 原始定义

/** lwip 内存堆 heap 的数组定义
 ** the heap. 使用宏,定义一个数组作为lwipcore的堆空间,mem.c文件中定义:
 ** ram_heap:堆实现方式是全局静态数据,ram_heap就是数组名;
 ** MEM_SIZE_ALIGNED:是堆大小进过对齐后的数值,也就是数组需要分配的大小;
 */
LWIP_DECLARE_MEMORY_ALIGNED(ram_heap, MEM_SIZE_ALIGNED + (2U * SIZEOF_STRUCT_MEM)); //等级1

/* ram_heap就是堆空间的数组名,也就是数组首地址,LWIP_RAM_HEAP_POINTER = ram_heap */
#define LWIP_RAM_HEAP_POINTER ram_heap

---------------------------------------------------
/* 第二 lwip 的内存池 */
struct memp {
  struct memp *next;    //就一个指针
#if MEMP_OVERFLOW_CHECK //溢出检测
  const char *file;
  int line;
#endif /* MEMP_OVERFLOW_CHECK */
};
/** Memory stats 内存池状态结构体 */
struct stats_mem {
#if defined(LWIP_DEBUG) || LWIP_STATS_DISPLAY
  const char *name;
#endif /* defined(LWIP_DEBUG) || LWIP_STATS_DISPLAY */
  STAT_COUNTER err;
  mem_size_t avail; //可用
  mem_size_t used;  //已用
  mem_size_t max;   //最大
  STAT_COUNTER illegal;
};
/** Memory pool descriptor 内存池描述符结构体 */
struct memp_desc {
#if defined(LWIP_DEBUG) || MEMP_OVERFLOW_CHECK || LWIP_STATS_DISPLAY
  /** Textual description */
  const char *desc;
#endif /* LWIP_DEBUG || MEMP_OVERFLOW_CHECK || LWIP_STATS_DISPLAY */
#if MEMP_STATS
  /** Statistics */
  struct stats_mem *stats;
#endif

  /** Element size */
  u16_t size;

#if !MEMP_MEM_MALLOC
  /** Number of elements */
  u16_t num;

  /** Base address */
  u8_t *base;

  /** First free element of each pool. Elements form a linked list. */
  struct memp **tab;
#endif /* MEMP_MEM_MALLOC */
};
/* MEMP_SIZE: save space for struct memp and for sanity check */
#define MEMP_SIZE          (LWIP_MEM_ALIGN_SIZE(sizeof(struct memp)) + MEM_SANITY_REGION_BEFORE_ALIGNED)
#define MEMP_ALIGN_SIZE(x) (LWIP_MEM_ALIGN_SIZE(x) + MEM_SANITY_REGION_AFTER_ALIGNED)
#define LWIP_MEMPOOL_DECLARE_STATS_INSTANCE(name) static struct stats_mem name;

/* lwip 内存池 memp 的结构体定义 */
#define LWIP_MEMPOOL_DECLARE(name,num,size,desc) \
  LWIP_DECLARE_MEMORY_ALIGNED(memp_memory_ ## name ## _base, ((num) * (MEMP_SIZE + MEMP_ALIGN_SIZE(size)))); \ /* 定义一个数组 */
    \
  LWIP_MEMPOOL_DECLARE_STATS_INSTANCE(memp_stats_ ## name) \ /* 定义一个内存池状态结构体 */
    \
  static struct memp *memp_tab_ ## name; \  /* 定义一个指针 */
    \
  const struct memp_desc memp_ ## name = { \
    DECLARE_LWIP_MEMPOOL_DESC(desc) \
    LWIP_MEMPOOL_DECLARE_STATS_REFERENCE(memp_stats_ ## name) \
    LWIP_MEM_ALIGN_SIZE(size), \
    (num), \
    memp_memory_ ## name ## _base, \
    &memp_tab_ ## name \
  };
  
//ethernetif.c文件中定义:
/* Memory Pool Declaration */
#define ETH_RX_BUFFER_CNT             48U
LWIP_MEMPOOL_DECLARE(RX_POOL, ETH_RX_BUFFER_CNT, sizeof(RxBuff_t), "Zero-copy RX PBUF pool"); //等级2

#define LWIP_MEMPOOL(name,num,size,desc) LWIP_MEMPOOL_DECLARE(name,num,size,desc) //等级2
#define LWIP_PBUF_MEMPOOL(name, num, payload, desc) LWIP_MEMPOOL(name, num, (LWIP_MEM_ALIGN_SIZE(sizeof(struct pbuf)) + LWIP_MEM_ALIGN_SIZE(payload)), desc) //等级2

-------------------
//file memp_std.h

/*
 * A list of internal pools used by LWIP.
 *
 * LWIP_MEMPOOL(pool_name, number_elements, element_size, pool_description)
 *     creates a pool name MEMP_pool_name. description is used in stats.c
 */
#if LWIP_RAW
LWIP_MEMPOOL(RAW_PCB,        MEMP_NUM_RAW_PCB,         sizeof(struct raw_pcb),        "RAW_PCB")
#endif /* LWIP_RAW */

#if LWIP_UDP
LWIP_MEMPOOL(UDP_PCB,        MEMP_NUM_UDP_PCB,         sizeof(struct udp_pcb),        "UDP_PCB")
#endif /* LWIP_UDP */

#if LWIP_TCP
LWIP_MEMPOOL(TCP_PCB,        MEMP_NUM_TCP_PCB,         sizeof(struct tcp_pcb),        "TCP_PCB")
LWIP_MEMPOOL(TCP_PCB_LISTEN, MEMP_NUM_TCP_PCB_LISTEN,  sizeof(struct tcp_pcb_listen), "TCP_PCB_LISTEN")
LWIP_MEMPOOL(TCP_SEG,        MEMP_NUM_TCP_SEG,         sizeof(struct tcp_seg),        "TCP_SEG")
#endif /* LWIP_TCP */

#if LWIP_ALTCP && LWIP_TCP
LWIP_MEMPOOL(ALTCP_PCB,      MEMP_NUM_ALTCP_PCB,       sizeof(struct altcp_pcb),      "ALTCP_PCB")
#endif /* LWIP_ALTCP && LWIP_TCP */

#if LWIP_IPV4 && IP_REASSEMBLY
LWIP_MEMPOOL(REASSDATA,      MEMP_NUM_REASSDATA,       sizeof(struct ip_reassdata),   "REASSDATA")
#endif /* LWIP_IPV4 && IP_REASSEMBLY */
#if (IP_FRAG && !LWIP_NETIF_TX_SINGLE_PBUF) || (LWIP_IPV6 && LWIP_IPV6_FRAG)
LWIP_MEMPOOL(FRAG_PBUF,      MEMP_NUM_FRAG_PBUF,       sizeof(struct pbuf_custom_ref),"FRAG_PBUF")
#endif /* IP_FRAG && !LWIP_NETIF_TX_SINGLE_PBUF || (LWIP_IPV6 && LWIP_IPV6_FRAG) */

#if LWIP_NETCONN || LWIP_SOCKET
LWIP_MEMPOOL(NETBUF,         MEMP_NUM_NETBUF,          sizeof(struct netbuf),         "NETBUF")
LWIP_MEMPOOL(NETCONN,        MEMP_NUM_NETCONN,         sizeof(struct netconn),        "NETCONN")
#endif /* LWIP_NETCONN || LWIP_SOCKET */

#if NO_SYS==0
LWIP_MEMPOOL(TCPIP_MSG_API,  MEMP_NUM_TCPIP_MSG_API,   sizeof(struct tcpip_msg),      "TCPIP_MSG_API")
#if LWIP_MPU_COMPATIBLE
LWIP_MEMPOOL(API_MSG,        MEMP_NUM_API_MSG,         sizeof(struct api_msg),        "API_MSG")
#if LWIP_DNS
LWIP_MEMPOOL(DNS_API_MSG,    MEMP_NUM_DNS_API_MSG,     sizeof(struct dns_api_msg),    "DNS_API_MSG")
#endif
#if LWIP_SOCKET && !LWIP_TCPIP_CORE_LOCKING
LWIP_MEMPOOL(SOCKET_SETGETSOCKOPT_DATA, MEMP_NUM_SOCKET_SETGETSOCKOPT_DATA, sizeof(struct lwip_setgetsockopt_data), "SOCKET_SETGETSOCKOPT_DATA")
#endif
#if LWIP_SOCKET && (LWIP_SOCKET_SELECT || LWIP_SOCKET_POLL)
LWIP_MEMPOOL(SELECT_CB,      MEMP_NUM_SELECT_CB,       sizeof(struct lwip_select_cb), "SELECT_CB")
#endif /* LWIP_SOCKET && (LWIP_SOCKET_SELECT || LWIP_SOCKET_POLL) */
#if LWIP_NETIF_API
LWIP_MEMPOOL(NETIFAPI_MSG,   MEMP_NUM_NETIFAPI_MSG,    sizeof(struct netifapi_msg),   "NETIFAPI_MSG")
#endif
#endif /* LWIP_MPU_COMPATIBLE */
#if !LWIP_TCPIP_CORE_LOCKING_INPUT
LWIP_MEMPOOL(TCPIP_MSG_INPKT,MEMP_NUM_TCPIP_MSG_INPKT, sizeof(struct tcpip_msg),      "TCPIP_MSG_INPKT")
#endif /* !LWIP_TCPIP_CORE_LOCKING_INPUT */
#endif /* NO_SYS==0 */

#if LWIP_IPV4 && LWIP_ARP && ARP_QUEUEING
LWIP_MEMPOOL(ARP_QUEUE,      MEMP_NUM_ARP_QUEUE,       sizeof(struct etharp_q_entry), "ARP_QUEUE")
#endif /* LWIP_IPV4 && LWIP_ARP && ARP_QUEUEING */

#if LWIP_IGMP
LWIP_MEMPOOL(IGMP_GROUP,     MEMP_NUM_IGMP_GROUP,      sizeof(struct igmp_group),     "IGMP_GROUP")
#endif /* LWIP_IGMP */

#if LWIP_TIMERS && !LWIP_TIMERS_CUSTOM
LWIP_MEMPOOL(SYS_TIMEOUT,    MEMP_NUM_SYS_TIMEOUT,     sizeof(struct sys_timeo),      "SYS_TIMEOUT")
#endif /* LWIP_TIMERS && !LWIP_TIMERS_CUSTOM */

#if LWIP_DNS && LWIP_SOCKET
LWIP_MEMPOOL(NETDB,          MEMP_NUM_NETDB,           NETDB_ELEM_SIZE,               "NETDB")
#endif /* LWIP_DNS && LWIP_SOCKET */
#if LWIP_DNS && DNS_LOCAL_HOSTLIST && DNS_LOCAL_HOSTLIST_IS_DYNAMIC
LWIP_MEMPOOL(LOCALHOSTLIST,  MEMP_NUM_LOCALHOSTLIST,   LOCALHOSTLIST_ELEM_SIZE,       "LOCALHOSTLIST")
#endif /* LWIP_DNS && DNS_LOCAL_HOSTLIST && DNS_LOCAL_HOSTLIST_IS_DYNAMIC */

#if LWIP_IPV6 && LWIP_ND6_QUEUEING
LWIP_MEMPOOL(ND6_QUEUE,      MEMP_NUM_ND6_QUEUE,       sizeof(struct nd6_q_entry),    "ND6_QUEUE")
#endif /* LWIP_IPV6 && LWIP_ND6_QUEUEING */

#if LWIP_IPV6 && LWIP_IPV6_REASS
LWIP_MEMPOOL(IP6_REASSDATA,  MEMP_NUM_REASSDATA,       sizeof(struct ip6_reassdata),  "IP6_REASSDATA")
#endif /* LWIP_IPV6 && LWIP_IPV6_REASS */

#if LWIP_IPV6 && LWIP_IPV6_MLD
LWIP_MEMPOOL(MLD6_GROUP,     MEMP_NUM_MLD6_GROUP,      sizeof(struct mld_group),      "MLD6_GROUP")
#endif /* LWIP_IPV6 && LWIP_IPV6_MLD */


/*
 * A list of pools of pbuf's used by LWIP.
 *
 * LWIP_PBUF_MEMPOOL(pool_name, number_elements, pbuf_payload_size, pool_description)
 *     creates a pool name MEMP_pool_name. description is used in stats.c
 *     This allocates enough space for the pbuf struct and a payload.
 *     (Example: pbuf_payload_size=0 allocates only size for the struct)
 */
LWIP_MEMPOOL(PBUF,           MEMP_NUM_PBUF,            sizeof(struct pbuf),           "PBUF_REF/ROM")
LWIP_PBUF_MEMPOOL(PBUF_POOL, PBUF_POOL_SIZE,           PBUF_POOL_BUFSIZE,             "PBUF_POOL")

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值