阶段一: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 (发送溢出错误)
各区域详解
- 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。
- 接收缓冲区 (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 后归还。
- 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>© 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 */