STM32H750配置LAN PHY芯片LAN8742

阶段一:MPU + Cache 初始化

MPU_Config()

├─ Region0: 0x30040000 / 256B / Device / Not Cacheable / Not Shareable ← ETH DMA 描述符

└─ Region1: 0x30044000 / 16KB / Cacheable Write-through / Not Shareable ← LwIP 堆

SCB_EnableICache() ← 指令缓存

SCB_EnableDCache() ← 数据缓存

SCB->CACR |= 1<<2 ← D-Cache 设为 Write-through 模式

阶段二:LwIP 协议栈内核初始化

lwip_init() // LwIP 内核

├─ mem_init / memp_init ← 内存池管理

├─ pbuf_init ← pbuf 链表管理

├─ tcp_init / udp_init / ip_init ← 各协议模块

└─ dhcp_init / arp_init / icmp_init ← 上层服务

阶段三:网络接口注册

Netif_Config()

├─ IP_ADDR4(&ipaddr, 192.168.3.122) ← 组装静态 IP

├─ IP_ADDR4(&netmask, 255.255.255.0) ← 子网掩码

├─ IP_ADDR4(&gw, 192.168.3.1) ← 网关

├─ netif_add(&gnetif, ..., ethernetif_init, ethernet_input)

│ │

│ └──► 触发 ethernetif_init() 回调 ← 进入阶段四

├─ netif_set_default(&gnetif) ← 设为默认网卡

└─ netif_set_up() / netif_set_down() ← 根据链路状态启停

阶段三:网络接口注册 (Netif_Config())

app_ethernet.c

```

Netif_Config()

├─ IP_ADDR4(&ipaddr, 192.168.3.122) ← 组装静态 IP

├─ IP_ADDR4(&netmask, 255.255.255.0) ← 子网掩码

├─ IP_ADDR4(&gw, 192.168.3.1) ← 网关

├─ netif_add(&gnetif, ..., ethernetif_init, ethernet_input)

│ │

│ └──► 触发 ethernetif_init() 回调 ← 进入阶段四

├─ netif_set_default(&gnetif) ← 设为默认网卡

└─ netif_set_up() / netif_set_down() ← 根据链路状态启停

```

阶段四:底层硬件驱动初始化

ethernetif_init(netif)

├─ mem_malloc(sizeof(ethernetif)) ← 分配私有结构体

├─ netif->name = "st" ← 接口名

├─ netif->output = etharp_output ← ★ LwIP 发包: 先 ARP 再发送

├─ netif->linkoutput = low_level_output ← ★ 最终硬件发送函数

└─ low_level_init(netif) ─────────────────────────────────────┐

│ │

├─ 填充 EthHandle 结构体: │

│ .Instance = ETH │

│ .MACAddr = {} │

│ .MediaInterface = HAL_ETH_RMII_MODE │

│ .RxDesc = DMARxDscrTab (0x30040000) │

│ .TxDesc = DMATxDscrTab (0x30040060) │

│ .RxBuffLen = 1536 │

│ │

├─ HAL_ETH_Init(&EthHandle) ────────────────────────────┐ │

│ │ │ │

│ └─ HAL_ETH_MspInit() ← 阶段五 │ │

│ │

├─ netif->hwaddr = MAC 地址 │

├─ netif->mtu = ETH_MAX_PAYLOAD (1500) │

├─ netif->flags |= BROADCAST | ETHARP │

│ │

├─ HAL_ETH_DescAssignMemory() × 4 │

│ ← 将 Rx_Buffidx 绑定到每个 DMA 描述符 │

│ │

├─ LWIP_MEMPOOL_INIT(RX_POOL) │

│ ← 初始化 Zero-Copy pbuf 池 (20个) │

│ │

├─ TxConfig 配置: │

│ .Attributes = CSUM | CRCPAD │

│ .ChecksumCtrl = IP+Payload 硬件校验和 │

│ .CRCPadCtrl = 自动 CRC 填充 │

│ │

└─ LAN8720_Init() ──► ethernet_link_check_state() ← 阶段六│

阶段五:ETH GPIO + 中断初始化

HAL_ETH_MspInit()

├─ ETH_GPIO_Config() ← 逐个配置 9 个 RMII 引脚 (AF11)

│ ├─ PC2 → GPIO_OUTPUT (PHY NRST 复位脚)

│ ├─ PA2 → ETH_MDIO

│ ├─ PC1 → ETH_MDC

│ ├─ PA1 → ETH_RMII_REF_CLK

│ ├─ PA7 → ETH_RMII_CRS_DV

│ ├─ PC4 → ETH_RMII_RXD0

│ ├─ PC5 → ETH_RMII_RXD1

│ ├─ PB11 → ETH_RMII_TX_EN

│ ├─ PB12 → ETH_RMII_TXD0

│ └─ PB13 → ETH_RMII_TXD1

├─ HAL_NVIC_SetPriority(ETH_IRQn, 6, 0) ← 抢占优先级6,子优先级0

├─ HAL_NVIC_EnableIRQ(ETH_IRQn) ← 使能 ETH 中断

├─ __HAL_RCC_ETH1MAC_CLK_ENABLE() ← 使能 ETH MAC 时钟

├─ __HAL_RCC_ETH1TX_CLK_ENABLE() ← 使能 ETH TX 时钟

├─ __HAL_RCC_ETH1RX_CLK_ENABLE() ← 使能 ETH RX 时钟

└─ PHY 硬件复位时序:

PC2=1 → Delay(100ms) → PC2=0 → Delay(100ms) → PC0=1

阶段六:PHY 初始化 + 链路检测

LAN8720_Init()

├─ 写 PHY_BCR 寄存器: 软件复位 (PHY_RESET)

├─ Delay(PHY_RESET_DELAY)

├─ 写 PHY_BCR 寄存器: 使能自协商 (PHY_AUTONEGOTIATION)

