本文记录在 STM32H7S78-DK 上基于 Zephyr 4.5 实现以太网 DHCP 联网 + LVGL 状态栏显示的过程,重点是一次"裸跑正常、加上显示就挂"的疑难问题排查,根因是 MPU 内存属性区域覆盖破坏了 ETH DMA 的缓存一致性。

1. 目标与实现
屏幕顶部做一条状态栏:红圆点 = 断链,橙圆点 = 链路正常/DHCP 获取中,绿圆点 + IP 地址 = 联网成功。
prj.conf 关键配置:
CONFIG_DISPLAY=y CONFIG_LVGL=y CONFIG_LV_COLOR_DEPTH_24=y CONFIG_NETWORKING=y CONFIG_NET_L2_ETHERNET=y CONFIG_NET_IPV4=y CONFIG_NET_IPV6=n CONFIG_NET_DHCPV4=y CONFIG_NET_MGMT=y CONFIG_NET_MGMT_EVENT=y
main.c 核心逻辑:订阅网络管理事件,同时每秒轮询兜底(防止启动早错过事件);链路正常但没地址时主动发起 DHCP:
static void refresh_status(void)
{
struct net_if *iface = net_if_get_default();
if (iface == NULL || !net_if_is_carrier_ok(iface)) {
dhcp_started = false; /* 断链后重新插线时重新发起 */
set_net_status(lv_color_hex(0xff4040), "Link down");
return;
}
struct net_in_addr *in = net_if_ipv4_get_global_addr(iface, NET_ADDR_PREFERRED);
if (in != NULL) {
char addr[NET_IPV4_ADDR_LEN];
net_addr_ntop(AF_INET, in, addr, sizeof(addr));
snprintf(text, sizeof(text), "IP: %s", addr);
set_net_status(lv_color_hex(0x20c040), text);
} else {
set_net_status(lv_color_hex(0xffa000), "Link up, DHCP...");
if (!dhcp_started) {
dhcp_started = true;
net_dhcpv4_start(iface); /* DHCP 要主动发起 */
}
}
}注意两个新版本的 API 细节:事件名是 NET_EVENT_ETHERNET_CARRIER_ON/OFF(不是 ETH_);取地址用 net_if_ipv4_get_global_addr(),不要自己遍历 unicast[](4.5 里 net_if_addr 结构变了)。
2. 故障现象与排查过程
首次运行:PHY 识别正常(LAN8742,ID 7C131),链路 100Mb 全双工,但 DHCP 发出不去:
<err> eth_stm32_hal: HAL_ETH_TransmitIT tx_int_sem take timeout
TX 完成中断永远不来。排查采用了逐层隔离变量的方法:
| 片内 Flash 裸跑(无 MCUboot、无显示),DHCP 结果用 LED 指示 | 正常拿到地址 |
| 外部 Flash + MCUboot,无显示 | 正常 |
| 外部 Flash + MEMC/PSRAM(无显示) | 正常 |
| 外部 Flash + LTDC 显示 | 失败,TX 超时复现 |
(小技巧:没串口输出时用 LED 做结果指示;读 RAM 变量验证也可用 SWD,但低功耗下 hotplug 容易连不上。)
3. 根因:MPU 区域覆盖,DMA 缓冲区变成了 cacheable
H7RS 上 ETH 的 DMA 描述符和缓冲区通过 mac 节点的 memory-regions = <&sram1> 放在 SRAM1(0x30000000)。soc/st/stm32/stm32h7rsx/mpu_regions.c 会为它建两个 MPU 区域:NOCACHE 的数据缓冲区 + PPB 属性的描述符区。
但 SoC dtsi 里 sram1 节点同时带着 cacheable 属性:
sram1: memory@30000000 {
zephyr,memory-region = "SRAM1";
zephyr,memory-attr = <DT_MEM_ARM(ATTR_MPU_RAM)>; /* ← cacheable */
};MPU 区域序号大的优先级高:dts memory-attr 生成的区域排在 mpu_regions.c 的静态区域之后,于是 cacheable 区域把 ETH 的 NOCACHE 区域盖掉了。CPU 写的 TX 数据留在 D-Cache 里,ETH DMA 读到的是旧数据;DMA 出错后 TX 完成中断不来,表现为 tx_int_sem 超时。
这个坑只在显示/MEMC 加入后才暴露(区域布局变化导致覆盖关系改变),纯网络裸跑时恰好没事——这也解释了为什么社区里"同样的驱动,有人能通有人不通"。
4. 修复
应用层 overlay(app/eth_demo/boards/stm32h7s78_dk_stm32h7s7xx_ext_flash_app.overlay)把 sram1 显式改回 NOCACHE:
&sram1 {
zephyr,memory-attr = <DT_MEM_ARM_MPU_RAM_NOCACHE>;
};修复后串口日志:

PC 侧 ping 验证:4/4 回复,1ms,0% 丢包。屏幕状态栏绿点 + IP 显示正常。
5. 踩坑记录
DHCP 要主动发起:链路 up 后调 net_dhcpv4_start(iface),别假设全自动
MPU 区域覆盖优先级:mpu_regions.c 静态区域在前、dts zephyr,memory-attr 区域在后,后者覆盖前者——DMA 缓冲区的 NOCACHE 属性会被静默盖掉
dhcpv4.h 要在 net_if.h 之后包含:它自己不带 net_if 定义,顺序错了报 "incompatible pointer type"(同名不同类型)
-DCONF_FILE= 要用 Windows 路径:Git Bash 的 /tmp 路径原生 CMake 读不到
烧过片内 Flash 测试程序会覆盖 MCUboot:之后跑 ext_flash_app 要重新完整 west flash
我要赚赏金
