STM32H743 + CMSIS-RTOS2 + Mongoose 网络协议栈移植教程

STM32H743 + CMSIS-RTOS2 + Mongoose 网络协议栈移植教程

一、概述

本文档详细介绍如何在 STM32H743 平台上移植 Mongoose 自带 TCP/IP 协议栈,配合 CMSIS-RTOS2(基于 FreeRTOS)实现网络通信功能。

硬件平台 :STM32H743 + LAN8720 PHY(RMII 接口)

软件栈 :STM32 HAL + CMSIS-RTOS V2 + Mongoose

功能特性

  • 支持 DHCP 和静态 IP 两种模式
  • 网线拔插检测(PHY 链路状态轮询)
  • 运行时配置网络参数(保存至 Flash)
  • 网络状态事件回调

二、目录结构

复制代码
project_root/
├── app/                    # 应用代码
│   ├── mongoose_net.c      # Mongoose 网络管理模块(核心)
│   ├── mongoose_net.h      # 网络接口头文件
│   ├── main.c              # 主入口
│   ├── main.h              # 中断优先级定义
│   ├── FreeRTOSConfig.h    # FreeRTOS 配置
│   ├── app_param.h         # 网络参数结构体定义
│   ├── stm32h7xx_hal_msp.c # HAL MSP 初始化
│   └── bsp_system.c        # 系统初始化(MPU配置等)
├── mongoose/               # Mongoose 库
│   ├── mongoose.c          # Mongoose 主源码
│   └── mongoose_config.h   # Mongoose 配置文件
├── libraries/              # HAL/CMSIS 库
│   ├── STM32H7xx_HAL_Driver/
│   └── CMSIS/
├── freertos/               # FreeRTOS 源码
│   └── Source/
│       └── CMSIS_RTOS_V2/  # CMSIS-RTOS2 适配层
└── project/
    └── stm32_h7.sct        # 链接脚本

三、移植步骤

3.1 准备基础工程

  1. 使用 STM32CubeMX 生成基础工程:

    • 选择 STM32H743 系列芯片
    • 启用 ETH(RMII 模式)
    • 启用 FreeRTOS(CMSIS-RTOS V2 接口)
    • 配置时钟(系统时钟 400MHz,PLLN=100, PLLM=1, PLLP=2)
  2. 添加必要的 HAL 驱动文件到工程:

    • stm32h7xx_hal_eth.c
    • stm32h7xx_hal_rcc.c
    • stm32h7xx_hal_gpio.c

3.2 添加 Mongoose 库

  1. 下载 Mongoose 源码,将 mongoose.cmongoose.h 放入 mongoose/ 目录

  2. 创建 mongoose_config.h 配置文件,关键配置如下:

c 复制代码
#define MG_ARCH                  MG_ARCH_CMSIS_RTOS2
#define MG_ENABLE_DRIVER_STM32H  1
#define MG_ENABLE_TCPIP          1
#define MG_ENABLE_TCPIP_DRIVER_INIT 0
#define MG_IO_SIZE               2048
#define MG_ENABLE_FREERTOS       1
#define NET_DRIVER_MDC_CR        4
#define NET_PHY_ADDR             0

#define __sync_synchronize()     __dsb(0xf)
#define MG_ETH_RAM  __attribute__((section(".RAM_D1")))

#define MGUID ((uint32_t *) 0x1ff1e800)
#define MG_SET_MAC_ADDRESS(mac)      \
  do {                               \
    mac[0] = 2;                      \
    mac[1] = MGUID[0] & 255;         \
    mac[2] = (MGUID[0] >> 10) & 255; \
    mac[3] = (MGUID[0] >> 19) & 255; \
    mac[4] = MGUID[1] & 255;         \
    mac[5] = MGUID[2] & 255;         \
  } while (0)

配置说明