├─ 轮询 PHY_BSR 寄存器,等待自协商完成 (超时 PHY_READ_TO)

└─ ethernet_link_check_state(netif)

├─ 读取 PHY SR 寄存器,获取链路状态和协商结果

├─ 根据自协商结果配置 MAC 双工模式和速率

└─ HAL_ETH_Start_IT() ← 启动 ETH 接收中断

DMACSR=0x00205504 → RXSTS=0 (RX DMA 已停止) 且有 TU (发送溢出错误)

各区域详解

  1. DMA 描述符区域 (0x30040000 ~ 0x300400DF)

项目 地址 大小

Rx 描述符 ×4 0x30040000 4 × ~32B = 128B

Tx 描述符 ×4 0x30040060 4 × ~32B = 128B

备注 : 代码中 RxDesc 地址 0x30040000 与 TxDesc 0x30040060 存在重叠区域 ( 0x30040060 ~ 0x3004007F ),建议将 RxDesc 改为 0x30040000 、TxDesc 改为 0x30040080 (或按 IOC 原值 RxDesc= 0x30040200 )以避免冲突。

MPU 保护: Region0 --- 起始 0x30040000 ,大小 256B ,属性 Device / Not Cacheable / Bufferable / Not Shareable ,确保 DMA 访问不经过 D-Cache。

  1. 接收缓冲区 (0x30040200 ~ 0x30041A00)

定义位置: ethernetif.c

缓冲区 地址 大小

Rx_Buff0 0x30040200 1536B

Rx_Buff1 0x30040800 1536B

Rx_Buff2 0x30040E00 1536B

Rx_Buff3 0x30041400 1536B

使用 Zero-Copy 机制:DMA 直接将收到的数据写入这里,LwIP 通过 pbuf_alloced_custom() 创建引用这些内存的 pbuf,不产生拷贝。释放时通过 pbuf_free_custom() 调用 SCB_InvalidateDCache_by_Addr() 刷新 D-Cache 后归还。

  1. LwIP RAM 堆 (0x30044000 ~ 0x30046800)

MPU 保护: Region1 --- 起始 0x30044000 ,大小 16KB ,属性 Cacheable / Write-through / Not Bufferable / Not Shareable 。使用 Write-through 确保写操作立即到达内存,DMA 可以读取到最新的 Tx 数据。

该堆由 LwIP 内部 mem_malloc/mem_free 管理,实际分配到(通过 LwIP 内核源码 mem.c ):

分配项 来源 用途

ethernetif 结构体 mem_malloc() 网络接口私有数据

Tx pbuf pbuf_alloc(PBUF_POOL) 发送缓冲区池 (PBUF_POOL_SIZE=4)

TCP PCB memp_alloc(MEMP_TCP_PCB) TCP 协议控制块 (×10)

UDP PCB memp_alloc(MEMP_UDP_PCB) UDP 协议控制块 (×6)

TCP 分段 memp_alloc(MEMP_TCP_SEG) TCP 重传队列 (×8) pbuf 结构体 memp_alloc(MEMP_PBUF)

pbuf 链表节点 (×10) 系统超时 memp_alloc(MEMP_SYS_TIMEOUT) 定时器节点 (×10)

DHCP 结构体 mem_malloc() DHCP 客户端 ARP 表项 memp_alloc(MEMP_ARP_TABLE) ARP 缓存

CDatex 总结

区域 D-Cache 策略 原因

DMA 描述符 (0x30040000) Not Cacheable 硬件 DMA 直接读写,绕过 Cache 防止数据不一致

DMA 接收缓冲 (0x30040200) 无专门 MPU 保护 接收时手动 SCB_InvalidateDCache_by_Addr() 刷新

LwIP 堆 (0x30044000) Write-through CPU 写入立即到 SRAM,DMA 发送时能读到最新数据

普通变量 (DTCM/AXI) Write-back (D-Cache 默认) 高速正常运行

相关注意点

1、硬件复位

// ★ PHY 复位时序

HAL_GPIO_WritePin(GPIOC, GPIO_PIN_2, GPIO_PIN_RESET); // PC2 拉低

HAL_Delay(100);

HAL_GPIO_WritePin(GPIOC, GPIO_PIN_2, GPIO_PIN_SET); // PC2 拉高

HAL_Delay(1000); // 等待 LAN8720 稳定

2、ld链接文件的修改

/* DMA 描述符 --- 与 MPU Region0 (0x30040000) 精确对齐 */

.RxDecripSection 0x30040000 (NOLOAD) :

{

. = ALIGN(32);

*(.RxDecripSection)

} >RAM_D2

/* TxDesc 偏移 0x80 避免与 RxDesc 重叠 (4×32B=128B) */

.TxDecripSection 0x30040080 (NOLOAD) :

{

. = ALIGN(32);

*(.TxDecripSection)

} >RAM_D2

/* Rx 缓冲区 --- 紧随描述符之后 */

.RxBuffSection 0x30040200 (NOLOAD) :

{

. = ALIGN(32);

*(.RxBuffSection)

} >RAM_D2

3、底层驱动

// ★ DMA 描述符 --- GCC section 属性

ETH_DMADescTypeDef DMARxDscrTabETH_RX_DESC_CNT

attribute((section(".RxDecripSection")));

ETH_DMADescTypeDef DMATxDscrTabETH_TX_DESC_CNT

attribute((section(".TxDecripSection")));

// ★ Rx_Buff --- 从 LWIP_MEMPOOL 改为固定地址数组

#define ETH_RX_BUFFER_CNT 12U

typedef struct {

struct pbuf_custom pbuf_custom;

uint8_t buffETH_RX_BUFFER_SIZE __ALIGNED(32);

} RxBuff_t;

static RxBuff_t Rx_BuffETH_RX_BUFFER_CNT

attribute((section(".RxBuffSection")));

static uint8_t rx_buff_usedETH_RX_BUFFER_CNT; // 位图分配器

