2. 无操作系统环境下的LwIP移植准备

任务目标

在无操作系统的裸机环境中,完成LwIP协议栈移植前的准备工作。本案例以STM32F407 + LAN8720A(RMII接口)为硬件平台,搭建一个可直接编译的最小工程骨架,为后续LwIP源码移植与网卡驱动编写提供清晰的代码组织结构和验证入口。

环境与依赖

  • 硬件:STM32F407VET6开发板,LAN8720A PHY芯片,25MHz外部晶振
  • 工具链:arm-none-eabi-gcc + Makefile,或Keil MDK-ARM 5.x(二选一)
  • 必要固件库:STM32F4xx HAL库(仅使用GPIO、RCC、SYSTICK模块)
  • 本案例不涉及LwIP源码,仅完成移植前的工程骨架与硬件初始化验证

工程结构设计

采用分层目录结构,将硬件相关代码与后续LwIP移植层分离:

lwip_port_prep/
├── Makefile
├── core/
│   ├── main.c              // 入口,调用各模块初始化
│   ├── sys_init.c          // 时钟、GPIO、SysTick
│   └── sys_init.h
├── eth/
│   ├── phy_lan8720.c       // PHY复位与ID读取
│   └── phy_lan8720.h
└── lwip_port/              // 后续LwIP移植代码放置目录(当前为空占位)

关键完整代码

以下代码实现:系统时钟初始化(168MHz)、PHY芯片复位及ID读取验证、LED闪烁指示运行状态。该实现直接验证硬件链路可用性,为LwIP的Ethernet接口驱动提供底层基础。

// core/sys_init.h
#ifndef SYS_INIT_H
#define SYS_INIT_H

#include "stm32f4xx_hal.h"

void System_Init(void);
void Error_Handler(void);

#endif
// core/sys_init.c
#include "sys_init.h"

void System_Init(void) {
    HAL_Init();
    __HAL_RCC_SYSCFG_CLK_ENABLE();
    __HAL_RCC_PWR_CLK_ENABLE();

    // 配置168MHz主频:HSE 25MHz → PLL x 168/25
    RCC_OscInitTypeDef osc = {0};
    osc.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    osc.HSEState = RCC_HSE_ON;
    osc.PLL.PLLState = RCC_PLL_ON;
    osc.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    osc.PLL.PLLM = 25;
    osc.PLL.PLLN = 336;
    osc.PLL.PLLP = RCC_PLLP_DIV2;
    osc.PLL.PLLQ = 7;
    if (HAL_RCC_OscConfig(&osc) != HAL_OK) Error_Handler();

    RCC_ClkInitTypeDef clk = {0};
    clk.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK |
                    RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
    clk.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    clk.AHBCLKDivider = RCC_SYSCLK_DIV1;
    clk.APB1CLKDivider = RCC_HCLK_DIV4;
    clk.APB2CLKDivider = RCC_HCLK_DIV2;
    if (HAL_RCC_ClockConfig(&clk, FLASH_LATENCY_5) != HAL_OK) Error_Handler();
}

void Error_Handler(void) {
    while (1) { /* 错误状态:LED快闪(由外部看门狗或调试器观察) */ }
}
// eth/phy_lan8720.h
#ifndef PHY_LAN8720_H
#define PHY_LAN8720_H

#include "stm32f4xx_hal.h"

#define PHY_ADDR        0x01
#define PHY_REG_ID1     0x02
#define PHY_REG_ID2     0x03

void PHY_Init(void);
uint32_t PHY_ReadID(void);

#endif
// eth/phy_lan8720.c
#include "phy_lan8720.h"

// 使用模拟SMI接口(GPIOB Pin0=MDC, Pin1=MDIO)
#define MDC_PORT   GPIOB
#define MDC_PIN    GPIO_PIN_0
#define MDIO_PORT  GPIOB
#define MDIO_PIN   GPIO_PIN_1

static void SMI_GPIO_Init(void) {
    __HAL_RCC_GPIOB_CLK_ENABLE();
    GPIO_InitTypeDef gpio = {0};
    gpio.Pin = MDC_PIN | MDIO_PIN;
    gpio.Mode = GPIO_MODE_OUTPUT_PP;
    gpio.Pull = GPIO_PULLUP;
    gpio.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOB, &gpio);
}

static void SMI_Delay(void) {
    for (volatile int i = 0; i < 10; i++);  // 约100ns @168MHz
}

