目录
扩展: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")