// ★ RxAllocateCallback --- 简单遍历空闲位图

for (i = 0; i < ETH_RX_BUFFER_CNT; i++) {

if (!rx_buff_usedi) {

rx_buff_usedi = 1;

rxb->pbuf_custom.custom_free_function = pbuf_free_custom;

pbuf_alloced_custom(PBUF_RAW, 0, PBUF_REF, ...);

*buff = rxb->buff;

return;

}

}

// ★ pbuf_free_custom --- 归还缓冲区

rxb = (RxBuff_t *)p;

rx_buff_usedrxb - Rx_Buff = 0;

SCB_InvalidateDCache_by_Addr(rxb->buff, ETH_RX_BUFFER_SIZE);

// ★ 中断模式启动

HAL_ETH_Start_IT(&heth); // 原 HAL_ETH_Start

4中断模式

D2 SRAM (Non-Cacheable)

╔═════════════════════════════════╗

║ 0x30040000 DMARxDscrTab4 ║ ← MPU Region0: Device, Not Cacheable

║ 0x30040080 DMATxDscrTab4

║ 0x30040200 Rx_Buff0..11 ║ ← 固定地址 Zero-Copy

║ 0x30044B80 (end) ║

║ ║

║ 0x30044000 LwIP Heap (空) ║ ← MPU Region1: Write-through

║ 0x30046800 ║

╚═════════════════════════════════╝

DTCMRAM (CPU 独占,不缓存)

╔═════════════════════════════════╗

║ 0x20000000 .data / .bss ║

║ LwIP 内部结构体 (TCP/UDP PCB, ║

║ pbuf 节点, ARP 表, etc.) ║

╚═════════════════════════════════╝

类别 要点 时钟

STM32H750 最高 400MHz, 必须 VOS1 + FLASH_LATENCY=3 ;APB4 分频关系到 ETH 时钟

DMA 描述符 Cortex-M7 有 D-Cache,描述符必须放 non-cacheable 区域(D2 SRAM 或通过 MPU 标记)

MPU 对齐 链接脚本中 section 地址必须与 MPU Region 基地址 严格一致

PHY 复位 PC2 拉低 ≥100ms 再拉高,等待 ≥1s 让 LAN8720 内部 PLL 锁定

中断模式 必须 HAL_ETH_Start_IT ,不是 HAL_ETH_Start (后者轮询,不触发 IRQ)

Rx_Buff 数量 至少 2×ETH_RX_DESC_CNT(推荐 12),否则 LwIP 释放慢时 DMA 无缓冲可用

Cache 维护 接收后 SCB_InvalidateDCache_by_Addr ;发送前 SCB_CleanDCache_by_Addr

静态 IP 确保与 PC 同网段(192.168.7.xx),防火墙允许 ping

复制代码
/*
******************************************************************************
**

**  File        : LinkerScript.ld
**
**  Author		: STM32CubeMX
**
**  Abstract    : Linker script for STM32H750VBTx series
**                128Kbytes FLASH and 1056Kbytes RAM
**
**                Set heap size, stack size and stack location according
**                to application requirements.
**
**                Set memory bank area and size if external memory is used.
**
**  Target      : STMicroelectronics STM32
**
**  Distribution: The file is distributed "as is," without any warranty
**                of any kind.
**
*****************************************************************************
** @attention
**
** <h2><center>&copy; COPYRIGHT(c) 2025 STMicroelectronics</center></h2>
**
** Redistribution and use in source and binary forms, with or without modification,
** are permitted provided that the following conditions are met:
**   1. Redistributions of source code must retain the above copyright notice,
**      this list of conditions and the following disclaimer.
**   2. Redistributions in binary form must reproduce the above copyright notice,
**      this list of conditions and the following disclaimer in the documentation
**      and/or other materials provided with the distribution.
**   3. Neither the name of STMicroelectronics nor the names of its contributors
**      may be used to endorse or promote products derived from this software
**      without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
** DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
** OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
*****************************************************************************
*/

/* Entry Point */
ENTRY(Reset_Handler)

/* Highest address of the user mode stack */
_estack = ORIGIN(DTCMRAM) + LENGTH(DTCMRAM);    /* end of RAM */
_sstack = _estack - _Min_Stack_Size;
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x200;      /* required amount of heap  */
_Min_Stack_Size = 0x400; /* required amount of stack */

