一、STM32H563 与 LWIP 简介

STM32H563 是 STMicroelectronics 推出的一款高性能 32 位微控制器,具有丰富的外设和强大的处理能力,适用于各种复杂的嵌入式系统应用。LWIP(Lightweight IP)是一个小型开源的 TCP/IP 协议栈,旨在为嵌入式系统提供轻量级的网络功能,它能够在资源有限的系统中实现网络通信,如实现以太网通信、HTTP 服务器、UDP 和 TCP 连接等功能。

二、LWIP 裸机移植的准备工作

  1. 获取 LWIP 源代码

    • 首先,从 LWIP 的官方网站下载最新的 LWIP 源代码。你可以选择合适的版本,通常需要根据 STM32H563 的资源和项目需求来选择。
    • 将 LWIP 源代码添加到你的 STM32 工程目录中,一般可以将其放在一个单独的文件夹中,例如 LWIP
  2. 配置 LWIP

    • LWIP 提供了一个 lwipopts.h 文件,用于配置协议栈的各种参数,如内存池大小、TCP 窗口大小、是否支持 IPv6 等。根据 STM32H563 的可用资源和应用需求,对这些参数进行调整。
    • 例如,如果 STM32H563 内存有限,可以适当减小内存池的大小,但要确保不会导致内存不足而影响网络性能。
  3. STM32H563 硬件配置

    • 确保 STM32H563 的以太网外设已正确配置。使用 HAL 库的以太网相关函数,配置以太网的 PHY 和 MAC 层。
    • 配置相应的 GPIO 引脚,将以太网的 RMII 或 MII 接口引脚配置为正确的复用功能。

三、LWIP 移植步骤

  1. 添加必要的文件

    • 将 LWIP 的核心文件(如 core 文件夹下的源文件)添加到工程中,包括 ipv4ipv6tcpudp 等模块的源文件。
    • 同时,添加 netif 文件夹下的文件,用于网络接口的实现。
    • 对于 STM32H563,还需要添加 arch 文件夹下的文件,该文件夹包含了与 STM32 架构相关的适配文件。
  2. 修改 sys_arch.c 文件

    • sys_arch.c 文件中,需要实现与操作系统相关的函数。对于裸机移植,需要实现一些简单的任务管理函数,如 sys_mbox_newsys_mbox_free 等,这些函数在 LWIP 中用于消息传递和任务同步。
    • 可以使用简单的邮箱或队列机制,使用全局变量和标志位来实现这些功能,而不是依赖于操作系统的任务调度。
  3. 实现网络接口函数

    • ethernetif.c 文件中,实现 low_level_initlow_level_outputlow_level_input 等函数。
    • low_level_init 函数用于初始化以太网硬件,包括 MAC 地址的设置、PHY 的初始化和复位。
    • low_level_output 函数负责将数据从 LWIP 协议栈发送到以太网硬件,将数据封装成以太网帧并通过以太网外设发送出去。
    • low_level_input 函数从以太网外设接收数据,并将其传递给 LWIP 协议栈。

四、代码示例

#include "stm32h563xx_hal.h"
#include "lwip/opt.h"
#include "lwip/init.h"
#include "lwip/netif.h"
#include "lwip/etharp.h"
#include "lwip/timeouts.h"
#include "ethernetif.h"

// 以太网句柄
ETH_HandleTypeDef heth;

// 网络接口
struct netif gnetif;

// 系统时钟配置
void SystemClock_Config(void);
// GPIO 初始化
static void MX_GPIO_Init(void);
// ETH 初始化
static void MX_ETH_Init(void);


// 主函数
int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_ETH_Init();

    // 初始化 LWIP
    lwip_init();

    // 添加网络接口
    ip4_addr_t ipaddr, netmask, gw;
    IP4_ADDR(&ipaddr, 192, 168, 1, 100);
    IP4_ADDR(&netmask, 255, 255, 255, 0);
    IP4_ADDR(&gw, 192, 168, 1, 1);
    netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &ethernetif_input);
    netif_set_default(&gnetif);
    netif_set_up(&gnetif);

    while (1)
    {
        // 处理 LWIP 协议栈任务
        sys_check_timeouts();
        // 可以添加更多的应用程序代码,如处理 TCP 连接、接收数据等
    }
}


// 系统时钟配置
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
    RCC_OscInitStruct.HSIState = RCC_HSI_ON;
    RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
    RCC_OscInitStruct.PLL.PLLM = 16;
    RCC_OscInitStruct.PLL.PLLN = 336;
    RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4;
    RCC_OscInitStruct.PLL.PLLQ = 4;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct)!= HAL_OK)
    {
        while(1);
    }
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APCLK1Divider = RCC_HCLK_DIV4;
    RCC_ClkInitStruct.APCLK2Divider = RCC_HCLK_DIV2;
    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5)!= HAL_OK)
    {
        while(1);
    }
}


// GPIO 初始化
static void MX_GPIO_Init(void)
{
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();
    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_GPIOG_CLK_ENABLE();

    GPIO_InitTypeDef GPIO_InitStruct = {0};

    // 配置以太网引脚
    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_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);

    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_11|GPIO_PIN_13|GPIO_PIN_14;
    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(GPIOG, &GPIO_InitStruct);
}


// ETH 初始化
static void MX_ETH_Init(void)
{
    heth.Instance = ETH;
    heth.Init.AutoNegotiation = ETH_AUTONEGOTIATION_ENABLE;
    heth.Init.Speed = ETH_SPEED_100M;
    heth.Init.DuplexMode = ETH_MODE_FULLDUPLEX;
    heth.Init.PhyAddress = 0;
    heth.Init.MACAddr[0] = 0x00;
    heth.Init.MACAddr[1] = 0x80;
    heth.Init.MACAddr[2] = 0xE1;
    heth.Init.MACAddr[3] = 0x00;
    heth.Init.MACAddr[4] = 0x00;
    heth.Init.MACAddr[5] = 0x00;
    heth.Init.RxMode = ETH_RXPOLLING_MODE;
    heth.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE;
    heth.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII;
    if (HAL_ETH_Init(&heth)!= HAL_OK)
    {
        while(1);
    }
}


