1. 搭建RTOS与LwIP开发环境

任务说明

本案例目标是在集成开发环境中创建一个基础工程,同时集成RTOS内核与LwIP协议栈源码,并验证两者能够编译通过。这是后续所有LwIP移植实验的公共基础,因此不涉及具体网卡驱动或业务逻辑,只完成工程骨架与编译验证。

环境与依赖

  • IDE:STM32CubeIDE 1.15+(或任意支持ARM GCC的Eclipse系IDE)
  • 目标芯片:STM32F407VET6(本案例以该芯片为例,其他Cortex-M4芯片同理)
  • RTOS源码:FreeRTOS Kernel V11.1.0(Source目录)
  • LwIP源码:LwIP V2.2.0(src目录)
  • CMSIS:由STM32CubeIDE自动提供

工程结构

在STM32CubeIDE中新建空工程后,将FreeRTOS与LwIP源码按以下结构加入工程(仅列出关键路径):

project_root/
├── Core/
│   ├── Inc/
│   └── Src/
│       └── main.c
├── FreeRTOS/
│   ├── include/       (FreeRTOSConfig.h 放置于此)
│   ├── portable/
│   │   ├── GCC/ARM_CM4F/
│   │   └── MemMang/heap_4.c
│   └── tasks.c, queue.c, list.c, timers.c, event_groups.c, stream_buffer.c, croutine.c
├── LwIP/
│   ├── include/
│   ├── core/
│   ├── netif/
│   └── api/
└── STM32F407VETx_FLASH.ld

关键配置

FreeRTOSConfig.h中必须定义以下与LwIP配合的宏(其余宏按标准模板保留):

#define configUSE_PREEMPTION                    1
#define configUSE_TICKLESS_IDLE                 0
#define configCPU_CLOCK_HZ                      (168000000)
#define configTICK_RATE_HZ                      (1000)
#define configMAX_PRIORITIES                    32
#define configMINIMAL_STACK_SIZE                128
#define configTOTAL_HEAP_SIZE                   (50 * 1024)
#define configUSE_MUTEXES                       1
#define configUSE_RECURSIVE_MUTEXES             1
#define configUSE_COUNTING_SEMAPHORES           1
#define configSUPPORT_DYNAMIC_ALLOCATION        1
#define configCHECK_FOR_STACK_OVERFLOW          2
#define configUSE_IDLE_HOOK                     0
#define configUSE_TICK_HOOK                     0

LwIP的lwipopts.h中必须启用操作系统封装层:

#define NO_SYS                     0
#define LWIP_TIMERS                1
#define LWIP_NETCONN               1
#define LWIP_SOCKET                0
#define MEM_LIBC_MALLOC            0
#define MEMP_MEM_MALLOC            0

完整实现

以下代码为main.c的最小验证实现——创建两个RTOS任务,一个周期性翻转LED,另一个周期性调用LwIP的tcpip_init确认协议栈初始化路径可执行(不连接网络):

/* main.c - RTOS + LwIP 编译验证 */
#include "stm32f4xx_hal.h"
#include "FreeRTOS.h"
#include "task.h"
#include "lwip/init.h"
#include "lwip/tcpip.h"

void SystemClock_Config(void);
static void MX_GPIO_Init(void);

/* 任务句柄 */
TaskHandle_t led_task_handle = NULL;
TaskHandle_t lwip_check_task_handle = NULL;

/* LED任务:每500ms翻转一次 */
void vLEDTask(void *argument) {
    for (;;) {
        HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

/* LwIP检查任务:每2秒调用tcpip_init确认协议栈可初始化 */
void vLwIPCheckTask(void *argument) {
    for (;;) {
        tcpip_init(NULL, NULL);
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}

int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();

    /* 创建任务 */
    xTaskCreate(vLEDTask, "LED", 128, NULL, 1, &led_task_handle);
    xTaskCreate(vLwIPCheckTask, "LwIP", 256, NULL, 2, &lwip_check_task_handle);

    /* 启动调度器 */
    vTaskStartScheduler();

    /* 正常不会到达此处 */
    for (;;) {}
}

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

    __HAL_RCC_PWR_CLK_ENABLE();
    __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

    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 = 8;
    RCC_OscInitStruct.PLL.PLLN = 336;
    RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
    RCC_OscInitStruct.PLL.PLLQ = 7;
    HAL_RCC_OscConfig(&RCC_OscInitStruct);

    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
                                | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
    HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5);
}

/* GPIO初始化:PD12接LED */
static void MX_GPIO_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    __HAL_RCC_GPIOD_CLK_ENABLE();

    GPIO_InitStruct.Pin = GPIO_PIN_12;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
}

/* 断言失败处理 */
void vAssertCalled(const char *file, int line) {
    (void)file;
    (void)line;
    for (;;) {}
}

编译与运行

  1. 在STM32CubeIDE中将上述源码文件加入工程,确保FreeRTOS与LwIP的所有.c文件均被包含在编译列表中。
  2. 将FreeRTOSConfig.h与lwipopts.h所在目录加入C/C++ Include路径。
  3. 在工程属性中定义宏 STM32F407xx,并选择ARM GCC工具链。
  4. 点击Build(Ctrl+B)编译工程。

验证方法与通过标准

  • 编译通过:编译完成后Console窗口显示Finished building target,且0 Error。
  • 链接通过:生成的.elf文件大小合理(通常100KB以上),无未定义符号错误。
  • 运行验证:将程序烧录至开发板后,观察PD12引脚LED以约1Hz频率闪烁(500ms翻转一次)。若LED正常闪烁,说明RTOS调度器运行正常,两个任务均被成功创建与调度。
  • 协议栈验证:在vLwIPCheckTask的tcpip_init调用处设置断点,调试运行后若断点被命中且不进入vAssertCalled,说明LwIP协议栈初始化路径可正常执行,内存分配与信号量机制工作正常。

常见问题

  • 编译报错undefined reference to vApplicationStackOverflowHook:在任意.c文件中补充该钩子函数的空实现。
  • 编译报错FreeRTOSConfig.h: No such file:检查Include路径是否已添加FreeRTOS的include目录。
  • 链接报错region FLASH overflowed:LwIP默认配置内存占用较大,可在lwipopts.h中裁剪MEM_SIZE至1024*10,或关闭LWIP_NETCONN(本案例仅验证编译,可关闭以减小体积)。