/* Specify the memory areas */
MEMORY
{
DTCMRAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 128K
RAM (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
}

/* Define output sections */
SECTIONS
{
  /* The startup code goes first into FLASH */
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector)) /* Startup code */
    . = ALIGN(4);
  } >FLASH

  /* The program code and other data goes into FLASH */
  .text :
  {
    . = ALIGN(4);
    *(.text)           /* .text sections (code) */
    *(.text*)          /* .text* sections (code) */
    *(.glue_7)         /* glue arm to thumb code */
    *(.glue_7t)        /* glue thumb to arm code */
    *(.eh_frame)

    KEEP (*(.init))
    KEEP (*(.fini))

    . = ALIGN(4);
    _etext = .;        /* define a global symbols at end of code */
  } >FLASH

  /* Constant data goes into FLASH */
  .rodata :
  {
    . = ALIGN(4);
    *(.rodata)         /* .rodata sections (constants, strings, etc.) */
    *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
    . = ALIGN(4);
  } >FLASH

  .ARM.extab (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
  {
    . = ALIGN(4);
    *(.ARM.extab* .gnu.linkonce.armextab.*)
    . = ALIGN(4);
  } >FLASH

  .ARM (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
  {
    . = ALIGN(4);
    __exidx_start = .;
    *(.ARM.exidx*)
    __exidx_end = .;
    . = ALIGN(4);
  } >FLASH

  .preinit_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
  {
    . = ALIGN(4);
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
    . = ALIGN(4);
  } >FLASH

  .init_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
  {
    . = ALIGN(4);
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array*))
    PROVIDE_HIDDEN (__init_array_end = .);
    . = ALIGN(4);
  } >FLASH

  .fini_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
  {
    . = ALIGN(4);
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT(.fini_array.*)))
    KEEP (*(.fini_array*))
    PROVIDE_HIDDEN (__fini_array_end = .);
    . = ALIGN(4);
  } >FLASH

  /* used by the startup to initialize data */
  _sidata = LOADADDR(.data);

  /* Initialized data sections goes into RAM, load LMA copy after code */
  .data :
  {
    . = ALIGN(4);
    _sdata = .;        /* create a global symbol at data start */
    *(.data)           /* .data sections */
    *(.data*)          /* .data* sections */
    *(.RamFunc)        /* .RamFunc sections */
    *(.RamFunc*)       /* .RamFunc* sections */

    . = ALIGN(4);
    _edata = .;        /* define a global symbol at data end */
  } >DTCMRAM AT> FLASH


  /* Uninitialized data section */
  . = ALIGN(4);
  .bss :
  {
    /* This is used by the startup in order to initialize the .bss secion */
    _sbss = .;         /* define a global symbol at bss start */
    __bss_start__ = _sbss;
    *(.bss)
    *(.bss*)
    *(COMMON)

    . = ALIGN(4);
  _ebss = .;         /* define a global symbol at bss end */
  __bss_end__ = _ebss;
  } >DTCMRAM

  /* ETH DMA descriptors at 0x30040000 (matches MPU Region0) */
  .RxDecripSection 0x30040000 (NOLOAD) :
  {
    . = ALIGN(32);
    *(.RxDecripSection)
  } >RAM_D2

  /* TxDesc at 0x30040080 avoids overlap with RxDesc (4x32B=128B) */
  .TxDecripSection 0x30040080 (NOLOAD) :
  {
    . = ALIGN(32);
    *(.TxDecripSection)
  } >RAM_D2

  /* ETH Rx buffers at 0x30040200 (4 x 1536B = 0x1800) */
  .RxBuffSection 0x30040200 (NOLOAD) :
  {
    . = ALIGN(32);
    *(.RxBuffSection)
  } >RAM_D2

  /* 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



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

}


/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * File Name          : ethernetif.c
 * Description        : This file provides code for the configuration
 *                      of the ethernetif.c MiddleWare.
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2026 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "lwip/opt.h"
#include "lwip/mem.h"
#include "lwip/memp.h"
#include "lwip/timeouts.h"
#include "netif/ethernet.h"
#include "netif/etharp.h"
#include "lwip/ethip6.h"
#include "ethernetif.h"
#include "lan8742.h"
#include <string.h>

/* Within 'USER CODE' section, code will be kept by default at each generation */
/* USER CODE BEGIN 0 */
/*
 * ===== CubeMX 再生保护 (Regeneration Protection) =====
 * 当 CubeMX 重新生成代码后, 以下项目需手动恢复:
 *   1. RxBuff_t.buff 需要添加 __ALIGNED(32) 对齐修饰符
 *   2. 见下方 USER CODE BEGIN 2 / 4 / 6 中的自定义代码
 * ======================================================
 */

/* USER CODE END 0 */

/* Private define ------------------------------------------------------------*/

/* Network interface name */
#define IFNAME0 's'
#define IFNAME1 't'

/* ETH Setting  */
#define ETH_DMA_TRANSMIT_TIMEOUT (20U)
#define ETH_TX_BUFFER_MAX ((ETH_TX_DESC_CNT) * 2U)
/* ETH_RX_BUFFER_SIZE parameter is defined in lwipopts.h */

/* USER CODE BEGIN 1 */

/* USER CODE END 1 */

/* Private variables ---------------------------------------------------------*/
/*
@Note: This interface is implemented to operate in zero-copy mode only:
        - Rx buffers will be allocated from LwIP stack memory heap,
          then passed to ETH HAL driver.
        - Tx buffers will be allocated from LwIP stack memory heap,
          then passed to ETH HAL driver.

@Notes:
  1.a. ETH DMA Rx descriptors must be contiguous, the default count is 4,
       to customize it please redefine ETH_RX_DESC_CNT in ETH GUI (Rx Descriptor Length)
       so that updated value will be generated in stm32xxxx_hal_conf.h
  1.b. ETH DMA Tx descriptors must be contiguous, the default count is 4,
       to customize it please redefine ETH_TX_DESC_CNT in ETH GUI (Tx Descriptor Length)
       so that updated value will be generated in stm32xxxx_hal_conf.h

  2.a. Rx Buffers number must be between ETH_RX_DESC_CNT and 2*ETH_RX_DESC_CNT
  2.b. Rx Buffers must have the same size: ETH_RX_BUFFER_SIZE, this value must
       passed to ETH DMA in the init field (heth.Init.RxBuffLen)
  2.c  The RX Ruffers addresses and sizes must be properly defined to be aligned
       to L1-CACHE line size (32 bytes).
*/

/* Data Type Definitions */
typedef enum
{
  RX_ALLOC_OK = 0x00,
  RX_ALLOC_ERROR = 0x01
} RxAllocStatusTypeDef;

typedef struct
{
  struct pbuf_custom pbuf_custom;
  uint8_t buff[(ETH_RX_BUFFER_SIZE + 31) & ~31] __ALIGNED(32);
} RxBuff_t;

/* Memory Pool Declaration */
#define ETH_RX_BUFFER_CNT 12U
LWIP_MEMPOOL_DECLARE(RX_POOL, ETH_RX_BUFFER_CNT, sizeof(RxBuff_t), "Zero-copy RX PBUF pool");

/* Variable Definitions */
static uint8_t RxAllocStatus;

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

#pragma location = 0x30040000
ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT]; /* Ethernet Rx DMA Descriptors */
#pragma location = 0x30040080
ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT]; /* Ethernet Tx DMA Descriptors */

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