宏定义 说明
MG_ARCH_CMSIS_RTOS2 使用 CMSIS-RTOS2 架构适配
MG_ENABLE_DRIVER_STM32H 启用 STM32H 内置以太网驱动
MG_ENABLE_TCPIP_DRIVER_INIT 0 关键 :禁止 mg_mgr_init() 自动初始化驱动,手动调用 mg_tcpip_init()
NET_DRIVER_MDC_CR MDC 时钟分频(根据 PHY 调整,LAN8720 用 4)
NET_PHY_ADDR PHY 地址(LAN8720 默认 0)
MG_ETH_RAM DMA 描述符放入非缓存 RAM(.RAM_D1 段)
__sync_synchronize() ARMCC/Keil 编译器内存屏障,确保 DMA 操作的缓存一致性;GCC 编译器可替换为 __sync_synchronize()__dsb(0xf)
MG_PHY_CLOCKS_MAC PHY 时钟源配置,LAN8720 使用 MAC 侧提供时钟;使用 DP83848 等需外部时钟的 PHY 时改为 MG_PHY_CLOCKS_PHY

3.3 配置链接脚本

.sct 文件中添加 .RAM_D1 段,必须放在非缓存 RAM 区域

plaintext 复制代码
LR_IROM1 0x08000000 0x00200000  {
  ER_IROM1 0x08000000 0x00200000  {
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
  }
  
  RW_IRAM1 0x20000000 0x00020000  {  ; DTCM 128KB
   .ANY (+RW +ZI)
  }
  
  RW_IRAM2 0x24000000 0x00080000  {  ; AXI SRAM 512KB
   .ANY (+RW +ZI)
  }
  
  RW_IRAM3 0x30000000 0x00040000  {  ; SRAM1+SRAM2 256KB
   .ANY (+RW +ZI)
  }
  
  RW_IRAM4 0x30040000 0x00008000  {  ; SRAM3 32KB - Non-Cacheable
   *(.RAM_D1)                          ; DMA 描述符必须放这里!
  }
  
  RW_IRAM5 0x38000000 0x00010000  {  ; SRAM4 64KB
   *(.RAM_D2)
  }
}

关键.RAM_D1 段必须放在非缓存 RAM(SRAM3),否则 DMA 描述符缓存不一致会导致接收中断无法正常工作。

3.4 配置 MPU

bsp_system.c 中配置 MPU,确保 SRAM3 为非缓存:

c 复制代码
void mpu_config(void)
{
    MPU_Region_InitTypeDef MPU_InitStruct;
    HAL_MPU_Disable();
    
    /* SRAM3 - 非缓存区域,用于 DMA 描述符 */
    MPU_InitStruct.Enable = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress = 0x30040000;
    MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_32KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number = MPU_REGION_NUMBER2;
    HAL_MPU_ConfigRegion(&MPU_InitStruct);
    
    HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}

3.4.1 D-Cache 写透配置

stm32h7xx_hal_msp.cHAL_MspInit() 中添加 D-Cache 写透配置,这对 ETH DMA 缓存一致性至关重要

c 复制代码
void HAL_MspInit(void)
{
    __HAL_RCC_SYSCFG_CLK_ENABLE();
    HAL_NVIC_SetPriority(PendSV_IRQn, 15, 0);
    
    SCB_EnableICache();
    SCB_EnableDCache();
    SCB->CACR |= 1 << 2;
}

说明SCB->CACR |= 1 << 2 强制 D-Cache 写透模式,确保 CPU 写入的数据能立即写回内存,避免 DMA 读取到过期的缓存数据。

3.5 编写 ETH MSP 初始化

stm32h7xx_hal_msp.c 中实现 HAL_ETH_MspInit()只做硬件初始化

c 复制代码
void HAL_ETH_MspInit(ETH_HandleTypeDef* heth)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    if(heth->Instance==ETH)
    {
        __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();

        /* RMII GPIO 配置 - AF11 */
        GPIO_InitStruct.Pin = GPIO_PIN_1|GPIO_PIN_4|GPIO_PIN_5;  /* PC1=MDC, PC4=RXD0, PC5=RXD1 */
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_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;  /* PA1=REF_CLK, PA2=MDIO, PA7=CRS_DV */
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

        GPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13;  /* PB11=TX_EN, PB12=TXD0, PB13=TXD1 */
        HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

        /* PHY 硬件复位 - PB0 */
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
        GPIO_InitStruct.Pin = GPIO_PIN_0;
        GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
        
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
        HAL_Delay(100);
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
        HAL_Delay(100);
    }
}

3.6 编写网络管理模块

创建 mongoose_net.h

c 复制代码
#ifndef __MONGOOSE_NET_H__
#define __MONGOOSE_NET_H__

#include "stdint.h"

