> 技术文档 > STM32 HAL库 以太网 LWIP_stm32 以太网

STM32 HAL库 以太网 LWIP_stm32 以太网

野火开发板STM32H743IIT6

一、以太网硬件接口

                                                                                                   

二、STM32cubeMX配置

根据野火例程进行配置

1、配置Cache

STM32H743+CubeMX-梳理MPU的设置_background region privileged accesses only-CSDN博客

这篇文章帮助理解Cache

野火例程配置MPU的代码

 /* 对ETH使用的内存开启保护*/ MPU_Config(); /* Enable I-Cache */ SCB_EnableICache(); /* Enable D-Cache */ SCB_EnableDCache(); //将Cache设置write-through方式 SCB->CACR|=1<<2;
/** * @brief 配置MPU外设 * @param None * @retval None */static void MPU_Config(void){ MPU_Region_InitTypeDef MPU_InitStruct; /* Disable the MPU */ HAL_MPU_Disable(); /* Configure the MPU attributes as Device not cacheable for ETH DMA descriptors */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x30040000; MPU_InitStruct.Size = MPU_REGION_SIZE_256B; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER0; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /* Configure the MPU attributes as Cacheable write through for LwIP RAM heap which contains the Tx buffers */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x30044000; MPU_InitStruct.Size = MPU_REGION_SIZE_16KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER1; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /* Enable the MPU */ HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);}

配置 ETH DMA 描述符区域(Region 0)

基地址和大小MPU_InitStruct.BaseAddress = 0x30040000; 和 MPU_InitStruct.Size = MPU_REGION_SIZE_256B; 确定了该区域在内存中的起始位置和大小,因为 ETH DMA 描述符存放在这个地址起始的 256 字节空间内,所以要这样设置。

访问权限MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; 赋予该区域全访问权限,是因为以太网驱动程序需要对 DMA 描述符进行读写操作,以设置数据传输的相关参数,如源地址、目的地址、数据长度等。

缓存属性MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; 将该区域设置为不可缓存,这是因为 DMA 控制器直接访问物理内存,若启用缓存,可能会出现 CPU 缓存中的数据与实际内存中的数据不一致的情况。例如,DMA 已经更新了内存中的数据,但 CPU 缓存还未更新,后续 CPU 读取的就是旧数据。而MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; 设置为可缓冲,能让写操作先进入写缓冲区,再批量写入内存,提高数据传输效率。

共享属性:MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; 设置为不可共享,因为在大多数单 CPU 的 STM32 应用场景中,ETH DMA 描述符区域不需要被多个主设备共享,这样可以简化系统设计,减少不必要的复杂度。

指令访问:MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; 允许从该区域执行指令,虽然 DMA 描述符区域主要存放数据,但这样设置可以增加灵活性,避免因误操作导致的指令执行异常。

配置 LwIP RAM 堆区域(Region 1)

基地址与大小MPU_InitStruct.BaseAddress = 0x30044000; 和 MPU_InitStruct.Size = MPU_REGION_SIZE_16KB; 确定了 LwIP RAM 堆在内存中的位置和大小,LwIP 协议栈的发送缓冲区存放在这个区域,需要足够的空间来存储网络数据。

访问权限MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; 赋予全访问权限,是因为 LwIP 协议栈在运行过程中需要频繁地读写发送缓冲区,如将应用层的数据封装成网络数据包写入缓冲区,以及从缓冲区读取数据进行发送等操作。

缓存属性MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE; 设置为可缓存,并且是写通(Write-Through)模式(虽然代码中没有直接体现写通模式的设置,但在默认配置下,结合芯片特性和常见需求可以推断),写通模式下数据会同时写入缓存和主内存,这样可以加速 CPU 对发送缓冲区的访问,提高网络数据处理的速度。同时,MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; 设置为不可缓冲,是为了保证数据能够及时写入内存,避免因缓冲导致的数据延迟,确保网络数据包能够及时发送。

共享属性MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; 设置为不可共享,原因和 ETH DMA 描述符区域类似,在单 CPU 场景下不需要被多个主设备共享。

指令访问MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; 允许从该区域执行指令,同样是为了增加灵活性,防止不必要的指令执行异常。

2、时钟配置