__attribute__((at(0x30040000))) ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT]; /* Ethernet Rx DMA Descriptors */
__attribute__((at(0x30040080))) ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT]; /* Ethernet Tx DMA Descriptors */

#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 */

#endif

/* USER CODE BEGIN 2 */
/*
 * ===== 自定义以太网缓冲区 (CubeMX 再生后保留) =====
 */
#define ETH_RX_BUFFER_CNT 12U
static RxBuff_t Rx_Buff[ETH_RX_BUFFER_CNT] __attribute__((section(".RxBuffSection")));
static uint8_t rx_buff_used[ETH_RX_BUFFER_CNT];
/* USER CODE END 2 */

/* Global Ethernet handle */
ETH_HandleTypeDef heth;
ETH_TxPacketConfig TxConfig;

/* Private function prototypes -----------------------------------------------*/
int32_t ETH_PHY_IO_Init(void);
int32_t ETH_PHY_IO_DeInit(void);
int32_t ETH_PHY_IO_ReadReg(uint32_t DevAddr, uint32_t RegAddr, uint32_t *pRegVal);
int32_t ETH_PHY_IO_WriteReg(uint32_t DevAddr, uint32_t RegAddr, uint32_t RegVal);
int32_t ETH_PHY_IO_GetTick(void);

lan8742_Object_t LAN8742;
lan8742_IOCtx_t LAN8742_IOCtx = {ETH_PHY_IO_Init,
                                 ETH_PHY_IO_DeInit,
                                 ETH_PHY_IO_WriteReg,
                                 ETH_PHY_IO_ReadReg,
                                 ETH_PHY_IO_GetTick};

/* USER CODE BEGIN 3 */

/* USER CODE END 3 */

/* Private functions ---------------------------------------------------------*/
void pbuf_free_custom(struct pbuf *p);

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/*******************************************************************************
                       LL Driver Interface ( LwIP stack --> ETH)
*******************************************************************************/
/**
 * @brief In this function, the hardware should be initialized.
 * Called from ethernetif_init().
 *
 * @param netif the already initialized lwip network interface structure
 *        for this ethernetif
 */
static void low_level_init(struct netif *netif)
{
  HAL_StatusTypeDef hal_eth_init_status = HAL_OK;
  /* Start ETH HAL Init */

  uint8_t MACAddr[6];
  heth.Instance = ETH;
  MACAddr[0] = 0x00;
  MACAddr[1] = 0x80;
  MACAddr[2] = 0xE1;
  MACAddr[3] = 0x00;
  MACAddr[4] = 0x00;
  MACAddr[5] = 0x00;
  heth.Init.MACAddr = &MACAddr[0];
  heth.Init.MediaInterface = HAL_ETH_RMII_MODE;
  heth.Init.TxDesc = DMATxDscrTab;
  heth.Init.RxDesc = DMARxDscrTab;
  heth.Init.RxBuffLen = 1536;

  /* USER CODE BEGIN MACADDRESS */

  /* USER CODE END MACADDRESS */

  hal_eth_init_status = HAL_ETH_Init(&heth);

  memset(&TxConfig, 0, sizeof(ETH_TxPacketConfig));
  TxConfig.Attributes = ETH_TX_PACKETS_FEATURES_CSUM | ETH_TX_PACKETS_FEATURES_CRCPAD;
  TxConfig.ChecksumCtrl = ETH_CHECKSUM_IPHDR_PAYLOAD_INSERT_PHDR_CALC;
  TxConfig.CRCPadCtrl = ETH_CRC_PAD_INSERT;

  /* End ETH HAL Init */

  /* Initialize the RX POOL */
  LWIP_MEMPOOL_INIT(RX_POOL);

#if LWIP_ARP || LWIP_ETHERNET

  /* set MAC hardware address length */
  netif->hwaddr_len = ETH_HWADDR_LEN;

  /* set MAC hardware address */
  netif->hwaddr[0] = heth.Init.MACAddr[0];
  netif->hwaddr[1] = heth.Init.MACAddr[1];
  netif->hwaddr[2] = heth.Init.MACAddr[2];
  netif->hwaddr[3] = heth.Init.MACAddr[3];
  netif->hwaddr[4] = heth.Init.MACAddr[4];
  netif->hwaddr[5] = heth.Init.MACAddr[5];

  /* maximum transfer unit */
  netif->mtu = ETH_MAX_PAYLOAD;

/* Accept broadcast address and ARP traffic */
/* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
#if LWIP_ARP
  netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;
#else
  netif->flags |= NETIF_FLAG_BROADCAST;
#endif /* LWIP_ARP */

  /* USER CODE BEGIN PHY_PRE_CONFIG */
  /* Hardware reset PHY via PC2 */
  printf("[PHY] Hardware reset via PC2...\r\n");
  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_2, GPIO_PIN_RESET);
  HAL_Delay(100);
  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_2, GPIO_PIN_SET);
  HAL_Delay(500);
  printf("[PHY] Reset done\r\n");
  /* USER CODE END PHY_PRE_CONFIG */
  /* Set PHY IO functions */
  LAN8742_RegisterBusIO(&LAN8742, &LAN8742_IOCtx);

  /* Initialize the LAN8742 ETH PHY */
  LAN8742_Init(&LAN8742);

  if (hal_eth_init_status == HAL_OK)
  {
    /* Get link state */
    ethernet_link_check_state(netif);
  }
  else
  {
    Error_Handler();
  }
#endif /* LWIP_ARP || LWIP_ETHERNET */

  /* USER CODE BEGIN LOW_LEVEL_INIT */

  /* USER CODE END LOW_LEVEL_INIT */
}

/**
 * @brief This function should do the actual transmission of the packet. The packet is
 * contained in the pbuf that is passed to the function. This pbuf
 * might be chained.
 *
 * @param netif the lwip network interface structure for this ethernetif
 * @param p the MAC packet to send (e.g. IP packet including MAC addresses and type)
 * @return ERR_OK if the packet could be sent
 *         an err_t value if the packet couldn't be sent
 *
 * @note Returning ERR_MEM here if a DMA queue of your MAC is full can lead to
 *       strange results. You might consider waiting for space in the DMA queue
 *       to become available since the stack doesn't retry to send a packet
 *       dropped because of memory failure (except for the TCP timers).
 */