typedef enum {
    NET_LINK_DOWN = 0,
    NET_LINK_UP,
    NET_IP_READY
} net_link_state_t;

void mongoose_net_init(void);
void mongoose_net_task(void* param);
net_link_state_t mongoose_net_get_link_state(void);

#endif

创建 mongoose_net.c这是移植的核心

c 复制代码
#include "mongoose_net.h"
#include "mongoose.h"
#include "app_param.h"
#include "main.h"
#include <string.h>
#include <stdio.h>

ETH_HandleTypeDef heth;
#define NET_POLL_TIMEOUT_MS   100

struct mg_mgr g_mgr;
static struct mg_tcpip_if s_ifp;
static struct mg_tcpip_driver_stm32h_data s_driver_data;
static volatile net_link_state_t s_link_state = NET_LINK_DOWN;

static void net_event_handler(struct mg_tcpip_if* ifp, int ev, void* ev_data)
{
    switch (ev)
    {
    case MG_TCPIP_EV_STATE_CHANGE:
    {
        uint8_t state = ifp->state;
        switch (state)
        {
        case MG_TCPIP_STATE_DOWN:
            s_link_state = NET_LINK_DOWN;
            printf("[NET] Link DOWN\r\n");
            break;
        case MG_TCPIP_STATE_LINK_UP:
        case MG_TCPIP_STATE_UP:
            s_link_state = NET_LINK_UP;
            printf("[NET] Link UP\r\n");
            break;
        case MG_TCPIP_STATE_REQ:
            printf("[NET] DHCP requesting...\r\n");
            break;
        case MG_TCPIP_STATE_IP:
        {
            uint8_t* ip = (uint8_t*)&ifp->ip;
            printf("[NET] IP: %u.%u.%u.%u\r\n", ip[0], ip[1], ip[2], ip[3]);
        }
        break;
        case MG_TCPIP_STATE_READY:
            s_link_state = NET_IP_READY;
            printf("[NET] Network READY\r\n");
            break;
        }
        break;
    }
    }
}

void mongoose_net_init(void)
{
    uint8_t mac[6];
    const net_config_t* cfg = get_netif_param();

    heth.Instance = ETH;
    HAL_ETH_MspInit(&heth);
    HAL_SYSCFG_ETHInterfaceSelect(SYSCFG_ETH_RMII);
    HAL_NVIC_SetPriority(ETH_IRQn, PRO_ETH, 0);
    HAL_NVIC_EnableIRQ(ETH_IRQn);

    mg_mgr_init(&g_mgr);

    get_mac_param(mac);
    memcpy(s_ifp.mac, mac, 6);

    s_driver_data.mdc_cr = NET_DRIVER_MDC_CR;
    s_driver_data.phy_addr = NET_PHY_ADDR;
    s_driver_data.phy_conf = MG_PHY_CLOCKS_MAC;

    s_ifp.driver = &mg_tcpip_driver_stm32h;
    s_ifp.driver_data = &s_driver_data;

    if (cfg->dhcp)
    {
        s_ifp.ip = 0;
        s_ifp.mask = 0;
        s_ifp.gw = 0;
        s_ifp.enable_dhcp_client = true;
        printf("[NET] DHCP mode enabled\r\n");
    }
    else
    {
        s_ifp.ip = MG_IPV4(cfg->ip[0], cfg->ip[1], cfg->ip[2], cfg->ip[3]);
        s_ifp.mask = MG_IPV4(cfg->mask[0], cfg->mask[1], cfg->mask[2], cfg->mask[3]);
        s_ifp.gw = MG_IPV4(cfg->gw[0], cfg->gw[1], cfg->gw[2], cfg->gw[3]);
        s_ifp.enable_dhcp_client = false;
        printf("[NET] Static IP: %u.%u.%u.%u\r\n", 
               cfg->ip[0], cfg->ip[1], cfg->ip[2], cfg->ip[3]);
    }

    s_ifp.fn = net_event_handler;
    mg_tcpip_init(&g_mgr, &s_ifp);
}

void mongoose_net_task(void* param)
{
    (void)param;
    for (;;)
    {
        mg_mgr_poll(&g_mgr, NET_POLL_TIMEOUT_MS);
    }
}

net_link_state_t mongoose_net_get_link_state(void)
{
    return s_link_state;
}

