下面是mpu的配置,如果和cubemx有出入按照代码走,代码已经跑通了1.先记一下发现的bug,ping响应时间很长,大概在10ms,原因是打印太多了,lwip的icmp或者其他的debug日志开启后没有关,在ping的时候会造成打印占用回复时间,导致延迟大于10ms,正常应该是1ms。

2.mpu配置会直接影响到你的运行是否hardfault、串口接收的数据和ethnet是否正常。下面我会贴上sct文件的分配写法,以及mpu的配置文件。

3.yt8521和lan8720并不一样,寄存器配置我也会在下面贴出来。

首先是cubemx配置

中断优先级我这里忘记改了,你们自己按工程需要改吧

串口正常和别的板子没区别,图不贴了,注意一个问题,如果晶振频率不同需要改,之前用在开发板验证的程序,晶振频率差1Mhz导致串口数据发送正确,但是接收有时候正确有时候出错,查了一下开发板晶振和画的板子不通导致这个问题。

sram1、2、3都是D2区,那个都可以我这里放到了SRAM3中,SRAM3中mpu配置也不能使能cache访问,否则会出错

接收缓冲区cubemx配置是在0x30040200,但是他这个生成出来之后是ac5的编译器配置,使用ac6时是需要自己重新分配的,否则会从0x24000000开始,这个你们可以从生成的map中搜一下,这个rxbuffers确实在0x24000000  512kb这个区。

sai我是双声道,16bit的i2s,256khz,8k的采样率,连了一个模块,我stm32h7做从设备,sai的配置有个坑是在代码里,采样的数量那块在我另外一个帖子写了,不懂可以看那个帖子

我是直连电脑,电脑的ip给了188.3,板子是188.6,这个不能相同

没贴出的都是默认的

时钟我是24m的,按自己的配置

这是sct文件,我是和mpu的区域配置是相符的,里面的注释里,区域是对的,注释的内容可能有误,后续我也是边实验边改,注释可能忘了改了,按照代码的为准,

; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************