static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
  uint32_t i = 0U;
  struct pbuf *q = NULL;
  err_t errval = ERR_OK;
  ETH_BufferTypeDef Txbuffer[ETH_TX_DESC_CNT] = {0};

  memset(Txbuffer, 0, ETH_TX_DESC_CNT * sizeof(ETH_BufferTypeDef));

  for (q = p; q != NULL; q = q->next)
  {
    if (i >= ETH_TX_DESC_CNT)
      return ERR_IF;

    Txbuffer[i].buffer = q->payload;
    Txbuffer[i].len = q->len;

    if (i > 0)
    {
      Txbuffer[i - 1].next = &Txbuffer[i];
    }

    if (q->next == NULL)
    {
      Txbuffer[i].next = NULL;
    }

    i++;
  }

  TxConfig.Length = p->tot_len;
  TxConfig.TxBuffer = Txbuffer;
  TxConfig.pData = p;

  HAL_ETH_Transmit(&heth, &TxConfig, ETH_DMA_TRANSMIT_TIMEOUT);

  return errval;
}

/**
 * @brief Should allocate a pbuf and transfer the bytes of the incoming
 * packet from the interface into the pbuf.
 *
 * @param netif the lwip network interface structure for this ethernetif
 * @return a pbuf filled with the received packet (including MAC header)
 *         NULL on memory error
 */
static struct pbuf *low_level_input(struct netif *netif)
{
  struct pbuf *p = NULL;

  if (RxAllocStatus == RX_ALLOC_OK)
  {
    HAL_ETH_ReadData(&heth, (void **)&p);
  }

  return p;
}

/**
 * @brief This function should be called when a packet is ready to be read
 * from the interface. It uses the function low_level_input() that
 * should handle the actual reception of bytes from the network
 * interface. Then the type of the received packet is determined and
 * the appropriate input function is called.
 *
 * @param netif the lwip network interface structure for this ethernetif
 */
void ethernetif_input(struct netif *netif)
{
  struct pbuf *p = NULL;

  do
  {
    p = low_level_input(netif);
    if (p != NULL)
    {
      if (netif->input(p, netif) != ERR_OK)
      {
        pbuf_free(p);
      }
    }
  } while (p != NULL);
}

#if !LWIP_ARP
/**
 * This function has to be completed by user in case of ARP OFF.
 *
 * @param netif the lwip network interface structure for this ethernetif
 * @return ERR_OK if ...
 */
static err_t low_level_output_arp_off(struct netif *netif, struct pbuf *q, const ip4_addr_t *ipaddr)
{
  err_t errval;
  errval = ERR_OK;

  /* USER CODE BEGIN 5 */

  /* USER CODE END 5 */

  return errval;
}
#endif /* LWIP_ARP */

/**
 * @brief Should be called at the beginning of the program to set up the
 * network interface. It calls the function low_level_init() to do the
 * actual setup of the hardware.
 *
 * This function should be passed as a parameter to netif_add().
 *
 * @param netif the lwip network interface structure for this ethernetif
 * @return ERR_OK if the loopif is initialized
 *         ERR_MEM if private data couldn't be allocated
 *         any other err_t on error
 */
err_t ethernetif_init(struct netif *netif)
{
  LWIP_ASSERT("netif != NULL", (netif != NULL));

#if LWIP_NETIF_HOSTNAME
  /* Initialize interface hostname */
  netif->hostname = "lwip";
#endif /* LWIP_NETIF_HOSTNAME */

  /*
   * Initialize the snmp variables and counters inside the struct netif.
   * The last argument should be replaced with your link speed, in units
   * of bits per second.
   */
  // MIB2_INIT_NETIF(netif, snmp_ifType_ethernet_csmacd, LINK_SPEED_OF_YOUR_NETIF_IN_BPS);

  netif->name[0] = IFNAME0;
  netif->name[1] = IFNAME1;
  /* We directly use etharp_output() here to save a function call.
   * You can instead declare your own function an call etharp_output()
   * from it if you have to do some checks before sending (e.g. if link
   * is available...) */

#if LWIP_IPV4
#if LWIP_ARP || LWIP_ETHERNET
#if LWIP_ARP
  netif->output = etharp_output;
#else
  /* The user should write its own code in low_level_output_arp_off function */
  netif->output = low_level_output_arp_off;
#endif /* LWIP_ARP */
#endif /* LWIP_ARP || LWIP_ETHERNET */
#endif /* LWIP_IPV4 */

#if LWIP_IPV6
  netif->output_ip6 = ethip6_output;
#endif /* LWIP_IPV6 */

  netif->linkoutput = low_level_output;

  /* initialize the hardware */
  low_level_init(netif);

  return ERR_OK;
}

/**
 * @brief  Custom Rx pbuf free callback
 * @param  pbuf: pbuf to be freed
 * @retval None
 */
void pbuf_free_custom(struct pbuf *p)
{
  RxBuff_t *rxb = (RxBuff_t *)p;
  uint32_t idx = rxb - Rx_Buff;

  if (idx < ETH_RX_BUFFER_CNT)
  {
    rx_buff_used[idx] = 0;
    SCB_InvalidateDCache_by_Addr((uint32_t *)rxb->buff, ETH_RX_BUFFER_SIZE);
  }

  if (RxAllocStatus == RX_ALLOC_ERROR)
  {
    RxAllocStatus = RX_ALLOC_OK;
  }
}

/* USER CODE BEGIN 6 */

/**
 * @brief  Custom Rx pbuf free callback
 * @param  pbuf: pbuf to be freed
 * @retval None
 */