3.7 编写网络参数结构体

app_param.h 中定义网络配置结构体:

c 复制代码
typedef struct
{
    uint8_t dhcp;
    uint8_t ip[4];
    uint8_t mask[4];
    uint8_t gw[4];
} net_config_t;

typedef struct
{
    net_config_t net_config;
    uint8_t mac[6];
    // ... 其他参数
} app_param_t;

const net_config_t* get_netif_param(void);
void set_netif_param(net_config_t* netif_info);
void get_mac_param(uint8_t* mac);

3.8 主函数集成

main.c 中初始化 RTOS 并创建网络任务:

c 复制代码
#include "main.h"
#include "mongoose_net.h"
#include "cmsis_os2.h"
#include "app_param.h"

void init_task(void* param);

int main(void)
{
    HAL_Init();
    mpu_config();
    SystemClock_Config();
    dev_param_init();

    osKernelInitialize();

    const osThreadAttr_t init_attr = {
        .name       = "Init",
        .stack_size = 1024,
        .priority   = osPriorityAboveNormal,
    };
    osThreadNew(init_task, NULL, &init_attr);

    osKernelStart();
    for(;;);
}

void init_task(void* param)
{
    dbg_uart_init(115200);

    mongoose_net_init();

    const osThreadAttr_t net_attr = {
        .name       = "net",
        .stack_size = 8192,
        .priority   = osPriorityNormal2,
    };
    osThreadNew(mongoose_net_task, NULL, &net_attr);

    osThreadExit();
}

四、关键注意事项

4.1 严禁调用 HAL_ETH_Init()

这是最容易踩坑的地方!

HAL_ETH_Init() 会执行以下操作:

  1. ETH DMA 软件复位
  2. MAC 配置
  3. 设置 HAL DMA 描述符

mg_tcpip_driver_stm32h_init() 又会执行一次:

  1. ETH DMA 软件复位
  2. 设置 Mongoose 自己的 DMA 描述符

双重初始化会导致 ETH 状态不一致,RX 中断无法正常工作。

正确做法 :只调用 HAL_ETH_MspInit() 做硬件初始化。

4.2 ETH_IRQHandler 由 Mongoose 内部定义

ETH_IRQHandlermongoose.c 中已经实现,负责接收中断处理,通过 mg_tcpip_qwrite 将数据推入队列。

不需要在 stm32h7xx_it.c 中重新定义。

4.3 中断优先级配置

c 复制代码
#define PRO_ETH         6

main.h 中定义 ETH 中断优先级为 6。由于 CMSIS-RTOS2 下 MG_ENABLE_ETH_IRQ() 为空宏,需要手动使能中断:

c 复制代码
HAL_NVIC_SetPriority(ETH_IRQn, PRO_ETH, 0);
HAL_NVIC_EnableIRQ(ETH_IRQn);

中断优先级必须低于 configMAX_SYSCALL_INTERRUPT_PRIORITY(FreeRTOSConfig.h 中配置为 5),否则中断中调用 FreeRTOS API 会出问题。

4.4 网络配置从 Flash 读取

网络配置(IP/MASK/GW/DHCP 模式)必须从 Flash 参数读取,不能硬编码在头文件中。

修改 mongoose_config.h,注释掉硬编码的 IP 宏:

c 复制代码
// IP/MASK/GW are read from Flash parameters at runtime
// Default to 0.0.0.0 (DHCP mode) via mongoose.h's default definitions

4.5 网络轮询任务栈大小

网络任务需要较大的栈空间(建议 8192 字节),因为 mg_mgr_poll() 内部会处理多种网络事件。


五、状态机说明

Mongoose 网络驱动每秒轮询 PHY 链路状态,状态变化时触发 MG_TCPIP_EV_STATE_CHANGE 事件:

状态值 状态宏 说明
0 MG_TCPIP_STATE_DOWN 物理链路断开(网线拔出)
1 MG_TCPIP_STATE_LINK_UP 物理链路已连接
2 MG_TCPIP_STATE_UP L2 层就绪
3 MG_TCPIP_STATE_REQ DHCP 请求中
4 MG_TCPIP_STATE_IP IP 已分配
5 MG_TCPIP_STATE_READY 完全就绪(网关 MAC 已解析)

六、模式切换

DHCP 模式