// 以太网中断服务程序
void ETH_IRQHandler(void)
{
    HAL_ETH_IRQHandler(&heth);
}


// 以太网接收中断回调函数
void HAL_ETH_RxCpltCallback(ETH_HandleTypeDef *heth)
{
    ethernetif_input(&gnetif);
    // 重新启动接收中断
    HAL_ETH_Receive_IT(heth);
}


// 以太网接收函数
void ethernetif_input(struct netif *netif)
{
    struct pbuf *p;
    uint8_t *buffer;
    int len;

    // 从以太网外设接收数据
    len = HAL_ETH_GetReceivedFrame(&heth, &buffer);
    if (len > 0) {
        p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
        if (p!= NULL) {
            pbuf_take(p, buffer, len);
            if (etharp_input(netif, p) == ERR_OK) {
                // 数据传递给 LWIP 协议栈
                netif->input(p, netif);
            } else {
                pbuf_free(p);
            }
        }
        // 释放接收缓冲区
        HAL_ETH_ReleaseRxBuffer(&heth);
    }
}


// 以太网初始化函数
err_t ethernetif_init(struct netif *netif)
{
    netif->name[0] = 'e';
    netif->name[1] = '0';
    netif->output = etharp_output;
    netif->linkoutput = low_level_output;
    netif->mtu = 1500;
    netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;

    // 初始化以太网硬件
    if (low_level_init(netif) == ERR_OK) {
        // 启动接收中断
        HAL_ETH_Receive_IT(&heth);
        return ERR_OK;
    } else {
        return ERR_MEM;
    }
}


// 低级别发送函数
err_t low_level_output(struct netif *netif, struct pbuf *p)
{
    // 将 pbuf 中的数据发送到以太网硬件
    HAL_ETH_Transmit(&heth, (uint8_t *)p->payload, p->len);
    return ERR_OK;
}


// 低级别初始化函数
err_t low_level_init(struct netif *netif)
{
    // 复位 PHY
    HAL_ETH_DeInit(&heth);
    // 重新初始化 ETH
    if (HAL_ETH_Init(&heth) == HAL_OK) {
        // 设置 MAC 地址
        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];
        return ERR_OK;
    } else {
        return ERR_MEM;
    }
}

代码解释

  • #include "stm32h563xx_hal.h":包含 STM32H563 的 HAL 库头文件。
  • #include "lwip/opt.h", #include "lwip/init.h" 等:包含 LWIP 的相关头文件。
  • ETH_HandleTypeDef heth;:定义以太网句柄,用于存储以太网的配置信息。
  • struct netif gnetif;:定义网络接口结构体。
  • SystemClock_Config():配置 STM32H563 的系统时钟。
  • MX_GPIO_Init():配置以太网所需的 GPIO 引脚,将它们设置为相应的复用功能。
  • MX_ETH_Init():初始化以太网外设,设置速度、双工模式、MAC 地址等。
  • ETH_IRQHandler():以太网中断服务程序,调用 HAL_ETH_IRQHandler 处理中断。
  • HAL_ETH_RxCpltCallback(ETH_HandleTypeDef *heth):接收完成回调函数,调用 ethernetif_input 将数据传递给 LWIP 协议栈并重新启动接收中断。
  • ethernetif_input(struct netif *netif):从以太网外设接收数据并传递给 LWIP 协议栈。
  • ethernetif_init(struct netif *netif):初始化网络接口,设置网络接口的属性,并启动接收中断。
  • low_level_output(struct netif *netif, struct pbuf *p):将数据从 LWIP 协议栈发送到以太网硬件。
  • low_level_init(struct netif *netif):初始化以太网硬件,包括 PHY 的复位和 MAC 地址的设置。

五、总结

STM32H563 与 LWIP 的裸机移植需要仔细的配置和文件组织。通过上述步骤,我们可以将 LWIP 协议栈成功地移植到 STM32H563 上,实现基本的网络通信功能。在移植过程中,要注意 LWIP 参数的配置,确保其适应 STM32H563 的资源。同时,对于以太网硬件的配置和中断处理要准确无误,以保证数据的正常接收和发送。在实际应用中,可以进一步扩展该移植代码,实现更复杂的网络功能,如实现一个简单的 Web 服务器或一个 TCP 客户端,为 STM32H563 开发更强大的网络应用提供基础。

请记住,在调试过程中,需要使用网络调试工具,如 Wireshark 来检查网络数据包的发送和接收,确保网络通信正常。同时,根据实际情况调整 LWIP 的配置和 STM32H563 的硬件配置,以优化网络性能和系统资源利用。通过不断地优化和调试,你可以充分利用 STM32H563 的性能,实现高效可靠的网络通信。

以上内容提供了 STM32H563 HAL 库下 LWIP 裸机移植的基本信息和代码示例,希望对你的开发工作有所帮助,让你可以顺利实现 STM32H563 的网络通信功能。

✅作者简介:热爱科研的嵌入式开发者,修心和技术同步精进

❤欢迎关注我的知乎:对error视而不见

代码获取、问题探讨及文章转载可私信。

☁ 愿你的生命中有够多的云翳,来造就一个美丽的黄昏。

🍎获取更多嵌入式资料可点击链接进群领取,谢谢支持!👇

点击领取更多详细资料

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