/** * @brief System Clock 配置 * system Clock 配置如下: * System Clock source = PLL (HSE)* SYSCLK(Hz)  = 400000000 (CPU Clock)* HCLK(Hz) = 200000000 (AXI and AHBs Clock)* AHB Prescaler = 2* D1 APB3 Prescaler = 2 (APB3 Clock 100MHz)* D2 APB1 Prescaler = 2 (APB1 Clock 100MHz)* D2 APB2 Prescaler = 2 (APB2 Clock 100MHz)* D3 APB4 Prescaler = 2 (APB4 Clock 100MHz)* HSE Frequency(Hz) = 25000000* PLL_M = 5* PLL_N = 160* PLL_P = 2* PLL_Q = 4* PLL_R = 2* VDD(V)  = 3.3* Flash Latency(WS) = 4 * @param None * @retval None */static void SystemClock_Config(void){ RCC_ClkInitTypeDef RCC_ClkInitStruct; RCC_OscInitTypeDef RCC_OscInitStruct; HAL_StatusTypeDef ret = HAL_OK; /*使能供电配置更新 */ MODIFY_REG(PWR->CR3, PWR_CR3_SCUEN, 0); /* 当器件的时钟频率低于最大系统频率时,电压调节可以优化功耗, 关于系统频率的电压调节值的更新可以参考产品数据手册。 */ __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {} /* 启用HSE振荡器并使用HSE作为源激活PLL */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSIState = RCC_HSI_OFF; RCC_OscInitStruct.CSIState = RCC_CSI_OFF; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLM = 5; RCC_OscInitStruct.PLL.PLLN = 160; RCC_OscInitStruct.PLL.PLLP = 2; RCC_OscInitStruct.PLL.PLLR = 2; RCC_OscInitStruct.PLL.PLLQ = 4; RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE; RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_2; ret = HAL_RCC_OscConfig(&RCC_OscInitStruct); if(ret != HAL_OK) { while(1) { ; } } /* 选择PLL作为系统时钟源并配置总线时钟分频器 */ RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | \\ RCC_CLOCKTYPE_HCLK | \\ RCC_CLOCKTYPE_D1PCLK1 | \\ RCC_CLOCKTYPE_PCLK1 | \\  RCC_CLOCKTYPE_PCLK2 | \\ RCC_CLOCKTYPE_D3PCLK1); 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; ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); if(ret != HAL_OK) { while(1) { ; } }}

Scale 0:更高电压,支持更高系统频率(如芯片最大频率),功耗略高。

Scale 1:电压略低,功耗更低,适合对性能要求不极端的场景。

野火例程是Scale 1

3、ETH配置

//以太网描述符和缓冲区__attribute__((at(0x30040000))) ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT]; //以太网Rx DMA描述符__attribute__((at(0x30040060))) ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT]; //以太网Tx DMA描述符__attribute__((at(0x30040200))) uint8_t Rx_Buff[ETH_RX_DESC_CNT][ETH_RX_BUFFER_SIZE]; //以太网接收缓冲区

#define ETH_RX_BUFFER_SIZE  (1536UL)
#ifndef ETH_TX_DESC_CNT #define ETH_TX_DESC_CNT 4 #endif #ifndef ETH_RX_DESC_CNT #define ETH_RX_DESC_CNT 4 #endif
//MAC地址#define MAC_ADDR0 0x02U#define MAC_ADDR1 0x00U#define MAC_ADDR2 0x00U#define MAC_ADDR3 0x00U#define MAC_ADDR4 0x00U#define MAC_ADDR5 0x00U#define ETH_RX_BUF_SIZE ETH_MAX_PACKET_SIZE //接受数据的长度#define ETH_TX_BUF_SIZE ETH_MAX_PACKET_SIZE //发送数据的长度#define ETH_RXBUFNB  ((uint32_t)4) //要接受数据的个数#define ETH_TXBUFNB  ((uint32_t)4) //要发送数据的个数

野火MAC地址使用的是02:00:00:00:00:00

/** * @brief 初始化ETH外设时钟,引脚. * @param heth: ETH handle * @retval None */ void HAL_ETH_MspInit(ETH_HandleTypeDef *heth){ ETH_GPIO_Config(); HAL_NVIC_SetPriority(ETH_IRQn, 6, 0); HAL_NVIC_EnableIRQ(ETH_IRQn); /* 使能以太网时钟 */ __HAL_RCC_ETH1MAC_CLK_ENABLE(); __HAL_RCC_ETH1TX_CLK_ENABLE(); __HAL_RCC_ETH1RX_CLK_ENABLE(); } 

打开中断