// void pbuf_free_custom(struct pbuf *p)
// {
//   RxBuff_t *rxb = (RxBuff_t *)p; /* pbuf_custom is at start of RxBuff_t */
//   uint32_t idx = rxb - Rx_Buff;

//   if (idx < ETH_RX_BUFFER_CNT)
//   {
//     rx_buff_used[idx] = 0;
//     SCB_InvalidateDCache_by_Addr((uint32_t *)rxb->buff, ETH_RX_BUFFER_SIZE);
//   }

//   if (RxAllocStatus == RX_ALLOC_ERROR)
//   {
//     RxAllocStatus = RX_ALLOC_OK;
//   }
// }

/**
 * @brief  Returns the current time in milliseconds
 *         when LWIP_TIMERS == 1 and NO_SYS == 1
 * @param  None
 * @retval Current Time value
 */
u32_t sys_now(void)
{
  return HAL_GetTick();
}

/* USER CODE END 6 */

/**
 * @brief  Initializes the ETH MSP.
 * @param  ethHandle: ETH handle
 * @retval None
 */

void HAL_ETH_MspInit(ETH_HandleTypeDef *ethHandle)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if (ethHandle->Instance == ETH)
  {
    /* USER CODE BEGIN ETH_MspInit 0 */

    /* USER CODE END ETH_MspInit 0 */
    /* Enable Peripheral clock */
    __HAL_RCC_ETH1MAC_CLK_ENABLE();
    __HAL_RCC_ETH1TX_CLK_ENABLE();
    __HAL_RCC_ETH1RX_CLK_ENABLE();

    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**ETH GPIO Configuration
    PC1     ------> ETH_MDC
    PA1     ------> ETH_REF_CLK
    PA2     ------> ETH_MDIO
    PA7     ------> ETH_CRS_DV
    PC4     ------> ETH_RXD0
    PC5     ------> ETH_RXD1
    PB11     ------> ETH_TX_EN
    PB12     ------> ETH_TXD0
    PB13     ------> ETH_TXD1
    */
    GPIO_InitStruct.Pin = GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF11_ETH;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF11_ETH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF11_ETH;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    /* Peripheral interrupt init */
    HAL_NVIC_SetPriority(ETH_IRQn, 6, 0);
    HAL_NVIC_EnableIRQ(ETH_IRQn);
    /* USER CODE BEGIN ETH_MspInit 1 */

    /* USER CODE END ETH_MspInit 1 */
  }
}

void HAL_ETH_MspDeInit(ETH_HandleTypeDef *ethHandle)
{
  if (ethHandle->Instance == ETH)
  {
    /* USER CODE BEGIN ETH_MspDeInit 0 */

    /* USER CODE END ETH_MspDeInit 0 */
    /* Disable Peripheral clock */
    __HAL_RCC_ETH1MAC_CLK_DISABLE();
    __HAL_RCC_ETH1TX_CLK_DISABLE();
    __HAL_RCC_ETH1RX_CLK_DISABLE();

    /**ETH GPIO Configuration
    PC1     ------> ETH_MDC
    PA1     ------> ETH_REF_CLK
    PA2     ------> ETH_MDIO
    PA7     ------> ETH_CRS_DV
    PC4     ------> ETH_RXD0
    PC5     ------> ETH_RXD1
    PB11     ------> ETH_TX_EN
    PB12     ------> ETH_TXD0
    PB13     ------> ETH_TXD1
    */
    HAL_GPIO_DeInit(GPIOC, GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5);

    HAL_GPIO_DeInit(GPIOA, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_7);

    HAL_GPIO_DeInit(GPIOB, GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13);

    /* Peripheral interrupt Deinit*/
    HAL_NVIC_DisableIRQ(ETH_IRQn);

    /* USER CODE BEGIN ETH_MspDeInit 1 */

    /* USER CODE END ETH_MspDeInit 1 */
  }
}

/*******************************************************************************
                       PHI IO Functions
*******************************************************************************/
/**
 * @brief  Initializes the MDIO interface GPIO and clocks.
 * @param  None
 * @retval 0 if OK, -1 if ERROR
 */
int32_t ETH_PHY_IO_Init(void)
{
  /* We assume that MDIO GPIO configuration is already done
     in the ETH_MspInit() else it should be done here
  */

  /* Configure the MDIO Clock */
  HAL_ETH_SetMDIOClockRange(&heth);

  return 0;
}

/**
 * @brief  De-Initializes the MDIO interface .
 * @param  None
 * @retval 0 if OK, -1 if ERROR
 */
int32_t ETH_PHY_IO_DeInit(void)
{
  return 0;
}

/**
 * @brief  Read a PHY register through the MDIO interface.
 * @param  DevAddr: PHY port address
 * @param  RegAddr: PHY register address
 * @param  pRegVal: pointer to hold the register value
 * @retval 0 if OK -1 if Error
 */
int32_t ETH_PHY_IO_ReadReg(uint32_t DevAddr, uint32_t RegAddr, uint32_t *pRegVal)
{
  if (HAL_ETH_ReadPHYRegister(&heth, DevAddr, RegAddr, pRegVal) != HAL_OK)
  {
    return -1;
  }

  return 0;
}

/**
 * @brief  Write a value to a PHY register through the MDIO interface.
 * @param  DevAddr: PHY port address
 * @param  RegAddr: PHY register address
 * @param  RegVal: Value to be written
 * @retval 0 if OK -1 if Error
 */
int32_t ETH_PHY_IO_WriteReg(uint32_t DevAddr, uint32_t RegAddr, uint32_t RegVal)
{
  if (HAL_ETH_WritePHYRegister(&heth, DevAddr, RegAddr, RegVal) != HAL_OK)
  {
    return -1;
  }

  return 0;
}

/**
 * @brief  Get the time in millisecons used for internal PHY driver process.
 * @retval Time value
 */
int32_t ETH_PHY_IO_GetTick(void)
{
  return HAL_GetTick();
}

