STM32H735-DK带有 Eth接口,可以用于以太网通讯。STM32 Hostpot提供STM32H7系列的lwip移植用于参考。
https://github.com/stm32-hotspot/STM32H7-LwIP-Examples
官方提供的示例程序是基于CubeMX 6.5.0 和 H7 V1.10.0 MCU package,高版本的CubeMX可以使用兼容包生成老版本的MCU对应的工程,但是无法创建老版本的MCU Package的工程,需要额外安装老版本的CubeMX程序用于生成代码。而且按照官方例程的步骤,在最新的CubeMX上进行相同的设置,无法正确获取DHCP地址,和lwip的版本相关。

STM32H735-DK带有RJ45以太网接口,可以用于网络通讯。CubeMX支持lwip协议栈,本文介绍在CubeMX中配置以太网外设、lwip协议栈,通过Ping指令测量网络连接情况。
1、CubeMX配置Ethernet外设
STM32H735DK使用的Ethernet芯片LAN8742的原理图如下。

LAN8742的复位引脚连接在系统的NRST引脚上,在开发板复位的同时复位,无需在程序中对LAN8742进行复位操作,相应的LAN8742的复位引脚无需配置,对于其他NRST引脚连接到GPIO的情况,需要配置相应的引脚,并在软件中对NRST的控制引脚进行从低电平到高电平的复位操作。
“Connectivity->Eth”中使能以太网PHY芯片的接口形式为RMII,设置其中的TX/RX描述符的内存地址以及外设的引脚配置,默认配置中,部分引脚和原理图中的引脚不匹配,在Pinout视图中通过查找功能,调整相应的引脚设置。

使能Ethernet外设的中断。

2、调整FreeRTOS和lwip模块参数
使能FreeRTOS模块并调整其中的参数如下。

使能lwip模块和DHCP功能

设置lwip协议栈使用的RAM Heap大小以及起始地址,调整TCP缓存区的大小。

调整TCP/IP线程的Stack大小。

保存并生成相应的代码。
3、添加DTCM地址空间配置
官方的示例中,将Ethernet TX\RX 描述符以及LWIP HEAP的地址空间分配到SDRAM1中。在H7xx系列的Reference Manual中的内存组成章节可以找到地址空间的分配表格如下。

