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
运行与验证
- 将上述文件放入工程目录,补充HAL库源码路径(
HAL_DIR、CMSIS_DIR)及STM32F407启动文件startup.s和链接脚本stm32f407.ld(标准HAL工程模板自带) - 执行
make编译,生成firmware.elf,通过ST-Link烧录至开发板 - 观察板载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文件并编译的条件。