/**
 * @brief  Check the ETH link state then update ETH driver and netif link accordingly.
 * @retval None
 */
void ethernet_link_check_state(struct netif *netif)
{
  ETH_MACConfigTypeDef MACConf = {0};
  int32_t PHYLinkState = 0;
  uint32_t linkchanged = 0U, speed = 0U, duplex = 0U;

  PHYLinkState = LAN8742_GetLinkState(&LAN8742);

  if (netif_is_link_up(netif) && (PHYLinkState <= LAN8742_STATUS_LINK_DOWN))
  {
    HAL_ETH_Stop(&heth);
    netif_set_down(netif);
    netif_set_link_down(netif);
  }
  else if (!netif_is_link_up(netif) && (PHYLinkState > LAN8742_STATUS_LINK_DOWN))
  {
    switch (PHYLinkState)
    {
    case LAN8742_STATUS_100MBITS_FULLDUPLEX:
      duplex = ETH_FULLDUPLEX_MODE;
      speed = ETH_SPEED_100M;
      linkchanged = 1;
      break;
    case LAN8742_STATUS_100MBITS_HALFDUPLEX:
      duplex = ETH_HALFDUPLEX_MODE;
      speed = ETH_SPEED_100M;
      linkchanged = 1;
      break;
    case LAN8742_STATUS_10MBITS_FULLDUPLEX:
      duplex = ETH_FULLDUPLEX_MODE;
      speed = ETH_SPEED_10M;
      linkchanged = 1;
      break;
    case LAN8742_STATUS_10MBITS_HALFDUPLEX:
      duplex = ETH_HALFDUPLEX_MODE;
      speed = ETH_SPEED_10M;
      linkchanged = 1;
      break;
    default:
      break;
    }

    if (linkchanged)
    {
      /* Get MAC Config MAC */
      HAL_ETH_GetMACConfig(&heth, &MACConf);
      MACConf.DuplexMode = duplex;
      MACConf.Speed = speed;
      HAL_ETH_SetMACConfig(&heth, &MACConf);
      HAL_ETH_Start_IT(&heth); // 中断模式 → 描述符正常构建(&heth);
      netif_set_up(netif);
      netif_set_link_up(netif);
    }
  }
}

void HAL_ETH_RxAllocateCallback(uint8_t **buff)
{
  /* USER CODE BEGIN HAL ETH RxAllocateCallback */
  int i;

  for (i = 0; i < ETH_RX_BUFFER_CNT; i++)
  {
    if (!rx_buff_used[i])
    {
      rx_buff_used[i] = 1;
      RxBuff_t *rxb = &Rx_Buff[i];
      rxb->pbuf_custom.custom_free_function = pbuf_free_custom;
      pbuf_alloced_custom(PBUF_RAW, 0, PBUF_REF, &rxb->pbuf_custom, rxb->buff, ETH_RX_BUFFER_SIZE);
      *buff = rxb->buff;
      return;
    }
  }

  RxAllocStatus = RX_ALLOC_ERROR;
  *buff = NULL;
  /* USER CODE END HAL ETH RxAllocateCallback */
}

void HAL_ETH_RxLinkCallback(void **pStart, void **pEnd, uint8_t *buff, uint16_t Length)
{
  /* USER CODE BEGIN HAL ETH RxLinkCallback */

  struct pbuf **ppStart = (struct pbuf **)pStart;
  struct pbuf **ppEnd = (struct pbuf **)pEnd;
  struct pbuf *p = NULL;

  /* Get the struct pbuf from the buff address. */
  p = (struct pbuf *)(buff - offsetof(RxBuff_t, buff));
  p->next = NULL;
  p->tot_len = 0;
  p->len = Length;

  /* Chain the buffer. */
  if (!*ppStart)
  {
    /* The first buffer of the packet. */
    *ppStart = p;
  }
  else
  {
    /* Chain the buffer to the end of the packet. */
    (*ppEnd)->next = p;
  }
  *ppEnd = p;

  /* Update the total length of all the buffers of the chain. Each pbuf in the chain should have its tot_len
   * set to its own length, plus the length of all the following pbufs in the chain. */
  for (p = *ppStart; p != NULL; p = p->next)
  {
    p->tot_len += Length;
  }

  /* Invalidate data cache because Rx DMA's writing to physical memory makes it stale. */
  SCB_InvalidateDCache_by_Addr((uint32_t *)buff, Length);

  /* USER CODE END HAL ETH RxLinkCallback */
}

void HAL_ETH_TxFreeCallback(uint32_t *buff)
{
  /* USER CODE BEGIN HAL ETH TxFreeCallback */

  pbuf_free((struct pbuf *)buff);

  /* USER CODE END HAL ETH TxFreeCallback */
}

/* USER CODE BEGIN 8 */

/* USER CODE END 8 */
相关推荐
iCxhust1 小时前
如何利用iret修改cs ip
汇编·单片机·嵌入式硬件·微机原理·8088单板机
阿部多瑞 ABU1 小时前
AI红队攻防演化史(2023-2026):从虚拟角色到RLHF劫持——所有攻击方法全景总结与最新趋势分析
网络·人工智能·安全
博客-小覃2 小时前
Zabbix之华为交换机的日志记录信息操作详细教程
服务器·网络·华为·zabbix
m0_377108142 小时前
stm32平衡车
stm32·单片机·嵌入式硬件
stolentime2 小时前
FreeDomain 本地开发环境快速搭建指南
运维·服务器·网络
小+不通文墨3 小时前
把树莓派外接的DHT11接收的温湿度发送到emqx上
经验分享·笔记·嵌入式硬件·学习·树莓派
ytdbc3 小时前
OSPF综合实验
网络
Deitymoon3 小时前
FreeRTOS——列表与列表项
stm32·单片机·嵌入式硬件
总结所学3 小时前
电路定理 叠加定理 基尔霍夫定律
单片机·嵌入式硬件