static void SMI_Start(void) {
    HAL_GPIO_WritePin(MDC_PORT, MDC_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(MDIO_PORT, MDIO_PIN, GPIO_PIN_RESET);
    for (int i = 0; i < 32; i++) {  // 32个全1 preamble
        HAL_GPIO_WritePin(MDIO_PORT, MDIO_PIN, GPIO_PIN_SET);
        HAL_GPIO_WritePin(MDC_PORT, MDC_PIN, GPIO_PIN_SET);
        SMI_Delay();
        HAL_GPIO_WritePin(MDC_PORT, MDC_PIN, GPIO_PIN_RESET);
        SMI_Delay();
    }
    // Start code: 01
    HAL_GPIO_WritePin(MDIO_PORT, MDIO_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(MDC_PORT, MDC_PIN, GPIO_PIN_SET); SMI_Delay();
    HAL_GPIO_WritePin(MDC_PORT, MDC_PIN, GPIO_PIN_RESET); SMI_Delay();
    HAL_GPIO_WritePin(MDIO_PORT, MDIO_PIN, GPIO_PIN_SET);
    HAL_GPIO_WritePin(MDC_PORT, MDC_PIN, GPIO_PIN_SET); SMI_Delay();
    HAL_GPIO_WritePin(MDC_PORT, MDC_PIN, GPIO_PIN_RESET); SMI_Delay();
}

static void SMI_WriteBits(uint16_t data, int bits) {
    for (int i = bits - 1; i >= 0; i--) {
        HAL_GPIO_WritePin(MDIO_PORT, MDIO_PIN,
            (data >> i) & 1 ? GPIO_PIN_SET : GPIO_PIN_RESET);
        HAL_GPIO_WritePin(MDC_PORT, MDC_PIN, GPIO_PIN_SET);
        SMI_Delay();
        HAL_GPIO_WritePin(MDC_PORT, MDC_PIN, GPIO_PIN_RESET);
        SMI_Delay();
    }
}

static uint16_t SMI_ReadBits(int bits) {
    uint16_t val = 0;
    HAL_GPIO_WritePin(MDIO_PORT, MDIO_PIN, GPIO_PIN_RESET);
    for (int i = 0; i < bits; i++) {
        HAL_GPIO_WritePin(MDC_PORT, MDC_PIN, GPIO_PIN_SET);
        SMI_Delay();
        val = (val << 1) | (HAL_GPIO_ReadPin(MDIO_PORT, MDIO_PIN) ? 1 : 0);
        HAL_GPIO_WritePin(MDC_PORT, MDC_PIN, GPIO_PIN_RESET);
        SMI_Delay();
    }
    return val;
}

void PHY_Init(void) {
    SMI_GPIO_Init();
    // 硬件复位:PB12 → PHY RST (低有效)
    __HAL_RCC_GPIOB_CLK_ENABLE();
    GPIO_InitTypeDef gpio = {0};
    gpio.Pin = GPIO_PIN_12;
    gpio.Mode = GPIO_MODE_OUTPUT_PP;
    HAL_GPIO_Init(GPIOB, &gpio);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);
    HAL_Delay(10);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);
    HAL_Delay(100);
}

uint32_t PHY_ReadID(void) {
    uint32_t id = 0;
    // 读PHY ID1寄存器 (reg 2)
    SMI_Start();
    SMI_WriteBits((PHY_ADDR << 5) | (PHY_REG_ID1 << 0) | (0x02 << 10), 14); // OP=10读
    SMI_WriteBits(0x02, 2);  // TA
    id = SMI_ReadBits(16) << 16;
    // 读PHY ID2寄存器 (reg 3)
    SMI_Start();
    SMI_WriteBits((PHY_ADDR << 5) | (PHY_REG_ID2 << 0) | (0x02 << 10), 14);
    SMI_WriteBits(0x02, 2);
    id |= SMI_ReadBits(16);
    return id;
}
// core/main.c
#include "sys_init.h"
#include "phy_lan8720.h"

int main(void) {
    System_Init();

    // LED:PD12(绿色)
    __HAL_RCC_GPIOD_CLK_ENABLE();
    GPIO_InitTypeDef led = {0};
    led.Pin = GPIO_PIN_12;
    led.Mode = GPIO_MODE_OUTPUT_PP;
    HAL_GPIO_Init(GPIOD, &led);

    PHY_Init();
    uint32_t phy_id = PHY_ReadID();

    // LAN8720A 期望ID: 0x0007C0F1 (ID1=0x0007, ID2=0xC0F1)
    if (phy_id == 0x0007C0F1UL) {
        while (1) {
            HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
            HAL_Delay(500);  // 慢闪:PHY正常
        }
    } else {
        while (1) {
            HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
            HAL_Delay(100);  // 快闪:PHY异常
        }
    }
}
# Makefile(核心编译配置)
CC = arm-none-eabi-gcc
CFLAGS = -mcpu=cortex-m4 -mthumb -DSTM32F407xx -Icore -Ieth \
         -I$(HAL_DIR)/Inc -I$(CMSIS_DIR)/Include -O2 -Wall
OBJS = core/main.o core/sys_init.o eth/phy_lan8720.o

all: firmware.elf

firmware.elf: $(OBJS) startup.o
	$(CC) $(CFLAGS) -T stm32f407.ld -o $@ $^

%.o: %.c
	$(CC) $(CFLAGS) -c -o $@ $<

clean:
	rm -f *.o core/*.o eth/*.o firmware.elf

运行与验证

  1. 将上述文件放入工程目录,补充HAL库源码路径(HAL_DIR、CMSIS_DIR)及STM32F407启动文件startup.s和链接脚本stm32f407.ld(标准HAL工程模板自带)
  2. 执行make编译,生成firmware.elf,通过ST-Link烧录至开发板
  3. 观察板载LED(PD12):
现象判定
LED以500ms间隔慢闪PHY ID读取正确(0x0007C0F1),硬件链路正常,移植准备完成
LED以100ms间隔快闪PHY ID不匹配,检查PHY_ADDR(LAN8720A的RX/REFOUT引脚电平决定地址为0x01)及SMI时序
LED不亮系统时钟或GPIO初始化异常,检查HSE晶振及HAL_Delay配置

验证通过后,lwip_port/目录即为后续LwIP协议栈源码及netif驱动的接入位置,当前工程骨架已具备直接添加LwIP文件并编译的条件。