/** * @brief 初始化ETH引脚. * @param 无 * @retval 无 */ static void ETH_GPIO_Config(void){ GPIO_InitTypeDef GPIO_InitStructure; /* 使能端口时钟 */ ETH_MDIO_GPIO_CLK_ENABLE(); ETH_MDC_GPIO_CLK_ENABLE(); ETH_RMII_REF_CLK_GPIO_CLK_ENABLE(); ETH_RMII_CRS_DV_GPIO_CLK_ENABLE(); ETH_RMII_RXD0_GPIO_CLK_ENABLE(); ETH_RMII_RXD1_GPIO_CLK_ENABLE(); ETH_RMII_TX_EN_GPIO_CLK_ENABLE(); ETH_RMII_TXD0_GPIO_CLK_ENABLE(); ETH_RMII_TXD1_GPIO_CLK_ENABLE(); /* 配置ETH_MDIO引脚 */ GPIO_InitStructure.Pin = ETH_MDIO_PIN; GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStructure.Mode = GPIO_MODE_AF_PP; GPIO_InitStructure.Pull = GPIO_NOPULL; GPIO_InitStructure.Alternate = ETH_MDIO_AF; HAL_GPIO_Init(ETH_MDIO_PORT, &GPIO_InitStructure); /* 配置ETH_MDC引脚 */ GPIO_InitStructure.Pin = ETH_MDC_PIN; GPIO_InitStructure.Alternate = ETH_MDC_AF; HAL_GPIO_Init(ETH_MDC_PORT, &GPIO_InitStructure); /* 配置ETH_RMII_REF_CLK引脚 */ GPIO_InitStructure.Pin = ETH_RMII_REF_CLK_PIN; GPIO_InitStructure.Alternate = ETH_RMII_REF_CLK_AF; HAL_GPIO_Init(ETH_RMII_REF_CLK_PORT, &GPIO_InitStructure); /* 配置ETH_RMII_CRS_DV引脚 */ GPIO_InitStructure.Pin = ETH_RMII_CRS_DV_PIN; GPIO_InitStructure.Alternate = ETH_RMII_CRS_DV_AF; HAL_GPIO_Init(ETH_RMII_CRS_DV_PORT, &GPIO_InitStructure); /* 配置ETH_RMII_RXD0引脚 */ GPIO_InitStructure.Pin = ETH_RMII_RXD0_PIN; GPIO_InitStructure.Alternate = ETH_RMII_RXD0_AF; HAL_GPIO_Init(ETH_RMII_RXD0_PORT, &GPIO_InitStructure); /* 配置ETH_RMII_RXD1引脚 */ GPIO_InitStructure.Pin = ETH_RMII_RXD1_PIN; GPIO_InitStructure.Alternate = ETH_RMII_RXD1_AF; HAL_GPIO_Init(ETH_RMII_RXD1_PORT, &GPIO_InitStructure); /* 配置ETH_RMII_TX_EN引脚 */ GPIO_InitStructure.Pin = ETH_RMII_TX_EN_PIN; GPIO_InitStructure.Alternate = ETH_RMII_TX_EN_AF; HAL_GPIO_Init(ETH_RMII_TX_EN_PORT, &GPIO_InitStructure); /* 配置ETH_RMII_TXD0引脚 */ GPIO_InitStructure.Pin = ETH_RMII_TXD0_PIN; GPIO_InitStructure.Alternate = ETH_RMII_TXD0_AF; HAL_GPIO_Init(ETH_RMII_TXD0_PORT, &GPIO_InitStructure); /* 配置ETH_RMII_TXD1引脚 */ GPIO_InitStructure.Pin = ETH_RMII_TXD1_PIN; GPIO_InitStructure.Alternate = ETH_RMII_TXD1_AF; HAL_GPIO_Init(ETH_RMII_TXD1_PORT, &GPIO_InitStructure); } 

4、LwIP配置

/** * @brief 通知用户有关网络接口配置状态 * @param netif: 网络接口 * @retval None */void User_notification(struct netif *netif) { if (netif_is_up(netif)) { printf(\"Static IP: %d.%d.%d.%d\\n\",IP_ADDRESS[0], IP_ADDRESS[1], IP_ADDRESS[2], IP_ADDRESS[3]); printf(\"NETMASK : %d.%d.%d.%d\\n\",NETMASK_ADDRESS[0], NETMASK_ADDRESS[1] , NETMASK_ADDRESS[2], NETMASK_ADDRESS[3]); printf(\"Gateway : %d.%d.%d.%d\\n\",GATEWAY_ADDRESS[0], GATEWAY_ADDRESS[1], GATEWAY_ADDRESS[2], GATEWAY_ADDRESS[3]); LED_G(ON); } else { printf (\"The network cable is not connected \\n\"); LED_R(ON); }}

5、配置LED

#define ON GPIO_PIN_RESET#define OFF GPIO_PIN_SET#define LED_R(a)HAL_GPIO_WritePin(LED_R_GPIO_Port,LED_R_Pin,a)#define LED_G(a)HAL_GPIO_WritePin(LED_G_GPIO_Port,LED_G_Pin,a)#define LED_B(a)HAL_GPIO_WritePin(LED_B_GPIO_Port,LED_B_Pin,a)

6、串口配置

重定向

#include 
///重定向c库函数printf到串口DEBUG_USART,重定向后可使用printf函数int fputc(int ch, FILE *f){/* 发送一个字节数据到串口DEBUG_USART */HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);return (ch);} ///重定向c库函数scanf到串口DEBUG_USART,重写向后可使用scanf、getchar等函数int fgetc(FILE *f){int ch;HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, 1000);return (ch);}

工程

【免费】野火STM32H743IIT6TCP服务器HAL库资源-CSDN下载

STM32H743UDPSTM32cubeMX资源-CSDN下载

节日礼物大全