LR_IROM1 0x08000000 0x00100000 {      ; 1MB Flash (加载区域)
  ER_IROM1 0x08000000 0x00100000 {    ; 执行地址 = 加载地址
    *.o(RESET, +First)                ; 中断向量表优先
    *(InRoot$$Sections)                ; ARM 库的特殊段
    .ANY(+RO)                         ; 所有只读数据
    .ANY(+XO)                         ; 所有可执行代码
  }
  
  ;-------------------- DTCM (128KB) ----------------------
  RW_IRAM1 0x20000000 0x00020000 {    ; DTCM - 高速数据内存
    .ANY(+RW +ZI)                     ; 全部RW/ZI数据(默认)
    ;*(.DTCM_Section)                 ; 手动分配的关键数据
  }
  
  ;-------------------- AXI SRAM (512 KB) ------------------
  RW_IRAM2 0x24000000 0x00040000 {    ; AXI RAM - 主内存区1 D1区,cache使能,dma关闭

    .ANY(+RW +ZI)                     ; 其余RW/ZI数据
  }
  RW_IRAM3 0x24040000 0x00040000 {    ; AXI RAM - 主内存区2 D1区,cache关闭,dma使能

    .ANY(+RW +ZI)                     ; 其余RW/ZI数据
	
  }

  ;-------------------- SRAM1 (128KB) ----------------------
  RW_DMARxDscrTab 0x30000000 0x20000 {; AHB RAM - 主内存区3 D2区 ,cache使能,dma关闭
  .ANY(+RW +ZI)                     ; 其余RW/ZI数据
  }
  ;-------------------- SRAM2 (128KB) ----------------------
  RW_DMATxDscrTab 0x30020000 0x20000{ ;AHB RAM - 主内存区3 D2区 ,cache使能,dma关闭
  .ANY(+RW +ZI)                     ; 其余RW/ZI数据
  ethernetif.o(.bss.memp_memory_RX_POOL_base); /* RX缓冲区 */
  }
  ;-------------------- SRAM3 (32KB) ----------------------
  RW_IRAM4 0x30040000 0x8000 {;AHB RAM - 主内存区3 D2区 用于以太网和USB缓冲
        ;. = ALIGN(32);              /* 32字节对齐 */
        /* LWIP 内存池 */
     *(.RxDecripSection)  /* 接收描述符 */
	*(.TxDecripSection)  /* 发送描述符 */
	
  }
	;-------------------- SRAM4 (64KB) ----------------------
  RW_IRAM6 0x38000000 0x10000 {;AHB RAM - 主内存区3 D3区
  .ANY(+RW +ZI)
  
  }
  ;-------------------- 添加备份SRAM (4KB) -----------------
  RW_BKPSRAM 0x38800000 0x00001000 { ; AHB RAM - 主内存区3 D3区,备份域内存
   .ANY(+RW +ZI) 
  }
}

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 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 "cmsis_os.h"
#include "dma.h"
#include "lwip.h"
#include "sai.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MPU_Config(void);
void MX_FREERTOS_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* �ض���fputc���� */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void) {

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MPU Configuration--------------------------------------------------------*/
  MPU_Config();

  /* Enable the CPU Cache */

  /* Enable I-Cache---------------------------------------------------------*/
  SCB_EnableICache();

  /* Enable D-Cache---------------------------------------------------------*/
  SCB_EnableDCache();

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick.
   */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* Enable D2 domain SRAM3 Clock (0x30040000 AXI)*/
  /* 手动启用 D2 域 SRAM 时钟 */
  __HAL_RCC_D2SRAM3_CLK_ENABLE(); // SRAM3 (0x30040000)
  __HAL_RCC_D2SRAM1_CLK_ENABLE(); // SRAM1 (0x30000000)
  __HAL_RCC_D2SRAM2_CLK_ENABLE(); // SRAM2 (0x30020000)

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();
  MX_SAI1_Init();
  MX_UART7_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Init scheduler */
  osKernelInitialize(); /* Call init function for freertos objects (in
                           cmsis_os2.c) */
  MX_FREERTOS_Init();

  /* Start scheduler */
  osKernelStart();

  /* We should never get here as control is now taken by the scheduler */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1) {

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void) {
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Supply configuration update enable
  */
  HAL_PWREx_ConfigSupply(PWR_LDO_SUPPLY);

  /** Configure the main internal regulator output voltage
  */
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE0);

  while (!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {
  }

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 2;
  RCC_OscInitStruct.PLL.PLLN = 80;
  RCC_OscInitStruct.PLL.PLLP = 2;
  RCC_OscInitStruct.PLL.PLLQ = 7;
  RCC_OscInitStruct.PLL.PLLR = 2;
  RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_3;
  RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE;
  RCC_OscInitStruct.PLL.PLLFRACN = 0;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK |
                                RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2 |
                                RCC_CLOCKTYPE_D3PCLK1 | RCC_CLOCKTYPE_D1PCLK1;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV2;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV2;
  RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK) {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */
// MPU_TEX_LEVEL1时,更高性能但需要显式缓存维护,DMA 访问必须手动清理/失效缓存
//开了MPU_ACCESS_SHAREABLE,MPU_ACCESS_CACHEABLE,MPU_ACCESS_BUFFERABLE之后,串口接收到的是最新值
//关了MPU_ACCESS_NOT_SHAREABLE,MPU_ACCESS_NOT_CACHEABLE,MPU_ACCESS_NOT_BUFFERABLE之后,串口接收到的是最新值
// MPU_ACCESS_NOT_SHAREABLE,MPU_ACCESS_CACHEABLE,MPU_ACCESS_NOT_BUFFERABLE之后,直接hardfault
// MPU_ACCESS_NOT_SHAREABLE,MPU_ACCESS_NOT_CACHEABLE,MPU_ACCESS_BUFFERABLE,直接hardfault
// MPU_ACCESS_NOT_SHAREABLE,MPU_ACCESS_CACHEABLE,MPU_ACCESS_BUFFERABLE,只有第一次能接收到值,之后就是0,但是长度可以接收到,中断可以触发
// MPU_ACCESS_SHAREABLE,MPU_ACCESS_NOT_CACHEABLE,MPU_ACCESS_NOT_BUFFERABLE,串口接收到的是最新值,
//开了MPU_ACCESS_SHAREABLE,MPU_ACCESS_CACHEABLE,MPU_ACCESS_NOT_BUFFERABLE之后,直接hardfault
//

// MPU_TEX_LEVEL0时,写直达,即写cache又写内存,适合简单应用,硬件自动维护缓存一致性DMA,访问无需软件干预
//开了MPU_ACCESS_SHAREABLE,MPU_ACCESS_CACHEABLE,MPU_ACCESS_NOT_BUFFERABLE之后,可以收到最新数据
//关了MPU_ACCESS_NOT_SHAREABLE,MPU_ACCESS_NOT_CACHEABLE,MPU_ACCESS_NOT_BUFFERABLE之后,hardfault
/* USER CODE END 4 */

/* MPU Configuration */

void MPU_Config(void) {
  MPU_Region_InitTypeDef MPU_InitStruct = {0};

  /* Disables the MPU */
  HAL_MPU_Disable();

  /** Initializes and configures the Region and the memory to be protected
  */
  MPU_InitStruct.Enable = MPU_REGION_ENABLE;
  MPU_InitStruct.Number = MPU_REGION_NUMBER0;
  MPU_InitStruct.BaseAddress = 0x24000000;
  MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
  MPU_InitStruct.SubRegionDisable = 0x00;
  MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
  MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
  MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
  MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
  MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;

  HAL_MPU_ConfigRegion(&MPU_InitStruct);

  /** Initializes and configures the Region and the memory to be protected
  */
  MPU_InitStruct.Number = MPU_REGION_NUMBER1;
  MPU_InitStruct.BaseAddress = 0x30000000;
  MPU_InitStruct.Size = MPU_REGION_SIZE_256KB;
  MPU_InitStruct.SubRegionDisable = 0x0;
  MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
  MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
  MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
  MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
  HAL_MPU_ConfigRegion(&MPU_InitStruct);

  /** Initializes and configures the Region and the memory to be protected
  */
  MPU_InitStruct.Number = MPU_REGION_NUMBER2;
  MPU_InitStruct.BaseAddress = 0x30040000;
  MPU_InitStruct.Size = MPU_REGION_SIZE_32KB;
  MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
  MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
  MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
  HAL_MPU_ConfigRegion(&MPU_InitStruct);

  /** Initializes and configures the Region and the memory to be protected
  */
  //  MPU_InitStruct.Number = MPU_REGION_NUMBER3;
  //  MPU_InitStruct.BaseAddress = 0x30044000;
  //  MPU_InitStruct.Size = MPU_REGION_SIZE_16KB;
  //  MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
  //  MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
  //  MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
  //  MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;

  //  HAL_MPU_ConfigRegion(&MPU_InitStruct);

  /** Initializes and configures the Region and the memory to be protected
  */
  MPU_InitStruct.Number = MPU_REGION_NUMBER4;
  MPU_InitStruct.BaseAddress = 0x38000000;
  MPU_InitStruct.Size = MPU_REGION_SIZE_64KB;
  MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
  MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
  MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;

  HAL_MPU_ConfigRegion(&MPU_InitStruct);
  /* Enables the MPU */
  HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}

/**
  * @brief  Period elapsed callback in non blocking mode
  * @note   This function is called  when TIM1 interrupt took place, inside
  * HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
  * a global variable "uwTick" used as application time base.
  * @param  htim : TIM handle
  * @retval None
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
  /* USER CODE BEGIN Callback 0 */

  /* USER CODE END Callback 0 */
  if (htim->Instance == TIM1) {
    HAL_IncTick();
  }
  /* USER CODE BEGIN Callback 1 */

  /* USER CODE END Callback 1 */
}

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void) {
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();

  while (1) {
  }
  /* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line) {
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line
     number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

main.c 是mpu的配置,如果和cubemx有出入按照代码走,代码已经跑通了,注意需要加上这几个时钟的开启

下面是yt8512h的驱动代码,用的是yt8512c的寄存器,不影响,都通用的

/* Includes ------------------------------------------------------------------*/
#include "yt8521h.h"

#include "user_log.h"
#define YT8512C_SW_RESET_TO ((uint32_t)500U) /* 软件复位等待时间 */
#define YT8512C_INIT_TO ((uint32_t)2000U)    /* 初始化等待时间 */
#define YT8512C_MAX_DEV_ADDR ((uint32_t)31U) /* PHY地址的最大值 */

#define YT8512C_AND_RTL8201BL_PHYREGISTER2 0x0000

/**
  * @brief       将IO函数注册到组件对象
  * @param       pobj:设备对象
  * @param       ioctx:保存设备IO功能
  * @retval      YT8512C_STATUS_OK:OK
  *              YT8512C_STATUS_ERROR:缺少功能
  */
int32_t yt8512c_regster_bus_io(yt8512c_object_t *pobj,
                               yt8512c_ioc_tx_t *ioctx) {
  if (!pobj || !ioctx->readreg || !ioctx->writereg || !ioctx->gettick) {
    return YT8512C_STATUS_ERROR;
  }

  pobj->io.init = ioctx->init;
  pobj->io.deinit = ioctx->deinit;
  pobj->io.readreg = ioctx->readreg;
  pobj->io.writereg = ioctx->writereg;
  pobj->io.gettick = ioctx->gettick;

  return YT8512C_STATUS_OK;
}

/**
  * @brief       初始化YT8512C并配置所需的硬件资源
  * @param       pobj: 设备对象
  * @retval      YT8512C_STATUS_OK:初始化YT8512C并配置所需的硬件资源成功
                 YT8512C_STATUS_ADDRESS_ERROR:找不到设备地址
                 YT8512C_STATUS_READ_ERROR:不能读取寄存器
                 YT8512C_STATUS_WRITE_ERROR:不能写入寄存器
                 YT8512C_STATUS_RESET_TIMEOUT:无法执行软件复位
  */
int32_t yt8512c_init(yt8512c_object_t *pobj) {
  uint32_t tickstart = 0, regvalue = 0, addr = 0;
  int32_t status = YT8512C_STATUS_OK;

  if (pobj->is_initialized == 0) {
    if (pobj->io.init != 0) {
      /* MDC时钟 */
      pobj->io.init();
    }

    /* 设置PHY地址为32 */
    pobj->devaddr = YT8512C_MAX_DEV_ADDR + 1;

    /* 主要为了查找PHY地址 */
    for (addr = 0; addr <= YT8512C_MAX_DEV_ADDR; addr++) {
      if (pobj->io.readreg(addr, YT8512C_PHYSCSR, &regvalue) < 0) {
        status = YT8512C_STATUS_READ_ERROR;
        /* 无法读取这个设备地址继续下一个地址 */
        continue;
      }
      /* 已经找到PHY地址了 */
      if ((regvalue & YT8512C_PHY_COUNT) == addr) {
        pobj->devaddr = addr;
        status = YT8512C_STATUS_OK;
        break;
      }
    }

    /* 判断这个PHY地址是否大于32(2^5)*/
    if (pobj->devaddr > YT8512C_MAX_DEV_ADDR) {
      status = YT8512C_STATUS_ADDRESS_ERROR;
    }

    /* 如果PHY地址有效 */
    if (status == YT8512C_STATUS_OK) {
      /* 设置软件复位  */
      if (pobj->io.writereg(pobj->devaddr, YT8512C_BCR,
                            YT8512C_BCR_SOFT_RESET) >= 0) {
        /* 获取软件重置状态 */
        if (pobj->io.readreg(pobj->devaddr, YT8512C_BCR, &regvalue) >= 0) {
          tickstart = pobj->io.gettick();

          /* 等待软件复位完成或超时  */
          while (regvalue & YT8512C_BCR_SOFT_RESET) {
            if ((pobj->io.gettick() - tickstart) <= YT8512C_SW_RESET_TO) {
              if (pobj->io.readreg(pobj->devaddr, YT8512C_BCR, &regvalue) < 0) {
                status = YT8512C_STATUS_READ_ERROR;
                break;
              }
            } else {
              status = YT8512C_STATUS_RESET_TIMEOUT;
              break;
            }
          }
        } else {
          status = YT8512C_STATUS_READ_ERROR;
        }
      } else {
        status = YT8512C_STATUS_WRITE_ERROR;
      }
    }
  }

  /* 到了这里,初始化完成!!! */
  if (status == YT8512C_STATUS_OK) {
    tickstart = pobj->io.gettick();

    /* 等待2s进行初始化 */
    while ((pobj->io.gettick() - tickstart) <= YT8512C_INIT_TO) {
    }
    pobj->is_initialized = 1;
  }

  return status;
}

/**
  * @brief       反初始化YT8512C及其硬件资源
  * @param       pobj: 设备对象
  * @retval      YT8512C_STATUS_OK:反初始化失败成功
                 YT8512C_STATUS_ERROR:反初始化失败
  */
int32_t yt8512c_deinit(yt8512c_object_t *pobj) {
  if (pobj->is_initialized) {
    if (pobj->io.deinit != 0) {
      if (pobj->io.deinit() < 0) {
        return YT8512C_STATUS_ERROR;
      }
    }

    pobj->is_initialized = 0;
  }

  return YT8512C_STATUS_OK;
}

/**
  * @brief       关闭YT8512C的下电模式
  * @param       pobj: 设备对象
  * @retval      YT8512C_STATUS_OK:关闭成功
                 YT8512C_STATUS_READ_ERROR:不能读取寄存器
                 YT8512C_STATUS_WRITE_ERROR:不能写寄存器
  */
int32_t yt8512c_disable_power_down_mode(yt8512c_object_t *pobj) {
  uint32_t readval = 0;
  int32_t status = YT8512C_STATUS_OK;

  if (pobj->io.readreg(pobj->devaddr, YT8512C_BCR, &readval) >= 0) {
    readval &= ~YT8512C_BCR_POWER_DOWN;

    /* 清除下电模式 */
    if (pobj->io.writereg(pobj->devaddr, YT8512C_BCR, readval) < 0) {
      status = YT8512C_STATUS_WRITE_ERROR;
    }
  } else {
    status = YT8512C_STATUS_READ_ERROR;
  }

  return status;
}

/**
  * @brief       使能YT8512C的下电模式
  * @param       pobj: 设备对象
  * @retval      YT8512C_STATUS_OK:关闭成功
                 YT8512C_STATUS_READ_ERROR:不能读取寄存器
                 YT8512C_STATUS_WRITE_ERROR:不能写寄存器
  */
int32_t yt8512c_enable_power_down_mode(yt8512c_object_t *pobj) {
  uint32_t readval = 0;
  int32_t status = YT8512C_STATUS_OK;

  if (pobj->io.readreg(pobj->devaddr, YT8512C_BCR, &readval) >= 0) {
    readval |= YT8512C_BCR_POWER_DOWN;

    /* 使能下电模式 */
    if (pobj->io.writereg(pobj->devaddr, YT8512C_BCR, readval) < 0) {
      status = YT8512C_STATUS_WRITE_ERROR;
    }
  } else {
    status = YT8512C_STATUS_READ_ERROR;
  }

  return status;
}

/**
  * @brief       启动自动协商过程
  * @param       pobj: 设备对象
  * @retval      YT8512C_STATUS_OK:关闭成功
                 YT8512C_STATUS_READ_ERROR:不能读取寄存器
                 YT8512C_STATUS_WRITE_ERROR:不能写寄存器
  */
int32_t yt8512c_start_auto_nego(yt8512c_object_t *pobj) {
  uint32_t readval = 0;
  int32_t status = YT8512C_STATUS_OK;

  if (pobj->io.readreg(pobj->devaddr, YT8512C_BCR, &readval) >= 0) {
    readval |= YT8512C_BCR_AUTONEGO_EN;

    /* 启动自动协商 */
    if (pobj->io.writereg(pobj->devaddr, YT8512C_BCR, readval) < 0) {
      status = YT8512C_STATUS_WRITE_ERROR;
    }
  } else {
    status = YT8512C_STATUS_READ_ERROR;
  }

  return status;
}

/**
  * @brief       获取YT8512C设备的链路状态
  * @param       pobj: 设备对象
  * @param       pLinkState: 指向链路状态的指针
  * @retval      YT8512C_STATUS_100MBITS_FULLDUPLEX:100M,全双工
                 YT8512C_STATUS_100MBITS_HALFDUPLEX :100M,半双工
                 YT8512C_STATUS_10MBITS_FULLDUPLEX:10M,全双工
                 YT8512C_STATUS_10MBITS_HALFDUPLEX :10M,半双工
                 YT8512C_STATUS_READ_ERROR:不能读取寄存器
  */
int32_t yt8512c_get_link_state(yt8512c_object_t *pobj) {
  uint32_t readval = 0;

  /* 检测特殊功能寄存器链接值 */
  if (pobj->io.readreg(pobj->devaddr, YT8512C_PHYSCSR, &readval) < 0) {
    log_error("Failed to read YT8512C_PHYSCSR register\r\n");
    return YT8512C_STATUS_READ_ERROR;
  }
  //log_debug("YT8512C_PHYSCSR = %08x\n", readval);
   /* 1. 检查链路状态 (Bit 10) */
  if (!(readval & (1 << 10))) {
    log_debug("Link is DOWN\r\n");
    return YT8512C_STATUS_LINK_DOWN;
  }
  //log_debug("Link is UP");
    /* 2. 检查状态是否已解析 (Bit 11) */
    if (!(readval & (1 << 11))) {
        log_debug("Speed/Duplex not resolved");
        return YT8512C_STATUS_AUTONEGO_NOTDONE;
    }
        /* 3. 提取速度模式 (Bits 15:14) */
    uint8_t speed_mode = (readval >> 14) & 0x03;
    
    /* 4. 提取双工模式 (Bit 13) */
    uint8_t duplex_mode = (readval >> 13) & 0x01;
    
    //log_debug("Speed mode: %d, Duplex mode: %d", speed_mode, duplex_mode);
    /* 5. 判断具体链路状态 */
    switch (speed_mode) {
        case 0x00: // 10 Mbps
            if (duplex_mode) {
                return YT8512C_STATUS_10MBITS_FULLDUPLEX;
            } else {
                return YT8512C_STATUS_10MBITS_HALFDUPLEX;
            }
            
        case 0x01: // 100 Mbps
            if (duplex_mode) {
                return YT8512C_STATUS_100MBITS_FULLDUPLEX;
            } else {
                return YT8512C_STATUS_100MBITS_HALFDUPLEX;
            }
            
        case 0x02: // 1000 Mbps
            // 根据您的状态宏定义,这里可能需要扩展
            log_warning("1000Mbps mode detected, returning as 1000M Full");
            return YT8512C_STATUS_1000MBITS_FULLDUPLEX;
            
        case 0x03: // Reserved
        default:
            log_error("Invalid speed mode: 0x%02X", speed_mode);
            return YT8512C_STATUS_ERROR;
    }
}

/**
  * @brief       设置YT8512C设备的链路状态
  * @param       pobj: 设备对象
  * @param       pLinkState: 指向链路状态的指针
  * @retval      YT8512C_STATUS_OK:设置成功
                 YT8512C_STATUS_ERROR :设置失败
                 YT8512C_STATUS_READ_ERROR:不能读取寄存器
                 YT8512C_STATUS_WRITE_ERROR :不能写入寄存器
  */
int32_t yt8512c_set_link_state(yt8512c_object_t *pobj, uint32_t linkstate) {
  uint32_t bcrvalue = 0;
  int32_t status = YT8512C_STATUS_OK;

  if (pobj->io.readreg(pobj->devaddr, YT8512C_BCR, &bcrvalue) >= 0) {
    /* 禁用链路配置(自动协商,速度和双工) */
    bcrvalue &= ~(YT8512C_BCR_AUTONEGO_EN | YT8512C_BCR_SPEED_SELECT |
                  YT8512C_BCR_DUPLEX_MODE);

    if (linkstate == YT8512C_STATUS_100MBITS_FULLDUPLEX) {
      bcrvalue |= (YT8512C_BCR_SPEED_SELECT | YT8512C_BCR_DUPLEX_MODE);
    } else if (linkstate == YT8512C_STATUS_100MBITS_HALFDUPLEX) {
      bcrvalue |= YT8512C_BCR_SPEED_SELECT;
    } else if (linkstate == YT8512C_STATUS_10MBITS_FULLDUPLEX) {
      bcrvalue |= YT8512C_BCR_DUPLEX_MODE;
    } else {
      /* 错误的链路状态参数 */
      status = YT8512C_STATUS_ERROR;
    }
  } else {
    status = YT8512C_STATUS_READ_ERROR;
  }

  if (status == YT8512C_STATUS_OK) {
    /* 写入链路状态 */
    if (pobj->io.writereg(pobj->devaddr, YT8512C_BCR, bcrvalue) < 0) {
      status = YT8512C_STATUS_WRITE_ERROR;
    }
  }

  return status;
}

/**
  * @brief       启用环回模式
  * @param       pobj: 设备对象
  * @param       pLinkState: 指向链路状态的指针
  * @retval      YT8512C_STATUS_OK:设置成功
                 YT8512C_STATUS_READ_ERROR:不能读取寄存器
                 YT8512C_STATUS_WRITE_ERROR :不能写入寄存器
  */
int32_t yt8512c_enable_loop_back_mode(yt8512c_object_t *pobj) {
  uint32_t readval = 0;
  int32_t status = YT8512C_STATUS_OK;

  if (pobj->io.readreg(pobj->devaddr, YT8512C_BCR, &readval) >= 0) {
    readval |= YT8512C_BCR_LOOPBACK;

    /* 启用环回模式 */
    if (pobj->io.writereg(pobj->devaddr, YT8512C_BCR, readval) < 0) {
      status = YT8512C_STATUS_WRITE_ERROR;
    }
  } else {
    status = YT8512C_STATUS_READ_ERROR;
  }

  return status;
}

/**
  * @brief       禁用环回模式
  * @param       pobj: 设备对象
  * @param       pLinkState: 指向链路状态的指针
  * @retval      YT8512C_STATUS_OK:设置成功
                 YT8512C_STATUS_READ_ERROR:不能读取寄存器
                 YT8512C_STATUS_WRITE_ERROR :不能写入寄存器
  */
int32_t yt8512c_disable_loop_back_mode(yt8512c_object_t *pobj) {
  uint32_t readval = 0;
  int32_t status = YT8512C_STATUS_OK;

  if (pobj->io.readreg(pobj->devaddr, YT8512C_BCR, &readval) >= 0) {
    readval &= ~YT8512C_BCR_LOOPBACK;

    /* 禁用环回模式 */
    if (pobj->io.writereg(pobj->devaddr, YT8512C_BCR, readval) < 0) {
      status = YT8512C_STATUS_WRITE_ERROR;
    }
  } else {
    status = YT8512C_STATUS_READ_ERROR;
  }

  return status;
}
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef YT8512C_H
#define YT8512C_H

#ifdef __cplusplus
 extern "C" {
#endif   
   
/* Includes ------------------------------------------------------------------*/
#include <stdint.h>
#include "global.h"

/* PHY芯片寄存器映射表 */ 
#define YT8512C_BCR                            ((uint16_t)0x0000U)
#define YT8512C_BSR                            ((uint16_t)0x0001U)
#define PHY_REGISTER2                           ((uint16_t)0x0002U)
#define PHY_REGISTER3                           ((uint16_t)0x0003U)

#define YT8512C_PHYI1R   											 ((uint16_t)0x0002U)
#define YT8512C_PHYI2R   											 ((uint16_t)0x0003U)
/* 操作SCR寄存器的值(一般不需要修改) */
#define YT8512C_BCR_SOFT_RESET                 ((uint16_t)0x8000U)
#define YT8512C_BCR_LOOPBACK                   ((uint16_t)0x4000U)
#define YT8512C_BCR_SPEED_SELECT               ((uint16_t)0x2000U)
#define YT8512C_BCR_AUTONEGO_EN                ((uint16_t)0x1000U)
#define YT8512C_BCR_POWER_DOWN                 ((uint16_t)0x0800U)
#define YT8512C_BCR_ISOLATE                    ((uint16_t)0x0400U)
#define YT8512C_BCR_RESTART_AUTONEGO           ((uint16_t)0x0200U)
#define YT8512C_BCR_DUPLEX_MODE                ((uint16_t)0x0100U) 

/* 操作BSR寄存器的值(一般不需要修改) */   
#define YT8512C_BSR_100BASE_T4                 ((uint16_t)0x8000U)
#define YT8512C_BSR_100BASE_TX_FD              ((uint16_t)0x4000U)
#define YT8512C_BSR_100BASE_TX_HD              ((uint16_t)0x2000U)
#define YT8512C_BSR_10BASE_T_FD                ((uint16_t)0x1000U)
#define YT8512C_BSR_10BASE_T_HD                ((uint16_t)0x0800U)
#define YT8512C_BSR_100BASE_T2_FD              ((uint16_t)0x0400U)
#define YT8512C_BSR_100BASE_T2_HD              ((uint16_t)0x0200U)
#define YT8512C_BSR_EXTENDED_STATUS            ((uint16_t)0x0100U)
#define YT8512C_BSR_AUTONEGO_CPLT              ((uint16_t)0x0020U)
#define YT8512C_BSR_REMOTE_FAULT               ((uint16_t)0x0010U)
#define YT8512C_BSR_AUTONEGO_ABILITY           ((uint16_t)0x0008U)
#define YT8512C_BSR_LINK_STATUS                ((uint16_t)0x0004U)
#define YT8512C_BSR_JABBER_DETECT              ((uint16_t)0x0002U)
#define YT8512C_BSR_EXTENDED_CAP               ((uint16_t)0x0001U)

/* PHY芯片进程状态 */
#define  YT8512C_STATUS_READ_ERROR             ((int32_t)-5)
#define  YT8512C_STATUS_WRITE_ERROR            ((int32_t)-4)
#define  YT8512C_STATUS_ADDRESS_ERROR          ((int32_t)-3)
#define  YT8512C_STATUS_RESET_TIMEOUT          ((int32_t)-2)
#define  YT8512C_STATUS_ERROR                  ((int32_t)-1)
#define  YT8512C_STATUS_OK                     ((int32_t) 0)
#define  YT8512C_STATUS_LINK_DOWN              ((int32_t) 1)
#define  YT8512C_STATUS_100MBITS_FULLDUPLEX    ((int32_t) 2)
#define  YT8512C_STATUS_100MBITS_HALFDUPLEX    ((int32_t) 3)
#define  YT8512C_STATUS_10MBITS_FULLDUPLEX     ((int32_t) 4)
#define  YT8512C_STATUS_10MBITS_HALFDUPLEX     ((int32_t) 5)
#define  YT8512C_STATUS_AUTONEGO_NOTDONE       ((int32_t) 6)
#define  YT8512C_STATUS_1000MBITS_FULLDUPLEX    ((int32_t) 7)
/* PHY地址 ---- 由用户设置 */
#define YT8512C_ADDR                           ((uint16_t)0x0000U)
/* PHY寄存器的数量 */
#define YT8512C_PHY_COUNT                      ((uint16_t)0x001FU)

#define YT8512C_PHYSCSR                        ((uint16_t)0x11)                       /*!< tranceiver status register */
#define YT8512C_SPEED_STATUS                   ((uint16_t)0x4010)                     /*!< configured information of speed: 100Mbit/s */
#define YT8512C_DUPLEX_STATUS                  ((uint16_t)0x2000)                     /*!< configured information of duplex: full-duplex */


#define YT8512C_PHY_COUNT                      ((uint16_t)0x001FU)
 
#define YT8512C_PHYSCSR                        ((uint16_t)0x11)                       /*!< tranceiver status register */
#define YT8512C_SPEED_STATUS                   ((uint16_t)0x4010)                     /*!< configured information of speed: 100Mbit/s */
#define YT8512C_DUPLEX_STATUS                  ((uint16_t)0x2000)                     /*!< configured information of duplex: full-duplex */

/* 定义函数指针 */ 
typedef int32_t  (*yt8512c_init_func)          (void); 
typedef int32_t  (*yt8512c_deinit_func)        (void);
typedef int32_t  (*yt8512c_readreg_func)       (uint32_t, uint32_t, uint32_t *);
typedef int32_t  (*yt8512c_writereg_func)      (uint32_t, uint32_t, uint32_t);
typedef int32_t  (*yt8512c_gettick_func)       (void);

/* PHY共用函数结构体 */ 
typedef struct 
{
    yt8512c_init_func          init;                   /* 指向PHY初始化函数 */ 
    yt8512c_deinit_func        deinit;                 /* 指向PHY反初始化函数 */ 
    yt8512c_writereg_func      writereg;               /* 指向PHY写寄存器函数 */ 
    yt8512c_readreg_func       readreg;                /* 指向PHY读寄存器函数 */ 
    yt8512c_gettick_func       gettick;                /* 指向节拍函数 */ 
} yt8512c_ioc_tx_t;  

/* 注册到组件对象结构体 */
typedef struct 
{
    uint32_t            devaddr;                        /* PHY地址 */
    uint32_t            is_initialized;                 /* 描述该设备是否初始化 */
    yt8512c_ioc_tx_t   io;                             /* 设备调用的函数入口 */
    void                *pdata;                         /* 传入的形参 */
}yt8512c_object_t;


int32_t yt8512c_regster_bus_io(yt8512c_object_t *pobj, yt8512c_ioc_tx_t *ioctx);             /* 将IO函数注册到组件对象 */
int32_t yt8512c_init(yt8512c_object_t *pobj);                                                 /* 初始化YT8512C并配置所需的硬件资源 */
int32_t yt8512c_deinit(yt8512c_object_t *pobj);                                               /* 反初始化YT8512C及其硬件资源 */
int32_t yt8512c_disable_power_down_mode(yt8512c_object_t *pobj);                              /* 关闭YT8512C的下电模式 */
int32_t yt8512c_enable_power_down_mode(yt8512c_object_t *pobj);                               /* 使能YT8512C的下电模式 */
int32_t yt8512c_start_auto_nego(yt8512c_object_t *pobj);                                      /* 启动自动协商过程 */
int32_t yt8512c_get_link_state(yt8512c_object_t *pobj);                                       /* 获取YT8512C设备的链路状态 */
int32_t yt8512c_set_link_state(yt8512c_object_t *pobj, uint32_t linkstate);                   /* 设置YT8512C设备的链路状态 */
int32_t yt8512c_enable_loop_back_mode(yt8512c_object_t *pobj);                                /* 启用环回模式 */
int32_t yt8512c_disable_loop_back_mode(yt8512c_object_t *pobj);                               /* 禁用环回模式 */



#ifdef __cplusplus
}
#endif
#endif /* YT8512C_H */

然后如果用ac6的话,需要修改这ethernetif.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * File Name          : ethernetif.c
  * Description        : This file provides code for the configuration
  *                      of the ethernetif.c MiddleWare.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 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/timeouts.h"
#include "netif/ethernet.h"
#include "netif/etharp.h"
#include "lwip/ethip6.h"
#include "ethernetif.h"
#include "lan8742.h"
#include <string.h>
#include "cmsis_os.h"
#include "lwip/tcpip.h"

/* Within 'USER CODE' section, code will be kept by default at each generation
 */
/* USER CODE BEGIN 0 */
#include "yt8521h.h"
#include "stdio.h"
#include "user_udp.h"
#include "user_log.h"
/* ETH_MACMDIOAR 寄存器位定义 */
#define ETH_MACMDIOAR_GB_Pos (0U)
#define ETH_MACMDIOAR_GB_Msk (0x1UL << ETH_MACMDIOAR_GB_Pos) /*!< 0x00000001   \
                                                                */
#define ETH_MACMDIOAR_GB ETH_MACMDIOAR_GB_Msk                /*!< MII Busy */

/* 其他自定义定义 */
#define PHY_YT8521H_ADDR 0x01 // YT8521H PHY地址
/* USER CODE END 0 */

/* Private define ------------------------------------------------------------*/
/* The time to block waiting for input. */
#define TIME_WAITING_FOR_INPUT (portMAX_DELAY)
/* Time to block waiting for transmissions to finish */
#define ETHIF_TX_TIMEOUT (2000U)
/* USER CODE BEGIN OS_THREAD_STACK_SIZE_WITH_RTOS */
/* Stack size of the interface thread */
#define INTERFACE_THREAD_STACK_SIZE (1024)
/* USER CODE END OS_THREAD_STACK_SIZE_WITH_RTOS */
/* 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 Rx memory pool,
          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 = 0x30040060
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(0x30040060))) 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 */
// ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT]
// __attribute__((section(".bss.ARM.__at_0x30040000"))); /* Ethernet Rx DMA
// Descriptors */
// ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT]
// __attribute__((section(".bss.ARM.__at_0x30040060")));   /* Ethernet Tx DMA
// Descriptors */

#endif

#if defined(__ICCARM__) /*!< IAR Compiler */
#pragma location = 0x30040200
extern u8_t memp_memory_RX_POOL_base[];

#elif defined(__CC_ARM) /* MDK ARM Compiler */
__attribute__((
    section(".Rx_PoolSection"))) extern u8_t memp_memory_RX_POOL_base[];

#elif defined(__GNUC__) /* GNU */
extern u8_t memp_memory_RX_POOL_base[];
#endif

/* USER CODE BEGIN 2 */
/* USER CODE END 2 */

osSemaphoreId RxPktSemaphore = NULL; /* Semaphore to signal incoming packets */
osSemaphoreId TxPktSemaphore =
    NULL; /* Semaphore to signal transmit packet complete */

/* 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 */
yt8512c_object_t YT8521H;
yt8512c_ioc_tx_t YT8521H_IOCtx = {ETH_PHY_IO_Init, ETH_PHY_IO_DeInit,
                                  ETH_PHY_IO_WriteReg, ETH_PHY_IO_ReadReg,
                                  ETH_PHY_IO_GetTick};
/* USER CODE END 3 */

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

/**
  * @brief  Ethernet Rx Transfer completed callback
  * @param  handlerEth: ETH handler
  * @retval None
  */
void HAL_ETH_RxCpltCallback(ETH_HandleTypeDef *handlerEth) {
  osSemaphoreRelease(RxPktSemaphore);
}
/**
  * @brief  Ethernet Tx Transfer completed callback
  * @param  handlerEth: ETH handler
  * @retval None
  */
void HAL_ETH_TxCpltCallback(ETH_HandleTypeDef *handlerEth) {
  osSemaphoreRelease(TxPktSemaphore);
}
/**
  * @brief  Ethernet DMA transfer error callback
  * @param  handlerEth: ETH handler
  * @retval None
  */
void HAL_ETH_ErrorCallback(ETH_HandleTypeDef *handlerEth) {
  if ((HAL_ETH_GetDMAError(handlerEth) & ETH_DMACSR_RBU) == ETH_DMACSR_RBU) {
    osSemaphoreRelease(RxPktSemaphore);
  }
}

/* USER CODE BEGIN 4 */
void ETH_PHY_Init(void);
uint8_t Find_PHY_Address(void);
/* 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;
  /* USER CODE BEGIN OS_THREAD_ATTR_CMSIS_RTOS_V2 */
  osThreadAttr_t attributes;
  /* USER CODE END OS_THREAD_ATTR_CMSIS_RTOS_V2 */
  uint32_t duplex, speed = 0;
  int32_t PHYLinkState = 0;
  ETH_MACConfigTypeDef MACConf = {0};
  /* 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 */
  HAL_GPIO_WritePin(ETH_RST_GPIO_Port, ETH_RST_Pin, GPIO_PIN_RESET);
  HAL_Delay(150);
  HAL_GPIO_WritePin(ETH_RST_GPIO_Port, ETH_RST_Pin, GPIO_PIN_SET);
  HAL_Delay(150);
  // Clean DCache
  SCB_CleanInvalidateDCache();
  /* 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 */

  /* create a binary semaphore used for informing ethernetif of frame reception
   */
  RxPktSemaphore = osSemaphoreNew(1, 0, NULL);

  /* create a binary semaphore used for informing ethernetif of frame
   * transmission */
  TxPktSemaphore = osSemaphoreNew(1, 0, NULL);

  /* create the task that handles the ETH_MAC */
  /* USER CODE BEGIN OS_THREAD_NEW_CMSIS_RTOS_V2 */
  memset(&attributes, 0x0, sizeof(osThreadAttr_t));
  attributes.name = "EthIf";
  attributes.stack_size = INTERFACE_THREAD_STACK_SIZE;
  attributes.priority = osPriorityRealtime;
  osThreadNew(ethernetif_input, netif, &attributes);
/* USER CODE END OS_THREAD_NEW_CMSIS_RTOS_V2 */

/* USER CODE BEGIN PHY_PRE_CONFIG */

/* USER CODE END PHY_PRE_CONFIG */
/* Set PHY IO functions */
#if 0
  LAN8742_RegisterBusIO(&LAN8742, &LAN8742_IOCtx);

  /* Initialize the LAN8742 ETH PHY */
  if(LAN8742_Init(&LAN8742) != LAN8742_STATUS_OK)
  {
    netif_set_link_down(netif);
    netif_set_down(netif);
    return;
  }

  if (hal_eth_init_status == HAL_OK)
  {
    PHYLinkState = LAN8742_GetLinkState(&LAN8742);

    /* Get link state */
    if(PHYLinkState <= LAN8742_STATUS_LINK_DOWN)
    {
      netif_set_link_down(netif);
      netif_set_down(netif);
    }
    else
    {
      switch (PHYLinkState)
      {
      case LAN8742_STATUS_100MBITS_FULLDUPLEX:
        duplex = ETH_FULLDUPLEX_MODE;
        speed = ETH_SPEED_100M;
        break;
      case LAN8742_STATUS_100MBITS_HALFDUPLEX:
        duplex = ETH_HALFDUPLEX_MODE;
        speed = ETH_SPEED_100M;
        break;
      case LAN8742_STATUS_10MBITS_FULLDUPLEX:
        duplex = ETH_FULLDUPLEX_MODE;
        speed = ETH_SPEED_10M;
        break;
      case LAN8742_STATUS_10MBITS_HALFDUPLEX:
        duplex = ETH_HALFDUPLEX_MODE;
        speed = ETH_SPEED_10M;
        break;
      default:
        duplex = ETH_FULLDUPLEX_MODE;
        speed = ETH_SPEED_100M;
        break;
      }

    /* 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);
    netif_set_up(netif);
    netif_set_link_up(netif);
#endif
  /* USER CODE BEGIN PHY_POST_CONFIG */
  /* 设置PHY IO功能 */
  yt8512c_regster_bus_io(&YT8521H, &YT8521H_IOCtx);

  /* 初始化ETH PHY */
  yt8512c_init(&YT8521H);

  /* 必须开启自动协商功能 */
  yt8512c_start_auto_nego(&YT8521H);

  osDelay(1000); /* 必须等待初始化 */
  PHYLinkState = yt8512c_get_link_state(&YT8521H);

  if (PHYLinkState == YT8512C_STATUS_READ_ERROR) {
    netif_set_link_down(netif);
    netif_set_down(netif);
    log_debug("YT8512C PHY Read Error\r\n");
    return;
  } else {
    if (hal_eth_init_status == HAL_OK) {
      switch (PHYLinkState) {
      case YT8512C_STATUS_100MBITS_FULLDUPLEX:
        duplex = ETH_FULLDUPLEX_MODE;
        speed = ETH_SPEED_100M;
        break;

      case YT8512C_STATUS_100MBITS_HALFDUPLEX:
        duplex = ETH_HALFDUPLEX_MODE;
        speed = ETH_SPEED_100M;
        break;

      case YT8512C_STATUS_10MBITS_FULLDUPLEX:
        duplex = ETH_FULLDUPLEX_MODE;
        speed = ETH_SPEED_10M;
        break;

      case YT8512C_STATUS_10MBITS_HALFDUPLEX:
        duplex = ETH_HALFDUPLEX_MODE;
        speed = ETH_SPEED_10M;
        break;

      default:
        duplex = ETH_FULLDUPLEX_MODE;
        speed = ETH_SPEED_100M;
        break;
      }
    } else {
      log_debug("YT8512C PHY Init Error\r\n");
      Error_Handler();
    }
  }

  /* 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);
  netif_set_up(netif);
  netif_set_link_up(netif);

/* USER CODE END PHY_POST_CONFIG */
#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;

  pbuf_ref(p);

  do {
    if (HAL_ETH_Transmit_IT(&heth, &TxConfig) == HAL_OK) {
      errval = ERR_OK;
    } else {

      if (HAL_ETH_GetError(&heth) & HAL_ETH_ERROR_BUSY) {
        /* Wait for descriptors to become available */
        osSemaphoreAcquire(TxPktSemaphore, ETHIF_TX_TIMEOUT);
        HAL_ETH_ReleaseTxPacket(&heth);
        errval = ERR_BUF;
      } else {
        /* Other error */
        pbuf_free(p);
        errval = ERR_IF;
      }
    }
  } while (errval == ERR_BUF);

  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(void *argument) {
  struct pbuf *p = NULL;
  struct netif *netif = (struct netif *)argument;

  for (;;) {
    if (osSemaphoreAcquire(RxPktSemaphore, TIME_WAITING_FOR_INPUT) == osOK) {
      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) {
  struct pbuf_custom *custom_pbuf = (struct pbuf_custom *)p;
  LWIP_MEMPOOL_FREE(RX_POOL, custom_pbuf);

  /* If the Rx Buffer Pool was exhausted, signal the ethernetif_input task to
   * call HAL_ETH_GetRxDataBuffer to rebuild the Rx descriptors. */

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

/* USER CODE BEGIN 6 */

/**
* @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, 5, 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_thread(void *argument) {
  ETH_MACConfigTypeDef MACConf = {0};
  int32_t PHYLinkState = 0;
  uint32_t linkchanged = 0U, speed = 0U, duplex = 0U;

  struct netif *netif = (struct netif *)argument;
/* USER CODE BEGIN ETH link init */

/* USER CODE END ETH link init */
#if 0
  for(;;)
  {
  PHYLinkState = LAN8742_GetLinkState(&LAN8742);

  if(netif_is_link_up(netif) && (PHYLinkState <= LAN8742_STATUS_LINK_DOWN))
  {
    HAL_ETH_Stop_IT(&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);
      netif_set_up(netif);
      netif_set_link_up(netif);
    }
  }
#endif
  /* USER CODE BEGIN ETH link Thread core code for User BSP */
  bool lwip_initialized = false;

  for (;;) {
    PHYLinkState = yt8512c_get_link_state(&YT8521H);
    // log_debug("[PHY] State: %d", PHYLinkState);
    if (netif_is_link_up(netif) && (PHYLinkState <= LAN8742_STATUS_LINK_DOWN)) {
      HAL_ETH_Stop_IT(&heth);
      netif_set_down(netif);
      netif_set_link_down(netif);
      /* 打印详细状态 */
      log_debug("[ETH] MACCR: 0x%08X\n", ETH->MACCR);

      log_debug("[ETH] DMACSR: 0x%08X\n", ETH->DMACSR);
    } else if (!netif_is_link_up(netif) &&
               (PHYLinkState > LAN8742_STATUS_LINK_DOWN)) {
      printf("[NET] Link up detected! Bringing interface up...\n");
      switch (PHYLinkState) {
      case YT8512C_STATUS_100MBITS_FULLDUPLEX:
        duplex = ETH_FULLDUPLEX_MODE;
        speed = ETH_SPEED_100M;
        linkchanged = 1;
        printf("[PHY] 100Mbps Full Duplex\n");
        break;

      case YT8512C_STATUS_100MBITS_HALFDUPLEX:
        duplex = ETH_HALFDUPLEX_MODE;
        speed = ETH_SPEED_100M;
        linkchanged = 1;
        printf("[PHY] 100Mbps Half Duplex\n");
        break;

      case YT8512C_STATUS_10MBITS_FULLDUPLEX:
        duplex = ETH_FULLDUPLEX_MODE;
        speed = ETH_SPEED_10M;
        linkchanged = 1;
        printf("[PHY] 10Mbps Full Duplex\n");
        break;

      case YT8512C_STATUS_10MBITS_HALFDUPLEX:
        duplex = ETH_HALFDUPLEX_MODE;
        speed = ETH_SPEED_10M;
        linkchanged = 1;
        printf("[PHY] 10Mbps Half Duplex\n");
        break;

      default:
        duplex = ETH_FULLDUPLEX_MODE;
        speed = ETH_SPEED_100M;
        linkchanged = 1;
        printf("[PHY] Unknown state! Defaulting to 100Mbps Full Duplex\n");
        break;
      }

      if (linkchanged) {
        /* Get MAC Config MAC */

        HAL_ETH_GetMACConfig(&heth, &MACConf);
        printf("[ETH] Current MAC config: Speed=%s, Duplex=%s\n",
               MACConf.Speed == ETH_SPEED_100M ? "100M" : "10M",
               MACConf.DuplexMode == ETH_FULLDUPLEX_MODE ? "Full" : "Half");
        MACConf.DuplexMode = duplex;
        MACConf.Speed = speed;
        HAL_ETH_SetMACConfig(&heth, &MACConf);
        printf("[ETH] New MAC config: Speed=%s, Duplex=%s\n",
               speed == ETH_SPEED_100M ? "100M" : "10M",
               duplex == ETH_FULLDUPLEX_MODE ? "Full" : "Half");
        int status = HAL_ETH_Start_IT(&heth);
        printf("[ETH] HAL_ETH_Start_IT status: %d\n", status);
        if (status != HAL_OK) {
          printf("[ETH] Error starting ETH! Code: %d\n", heth.ErrorCode);
        }
        netif_set_up(netif);
        netif_set_link_up(netif);
      }
    }

    if (lwip_initialized == false) {
      /*lwip 初始化完成 发送任务通知*/
      printf("[LWIP] Initialization complete\n");
      lwip_initialized = true;
    }

    /* USER CODE END ETH link Thread core code for User BSP */

    osDelay(100);
  }
}

void HAL_ETH_RxAllocateCallback(uint8_t **buff) {
  /* USER CODE BEGIN HAL ETH RxAllocateCallback */
  struct pbuf_custom *p = LWIP_MEMPOOL_ALLOC(RX_POOL);
  if (p) {
    /* Get the buff from the struct pbuf address. */
    *buff = (uint8_t *)p + offsetof(RxBuff_t, buff);
    p->custom_free_function = pbuf_free_custom;
    /* Initialize the struct pbuf.
    * This must be performed whenever a buffer's allocated because it may be
    * changed by lwIP or the app, e.g., pbuf_free decrements ref. */
    pbuf_alloced_custom(PBUF_RAW, 0, PBUF_REF, p, *buff, ETH_RX_BUFFER_SIZE);
  } else {
    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 */

然后是low_level_init初始化函数和ethernet_link_thread这个线程的修改,把之前的LAN8742的改成yt8512的

在cc.h中增加

/*
 * Copyright (c) 2001-2003 Swedish Institute of Computer Science.
 * All rights reserved. 
 * 
 * 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. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
 *
 * This file is part of the lwIP TCP/IP stack.
 * 
 * Author: Adam Dunkels <adam@sics.se>
 *
 */
#ifndef __CC_H__
#define __CC_H__

#include "cpu.h"
#include <stdlib.h>
#include <stdio.h>

typedef int sys_prot_t;

#define LWIP_PROVIDE_ERRNO

#if defined (__GNUC__) & !defined (__CC_ARM)

//#define LWIP_TIMEVAL_PRIVATE 0
//#include <sys/time.h>

#endif

/* define compiler specific symbols */
#if defined (__ICCARM__)

#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT 
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#define PACK_STRUCT_USE_INCLUDES

#elif defined (__GNUC__)

#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT __attribute__ ((__packed__))
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x

#elif defined (__CC_ARM)

#define PACK_STRUCT_BEGIN __packed
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x

#elif defined (__TASKING__)

#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x

#endif

#define LWIP_PLATFORM_ASSERT(x) do {printf("Assertion \"%s\" failed at line %d in %s\n", \
                                     x, __LINE__, __FILE__); } while(0)

/* Define random number generator function */
#define LWIP_RAND() ((u32_t)rand())

#endif /* __CC_H__ */

然后是udp的socket,有些我业务里的东西,你们删掉就行,定义的帧头啥的没啥用,

#include "user_udp.h"
#include "FreeRTOS.h"
#include "cmsis_os2.h"
#include "lwip/err.h"
#include "lwip/inet.h"
#include "lwip/ip_addr.h"
#include "lwip/netif.h"
#include "lwip/sockets.h"
#include "lwip/tcpip.h"
#include "semphr.h"
#include "task.h"
//#include "user_i2s.h"
#include "user_task.h"
#include "user_uart.h"
#include <stdbool.h>
#include <string.h>
#include "stream_buffer.h"
#include "user_info.h"
#include "htdm.h"
#include "user_uart.h"
#include "user_log.h"
//#include "i2s.h"
/********************udp任务相关*******************/
#define UDP_VOICE_PORT 5000 // 接收端口

#define UDP_MAX_CLIENTS 5 // 最大支持客户端连接数

#define UDP_CLIENT_TIMEOUT 5 * 60 * 1000 // 毫秒

#define STREAM_BUFFER_SIZE (10 * 1280)
#define SEND_THRESHOLD (5 * 1280)
#define BROADCAST_IP (0XFF78a8c0) //广播ip
uint8_t stream_discard_buf[1280]; //流缓冲丢弃区
//#define BROADCAST_IP 0xC0A878FF
// udp语音流缓冲区
StreamBufferHandle_t xStreamBuffer;
StreamBufferHandle_t xStreamBuffer_2;
//使用天通业务电话短信结构体
extern sms_phone_info_t phone_info;

udp_center_state_t udp_center_state;     //中心服务器状态
uint8_t udp_recvbuf[CLIENT_BUFFER_SIZE]; // 接收缓冲区,用流缓冲区替代
udp_client_t
    udp_voice_clients; //语音客户端结构体,在收到电话相关帧后记录ip和端口,在收到挂断后释放
udp_client_t
    udp_state_clients; //状态查询客户端结构体,在收到状态查询帧后记录ip和端口,在发送状态后释放
udp_client_t
    udp_sms_clients; //短信客户端结构体,在收到短信相关帧后记录ip和端口,在发送短信后释放
udp_client_t
    udp_contact_clients; //联系人客户端结构体,收到查询/设置联系人帧后记录ip和端口,在发送联系人后释放
udp_client_t
    udp_sms_template_clients; //联系人客户端结构体,收到查询/设置联系人帧后记录ip和端口,在发送联系人后释放

uint8_t cur_active_index = 0; // 当前活动IP

//// 用于存储客户端信息
typedef struct {
  ip_addr_t ip;                   // 客户端 IP
  uint16_t port;                  // 客户端端口
  uint32_t last_time;             // 最后活跃时间(tick)
  uint8_t active;                 // 是否已登记
  uint8_t *buffer;                // 指向动态缓冲区
  uint16_t buffer_size;           // 缓冲区大小
  client_type_flag_t client_flag; //指定客户端标记默认为常规客户端
} UdpClientInfo;

UdpClientInfo g_udp_clients[UDP_MAX_CLIENTS];

static int udp_sock = -1;
static SemaphoreHandle_t udp_mutex = NULL;
extern uint8_t udp_recvbuf[CLIENT_BUFFER_SIZE]; // 接收缓冲区,用流缓冲区替代
// extern uint8_t i2s_rx_2_udp_buffer[I2S_DMA_BUFFER_SIZE+7];
// extern I2S_DMA_Buffers_t i2s_tx_buffers;
// extern I2S_DMA_Buffers_t i2s_rx_buffers;
uint8_t udp_send_buf_i2s[SEND_THRESHOLD]; //语音流缓冲读出的发送缓冲区
osThreadId_t udpsThread_t;
osThreadId_t voicesThread_t;
void StartudpsdTask(void *argument);
void init_stream_buffer(void) {
  xStreamBuffer = xStreamBufferCreate(STREAM_BUFFER_SIZE, 1);
  if (xStreamBuffer == NULL) {
    // 处理流缓冲区创建失败的情况
    log_debug("xStreamBuffer is null\r\n");
  } else {
    // 设置流缓冲区的触发等级
    // xStreamBufferSetTriggerLevel(xStreamBuffer_2, 4);
  }
}
const osThreadAttr_t udpsTask_attributes = {
    .name = "udpsTask",
    .stack_size = 1024 * 6,
    .priority = (osPriority_t)osPriorityNormal,
};
void user_udp_init(void) {
  struct sockaddr_in local_addr;

  udp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  if (udp_sock < 0) {
    log_info("UDP socket create failed\n");
    return;
  }
  // log_debug("udp_sock[%i]\r\n",udp_sock);
  memset(&local_addr, 0, sizeof(local_addr));
  local_addr.sin_family = AF_INET;
  local_addr.sin_port = htons(UDP_VOICE_PORT);
  local_addr.sin_addr.s_addr = INADDR_ANY;

  if (bind(udp_sock, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0) {
    log_info("UDP socket bind failed\n");
    closesocket(udp_sock);
    udp_sock = -1;
    return;
  }
  // log_debug("udp_sock[%i]\r\n",udp_sock);
  struct timeval timeout = {0, 5000}; // 5ms recv timeout
  setsockopt(udp_sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
  // log_debug("udp_sock[%i]\r\n",udp_sock);
  udp_mutex = xSemaphoreCreateMutex();
  udpsThread_t = osThreadNew(StartudpsdTask, NULL, &udpsTask_attributes);
  // voicesThread_t = osThreadNew(StartVoiceSendTask, NULL,
  // &voiceTask_attributes);
}
void Udp_SetSocketNonBlocking(int sock) {
  int flags = fcntl(sock, F_GETFL, 0);
  if (flags < 0) {
    log_info("fcntl(F_GETFL) failed\n");
    return;
  }

  if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
    log_info("fcntl(F_SETFL) failed\n");
  } else {
    log_info("UDP socket set to non-blocking\n");
  }
}

/**
 * @brief UDP 接收语音数据任务
 */
void StartudpsdTask(void *arg) {
  struct sockaddr_in from;
  socklen_t addr_len = sizeof(from);

  if (udp_sock < 0) {
    log_info("UDP socket not initialized!\n");
    vTaskDelete(NULL);
  }
  // 设置非阻塞
  // Udp_SetSocketNonBlocking(udp_sock);
  log_debug("UDP recv task started (port %d)\n", UDP_VOICE_PORT);
  init_stream_buffer();
  while (1) {
    int len = recvfrom(udp_sock, udp_recvbuf, CLIENT_BUFFER_SIZE, 0,
                       (struct sockaddr *)&from, &addr_len);

    if (len > 0) {
      ip_addr_t from_ip;
      from_ip.addr = from.sin_addr.s_addr;
      uint16_t from_port = ntohs(from.sin_port);
      log_debug("UDP data from %s:%d, len: %d\n", ipaddr_ntoa(&from_ip),
                from_port, len);
// for(int i = 0; i < len; i++)
// {
//     log_info("%02x ", udp_recvbuf[i]);
// }
// log_info("\r\n");

// UdpVoiceClient_Update(from_ip, from_port, udp_recvbuf, len);
// udp_message_handle(udp_recvbuf, len);
#if 1
      // 回环验证
      sendto(udp_sock, udp_recvbuf, len, 0, (struct sockaddr *)&from,
             sizeof(from));
      log_debug("udp_sock:%x\r\nudp_recvbuf:%x\r\n", udp_sock, udp_recvbuf);
#elif 0
      // 回环验证
      Udp_SendToTarget(udp_state_clients.ip_str, udp_state_clients.port,
                       udp_recvbuf, len);

#endif

      // Audio_PushToPlayBuffer((uint8_t *)recv_buf, len);
    }
    // UdpVoiceClient_Cleanup();
    vTaskDelay(pdMS_TO_TICKS(1)); // 防止空转
  }
}

void user_network_up_or_down(uint8_t sta) {
  if (sta == 1) {
    // user_tcp_init();
    log_info("Network interface is up.\r\n");
    user_udp_init();

  } else {
    log_info("Network interface is down.\r\n");
  }
}

void udp_thread_notify_vaule(bool irq, uint32_t nofity_bit) {

  BaseType_t xHigherPriorityTaskWoken = pdFALSE;
  if (irq == true) {
    xTaskNotifyFromISR((TaskHandle_t)udpsThread_t, nofity_bit, eSetBits,
                       &xHigherPriorityTaskWoken); //
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
  } else {
    xTaskNotify((TaskHandle_t)udpsThread_t, nofity_bit, eSetBits);
  }
}
#ifndef __USER_UDP_H
#define __USER_UDP_H
//_F表示功能码
//_A表示操作类型
//_R表示应答类型
#include <stdint.h>
#include <stdbool.h>
#define UDP_I2S_TX_HALF_FINISH  0x01
#define UDP_I2S_TX_FINISHED     UDP_I2S_TX_HALF_FINISH << 1

#define CLIENT_BUFFER_SIZE 1500 // 每个客户端的缓冲区大小
/*************udp任务通知*************/
#define UDP_TASK_NOTIFY_LWIP_READY 0X01
typedef enum {
    UDP_CLIENT_NORMAL = 0,//常规客户端
  UDP_CLIENT_CALL,//电话客户端 = 1
  UDP_CLIENT_SMS,//短信客户端
  UDP_CLIENT_SOS,//SOS客户端
  STATUS_QUERY_CLIENT,//状态查询客户端
  CONTACT_QUERY_CLIENT,//联系人查询/设置联系人客户端
  SMS_TEMPLATE_CLIENT,//设置短信模板

}client_type_flag_t;
typedef struct{
    uint8_t center_state_info[128];//中心节点状态信息
    uint8_t lens;
}udp_center_state_t;
#define UDP_FRAME_HEAD 0xfd //默认帧头
#define UDP_FRAME_END 0Xdf  //默认帧尾

/**
 * @brief udp协议功能码
 * 
 */
typedef enum {
  UDP_LOG_F = 0x01,                    //用户ip登录/登出
  UDP_LOG_REP_F = 0x02,                //用户ip登录/登出应答
  UDP_COMMUNICATION_TEST_F = 0x03,     //通讯测试
  UDP_COMMUNICATION_TEST_REP_F = 0x04, //通讯测试应答
  UDP_DIAL_F = 0x05,                   //拨打电话
  UDP_DIAL_REP_F = 0x06,               //拨打电话应答帧
  UDP_HANG_F = 0x07,                   //挂断电话
  UDP_HANG_REP_F = 0x08,               //挂断电话应答帧
  UDP_SMS_F = 0x09,                    //发送短信
  UDP_SMS_REP_F = 0x0A,                //发送短信应答帧
  UDP_SET_PHONE_F = 0x0B,              //设置电话/紧急短信模板
  UDP_SET_PHONE_REP_F = 0x0C,          //设置电话/紧急短信模板应答帧
  UDP_SOS_F = 0x0E,                    // SOS请求帧
  UDP_SOS_REP_F = 0x0F,                // SOS请求应答帧
  UDP_STATUS_F = 0x10,                 //状态查询
  UDP_STATUS_REP_F = 0x11,             //状态查询应答帧
} udp_frame_funcode_t;
// 登录/登出操作类型枚举
typedef enum {
  LOGIN_ACTION_A = 0x01,   // 登录操作
  LOGOUT_ACTION_A = 0x02   // 登出操作
} udp_login_action_t;
// 登录/登出应答状态枚举
typedef enum {
  ACCOUNT_LOGIN_SUCCESS_R = 0x01, //登录成功
  ACCOUNT_LOGIN_FAILED_R,         //登录失败
  ACCOUNT_LOGOUT_SUCCESS_R,       //登出成功
  ACCOUNT_LOGOUT_FAILED_R,        //登出失败
} udp_login_state_rep_t;
// 保活操作类型枚举
typedef enum {
    UDP_ALIVE_A = 0x01, //请求保活
}udp_keep_rep_t;
// 保活应答状态枚举
typedef enum {
    UDP_KEEP_ALIVE_R = 0x01, //保活成功
}udp_keep_alive_rep_t;
// 拨打电话操作类型枚举
typedef enum {
    UDP_CALL_A = 0x01, //拨打电话
}udp_call_t;
// 拨打电话应答状态枚举
typedef enum {
    UDP_CALLING_R = 0x01, //正在拨打电话
    UDP_CALL_CONNECT_OK_R,         //对方接通
    UDP_CALL_NET_BUSY_R,          //卫星网络已被占用,电话拨打失败  //卫星网络已被占用,电话拨打失败
    UDP_CALL_NET_DISCONNECT_R,//网络连接中断,请重新连接
}udp_call_rep_t;
// 挂断电话操作类型枚举
typedef enum {
    UDP_HANGUP_A = 0x01, //挂断电话
}udp_hangup_t;
// 挂断电话应答状态枚举
typedef enum {
    UDP_HANGUP_OK_R =0x01, //挂断成功
}udp_hangup_rep_t;
// 发送短信操作类型枚举
typedef enum {
    UDP_SMS_SEND_A = 0x01, //发送短信
}udp_sms_t;
// 发送短信应答状态枚举
typedef enum {
    UDP_SMS_SEND_OK_R = 0x01,           // 发送成功
    UDP_SMS_SEND_NO_SIGNAL_R,           // 发送失败:天通无信号
    UDP_SMS_SEND_SATELLITE_FAIL_R,      // 发送失败:收到天通回复发送失败
    UDP_SMS_SEND_PDU_FAIL_R,            // 发送失败:生成PDU短信失败
    UDP_SMS_SENDING_R,                  // 正在发送短信
    UDP_SMS_SEND_WAITING_R              // 卫星网络占用
} udp_sms_rep_t;
// 设置电话模板操作类型枚举
typedef enum {
    UDP_SET_PHONE_A = 0x01, //设置紧急短信模板
}udp_set_phone_t;
// 设置短信模板操作类型枚举
typedef enum {
    UDP_SET_PHONE_SMS_A = 0x01,    // 设置紧急短信中心号码
    UDP_SET_EMERGENCY_CONTACT1_A,  // 设置紧急联系人1
    UDP_SET_EMERGENCY_CONTACT2_A,  // 设置紧急联系人2
    UDP_SET_EMERGENCY_CONTACT3_A,  // 设置紧急联系人3
    UDP_SET_SATELLITE_PHONE_A      // 本机卫星电话卡号
} udp_set_phone_sms_t;
// 设置电话/短信模板应答状态枚举
typedef enum {
    UDP_SET_OK_R =1,//设置成功
    UDP_SET_FAIL_R,//设置失败
}udp_set_phone_sms_rep_t;
// SOS操作类型枚举
typedef enum {
    UDP_SOS_A = 0x01, //SOS请求
}udp_sos_t;
// SOS应答状态枚举
typedef enum {
    UDP_SOS_OK_R = 1,        // 发起sos流程成功
    UDP_SOS_CONTACT_INVALID_R, // 发起sos流程失败:联系人无效
    UDP_SOS_NET_BUSY_R        // 发起sos流程失败:网络被占用
} udp_sos_rep_t;
// 状态查询应答状态枚举
typedef enum {
    UDP_STATUS_R = 0x01, //状态查询
}udp_status_t;
/**
 * @brief UDP状态查询应答状态枚举
 * 
 * 包含系统各模块的状态标识,用于UDP通信中的状态报告
 */
typedef enum {
    SATELLITE_CONNECTION_STATUS_R = 0x01,  // 卫星连接状态 (0: 断开, 1: 连接)
    SATELLITE_SIGNAL_QUALITY_R,     // 卫星信号质量+SNR (0-100表示质量百分比)
    CENTER_NODE_POWER_STATUS_R,    // 中心节点电源状态 (0: 关闭, 1: 开启)
    MOTION_STATUS_R,               // 运动状态 (0: 静止, 1: 运动中)
    SATELLITE_NETWORK_STATUS_R,    // 卫星网络状态 (0: 断开, 1: 连接)
    CENTER_NODE_BATTERY_LEVEL_R,   // 中心节点电源电量 (0-100表示电量百分比)
    CENTER_NODE_WORK_STATUS_R,     // 中心节点工作状态 (0: 待机, 1: 工作中)
    SIM_CARD_STATUS_R,             // SIM卡状态 (0: 异常, 1: 正常)
    SOS_EMERGENCY_STATUS_R,        // SOS状态 (0: 正常, 1: 紧急状态)
    CONNECTED_DEVICES_COUNT_R      // 当前连入中心节点设备数 (0-255)
} udp_status_rep_t;

typedef void (*udp_data_parser_hook_t)(uint8_t *buffer, uint16_t len);

/*功能码和回调函数*/
typedef struct {
  uint8_t udp_msg_func_code;
  udp_data_parser_hook_t hook;
} udp_dispatcher_t;
/**
 * @brief UDP语音客户端信息
 * 
 */
typedef struct{
    char ip_str[16];
    uint16_t port;
    uint8_t type;
}udp_client_t;


/**
 * @brief UDP初始化,该初始化在网线插入以后,LWIP初始化后调用
 *
 */
void user_udp_init(void);

/**
 * @brief UDP广播
 *
 * @param data
 * @param len
 * @return int
 */
int UdpVoice_SendToAll(const void *data, uint16_t len);

/**
 * @brief 向指定IP和端口发送UDP数据
 *
 * @param ip_str
 * @param port
 * @param data
 * @param len
 * @return int
 */
int Udp_SendToTarget(const char *ip_str, uint16_t port, const void *data,
                          uint16_t len);

/**
 * @brief 发送udp数据时分为语音数据和业务数据两种
 * 
 * @param data 
 * @param len 
 * @param client_flag 
 */
void user_send_udp_data_to_target(const void *data, uint16_t len,client_type_flag_t client_flag);
/**
 * @brief 接收到的整帧数据解析和执行
 * 
 * @param udp_recvbuf 接收到的数据
 * @param len 数据长度
 */
void udp_message_handle(uint8_t *udp_recvbuf, uint16_t len);
/**
 * @brief udp数据帧解析和分发处理
 * 
 * @param func_code 功能码
 * @param buffer 数据
 * @param len 数据长度
 */
void udp_frame_parser(uint8_t func_code, uint8_t *buffer, uint16_t len);
/**
 * @brief 发送数据到第一个活跃的ip
 * 
 * @param buffer 数据
 * @param len 数据长度
 */
int UdpVoice_SendToFirst(const void *data, uint16_t len);
/**
 * @brief 语音发送完成中断触发的任务通知,用于udp中发送语音时的状态同步
 * 
 * @param irq 
 * @param nofity_bit 
 */
void VoiceSend_thread_notify_vaule(bool irq, uint32_t nofity_bit);
void user_network_up_or_down(uint8_t sta);
void udp_thread_notify_vaule(bool irq, uint32_t nofity_bit);
#if 0
/**
 * @brief ip登录操作处理函数
 * 
 * @param buffer 
 * @param len 
 */
void udp_login_hook(uint8_t *buffer, uint16_t len);
/**
 * @brief ip登录操作应答处理函数
 * 
 * @param buffer 
 * @param len 
 */
void udp_login_rep_hook(uint8_t login_ruslt);
/**
 * @brief 保活测试
 * 
 * @param buffer 
 * @param len 
 */
void udp_communication_test_hook(uint8_t *buffer, uint16_t len);
/**
 * @brief 
 * 
 * @param buffer 
 * @param len 
 */
void udp_communication_test_rep_hook();
/**
 * @brief 拨打电话回调
 * 
 * @param buffer 
 * @param len 
 */
void udp_dial_hook(uint8_t *buffer, uint16_t len);
/**
 * @brief 拨打电话应答处理函数
 * 
 * @param call_result 
 */
void udp_dial_rep_hook( udp_call_rep_t call_result);
/**
 * @brief 挂断电话回调
 * 
 * @param buffer 
 * @param len 
 */
void udp_hang_hook(uint8_t *buffer, uint16_t len);
/**
 * @brief 
 * 
 * @param buffer 
 * @param len 
 */
void udp_hang_rep_hook();
/**
 * @brief 
 * 
 * @param buffer 
 * @param len 
 */
void udp_sms_hook(uint8_t *buffer, uint16_t len);
/**
 * @brief 
 * 
 * @param buffer 
 * @param len 
 */
void udp_sms_rep_hook(udp_sms_rep_t sms_send_sta);
/**
 * @brief 
 * 
 * @param buffer 
 * @param len 
 */
void udp_set_phone_hook(uint8_t *buffer, uint16_t len);
/**
 * @brief 
 * 
 * @param buffer 
 * @param len 
 */
void udp_set_phone_rep_hook();
/**
 * @brief 
 * 
 * @param buffer 
 * @param len 
 */
void udp_sos_hook(uint8_t *buffer, uint16_t len);
/**
 * @brief 
 * 
 * @param buffer 
 * @param len 
 */
void udp_sos_rep_hook();
/**
 * @brief 
 * 
 * @param buffer 
 * @param len 
 */
void udp_status_hook(uint8_t *buffer, uint16_t len);
/**
 * @brief 
 * 
 * @param status 
 */
void udp_status_rep_hook(udp_status_t status);


#endif

#endif

udp的部分主要看这里,这里就是收发和socket的精髓,其他不用管

void StartudpsdTask(void *argument);
void init_stream_buffer(void) {
  xStreamBuffer = xStreamBufferCreate(STREAM_BUFFER_SIZE, 1);
  if (xStreamBuffer == NULL) {
    // 处理流缓冲区创建失败的情况
    log_debug("xStreamBuffer is null\r\n");
  } else {
    // 设置流缓冲区的触发等级
    // xStreamBufferSetTriggerLevel(xStreamBuffer_2, 4);
  }
}
const osThreadAttr_t udpsTask_attributes = {
    .name = "udpsTask",
    .stack_size = 1024 * 6,
    .priority = (osPriority_t)osPriorityNormal,
};
void user_udp_init(void) {
  struct sockaddr_in local_addr;

  udp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  if (udp_sock < 0) {
    log_info("UDP socket create failed\n");
    return;
  }
  // log_debug("udp_sock[%i]\r\n",udp_sock);
  memset(&local_addr, 0, sizeof(local_addr));
  local_addr.sin_family = AF_INET;
  local_addr.sin_port = htons(UDP_VOICE_PORT);
  local_addr.sin_addr.s_addr = INADDR_ANY;

  if (bind(udp_sock, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0) {
    log_info("UDP socket bind failed\n");
    closesocket(udp_sock);
    udp_sock = -1;
    return;
  }
  // log_debug("udp_sock[%i]\r\n",udp_sock);
  struct timeval timeout = {0, 5000}; // 5ms recv timeout
  setsockopt(udp_sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
  // log_debug("udp_sock[%i]\r\n",udp_sock);
  udp_mutex = xSemaphoreCreateMutex();
  udpsThread_t = osThreadNew(StartudpsdTask, NULL, &udpsTask_attributes);
  // voicesThread_t = osThreadNew(StartVoiceSendTask, NULL,
  // &voiceTask_attributes);
}
void Udp_SetSocketNonBlocking(int sock) {
  int flags = fcntl(sock, F_GETFL, 0);
  if (flags < 0) {
    log_info("fcntl(F_GETFL) failed\n");
    return;
  }

  if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
    log_info("fcntl(F_SETFL) failed\n");
  } else {
    log_info("UDP socket set to non-blocking\n");
  }
}

/**
 * @brief UDP 接收语音数据任务
 */
void StartudpsdTask(void *arg) {
  struct sockaddr_in from;
  socklen_t addr_len = sizeof(from);

  if (udp_sock < 0) {
    log_info("UDP socket not initialized!\n");
    vTaskDelete(NULL);
  }
  // 设置非阻塞
  // Udp_SetSocketNonBlocking(udp_sock);
  log_debug("UDP recv task started (port %d)\n", UDP_VOICE_PORT);
  init_stream_buffer();
  while (1) {
    int len = recvfrom(udp_sock, udp_recvbuf, CLIENT_BUFFER_SIZE, 0,
                       (struct sockaddr *)&from, &addr_len);

    if (len > 0) {
      ip_addr_t from_ip;
      from_ip.addr = from.sin_addr.s_addr;
      uint16_t from_port = ntohs(from.sin_port);
      log_debug("UDP data from %s:%d, len: %d\n", ipaddr_ntoa(&from_ip),
                from_port, len);
// for(int i = 0; i < len; i++)
// {
//     log_info("%02x ", udp_recvbuf[i]);
// }
// log_info("\r\n");

// UdpVoiceClient_Update(from_ip, from_port, udp_recvbuf, len);
// udp_message_handle(udp_recvbuf, len);
#if 1
      // 回环验证
      sendto(udp_sock, udp_recvbuf, len, 0, (struct sockaddr *)&from,
             sizeof(from));
      log_debug("udp_sock:%x\r\nudp_recvbuf:%x\r\n", udp_sock, udp_recvbuf);
#elif 0
      // 回环验证
      Udp_SendToTarget(udp_state_clients.ip_str, udp_state_clients.port,
                       udp_recvbuf, len);

#endif

      // Audio_PushToPlayBuffer((uint8_t *)recv_buf, len);
    }
    // UdpVoiceClient_Cleanup();
    vTaskDelay(pdMS_TO_TICKS(1)); // 防止空转
  }
}

void user_network_up_or_down(uint8_t sta) {
  if (sta == 1) {
    // user_tcp_init();
    log_info("Network interface is up.\r\n");
    user_udp_init();

  } else {
    log_info("Network interface is down.\r\n");
  }
}

void udp_thread_notify_vaule(bool irq, uint32_t nofity_bit) {

  BaseType_t xHigherPriorityTaskWoken = pdFALSE;
  if (irq == true) {
    xTaskNotifyFromISR((TaskHandle_t)udpsThread_t, nofity_bit, eSetBits,
                       &xHigherPriorityTaskWoken); //
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
  } else {
    xTaskNotify((TaskHandle_t)udpsThread_t, nofity_bit, eSetBits);
  }
}

下面是lwip.c

/* USER CODE BEGIN Header */
/**
 ******************************************************************************
  * File Name          : LWIP.c
  * Description        : This file provides initialization code for LWIP
  *                      middleWare.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 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 "lwip.h"
#include "lwip/init.h"
#include "lwip/netif.h"
#if defined ( __CC_ARM )  /* MDK ARM Compiler */
#include "lwip/sio.h"
#endif /* MDK ARM Compiler */
#include "ethernetif.h"
#include <string.h>

/* USER CODE BEGIN 0 */
#include "user_udp.h"
/* USER CODE END 0 */
/* Private function prototypes -----------------------------------------------*/
static void ethernet_link_status_updated(struct netif *netif);
/* ETH Variables initialization ----------------------------------------------*/
void Error_Handler(void);

/* USER CODE BEGIN 1 */

/* USER CODE END 1 */

/* Variables Initialization */
struct netif gnetif;
ip4_addr_t ipaddr;
ip4_addr_t netmask;
ip4_addr_t gw;
uint8_t IP_ADDRESS[4];
uint8_t NETMASK_ADDRESS[4];
uint8_t GATEWAY_ADDRESS[4];
/* USER CODE BEGIN OS_THREAD_ATTR_CMSIS_RTOS_V2 */
#define INTERFACE_THREAD_STACK_SIZE ( 4*1024 )
osThreadAttr_t attributes;
/* USER CODE END OS_THREAD_ATTR_CMSIS_RTOS_V2 */

/* USER CODE BEGIN 2 */

/* USER CODE END 2 */

/**
  * LwIP initialization function
  */
void MX_LWIP_Init(void)
{
  /* IP addresses initialization */
  IP_ADDRESS[0] = 192;
  IP_ADDRESS[1] = 168;
  IP_ADDRESS[2] = 188;
  IP_ADDRESS[3] = 6;
  NETMASK_ADDRESS[0] = 255;
  NETMASK_ADDRESS[1] = 255;
  NETMASK_ADDRESS[2] = 255;
  NETMASK_ADDRESS[3] = 0;
  GATEWAY_ADDRESS[0] = 192;
  GATEWAY_ADDRESS[1] = 168;
  GATEWAY_ADDRESS[2] = 188;
  GATEWAY_ADDRESS[3] = 1;

/* USER CODE BEGIN IP_ADDRESSES */
  IP_ADDRESS[0] = 192;
  IP_ADDRESS[1] = 168;
  IP_ADDRESS[2] = 188;
  IP_ADDRESS[3] = 6;
  NETMASK_ADDRESS[0] = 255;
  NETMASK_ADDRESS[1] = 255;
  NETMASK_ADDRESS[2] = 255;
  NETMASK_ADDRESS[3] = 0;
  GATEWAY_ADDRESS[0] = 192;
  GATEWAY_ADDRESS[1] = 168;
  GATEWAY_ADDRESS[2] = 188;
  GATEWAY_ADDRESS[3] = 1;
/* USER CODE END IP_ADDRESSES */

  /* Initialize the LwIP stack with RTOS */
  tcpip_init( NULL, NULL );

  /* IP addresses initialization without DHCP (IPv4) */
  IP4_ADDR(&ipaddr, IP_ADDRESS[0], IP_ADDRESS[1], IP_ADDRESS[2], IP_ADDRESS[3]);
  IP4_ADDR(&netmask, NETMASK_ADDRESS[0], NETMASK_ADDRESS[1] , NETMASK_ADDRESS[2], NETMASK_ADDRESS[3]);
  IP4_ADDR(&gw, GATEWAY_ADDRESS[0], GATEWAY_ADDRESS[1], GATEWAY_ADDRESS[2], GATEWAY_ADDRESS[3]);

  /* add the network interface (IPv4/IPv6) with RTOS */
  netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &tcpip_input);

  /* Registers the default network interface */
  netif_set_default(&gnetif);

  /* We must always bring the network interface up connection or not... */
  netif_set_up(&gnetif);

  /* Set the link callback function, this function is called on change of link status*/
  netif_set_link_callback(&gnetif, ethernet_link_status_updated);

  /* Create the Ethernet link handler thread */
/* USER CODE BEGIN H7_OS_THREAD_NEW_CMSIS_RTOS_V2 */
  memset(&attributes, 0x0, sizeof(osThreadAttr_t));
  attributes.name = "EthLink";
  attributes.stack_size = INTERFACE_THREAD_STACK_SIZE;
  attributes.priority = osPriorityBelowNormal;
  osThreadNew(ethernet_link_thread, &gnetif, &attributes);
/* USER CODE END H7_OS_THREAD_NEW_CMSIS_RTOS_V2 */

/* USER CODE BEGIN 3 */

/* USER CODE END 3 */
}

#ifdef USE_OBSOLETE_USER_CODE_SECTION_4
/* Kept to help code migration. (See new 4_1, 4_2... sections) */
/* Avoid to use this user section which will become obsolete. */
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
#endif

/**
  * @brief  Notify the User about the network interface config status
  * @param  netif: the network interface
  * @retval None
  */
static void ethernet_link_status_updated(struct netif *netif)
{
  if (netif_is_up(netif))
  {
/* USER CODE BEGIN 5 */
	  user_network_up_or_down(1);
/* USER CODE END 5 */
  }
  else /* netif is down */
  {
/* USER CODE BEGIN 6 */
	  user_network_up_or_down(0);
/* USER CODE END 6 */
  }
}

#if defined ( __CC_ARM )  /* MDK ARM Compiler */
/**
 * Opens a serial device for communication.
 *
 * @param devnum device number
 * @return handle to serial device if successful, NULL otherwise
 */
sio_fd_t sio_open(u8_t devnum)
{
  sio_fd_t sd;

/* USER CODE BEGIN 7 */
  sd = 0; // dummy code
/* USER CODE END 7 */

  return sd;
}

/**
 * Sends a single character to the serial device.
 *
 * @param c character to send
 * @param fd serial device handle
 *
 * @note This function will block until the character can be sent.
 */
void sio_send(u8_t c, sio_fd_t fd)
{
/* USER CODE BEGIN 8 */
/* USER CODE END 8 */
}

/**
 * Reads from the serial device.
 *
 * @param fd serial device handle
 * @param data pointer to data buffer for receiving
 * @param len maximum length (in bytes) of data to receive
 * @return number of bytes actually received - may be 0 if aborted by sio_read_abort
 *
 * @note This function will block until data can be received. The blocking
 * can be cancelled by calling sio_read_abort().
 */
u32_t sio_read(sio_fd_t fd, u8_t *data, u32_t len)
{
  u32_t recved_bytes;

/* USER CODE BEGIN 9 */
  recved_bytes = 0; // dummy code
/* USER CODE END 9 */
  return recved_bytes;
}

/**
 * Tries to read from the serial device. Same as sio_read but returns
 * immediately if no data is available and never blocks.
 *
 * @param fd serial device handle
 * @param data pointer to data buffer for receiving
 * @param len maximum length (in bytes) of data to receive
 * @return number of bytes actually received
 */
u32_t sio_tryread(sio_fd_t fd, u8_t *data, u32_t len)
{
  u32_t recved_bytes;

/* USER CODE BEGIN 10 */
  recved_bytes = 0; // dummy code
/* USER CODE END 10 */
  return recved_bytes;
}
#endif /* MDK ARM Compiler */

lwip的所有协议都有一套原生的debug方式在debug.h和opt.h中开启,大家有问题可以查查,这里我就不贴了

/*
 * @file: .c
 * @brief:
 * @author: YangWentao
 * @version:
 * @date:
 * @Copyright: 1.0.0
 * @par Change Logs: 2024-10-23
 */
/*
 * @file: .c
 * @brief:
 * @author: YangWentao
 * @version:
 * @date:
 * @Copyright: 1.0.0
 * @par Change Logs: 2024-10-23
 */
#include "user_sai.h"
#include "user_uart.h"
#include "sai.h"
#include "usart.h"
#include <stdbool.h>
#include <string.h>
#include "user_log.h"
#include "user_task.h"

#define USE_OPUS_ENC 1

static uint8_t app_rec_buf[APP_REC_SIZE];
/**
 * @brief 当前从BLE接收到的语音数据长度
 *
 */
static uint32_t ble_sound_lens = 0;
/**
 * @brief 语音录音缓冲区
 *
 */
SAI_DEF sai_rx_t; 
SAI_DEF sai_tx_t;
/**语音启动播放标志位
 * 在语音接收到一定长度后即可播放语音
 */
static uint8_t user_playflag = 0;

/**
 * @brief 0: 板子与卫星、APP测试语音,双通测试
 *        1:板子与卫星语音回环测试
 *        3:板子与卫星,APP测试语音,单通。APP说话,卫星电话发起者收听
 */
static uint8_t opus_enable = 0;
//
static void sai_irq_set(bool enable);
static HAL_StatusTypeDef User_SAI_Disable(SAI_HandleTypeDef *hsai);
HAL_StatusTypeDef User_HAL_SAI_DeInit(SAI_HandleTypeDef *hsai);
HAL_StatusTypeDef User_HAL_SAI_Transmit_DMA(SAI_HandleTypeDef *hsai,
                                            uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef User_HAL_SAI_Receive_DMA(SAI_HandleTypeDef *hsai,
                                           uint8_t *pData, uint16_t Size);
// 8000次/s,0.125ms/次,4B/次,40ms = 1帧 = 320次,4B * 320 = 1280字节/帧,半满
// = 1280字节/帧,全满 = 2帧
void bsp_sai_init(void) {
  // 获取DMA半满的起始地址
  sai_tx_t.dma_buf2 = &sai_tx_t.dma_buf[WAV_SAI_TX_DMA_HALF_SIZE*2];
  sai_rx_t.dma_buf2 = &sai_rx_t.dma_buf[WAV_SAI_TX_DMA_HALF_SIZE*2];
  HAL_SAI_Transmit_DMA(&USER_SAI_TX, sai_tx_t.dma_buf,WAV_SAI_TX_DMA_ALL_SIZE);
  //__HAL_DMA_DISABLE_IT(USER_SAI_TX.hdmatx, DMA_IT_HT);
  //__HAL_DMA_DISABLE(USER_SAI_TX.hdmatx);
  __HAL_SAI_ENABLE(&USER_SAI_TX);
  HAL_SAI_Receive_DMA(&USER_SAI_RX, sai_rx_t.dma_buf,WAV_SAI_TX_DMA_ALL_SIZE);
  //__HAL_DMA_DISABLE(USER_SAI_RX.hdmarx);
  __HAL_SAI_ENABLE(&USER_SAI_RX);
}
#if 0
void u_record_ready(uint8_t flag) {
  opus_enable = flag;
  if ((USER_SAI_RX.hdmarx->Instance->CCR & DMA_CCR_EN) == true) {
    log_debug("sai alyread enable\r\n");
    return;
  }
  log_debug("ready record voice\r\n");

#if 0
  HAL_SAI_Receive_DMA(&USER_SAI_RX, sai_rx_t.dma_buf, WAV_SAI_TX_DMA_HALF_SIZE);
  __HAL_DMA_DISABLE_IT(USER_SAI_TX.hdmatx, DMA_IT_TC | DMA_IT_HT);
  HAL_SAI_Transmit_DMA(&USER_SAI_TX, app_rec_buf, APP_REC_SIZE / 2);
#else
  User_HAL_SAI_Transmit_DMA(&USER_SAI_TX, app_rec_buf, APP_REC_SIZE / 2);
  __HAL_DMA_DISABLE_IT(USER_SAI_TX.hdmatx, DMA_IT_TC | DMA_IT_HT);
  __HAL_SAI_ENABLE(&USER_SAI_TX);
  if (opus_enable != 3) {
    User_HAL_SAI_Receive_DMA(&USER_SAI_RX, sai_rx_t.dma_buf,
                             WAV_SAI_TX_DMA_HALF_SIZE);
    __HAL_SAI_ENABLE(&USER_SAI_RX);
  }
#endif
}

void u_sai_ready(void) {
  if ((USER_SAI_RX.hdmarx->Instance->CCR & DMA_CCR_EN) == true) {
    log_debug("sai alyread enable\r\n");
    return;
  }
  log_debug("ready record voice\r\n");

#if 0
  HAL_SAI_Receive_DMA(&USER_SAI_RX, sai_rx_t.dma_buf, WAV_SAI_TX_DMA_HALF_SIZE);
  __HAL_DMA_DISABLE_IT(USER_SAI_TX.hdmatx, DMA_IT_TC | DMA_IT_HT);
  HAL_SAI_Transmit_DMA(&USER_SAI_TX, app_rec_buf, APP_REC_SIZE / 2);
#endif
  sai_irq_set(true);
  // app->htdm
  __HAL_DMA_ENABLE(USER_SAI_TX.hdmatx);
  // htdm->app
  __HAL_DMA_ENABLE(USER_SAI_RX.hdmarx);
}
void user_sai_stop(void) {
  log_debug("sai stop\r\n");
  sai_irq_set(false);
  __HAL_DMA_DISABLE(USER_SAI_RX.hdmarx);
  __HAL_DMA_DISABLE(USER_SAI_TX.hdmatx);
  if ((USER_SAI_RX.hdmarx->Instance->CCR & DMA_CCR_EN) == true) {
    log_debug("sai alyread enable\r\n");
  }
  if ((USER_SAI_TX.hdmatx->Instance->CCR & DMA_CCR_EN) == true) {
    log_debug("sai alyread enable\r\n");
  }
  user_tx_buf_init();
}
#endif
void HAL_SAI_TxHalfCpltCallback(SAI_HandleTypeDef *hsai) {

  if (hsai->Instance == USER_SAI_TX.Instance) {
    sai_tx_t.half_flag = true;
    // log_debug("sHt\r\n");
  }
}
void HAL_SAI_TxCpltCallback(SAI_HandleTypeDef *hsai) {

  if (hsai->Instance == USER_SAI_TX.Instance) {

    sai_tx_t.done_flag = true;
    // log_debug("sCt\r\n");
  }
}
void HAL_SAI_RxHalfCpltCallback(SAI_HandleTypeDef *hsai) {
  if (hsai->Instance == USER_SAI_RX.Instance) {
    sai_rx_t.half_flag = true;
    // log_debug("rHt\r\n");
    debug_thread_notify_vaule(true, I2S_TASK_NOTIFY_RX_HALF);
  }
}
void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef *hsai) {
  if (hsai->Instance == USER_SAI_RX.Instance) {
    sai_rx_t.done_flag = true;
    // log_debug("rCt\r\n");
    debug_thread_notify_vaule(true, I2S_TASK_NOTIFY_RX_FULL);
  }
}

void HAL_SAI_ErrorCallback(SAI_HandleTypeDef *hsai) {
  if (hsai->Instance == USER_SAI_RX.Instance) {

    log_debug("saiB error:%d,,,,,,,,,,,,", hsai->ErrorCode);

    User_HAL_SAI_Receive_DMA(&USER_SAI_RX, sai_rx_t.dma_buf,
                             WAV_SAI_TX_DMA_HALF_SIZE);
    __HAL_SAI_ENABLE(&USER_SAI_RX);

  } else {
    log_debug("saiA error:%d,,,,,,,,,,,,,,,", hsai->ErrorCode);

    User_HAL_SAI_Transmit_DMA(&USER_SAI_TX, app_rec_buf, APP_REC_SIZE / 2);
    __HAL_DMA_DISABLE_IT(USER_SAI_TX.hdmatx, DMA_IT_HT | DMA_IT_TC);
    __HAL_DMA_DISABLE_IT(USER_SAI_TX.hdmatx, DMA_IT_TC);
    __HAL_SAI_ENABLE(&USER_SAI_TX);
  }
}

void user_tx_buf_init(void) {
  memset(app_rec_buf, 0, APP_REC_SIZE);
  ble_sound_lens = 0;
  memset(sai_rx_t.dma_buf, 0, WAV_SAI_TX_DMA_ALL_SIZE);
}
void bsp_cpoy_sound(uint8_t *buf, uint16_t lens) {
  uint32_t ramin_buf_lens = APP_REC_SIZE - ble_sound_lens; // 当前缓冲区剩余空间
  uint32_t re_write_lens = lens - ramin_buf_lens; // 重头写入的数据量
  // 如果剩余缓冲区的地址能够写入当前的全部数据
  // log_debug("%d\r\n", ble_sound_lens);
  if (ramin_buf_lens >= lens) {
    memcpy(&app_rec_buf[ble_sound_lens], buf, lens);
    ble_sound_lens += lens;
    if (ble_sound_lens == APP_REC_SIZE)
      ble_sound_lens = 0;
  } else {
    memcpy(&app_rec_buf[ble_sound_lens], buf, ramin_buf_lens);
    memcpy(app_rec_buf, &buf[ramin_buf_lens], re_write_lens);
    ble_sound_lens = re_write_lens;
  }
  // if (user_playflag == false && ble_sound_lens >= (512 * 4)) {
  if (user_playflag == false) {
    user_playflag = true;
    // TODO:
    log_debug("....................\r\n");
    //__HAL_DMA_ENABLE(USER_SAI_TX.hdmatx);
  }
}
#if 0
void user_sai_process(void) {
  extern uint8_t uart5_debug_flag;
  if (sai_rx_t.half_flag == true) {
    //
    sai_rx_t.half_flag = false;
    if (opus_enable == 1) {
      log_debug("rec buf 1\r\n");
      bsp_cpoy_sound(user_dec_msg_t.Data, user_dec_msg_t.Len);
    } else {
      Handle_Enc_data(sai_rx_t.dma_buf, &user_enc_msg_t);
      HAL_UART_Transmit_DMA(&huart5, user_enc_msg_t.Data, user_enc_msg_t.Len);
      if (uart5_debug_flag == 1) {
        log_info("mcu->ble:");
        for (uint8_t i = 0; i < user_enc_msg_t.Len; i++) {
          log_info("%02x-", user_enc_msg_t.Data[i]);
        }
        log_info("\r\n");
      }
    }
  }
  if (sai_rx_t.done_flag == true) {
    sai_rx_t.done_flag = false;
    //
    if (opus_enable == 1) {
      log_debug("rec buf 2\r\n");
      Handle_Enc_data(sai_rx_t.dma_buf2, &user_enc_msg_t);
      Handle_Dec_data(user_enc_msg_t.Data, user_enc_msg_t.Len, &user_dec_msg_t);
      bsp_cpoy_sound(user_dec_msg_t.Data, user_dec_msg_t.Len);
    } else {

      Handle_Enc_data(sai_rx_t.dma_buf2, &user_enc_msg_t);
      HAL_UART_Transmit_DMA(&huart5, user_enc_msg_t.Data, user_enc_msg_t.Len);
      if (uart5_debug_flag == 1) {
        log_info("mcu->ble:");
        for (uint8_t i = 0; i < user_enc_msg_t.Len; i++) {
          log_info("%02x-", user_enc_msg_t.Data[i]);
        }
        log_info("\r\n");
      }
    }
  }

  if (sai_tx_t.half_flag == true) {
    sai_tx_t.half_flag = false;
    memset(user_dec_msg_t.Data, 0, 640);
  } else if (sai_tx_t.done_flag == true) {
    sai_tx_t.done_flag = false;
    memset(&user_dec_msg_t.Data[640], 0, 640);
  }
}

void user_dec_uart_data(uint8_t *data, uint16_t lens) {
#if 1
  Handle_Dec_data(data, lens, &user_dec_msg_t);
  bsp_cpoy_sound(user_dec_msg_t.Data, user_dec_msg_t.Len);
#else
  HAL_UART_Transmit_DMA(&huart5, data, lens);
  __HAL_DMA_DISABLE_IT(huart5.hdmatx, DMA_IT_HT);
#endif
}
#endif
// SAi DMA API
//
//
//
//
//
#define User_SAI_DEFAULT_TIMEOUT 4U /* 4ms */
#define User_SAI_LONG_TIMEOUT 1000U

typedef enum { User_SAI_MODE_DMA, User_SAI_MODE_IT } User_SAI_ModeTypedef;

HAL_StatusTypeDef User_HAL_SAI_DeInit(SAI_HandleTypeDef *hsai) {
  /* Check the SAI handle allocation */
  if (hsai == NULL) {
    return HAL_ERROR;
  }

  hsai->State = HAL_SAI_STATE_BUSY;

  /* Disabled All interrupt and clear all the flag */
  hsai->Instance->IMR = 0;
  hsai->Instance->CLRFR = 0xFFFFFFFFU;

  /* Disable the SAI */
  if (User_SAI_Disable(hsai) != HAL_OK) {
    /* Reset SAI state to ready */
    hsai->State = HAL_SAI_STATE_READY;

    /* Release Lock */
    __HAL_UNLOCK(hsai);

    return HAL_ERROR;
  }

  /* Flush the fifo */
  SET_BIT(hsai->Instance->CR2, SAI_xCR2_FFLUSH);

  /* DeInit the low level hardware: GPIO, CLOCK, NVIC and DMA */
  HAL_SAI_MspDeInit(hsai);
  /* Initialize the error code */
  hsai->ErrorCode = HAL_SAI_ERROR_NONE;

  /* Initialize the SAI state */
  hsai->State = HAL_SAI_STATE_RESET;

  /* Release Lock */
  __HAL_UNLOCK(hsai);

  return HAL_OK;
}

static uint32_t User_SAI_InterruptFlag(const SAI_HandleTypeDef *hsai,
                                       uint32_t mode) {
  uint32_t tmpIT = SAI_IT_OVRUDR;

  if (mode == User_SAI_MODE_IT) {
    tmpIT |= SAI_IT_FREQ;
  }

  if ((hsai->Init.Protocol == SAI_AC97_PROTOCOL) &&
      ((hsai->Init.AudioMode == SAI_MODESLAVE_RX) ||
       (hsai->Init.AudioMode == SAI_MODEMASTER_RX))) {
    tmpIT |= SAI_IT_CNRDY;
  }

  if ((hsai->Init.AudioMode == SAI_MODESLAVE_RX) ||
      (hsai->Init.AudioMode == SAI_MODESLAVE_TX)) {
    tmpIT |= SAI_IT_AFSDET | SAI_IT_LFSDET;
  } else {
    /* hsai has been configured in master mode */
    tmpIT |= SAI_IT_WCKCFG;
  }
  return tmpIT;
}

static HAL_StatusTypeDef User_SAI_Disable(SAI_HandleTypeDef *hsai) {
  uint32_t count = User_SAI_DEFAULT_TIMEOUT * (SystemCoreClock / 7 / 1000);
  HAL_StatusTypeDef status = HAL_OK;

  /* Disable the SAI instance */
  __HAL_SAI_DISABLE(hsai);

  do {
    /* Check for the Timeout */
    if (count-- == 0) {
      /* Update error code */
      hsai->ErrorCode |= HAL_SAI_ERROR_TIMEOUT;
      status = HAL_TIMEOUT;
      break;
    }
  } while ((hsai->Instance->CR1 & SAI_xCR1_SAIEN) != RESET);

  return status;
}
static void User_SAI_DMATxHalfCplt(DMA_HandleTypeDef *hdma) {
  SAI_HandleTypeDef *hsai =
      (SAI_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent;

#if (USE_HAL_SAI_REGISTER_CALLBACKS == 1)
  hsai->TxHalfCpltCallback(hsai);
#else
  HAL_SAI_TxHalfCpltCallback(hsai);
#endif /* USE_HAL_SAI_REGISTER_CALLBACKS */
}
static void User_SAI_DMATxCplt(DMA_HandleTypeDef *hdma) {
  SAI_HandleTypeDef *hsai =
      (SAI_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent;

  if (hdma->Init.Mode != DMA_CIRCULAR) {
    hsai->XferCount = 0;

    /* Disable SAI Tx DMA Request */
    hsai->Instance->CR1 &= (uint32_t)(~SAI_xCR1_DMAEN);

    /* Stop the interrupts error handling */
    __HAL_SAI_DISABLE_IT(hsai, User_SAI_InterruptFlag(hsai, User_SAI_MODE_DMA));

    hsai->State = HAL_SAI_STATE_READY;
  }
  HAL_SAI_TxCpltCallback(hsai);
}
static void User_SAI_DMAError(DMA_HandleTypeDef *hdma) {
  SAI_HandleTypeDef *hsai =
      (SAI_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent;

  /* Set SAI error code */
  hsai->ErrorCode |= HAL_SAI_ERROR_DMA;

  if ((hsai->hdmatx->ErrorCode == HAL_DMA_ERROR_TE) ||
      (hsai->hdmarx->ErrorCode == HAL_DMA_ERROR_TE)) {
    /* Disable the SAI DMA request */
    hsai->Instance->CR1 &= ~SAI_xCR1_DMAEN;

    /* Disable SAI peripheral */
    User_SAI_Disable(hsai);

    /* Set the SAI state ready to be able to start again the process */
    hsai->State = HAL_SAI_STATE_READY;

    /* Initialize XferCount */
    hsai->XferCount = 0U;
  }
  /* SAI error Callback */
  HAL_SAI_ErrorCallback(hsai);
}
/**
 * @brief 不开启sai外设,只使能Dma传输和中断
 *
 * @param hsai
 * @param pData
 * @param Size
 * @return HAL_StatusTypeDef
 */
HAL_StatusTypeDef User_HAL_SAI_Transmit_DMA(SAI_HandleTypeDef *hsai,
                                            uint8_t *pData, uint16_t Size) {
  uint32_t tickstart = HAL_GetTick();

  if ((pData == NULL) || (Size == 0)) {
    return HAL_ERROR;
  }

  if (hsai->State == HAL_SAI_STATE_READY) {
    /* Process Locked */
    __HAL_LOCK(hsai);

    hsai->pBuffPtr = pData;
    hsai->XferSize = Size;
    hsai->XferCount = Size;
    hsai->ErrorCode = HAL_SAI_ERROR_NONE;
    hsai->State = HAL_SAI_STATE_BUSY_TX;

    /* Set the SAI Tx DMA Half transfer complete callback */
    hsai->hdmatx->XferHalfCpltCallback = User_SAI_DMATxHalfCplt;

    /* Set the SAI TxDMA transfer complete callback */
    hsai->hdmatx->XferCpltCallback = User_SAI_DMATxCplt;

    /* Set the DMA error callback */
    hsai->hdmatx->XferErrorCallback = User_SAI_DMAError;

    /* Set the DMA Tx abort callback */
    hsai->hdmatx->XferAbortCallback = NULL;

    /* Enable the Tx DMA Stream */
    if (HAL_DMA_Start_IT(hsai->hdmatx, (uint32_t)hsai->pBuffPtr,
                         (uint32_t)&hsai->Instance->DR,
                         hsai->XferSize) != HAL_OK) {
      __HAL_UNLOCK(hsai);
      return HAL_ERROR;
    }

    /* Enable the interrupts for error handling */
    __HAL_SAI_ENABLE_IT(hsai, User_SAI_InterruptFlag(hsai, User_SAI_MODE_DMA));

    /* Enable SAI Tx DMA Request */
    hsai->Instance->CR1 |= SAI_xCR1_DMAEN;

    /* Wait until FIFO is not empty */
    while ((hsai->Instance->SR & SAI_xSR_FLVL) == SAI_FIFOSTATUS_EMPTY) {
      /* Check for the Timeout */
      if ((HAL_GetTick() - tickstart) > User_SAI_LONG_TIMEOUT) {
        /* Update error code */
        hsai->ErrorCode |= HAL_SAI_ERROR_TIMEOUT;

        /* Process Unlocked */
        __HAL_UNLOCK(hsai);

        return HAL_TIMEOUT;
      }
    }
    /* Process Unlocked */
    __HAL_UNLOCK(hsai);

    return HAL_OK;
  } else {
    return HAL_BUSY;
  }
}
static void User_SAI_DMARxHalfCplt(DMA_HandleTypeDef *hdma) {
  SAI_HandleTypeDef *hsai =
      (SAI_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent;

  HAL_SAI_RxHalfCpltCallback(hsai);
}

static void User_SAI_DMARxCplt(DMA_HandleTypeDef *hdma) {
  SAI_HandleTypeDef *hsai =
      (SAI_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent;

  if (hdma->Init.Mode != DMA_CIRCULAR) {
    /* Disable Rx DMA Request */
    hsai->Instance->CR1 &= (uint32_t)(~SAI_xCR1_DMAEN);
    hsai->XferCount = 0;

    /* Stop the interrupts error handling */
    __HAL_SAI_DISABLE_IT(hsai, User_SAI_InterruptFlag(hsai, User_SAI_MODE_DMA));

    hsai->State = HAL_SAI_STATE_READY;
  }
  HAL_SAI_RxCpltCallback(hsai);
}
/**
 * @brief 不开启sai外设,只使能Dma传输和中断
 *
 * @param hsai
 * @param pData
 * @param Size
 * @return HAL_StatusTypeDef
 */
HAL_StatusTypeDef User_HAL_SAI_Receive_DMA(SAI_HandleTypeDef *hsai,
                                           uint8_t *pData, uint16_t Size) {

  if ((pData == NULL) || (Size == 0)) {
    return HAL_ERROR;
  }

  if (hsai->State == HAL_SAI_STATE_READY) {
    /* Process Locked */
    __HAL_LOCK(hsai);

    hsai->pBuffPtr = pData;
    hsai->XferSize = Size;
    hsai->XferCount = Size;
    hsai->ErrorCode = HAL_SAI_ERROR_NONE;
    hsai->State = HAL_SAI_STATE_BUSY_RX;

    /* Set the SAI Rx DMA Half transfer complete callback */
    hsai->hdmarx->XferHalfCpltCallback = User_SAI_DMARxHalfCplt;

    /* Set the SAI Rx DMA transfer complete callback */
    hsai->hdmarx->XferCpltCallback = User_SAI_DMARxCplt;

    /* Set the DMA error callback */
    hsai->hdmarx->XferErrorCallback = User_SAI_DMAError;

    /* Set the DMA Rx abort callback */
    hsai->hdmarx->XferAbortCallback = NULL;

    /* Enable the Rx DMA Stream */
    if (HAL_DMA_Start_IT(hsai->hdmarx, (uint32_t)&hsai->Instance->DR,
                         (uint32_t)hsai->pBuffPtr, hsai->XferSize) != HAL_OK) {
      __HAL_UNLOCK(hsai);
      return HAL_ERROR;
    }

    /* Enable the interrupts for error handling */
    __HAL_SAI_ENABLE_IT(hsai, User_SAI_InterruptFlag(hsai, User_SAI_MODE_DMA));

    /* Enable SAI Rx DMA Request */
    hsai->Instance->CR1 |= SAI_xCR1_DMAEN;
    //对比hal库,删减了以下代码
    /* Check if the SAI is already enabled */
    // if ((hsai->Instance->CR1 & SAI_xCR1_SAIEN) == RESET) {
    //   /* Enable SAI peripheral */
    //   __HAL_SAI_ENABLE(hsai);
    // }

    /* Process Unlocked */
    __HAL_UNLOCK(hsai);

    return HAL_OK;
  } else {
    return HAL_BUSY;
  }
}
#if 0
static void sai_irq_set(bool enable) {

  if (enable == true) {
    __HAL_DMA_CLEAR_FLAG(
        USER_SAI_RX.hdmarx,
        (DMA_ISR_GIF1 << (USER_SAI_RX.hdmarx->ChannelIndex & 0x1CU)));
    __HAL_SAI_CLEAR_FLAG(&USER_SAI_TX, SAI_FLAG_OVRUDR | SAI_FLAG_MUTEDET |
                                           SAI_FLAG_WCKCFG | SAI_FLAG_FREQ |
                                           SAI_FLAG_CNRDY | SAI_FLAG_AFSDET |
                                           SAI_FLAG_LFSDET);
    __HAL_SAI_CLEAR_FLAG(&USER_SAI_RX, SAI_FLAG_OVRUDR | SAI_FLAG_MUTEDET |
                                           SAI_FLAG_WCKCFG | SAI_FLAG_FREQ |
                                           SAI_FLAG_CNRDY | SAI_FLAG_AFSDET |
                                           SAI_FLAG_LFSDET);
    __HAL_SAI_ENABLE_IT(
        &USER_SAI_TX, User_SAI_InterruptFlag(&USER_SAI_TX, User_SAI_MODE_DMA));
    __HAL_SAI_ENABLE_IT(
        &USER_SAI_RX, User_SAI_InterruptFlag(&USER_SAI_RX, User_SAI_MODE_DMA));
  } else { //
    __HAL_SAI_DISABLE_IT(
        &USER_SAI_TX, User_SAI_InterruptFlag(&USER_SAI_TX, User_SAI_MODE_DMA));
    __HAL_SAI_DISABLE_IT(
        &USER_SAI_RX, User_SAI_InterruptFlag(&USER_SAI_RX, User_SAI_MODE_DMA));
    __HAL_DMA_CLEAR_FLAG(
        USER_SAI_RX.hdmarx,
        (DMA_ISR_GIF1 << (USER_SAI_RX.hdmarx->ChannelIndex & 0x1CU)));
    __HAL_SAI_CLEAR_FLAG(&USER_SAI_TX, SAI_FLAG_OVRUDR | SAI_FLAG_MUTEDET |
                                           SAI_FLAG_WCKCFG | SAI_FLAG_FREQ |
                                           SAI_FLAG_CNRDY | SAI_FLAG_AFSDET |
                                           SAI_FLAG_LFSDET);
    __HAL_SAI_CLEAR_FLAG(&USER_SAI_RX, SAI_FLAG_OVRUDR | SAI_FLAG_MUTEDET |
                                           SAI_FLAG_WCKCFG | SAI_FLAG_FREQ |
                                           SAI_FLAG_CNRDY | SAI_FLAG_AFSDET |
                                           SAI_FLAG_LFSDET);
  }
}
#endif

void user_get_i2s_buf(SAI_DEF *i2s_tx_buffers, SAI_DEF *i2s_rx_buffers) {
  i2s_tx_buffers = &sai_tx_t;
  i2s_rx_buffers = &sai_rx_t;
}
/*
 * @file: .c
 * @brief:
 * @author: YangWentao
 * @version:
 * @date:
 * @Copyright: 1.0.0
 * @par Change Logs: 2024-10-23
 */
#ifndef INC_BSP_SAI_H_
#define INC_BSP_SAI_H_
#include <stdint.h>

#define WAV_SAI_TX_DMA_HALF_SIZE 640
#define WAV_SAI_TX_DMA_ALL_SIZE WAV_SAI_TX_DMA_HALF_SIZE * 2
#define APP_REC_SIZE WAV_SAI_TX_DMA_ALL_SIZE
#define USER_SAI_TX hsai_BlockA1
#define USER_SAI_RX hsai_BlockB1
typedef struct {
  uint8_t dma_buf[WAV_SAI_TX_DMA_ALL_SIZE * 2]; // 语音发送缓冲区2560字节
  uint8_t *dma_buf2;
  uint8_t half_flag;     // 语音DMA发送一半
  uint8_t done_flag;     // 语音DMA发送完成
  uint8_t all_done_flag; // 语音全部发送完成
} SAI_DEF;
void u_sai_ready(void);
/**
 * @brief 语音缓冲区初始化
 *
 */
void bsp_sai_init(void);
/**
 * @brief 开启语音发送和接收DMA链路
 *
 * @param flag
 *  flag=3时仅开启发送,方便测试APP单项语音
 */
void u_record_ready(uint8_t flag);
/**
 * @brief 音频数据处理
 *
 */
void user_sai_process(void);
/**
 * @brief 停止语音dma
 *  已废弃,从模式下无法关闭
 *
 */
void user_sai_stop(void);

/**
 * @brief 将接收到的语音拷贝纸缓冲区进行播放
 *
 * @param buf
 * @param lens
 */
void bsp_cpoy_sound(uint8_t *buf, uint16_t lens);

/**
 * @brief 语音数据流关闭后,清空缓冲区
 *  否则下次语音通话建立会影响音质
 *
 */
void user_tx_buf_init(void);

/**
 * @brief  将蓝牙发送的语音数据进行解码,
 *     解码后送到SAI缓冲区,发送至天通进行播放
 *
 * @param data
 * @param lens
 */
void user_dec_uart_data(uint8_t *data, uint16_t lens);
/**
 * @brief 获取语音传输缓冲区
 *
 * @param i2s_tx_buffers
 * @param i2s_rx_buffers
 */
void user_get_i2s_buf(SAI_DEF *i2s_tx_buffers, SAI_DEF *i2s_rx_buffers);

#endif

上面的是sai的配置

以上就是全部的总结了,也可以参考【开发】2.1、H7/H743/H750 - lwIP移植(裸机/操作系统)这篇文档,但是这篇文章mpu的配置可能不能满足后续工程的开发,我这个mpu也是研究4天配出来的,目前ping通,串口也可以正常使用后续有问题我在往上加,目前先这样用吧

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