news 2026/2/3 22:13:46

从STM32F407到STM32H743:使用CubeMx实现基于Lwip+LAN8720A+FreeRTOS的以太网数据传输(MPU、D-Cache、I-Cache踩坑指南)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从STM32F407到STM32H743:使用CubeMx实现基于Lwip+LAN8720A+FreeRTOS的以太网数据传输(MPU、D-Cache、I-Cache踩坑指南)

从STM32F407到STM32H743:使用CubeMx实现基于Lwip+LAN8720A+FreeRTOS的以太网数据传输(MPU、D-Cache、I-Cache踩坑指南)

  • Chapter0 从STM32F407到STM32H743:使用CubeMx实现基于Lwip+LAN8720A+FreeRTOS的以太网数据传输(MPU、D-Cache、I-Cache踩坑指南)
    • 一、H7 架构的不同:缓存机制带来的好处与难点
      • 1.1 D-Cache 为什么会搞坏程序?
    • 二、如何过渡:从“关 Cache”到“正确使用 Cache”
      • 2.1 第一步:先关 D-Cache,把功能跑通
      • 2.2 第二步:重新打开 Cache,但只让“安全区域”被缓存
    • 三、具体如何配置 STM32CubeMX
      • 3.1 STM32CubeMx实操
    • 四、调试要点
      • 4.1 典型症状:怀疑是 Cache / MPU 问题时
      • 4.2 抓包 + 回调计数
      • 4.3 分步排除思路
      • 4.4 以太网联通测试
    • 五、UDP接收测试
      • 5.1 测试
      • 5.2 代码
    • 六、总结

Chapter0 从STM32F407到STM32H743:使用CubeMx实现基于Lwip+LAN8720A+FreeRTOS的以太网数据传输(MPU、D-Cache、I-Cache踩坑指南)

原文链接:https://blog.csdn.net/qq_39432978/article/details/155713794

一、H7 架构的不同:缓存机制带来的好处与难点

在 F1/F4 上写程序,几乎不用管 Cache 和 MPU;
到了 STM32H7,情况完全不同:

  • 内核换成 Cortex-M7,带 I-Cache + D-Cache,主频 400MHz/480MHz;
  • 片上 RAM 分为 D1 / D2 / D3 域,还有 ITCM/DTCM 等不同速度的存储器;
  • ETH、SDMMC、FMC、USB 等大量外设通过 DMA + 总线矩阵 高速访问内存。

设计目标是:在不加外部 RAM 的情况下,把性能榨干。
缺点是:所有用 DMA 的外设都必须考虑 和 D-Cache 的一致性。

1.1 D-Cache 为什么会搞坏程序?

开启 D-Cache 后:

CPU 读写数据 → 先在 Cache 里操作,不一定立刻写回 SRAM;
DMA(ETH、SDMMC 等)直接访问 SRAM,看不到 Cache 中尚未写回的修改。
如果以太网的 DMA 描述符、Rx/Tx 缓冲区 放在可缓存区域:

CPU 写描述符,只写进 Cache;
DMA 看到的仍是旧数据,可能跑飞;
DMA 向 SRAM 写 Rx 数据,CPU 还在读旧 Cache;
各种结构体和返回地址被“写花”,最终 随机 HardFault。

典型现象:

以太网链路灯亮,PC 能识别网卡,但 ping 不通;
Wireshark 只看到 PC 在不停发 ARP,板子一点反应没有;
程序跑一会儿就进 HardFault_Handler,PC 类似 0x32F9xxxx(明显是 SRAM),说明在执行被 DMA 写坏的“垃圾代码”。

二、如何过渡:从“关 Cache”到“正确使用 Cache”

对从 F4/F7 迁移过来的用户,推荐 两步走。

2.1 第一步:先关 D-Cache,把功能跑通

调试阶段,最简单粗暴:

intmain(void){SCB_DisableICache();SCB_DisableDCache();// 先关 D-Cache,保证 DMA 与 CPU 看同一份内存HAL_Init();SystemClock_Config();...}

此时:

- 以太网、SD 卡、USB 等 DMA 外设行为接近 F4;
- 不再出现“跑一阵随机 HardFault”的情况;
- 性能略降,但足以支撑大多数应用,适合 先把协议栈和业务逻辑全部打通。

2.2 第二步:重新打开 Cache,但只让“安全区域”被缓存

功能稳定后再考虑性能:

  1. I-Cache 基本可以一直打开,风险很小;

  2. D-Cache 必须配合 MPU:

  • 只给普通变量、算法数据等使用的 RAM 开 Cache;
  • 所有 DMA 访问的内存(ETH 描述符、Rx/Tx buffer、SD 缓冲区等)
    → 放到 Non-Cacheable 的 MPU 区域。

这样:

对 DMA 外设来说,相当于“关闭 Cache”,可靠;
对 CPU 来说,大部分内存仍可享受 D-Cache 带来的性能提升。

三、具体如何配置 STM32CubeMX

3.1 STM32CubeMx实操

以下以 H743 + LAN8720A + lwIP 为例。

配置RCC

选择TIM7

关键步骤,配置I-Cache和D-Cache,为 ETH DMA 区创建 Non-Cacheable Region

在 CubeMX 里打开System → Cortex-M7 → MPU,增加一个 Region 专门给 ETH DMA 使用的内存,例如:

Region X:ETH DMA 区

  • Base Address:0x30044000(按实际工程来)
  • Size:32KB(覆盖 0x30044000 ~ 0x30047FFF)
  • TEX:level 0
  • Access Permission:ALL ACCESS PERMITTED
  • Instruction Access:DISABLE
  • Shareable:ENABLE
  • Cacheable:DISABLE ← 关键
  • Bufferable:ENABLE
    这就把 0x30044000 ~ 0x30047FFF标记为 Non-Cacheable,
    ETH DMA 与 CPU 访问这块内存时不会被 D-Cache 影响。


配置以太网

记得打开中断

注意这里要添加以太网PHY的复位引脚,否则无法正常工作,默认输出为高即可,注意要和实际使用的引脚对应

配置LWIP,我这里选择关闭DHCP

注意这里的地址一定要对上(被坑过)

这里选择只有LAN8742,他和LAN8720A是通用的。ST默认只支持Microchip的,需要DS83484可以手动更改

FreeRTOS选择CMSIS_V2版本,裸机跑也可以,但是注意裸机需要手动在while(1)中手动调用LWIP_Process()这个函数,使用RTOS不需要手动干预

时钟这里选择400MHz

其它默认即可,最后生成代码

MPU配置总结
确保在 CubeMX 的 ETH 配置 中设置(示例):

  • Tx Descriptor Length:4
  • First Tx Descriptor Address:0x30044060
  • Rx Descriptor Length:4
  • First Rx Descriptor Address:0x30044000
  • Rx Buffers Address:0x30044200
  • Rx Buffers Length:1536
  • ETH_RX_BUFFER_CNT(LwIP 配置):例如 12
    只要保证这些地址全部落在同一块 D2 区域即可。

四、调试要点

4.1 典型症状:怀疑是 Cache / MPU 问题时

  • 链路灯和网卡都正常,但 ping 不通;

  • Wireshark 中只看到 PC 发 ARP:

    Who has 192.168.x.x? Tell 192.168.x.1

  • 运行一段时间后进入 HardFault_Handler,PC 落在类似 0x32F9xxxx 这样的 SRAM 地址上。

此时可以先做一个实验:

SCB_DisableICache();SCB_DisableDCache();

若立刻恢复正常,基本可以确认是 Cache/MPU 的问题。

4.2 抓包 + 回调计数

  • 用 Wireshark 看 ARP:
    只看到请求,看不到响应 → 板子没有回包;
  • 在 HAL_ETH_RxCpltCallback() 中加计数:
volatileuint32_teth_rx_cnt=0;voidHAL_ETH_RxCpltCallback(ETH_HandleTypeDef*heth){eth_rx_cnt++;}

= eth_rx_cnt == 0:说明 MAC 没收到帧,问题在更底层(时钟、GPIO、HAL_ETH_Start、描述符等);

  • eth_rx_cnt 在增加但 ARP 仍无响应:说明 lwIP 没有正常处理(可能是 ethernetif_input / sys_check_timeouts 没跑)。

4.3 分步排除思路

  1. 先关 D-Cache 验证是否为 Cache 问题;

  2. 检查 ETH 描述符和缓冲区地址/长度是否匹配;

  3. 确认这些地址全部包含在 Non-Cacheable 的 MPU Region 中;

  4. 确认 HAL_ETH_Start() 返回 HAL_OK;

  5. 确认主循环或任务中持续调用:

  • 裸机:MX_LWIP_Process()
    = RTOS:ethernetif_input(&gnetif); + sys_check_timeouts();

4.4 以太网联通测试

五、UDP接收测试

5.1 测试

在freertos.c文件中添加如下代码

uint8_tdata[65536];uint32_trcv_len=0;voidmulticast_receive_callback(void*arg,structudp_pcb*pcb,structpbuf*p,constip_addr_t*addr,u16_tport){if(p!=NULL){uint8_t*payload_ptr=p->payload;uint32_tdata_remaining=p->len;if(data_remaining>0){rcv_len+=data_remaining;// 执行内存拷贝memcpy(data,payload_ptr,data_remaining);}pbuf_free(p);}}// 初始化MULTICAST接收协议控制块voidmulticast_receive_init(void){structudp_pcb*pcb;pcb=udp_new();pcb->so_options|=SOF_REUSEADDR;// 允许地址重用if(pcb!=NULL){udp_bind(pcb,IP4_ADDR_ANY,5007);udp_recv(pcb,multicast_receive_callback,NULL);}}

在任务开始的位置调用

voidStartDefaultTask(void*argument){/* init code for LWIP */MX_LWIP_Init();/* USER CODE BEGIN StartDefaultTask */multicast_receive_init();/* Infinite loop */for(;;){osDelay(1);}/* USER CODE END StartDefaultTask */}

用python写个上位机测试

importsocketimportargparseimporttimeimportsysimportiofromtqdmimporttqdm# 进度条库importpsutilimportosimportthreading# 强制设置输出编码为 UTF-8sys.stdout=io.TextIOWrapper(sys.stdout.buffer,encoding='utf-8')# 定义默认值BUFFER_SIZE=1000# 每次发送的UDP数据包大小defset_cpu_affinity(processor_num):# 获取当前进程p=psutil.Process(os.getpid())# 将进程绑定到一个特定的CPU核心(如CPU 0)p.cpu_affinity([processor_num])# 绑定到核心0# 设置进程的优先级为高优先级p.nice(psutil.HIGH_PRIORITY_CLASS)defcalculate_delay(datarate):# 计算UDP数据包发送之间的延时bytes_per_second=datarate*1000000/8# 将bps转为每秒字节数(每字节8个比特)packets_per_second=bytes_per_second/BUFFER_SIZE# 每秒可发送的数据包数return1/packets_per_second# 发送每个数据包后的延时(秒)defbusy_wait(delay):start=time.perf_counter()whiletime.perf_counter()-start<delay:passdefudp_send_data(file,local_ip,multicast_ip,multicast_port,datarate):# 创建UDP套接字udp_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)# 强制绑定到物理网卡(示例地址需替换为实际值)udp_socket.setsockopt(socket.IPPROTO_IP,socket.IP_MULTICAST_IF,socket.inet_aton(local_ip)# 关键修改点[3](@ref))# 计算发送数据包之间的延时,防止超过串口速率delay=calculate_delay(datarate)try:# 打开要发送的文件withopen(file,'rb')asf:# 获取文件大小file_size=f.seek(0,2)# 移动到文件末尾,获取文件大小f.seek(0)# 重置文件指针到开头print(f"开始从{local_ip}发送文件{file}{multicast_ip}:{multicast_port}")print(f"按照速率{datarate}Mbps 发送数据,间隔{delay:.6f}秒")# 初始化进度条withtqdm(total=file_size,unit='B',unit_scale=True,desc="发送进度")aspbar:# 读取并发送数据whileTrue:data=f.read(BUFFER_SIZE)ifnotdata:breakudp_socket.sendto(data,(multicast_ip,multicast_port))pbar.update(len(data))# 更新进度条busy_wait(delay)# 使用忙等待控制发送速率print("文件发送完成")exceptFileNotFoundError:print(f"文件{file}未找到,请检查路径")finally:# 关闭套接字udp_socket.close()defmain():# 设置命令行参数解析parser=argparse.ArgumentParser(description="UDP 数据发送工具")parser.add_argument("--file",default='test1G.dat',help="要发送的文件名")parser.add_argument("--local_ip",default='192.168.137.1',help="目标 IP 地址")parser.add_argument("--multicast_ip",default='192.168.137.10',help="目标 IP 地址")parser.add_argument("--multicast_port",type=int,default=5007,help="目标端口")parser.add_argument("--datarate",type=int,default=2,help="UDP发送速率 (Mbps)")parser.add_argument("--cpu_affi",type=int,default=100,help="指定CPU亲和力的CPU编号")# 解析命令行参数args=parser.parse_args()# 设置CPU亲和力ifargs.cpu_affiisnotNone:set_cpu_affinity(args.cpu_affi)print(f"正在使用CPU核心:{args.cpu_affi}运行任务")else:print("未指定CPU亲和力")# 解析命令行参数args=parser.parse_args()# 调用发送函数udp_send_data(args.file,args.local_ip,args.multicast_ip,args.multicast_port,args.datarate)if__name__=="__main__":main()

开始测试,可以看到几乎打满了百兆网

IAR中打开Live Watch,可以看到接收到的数据刚好等于1GB,没有丢包,此时H7的主频为400MHz

根据之前的测试经验,F4只能跑到大约20Mbps,H7的上限确实比F4要高很多。

为了验证H7的架构优势,这里特地将H7的主频设置到170MHz,与F4的168MHz基本保持一致,再次进行测试,实测可以跑到70Mbps左右,可见同主频下H7相比F4确实有架构优势。

进一步的,在同样主频下,关掉D-Cache和I-Cache之后再测一遍,发现只能跑到40Mbps左右,可见优势很大一部分来源于指令和数据缓存机制,所以这部分功能尽量用起来,它是区别于传统MCU的核心

5.2 代码

https://gitee.com/dwgan/stm32h743

六、总结

  • STM32H7 相比 F4/F7,最大的变化就是引入了 I-Cache / D-Cache 和更复杂的内存体系。
    它极大提升了性能,但也让 DMA 外设变得“更脆弱”,必须正确处理缓存一致性。

  • 推荐迁移路径:

1. 第一阶段:关闭 D-Cache,只打开 I-Cache,把所有功能跑通;
2. 第二阶段:使用 MPU 把所有 DMA 内存标成 Non-Cacheable,再重新打开 D-Cache。

  • 在 CubeMX 中:
    把 ETH 的描述符和缓冲区放在 D2 SRAM 的一块连续区域;
    为这块区域配置 Non-Cacheable 的 MPU Region;
    main() 中先 MPU_Config() 再 EnableICache/EnableDCache。

  • 调试时,多利用:

Wireshark 抓包看 ARP 是否有响应;
中断回调计数看是否收到数据;
HardFault 时的 PC 地址判断是否被 DMA 写坏。

如果你也在 H7 上被以太网/SD 卡等外设折磨过,不妨先试试 关 D-Cache。
如果问题瞬间消失,那大概率就是 Cache/MPU 的“锅”,
按上文这套Non-Cacheable + MPU + 正确初始化顺序的配置,大多都能一次性解决。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/29 11:41:53

零基础PHP从零到一实现上一页和下一页的庖丁解牛

实现上一页和下一页&#xff0c;不是简单地用 LIMIT offset, size&#xff0c;而是通过 游标分页&#xff08;Cursor-based Pagination&#xff09; 实现高性能、可扩展的分页。 一、核心原理&#xff1a;为什么不用 OFFSET&#xff1f; ▶ 1. OFFSET 的致命缺陷 -- 跳过 100…

作者头像 李华
网站建设 2026/2/1 10:53:10

终极预测:2030年,AI将自动编写测试用例?

——软件测试从业者的专业视角 引言&#xff1a;AI重塑测试领域的必然趋势 随着人工智能技术的飞速发展&#xff0c;软件测试行业正经历前所未有的变革。到2030年&#xff0c;AI不仅将辅助测试活动&#xff0c;更可能主导核心流程&#xff0c;其中测试用例的自动编写成为关键…

作者头像 李华
网站建设 2026/2/2 20:52:08

‌AI生成测试用例的“可执行性”难题:它写的你能跑吗?

AI生成的测试用例&#xff0c;平均可执行率不足60%‌ 根据信通院2026年初发布的《AI在软件测试中的规模化应用报告》&#xff0c;当前70%的中大型企业已部署AI生成测试用例工具&#xff0c;但‌实际可成功执行、无需人工修正的用例比例仅为54%-59%‌。这意味着每100条AI生成的…

作者头像 李华
网站建设 2026/2/3 8:56:38

【挑选合适的开源前端项目是商城系统开发的重要一步】

挑选合适的开源前端项目是商城系统开发的重要一步。下面我为你梳理了主流的技术方案和具体的开源项目&#xff0c;并提供选型建议&#xff0c;希望能帮你快速定位适合的方案。 &#x1f6e0;️ 主流技术栈与框架 当前商城前端开发主要围绕以下两种跨端框架展开&#xff0c;它们…

作者头像 李华