SDRAM1的其实地址为0x30000000,大小为32KB。在工程默认创建的连接脚本文件中,可以找到对应的内存区域定义。
/* Specify the memory areas */
MEMORY
{
ITCMRAM (xrw) : ORIGIN = 0x00000000, LENGTH = 64K
DTCMRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
RAM_D1 (xrw) : ORIGIN = 0x24000000, LENGTH = 320K
RAM_D2 (xrw) : ORIGIN = 0x30000000, LENGTH = 32K
RAM_D3 (xrw) : ORIGIN = 0x38000000, LENGTH = 16K
}
SDRAM1对应的地址空间没有被分配用于链接,需要添加相应的链接定义。
分配到RAM_D1中的.bss字段添加(NOLOAD)描述表示.bss字段不在启动时进行初始化,添加.Rx_PoolSection字段用于RX Buffer的内存分配。SDRAM1对应的RAM_D2内存空间中添加.RxDecripSection和.TxDecripSection,用于以太网芯片接口的TX\RX描述符数据的存储。
/* Uninitialized data section */
. = ALIGN(4);
.bss (NOLOAD):
{
/* This is used by the startup in order to initialize the .bss section */
_sbss = .; /* define a global symbol at bss start */
__bss_start__ = _sbss;
*(.bss)
*(.bss*)
*(COMMON)
/* ETH_CODE: add placement of RX buffer. STM32H72x/H73x has small D2 RAM, so we need to put it there.
* (NOLOAD) attribute used for .bss section to avoid linker warning (.bss initialized by startup code)
*/
. = ALIGN(32);
*(.Rx_PoolSection)
. = ALIGN(4);
_ebss = .; /* define a global symbol at bss end */
__bss_end__ = _ebss;
} >RAM_D1
/* User_heap_stack section, used to check that there is enough RAM left */
._user_heap_stack :
{
. = ALIGN(8);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(8);
} >RAM_D1
/* ETH_CODE: add placement of DMA descriptors, rest is used by RX_POOL */
.lwip_sec (NOLOAD) :
{
. = ABSOLUTE(0x30000000);
*(.RxDecripSection)
. = ABSOLUTE(0x30000100);
*(.TxDecripSection)
} >RAM_D2
工程中对应的数组定义中,通过__attribute__((section("<xxxxx>")))语句,分配DMARxDscrTab、DMATxDscrTab以及memp_memory_RX_POOL_base到不同的地址空间中。在ethernetif.c中可以找到相关的定义。
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 */
LWIP_MEMPOOL_DECLARE(RX_POOL, ETH_RX_BUFFER_CNT, sizeof(RxBuff_t), "Zero-copy RX PBUF pool");
__attribute__((section(".Rx_PoolSection"))) extern u8_t memp_memory_RX_POOL_base[];
4、添加lwipopts.h配置
lwip通过lwipopts.h头文件添加配置选项调整lwip的功能,工程中的lwipopts.h文件的用户自定义区段中,添加解决编译报错、多线程支持的宏和函数声明。
/* USER CODE BEGIN 1 */
/* ETH_CODE: first 2 macros solve errno issue with GCC 10 and ST LwIP
* LWIPERF_CHECK_RX_DATA enables data check for iperf. Removing it might improve performance.
*/
#undef LWIP_PROVIDE_ERRNO
#define LWIP_ERRNO_STDINCLUDE
#define LWIPERF_CHECK_RX_DATA 1
/* ETH_CODE: macro and prototypes for proper (hopefuly?)
* multithreading support
*/
#define LOCK_TCPIP_CORE sys_lock_tcpip_core
#define UNLOCK_TCPIP_CORE sys_unlock_tcpip_core
#define LWIP_ASSERT_CORE_LOCKED sys_check_core_locking
#define LWIP_MARK_TCPIP_THREAD sys_mark_tcpip_thread
void sys_lock_tcpip_core(void);
void sys_unlock_tcpip_core(void);
void sys_check_core_locking(void);
void sys_mark_tcpip_thread(void);
/* USER CODE END 1 */
在ethernetif.c中添加相应的用于实现多线程支持的函数实现。
/* ETH_CODE: add functions needed for proper multithreading support and check */
static osThreadId_t lwip_core_lock_holder_thread_id;
static osThreadId_t lwip_tcpip_thread_id;
void sys_lock_tcpip_core(void){
sys_mutex_lock(&lock_tcpip_core);
lwip_core_lock_holder_thread_id = osThreadGetId();
}
void sys_unlock_tcpip_core(void){
lwip_core_lock_holder_thread_id = 0;
sys_mutex_unlock(&lock_tcpip_core);
}
void sys_check_core_locking(void){
/* Embedded systems should check we are NOT in an interrupt context here */
LWIP_ASSERT("Function called from interrupt context", (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) == 0);
if (lwip_tcpip_thread_id != 0) {
osThreadId_t current_thread_id = osThreadGetId();
#if LWIP_TCPIP_CORE_LOCKING
LWIP_ASSERT("Function called without core lock", current_thread_id == lwip_core_lock_holder_thread_id);
/* ETH_CODE: to easily check that example has correct handling of core lock
* This will trigger breakpoint (__BKPT)
*/
#warning Below check should be removed in production code
if(current_thread_id != lwip_core_lock_holder_thread_id) __BKPT(0);
#else /* LWIP_TCPIP_CORE_LOCKING */
LWIP_ASSERT("Function called from wrong thread", current_thread_id == lwip_tcpip_thread_id);
#endif /* LWIP_TCPIP_CORE_LOCKING */
LWIP_UNUSED_ARG(current_thread_id); /* for LWIP_NOASSERT */
}
}
void sys_mark_tcpip_thread(void){
lwip_tcpip_thread_id = osThreadGetId();
}
ethernet_link_thread函数中添加TCPIP多线程支持的函数调用。
/* USER CODE BEGIN ETH link init */
/* ETH_CODE: call HAL_ETH_Start_IT instead of HAL_ETH_Start
* This is required for operation with RTOS.
* This trick allows to keep this change through
* code re-generation by STM32CubeMX
*/
#define HAL_ETH_Start HAL_ETH_Start_IT
/* ETH_CODE: workaround to call LOCK_TCPIP_CORE when accessing netif link functions*/
LOCK_TCPIP_CORE();
/* USER CODE END ETH link init */
/* USER CODE BEGIN ETH link Thread core code for User BSP */
/* ETH_CODE: workaround to call LOCK_TCPIP_CORE when accessing netif link functions*/
UNLOCK_TCPIP_CORE();
osDelay(100);
LOCK_TCPIP_CORE();
continue; /* skip next osDelay */
/* USER CODE END ETH link Thread core code for User BSP */
lwip.c中添加tcpip 封装,用于支持多线程
/* USER CODE BEGIN 2 */
/* ETH_CODE: workaround to call LOCK_TCPIP_CORE after tcpip_init in MX_LWIP_Init
* This is to keep the code after MX code re-generation */
static inline void tcpip_init_wrap(tcpip_init_done_fn tcpip_init_done, void *arg){
tcpip_init(tcpip_init_done, arg);
LOCK_TCPIP_CORE();
}
#define tcpip_init tcpip_init_wrap
/* USER CODE END 2 */
lwip.c中的MX_LWIP_Init中添加以下代码
/* USER CODE BEGIN 3 */
/* ETH_CODE: call UNLOCK_TCPIP_CORE after we are done */
UNLOCK_TCPIP_CORE();
/* USER CODE END 3 */
5、使能lwip debug特性
lwip支持调试信息输出,lwipopts.h文件中添加使能Debug和DHCP DEGUB的宏定义,用于输出DHCP调试信息。
#define LWIP_DEBUG
#define DHCP_DEBUG LWIP_DBG_ON
开发板的PD8和PD9配置为USART3,可以用于printf函数输出重定向到串口输出。

添加以下代码,用于USART3串口重定向。
extern UART_HandleTypeDef huart3;
#ifdef __GNUC__
/* With GCC, small printf (option LD Linker->Libraries->Small printf
set to 'Yes') calls __io_putchar() */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
PUTCHAR_PROTOTYPE
{
/* Place your implementation of fputc here */
/* e.g. write a character to the USART1 and Loop until the end of transmission */
HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
编译并烧录程序到开发板,通过串口可以看到以下的输出。DHCP可以正常访问网关,并获取地址信息。