c 复制代码
s_ifp.ip = 0;
s_ifp.mask = 0;
s_ifp.gw = 0;
s_ifp.enable_dhcp_client = true;

静态 IP 模式

c 复制代码
s_ifp.ip = MG_IPV4(192, 168, 1, 100);
s_ifp.mask = MG_IPV4(255, 255, 255, 0);
s_ifp.gw = MG_IPV4(192, 168, 1, 1);
s_ifp.enable_dhcp_client = false;

运行时修改配置后,调用 set_netif_param() 保存到 Flash,重新上电生效。


七、快速上手示例

移植完成后,可通过以下示例快速验证网络功能:

7.1 创建简单的 HTTP 服务器

mongoose_net_init() 之后添加 HTTP 监听:

c 复制代码
#include "mongoose.h"

static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
    if (ev == MG_EV_HTTP_MSG) {
        struct mg_http_message *hm = (struct mg_http_message *) ev_data;
        mg_http_reply(c, 200, "", "Hello from STM32H743!\r\n");
    }
    (void) fn_data;
}

void mongoose_net_init(void)
{
    // ... 前面的初始化代码 ...
    
    mg_http_listen(&g_mgr, "http://0.0.0.0:80", fn, NULL);
    printf("[NET] HTTP server listening on port 80\r\n");
}

使用浏览器访问开发板 IP,即可看到 "Hello from STM32H743!" 响应。

7.2 创建 TCP Client 连接

c 复制代码
#include "mongoose.h"

static void tcp_fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
    if (ev == MG_EV_CONNECT) {
        if (((struct mg_connect_result *) ev_data)->is_success) {
            printf("[NET] TCP connected\r\n");
            mg_send(c, "Hello from STM32H743!\r\n", 24);
        } else {
            printf("[NET] TCP connect failed\r\n");
        }
    } else if (ev == MG_EV_READ) {
        printf("[NET] Received: %.*s\r\n", (int) c->recv.len, c->recv.buf);
    }
    (void) fn_data;
}

void start_tcp_client(void)
{
    mg_connect(&g_mgr, "tcp://192.168.1.10:8080", tcp_fn, NULL);
}

7.3 FreeRTOS 堆大小配置

Mongoose 需要动态分配内存用于网络连接和缓冲区,需确保 FreeRTOSConfig.h 中堆大小足够:

c 复制代码
#define configTOTAL_HEAP_SIZE    ((size_t)65536)

内存大的话,建议至少配置 64KB 堆空间。


八、调试技巧

  1. 串口日志 :通过 printf 输出网络状态变化
  2. 网线拔插测试 :观察 NET_LINK_DOWN / NET_LINK_UP 状态切换
  3. DHCP 获取测试 :连接路由器,观察 DHCP requesting...IP assigned 日志
  4. 静态 IP 测试:设置静态 IP 后,用 ping 命令测试连通性

相关推荐
miss6 小时前
海康威视 IoT 平台前端实战:视频流、海量设备与实时数据可视化
前端·javascript·物联网
hongmai66688817 小时前
FH8856V310芯片详解:6M高清+0.5TOPS算力,赋能智能安防新方案
人工智能·单片机·嵌入式硬件·物联网·智能家居
宏集科技工业物联网20 小时前
宏集EXOR物联网HMI赋能上海易勒篮式研磨机智能化升级
经验分享·物联网·工业4.0·工业自动化·hmi·工控屏
liguojun20251 天前
篮球馆自动计时收费系统:从规则配置到自动结算的全流程拆解
java·大数据·运维·人工智能·物联网·1024程序员节
Promise微笑1 天前
红外分辨率 160×120、320×240、384×288 与 640×480实战选型指南
大数据·运维·人工智能·物联网
Promise微笑1 天前
激光清障仪市场与技术深度分析:基于原理、应用及厂家格局的综述
大数据·人工智能·物联网
云起智控iot1 天前
如何通过智能照明控制模块实现智慧城市照明管理?
物联网·智慧城市
GlobalInfo1 天前
行业报告解读:可穿戴电子织物市场扩张背后,有哪些新机遇?
人工智能·物联网·microsoft
振南的单片机世界1 天前
单片机:一颗芯片包办CPU、内存、外设的“单芯片计算机”
arm开发·stm32·单片机·嵌入式硬件