ENC28J60+STM32F103在STM32CubeIDE上移植lwIP2.1.2

ENC28J60+STM32F103在STM32CubeIDE上移植lwIP2.1.2

  为了学习lwIP,网购了一块正点原子的Mini STM32开发板和一个ENC28J60以太网模块,发现正点原子所给的示例代码是基于lwIP1.4.1的,有点偏老,最新版本的lwIP是2.1.2,使用的开发平台是Keil uVison5,而自己习惯了在STM32CubeIDE进行编程。STM32CubeIDE是免费的,其图形界面配置生成代码的方式非常便捷,而且其代码编辑能力丝毫不比Keil差,这是我选择STM32CubeIDE进行开发的原因。于是就产生了在STM32CubeIDE上移植lwIP的想法,经过几天的摸索,踩了不少坑,终于移植成功了。现在把移植步骤和要点作简要记录。 
​  一开始采用从http://download.savannah.nongnu.org/releases/lwip/上下载lwip-2.1.2.zip和contrib-2.1.0.zip,参照网上移植步骤一步一步来,发现比较麻烦,且容易出错。后来发现STM32CubeIDE(1.5.0版本)自己带有lwIP库,是最新的2.1.2版本,而且可以通过图形界面配置lwIP的参数,真是非常的方便。只是因为STM32F103芯片不带以太网控制器,所以无法在IDE的图形配置中调出lwIP的配置选项。​  
​  要想在STM32F103中使用STM32CubeIDE自带的lwIP包,首先必须选择一款带有以太网控制器的芯片(本例中选择STM32F407VE), 来建立一个临时工程,在临时工程中配置好lwIP,然后把配置好的lwIP包拷贝到F103的项目工程中,再修改lwIP的底层接口函数和ENC28J60的驱动函数。具体步骤实现如下:​
​  首先,使用STM32F407VE建立临时工程,在Connectivity中选择以太网控制器“ETH”,在Mode中选“RMII”(选择其他也可,只要Mode不为“Disable”就行,主要目的是使能“ETH”后,才能配置lwIP),如下图所示:
 

在这里插入图片描述

图1 以太网控制器的配置
  ​  “ETH”中其他的配置选项(Configuration)不需要更改,因为这是配置STM32F407VE自带的以太网控制器硬件参数,在STM32F103中不需要,后面要手工修改ENC28J60的驱动。

​  在配置“ETH”后,可使能lwIP,并进行相应的配置了:
 

在这里插入图片描述

图2: lwIP使能
  ​  在使能lwIP后,需要简单的配置下基本的网络参数,为简单起见,在移植时关闭DHCP(在测试时,直连PC,不通过路由器,无DHCP功能),采用输入静态IP地址的方式,在本例中,IP地址设置为192.168.1.100,操作如下:  

在这里插入图片描述

图3: 设置IP地址
​  关闭STM32F407的硬件Checksum,在发送和接收网络包时,为保障数据的正确性,会对包的头部进行校验,在F407中,校验是由以太网控制器的硬件完成的,所以默认配置是开启硬件校验的,但在ENC28J60+STM32F103环境中,是没有硬件校验功能的,只能采用软件生成Checksum,所以必须关闭硬件校验,否自会出现ping不通、发送的报文被接收方认为是错误的数据而丢弃的现象。  

在这里插入图片描述

图4: Checksum配置

​  下面接着对Key Options中内存堆的大小进行调整,原来大小为1600字节,调整为2048字节(ENC28J60驱动在接收数据包时申请的内存大约1600字节),其他的保持默认配置不用改动。本例为无操作系统移植,所以没有使能Middleware中的FREERTOS,如果想要RTOS的支持,可采用类似的方式进行配置。因为我们只要临时项目生成配置好的lwIP包,其他的项目配置内容,如系统时钟、中断、GPIO口等,都不用去管。至此可以点击“Generate Code”生成代码了,在生成代码的过程中,如果有警告信息,请忽视,这是警告项目中还有很多配置没设置好的原因。代码生成完后,项目根目录下多出两个文件夹“LWIP”和“Middllewares”:
 
在这里插入图片描述

图5: 系统生成的lwIP文件夹
  ​  “LWIP”文件夹中包含了lwIP的基本配置文件和硬件驱动接口文件,​“Middllewares”中包含的“src”和“arch”为lwIP的核心文件和平台移植相关代码,所有的代码STM32CubeIDE开发环境都帮我们弄好了,基本不需要修改,唯一需要修改的是ethernetif.c文件,其实开发环境也帮我们自动生成了,只不过驱动是针对STM32F407内部的以太网控制器的,和我们实际使用的ENC28J60不符合而已。

​  下一步是生成STM32F103的项目(MiniSTM32使用的是STM32F103RCT6),并把F407临时项目生成的“LWIP”和“Middllewares”文件夹拷贝到F103项目的根目录下,并按照下图在项目的“Properties”->“C/C++ General”->"Paths and Symbols"中添加头文件、代码的包含路径。
 

在这里插入图片描述

图6: 添加头文件、代码路径
 

​  Include包含的头文件比较多,如果一个一个添加嫌麻烦的话,可用下面的“Export Settings…”在F407临时项目中导出xml配置文件,直接导入移植目标项目中,然后删除包含“STM32F4xx”的条目即可。至此,lwIP包的核心部分移植基本完成,下面进行lwIP底层和ENC28J60驱动接口的更改、SPI相关GPIO口的设置。
​  因为本移植是在正点原子的STM32 Mini开发板上进行的,所以ENC28J60的驱动也是在正点原子提供的驱动基础上进行修改的,具体下载地址为http://www.openedv.com/docs/book-videos/zdyzshipin/4free/Lwip.html,包含两个文件:enc28j60.c和enc28j60.h,把这两个文件拷贝到移植项目的“Core/Src”文件下,注意,这两个文件的编码方式为GB2312,需要使用notepad++或sublime等软件转换为UTF-8编码,不然在STM32CubeIDE中打开时,中文注释会出现乱码。

​  正点原子ENC28J60模块有8个引脚,分别是: GND、 RST、MISO、SCK、MOSI、INT、CS 和 V3.3。其中GND和 V3.3用于给模块供电,MISO/MOSI/SCK用于SPI通信,CS是片选信号,INT为中断输出引脚,RST为模块复位信号。网上有很多ENC28J60模块是10引脚的,不能和Mini开发板直接连接,这点请注意。除了电源和地,其他6个引脚和STM32F103的GPIO的对应关系如下:
 
在这里插入图片描述

图7: 引脚对应关系
 

​  在本项目中,ENC28J60模块是和STM32F103的SPI1连接通信的,所以接下来是对SPI1端口以及相关的片选、中断、复位的GPIO端口设置(STM32F103的RCC基本时钟、JTAG调试的配置请参阅相关资料,这里就不赘述了)。首先设置SPI1模块,Mode设置为“Full-Duplex Master”,波特率的分频系数设为8,其他的参数保持默认即可:
 

在这里插入图片描述

图8: SPI1设置
 

​  其他GPIO端口设置:PA1(INT)设置为中断模式,其“GPIO mode”配置为下降沿触发方式(External Interrupt Mode with Falling edge trigger detection),“GPIO Pull-up/Pull-down”设置为上拉模式(Pull-up),PA4(RST)和PC4(CS)都设置成:GPIO mode为“Output Push Pull”;GPIO output level为“High”,具体见图9。
 
在这里插入图片描述

图9: 中断、片选、复位GPIO设置

 
​  接下来在NVIC中配置ENC28J60模块的中断引脚,中断连接STM32F103的PA1口,使能EXTI line1 interrupt,并设置中断优先级为2(具体优先级的设置可由自己决定,但需掌握STM32的中断机制):
 
在这里插入图片描述

图10: ENC28J60引脚中断设置
 

​  至此,图形界面的软件、硬件配置基本完成,进入软件修改部分,主要修改的内容分三块:ENC28J60驱动、lwIP底层接口和main函数。下面依次来说明软件的修改过程。
  在修改ENC28J60驱动前,需要在“Core/Src”目录下新建一个头文件“”enc28j60_sys.h",该文件定义了一些驱动需要的数据类型、GPIO端口位操作宏等(参考正点原子驱动中的sys.h),其内容如下:

#ifndef SRC_ENC28J60_SYS_H_
#define SRC_ENC28J60_SYS_H_

#include "stm32f1xx_hal.h"
#include "stm32f103xe.h"
#include "ethernetif.h"

typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef __IO uint32_t  vu32;

#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr))
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))
#define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C
#define GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C

#define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808
#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出
#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //输出
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入

#define delay_ms(x) HAL_Delay(x)		//用HAL库的延时函数替代  delay_ms()
#define INTX_DISABLE() __disable_irq() 	//关中断
#define INTX_ENABLE() __enable_irq()	//开中断
#define printf(x,...)	//因为没有使能串口,未重定义printf()函数,用宏定义去掉原来驱动的打印函数
#endif /* SRC_ENC28J60_SYS_H_ */

 
​  在前面的步骤中,已经把ENC28J60的驱动拷贝到“Core/Src”目录下,对enc28j60.h的修改比较简单,只需要把原来的#include "sys.h"替换为#include "enc28j60_sys.h"即可。
​  enc28j60.c的修改相对来说麻烦点,需要声明两个外部变量:“hspi1”为IDE自动生成的SPI1句柄,在main.c中定义,netif结构体gnetif,在lwip.c中定义,增加三个函数,其中两个SPI1_ReadWriteByte()、SPI1_SetSpeed()为SPI1的读写和速度调整函数,另一个为ENC28J60接收到数据而触发的中断回调函数HAL_GPIO_EXTI_Callback(),原来的中断处理函数“void EXTI1_IRQHandler(void)”和“void ENC28J60_ISRHandler(void)”都注释掉,enc28j60.c中变动部分的代码如下

extern SPI_HandleTypeDef hspi1; //IDE自动生成,在main.c文件中定义
extern struct netif gnetif;		//lwIP netif结构体,在lwip.c文件中定义

void SPI1_SetSpeed(u8 SPI_BaudRatePrescaler)
{
    
    
    assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));//判断有效性
    __HAL_SPI_DISABLE(&hspi1);            //关闭SPI
    hspi1.Instance->CR1&=0XFFC7;          //位3-5清零,用来设置波特率
    hspi1.Instance->CR1|=SPI_BaudRatePrescaler;//设置SPI速度
    __HAL_SPI_ENABLE(&hspi1);             //使能SPI

}

u8 SPI1_ReadWriteByte(u8 TxData)
{
    
    
    u8 Rxdata;
    HAL_SPI_TransmitReceive(&hspi1,&TxData,&Rxdata,1, 1000);
 	return Rxdata;          		    //返回收到的数据
}

//初始化ENC28J60
//macaddr:MAC地址
//返回值:0,初始化成功;
//       1,初始化失败;
u8 ENC28J60_Init(void)
{
    
    
	u8 version;
	u16 retry=0;
	u32 temp;
	
	__HAL_SPI_ENABLE(&hspi1); //使能SPI外设
	//初始化MAC地址
	temp=*(vu32*)(0x1FFFF7E8);	//获取STM32的唯一ID的前24位作为MAC地址后三字节
	enc28j60_dev.macaddr[0]=2;
	enc28j60_dev.macaddr[1]=0;
	enc28j60_dev.macaddr[2]=0;
	enc28j60_dev.macaddr[3]=(temp>>16)&0XFF;	//低三字节用STM32的唯一ID
	enc28j60_dev.macaddr[4]=(temp>>8)&0XFFF;
	enc28j60_dev.macaddr[5]=temp&0XFF;

	ENC28J60_RST=0;			//复位ENC28J60
	delay_ms(10);	 
	ENC28J60_RST=1;			//复位结束				    
	delay_ms(10);	
	ENC28J60_Write_Op(ENC28J60_SOFT_RESET,0,ENC28J60_SOFT_RESET);	//软件复位
	while(!(ENC28J60_Read(ESTAT)&ESTAT_CLKRDY)&&retry<250)	//等待时钟稳定
	{
    
    
		retry++;
		delay_ms(1);
	}	
	if(retry>=250)return 1; //ENC28J60初始化失败
	version=ENC28J60_Get_EREVID();			//获取ENC28J60的版本号
	printf("ENC28J60 Version:%d\r\n",version);	
	
	enc28j60_dev.NextPacketPtr=RXSTART_INIT;
	ENC28J60_Write(ERXSTL,RXSTART_INIT&0XFF);	//设置接收缓冲区起始地址低8位
	ENC28J60_Write(ERXSTH,RXSTART_INIT>>8);		//设置接收缓冲区起始地址高8位
	//设置接收接收字节
	ENC28J60_Write(ERXNDL,RXSTOP_INIT&0XFF);	
	ENC28J60_Write(ERXNDH,RXSTOP_INIT>>8);
	//设置发送起始字节
	ENC28J60_Write(ETXSTL,TXSTART_INIT&0XFF);
	ENC28J60_Write(ETXSTH,TXSTART_INIT>>8);
	//设置发送结束字节
	ENC28J60_Write(ETXNDL,TXSTOP_INIT&0XFF);
	ENC28J60_Write(ETXNDH,TXSTOP_INIT>>8);
	//ERXWRPTH:ERXWRPTL 寄存器定义硬件向FIFO 中
	//的哪个位置写入其接收到的字节。 指针是只读的,在成
	//功接收到一个数据包后,硬件会自动更新指针。 指针可
	//用于判断FIFO 内剩余空间的大小  8K-1500。 
	//设置接收读指针字节
	ENC28J60_Write(ERXRDPTL,RXSTART_INIT&0XFF);
	ENC28J60_Write(ERXRDPTH,RXSTART_INIT>>8);
	//接收过滤器
	ENC28J60_Write(ERXFCON,0);
	ENC28J60_Write(EPMM0,0X3F);
	ENC28J60_Write(EPMM1,0X30);
	ENC28J60_Write(EPMCSL,0Xf9);
	ENC28J60_Write(EPMCSH,0Xf7);

	ENC28J60_Write(MACON1,MACON1_MARXEN|MACON1_TXPAUS|MACON1_RXPAUS);
	//将MACON2 中的MARST 位清零,使MAC 退出复位状态。
	ENC28J60_Write(MACON2,0x00);

	ENC28J60_Write(MACON3,MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN|MACON3_FULDPX);
	// 最大帧长度 1518
	ENC28J60_Write(MAMXFLL,MAX_FRAMELEN&0XFF);
	ENC28J60_Write(MAMXFLH,MAX_FRAMELEN>>8);
	ENC28J60_Write(MABBIPG,0x15);
	ENC28J60_Write(MAIPGL,0x12);
	ENC28J60_Write(MAIPGH,0x0C);
	//设置MAC地址
	ENC28J60_Write(MAADR5,enc28j60_dev.macaddr[0]);
	ENC28J60_Write(MAADR4,enc28j60_dev.macaddr[1]);
	ENC28J60_Write(MAADR3,enc28j60_dev.macaddr[2]);
	ENC28J60_Write(MAADR2,enc28j60_dev.macaddr[3]);
	ENC28J60_Write(MAADR1,enc28j60_dev.macaddr[4]);
	ENC28J60_Write(MAADR0,enc28j60_dev.macaddr[5]);
	//配置PHY为全双工  LEDB为拉电流
	ENC28J60_PHY_Write(PHCON1,PHCON1_PDPXMD);	
	ENC28J60_PHY_Write(PHCON2,PHCON2_HDLDIS);
 	ENC28J60_Set_Bank(ECON1);
	ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,EIE,EIE_INTIE|EIE_PKTIE|EIE_TXIE|EIE_TXERIE|EIE_RXERIE);
	ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON1,ECON1_RXEN);
	printf("ENC28J60 Duplex:%s\r\n",ENC28J60_Get_Duplex()?"Full Duplex":"Half Duplex");	//获取双工方式
	return 0;
}

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    
    
	while(ENC28J60_INT == 0)
		{
    
    
		u8 status;
		u8 packetnum;
		u16 temp;
		ENC28J60_Write_Op(ENC28J60_BIT_FIELD_CLR,EIE,EIE_INTIE);	//关闭ENC28J60的全局中断
		status=ENC28J60_Read(EIR);	//读取以太网中断标志寄存器
		if(status&EIR_PKTIF)		//接收到数据,处理数据
		{
    
    
			ENC28J60_Write_Op(ENC28J60_BIT_FIELD_CLR,EIR,EIR_PKTIF);	//清除ENC28J60的接收中断标志位
			ethernetif_input(&gnetif);
		}
		if(status&EIR_TXIF)			//以太网发送中断
		{
    
    
			ENC28J60_Write_Op(ENC28J60_BIT_FIELD_CLR,EIR,EIR_TXIF);	//清除ENC28J60的接收中断标志位

		}
		if(status&EIR_RXERIF)		//接收错误中断标志位
		{
    
    
			ENC28J60_Write_Op(ENC28J60_BIT_FIELD_CLR,EIR,EIR_RXERIF);
			packetnum=ENC28J60_Read(EPKTCNT);
			temp=ENC28J60_Read(ERXRDPTH)<<8;		//读取高字节
			temp|=ENC28J60_Read(ERXRDPTL);			//读取低字节
			temp++;
			ENC28J60_Write(ERXRDPTL,temp&0XFF);		//先写入低字节
			ENC28J60_Write(ERXRDPTH,temp>>8);		//先写入低字节
			ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON2,ECON2_PKTDEC);
			printf("接收错误!接收到数据包个数:%d\r\n",packetnum);
		}
		if(status&EIR_TXERIF)		//发送错误中断标志位
		{
    
    
			ENC28J60_Write_Op(ENC28J60_BIT_FIELD_CLR,EIR,EIR_TXERIF);
			ENC28J60_Write_Op(ENC28J60_BIT_FIELD_CLR,ESTAT,ESTAT_LATECOL|ESTAT_TXABRT);
			printf("发送错误!\r\n");
		}
		ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,EIE,EIE_INTIE);	//打开ENC28J60的全局中断
		}
}

 
  lwIP底层接口代码的修改。在文件中增加extern dev_strucrt enc28j60_dev变量的声明,主要修改的函数是low_level_init()、low_level_output()、low_level_input()、ethernetif_input()和ethernetif_init()五个函数,保留原来的sys_jiffies()和sys_now()函数,其余的函数都删除掉,修改后的ethernet.c驱动文件如下:

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "lwip/opt.h"
#include "lwip/mem.h"
#include "lwip/memp.h"
#include "lwip/timeouts.h"
#include "netif/ethernet.h"
#include "netif/etharp.h"
#include "lwip/ethip6.h"
#include "ethernetif.h"
#include <string.h>
#include "../../Core/Src/enc28j60.h"
/* Network interface name */
#define IFNAME0 's'
#define IFNAME1 't'
extern dev_strucrt enc28j60_dev;

static void low_level_init(struct netif *netif)
{
    
    
	netif->hwaddr_len = ETHARP_HWADDR_LEN; //设置MAC地址长度,为6个字节
		//初始化MAC地址,和ENC28J60驱动中的MAC地址一致
		netif->hwaddr[0] = enc28j60_dev.macaddr[0];
		netif->hwaddr[1] = enc28j60_dev.macaddr[1];
		netif->hwaddr[2] = enc28j60_dev.macaddr[2];
		netif->hwaddr[3] = enc28j60_dev.macaddr[3];
		netif->hwaddr[4] = enc28j60_dev.macaddr[4];
		netif->hwaddr[5] = enc28j60_dev.macaddr[5];
		netif->mtu=1500; //最大允许传输单元,允许该网卡广播和ARP功能
		netif->flags = NETIF_FLAG_BROADCAST|NETIF_FLAG_ETHARP|NETIF_FLAG_LINK_UP;

}

static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
    
    
	struct pbuf *q;
		int l=0;
		u8 *buffer;
		buffer=mem_malloc(p->tot_len);	//申请内存
		if(buffer==NULL)printf("发送数据缓冲区内存申请失败\r\n");
		for(q=p;q!=NULL;q=q->next)
		{
    
    
			memcpy((u8_t*)&buffer[l], q->payload, q->len);
			l=l+q->len;
		}
		ENC28J60_Packet_Send(p->tot_len,buffer);
		mem_free(buffer);				//释放内存
		return ERR_OK;
}

static struct pbuf * low_level_input(struct netif *netif)
{
    
    
	struct pbuf *p,*q;
		u32 len;
		u8 *buffer;
		int l=0;

		p=NULL;
		buffer=mem_malloc(1600);			//申请内存
		if(buffer!=NULL)len=ENC28J60_Packet_Receive(MAX_FRAMELEN,buffer);	//接收数据
		else
		{
    
    
			printf("接收数据缓冲区内存申请失败\r\n");
			return p;
		}
		p=pbuf_alloc(PBUF_RAW,len,PBUF_POOL);	//pbufs内存池分配pbuf
		if(p!=NULL)
		{
    
    
			for(q=p;q!=NULL;q=q->next)
			{
    
    
				memcpy((u8_t*)q->payload,(u8_t*)&buffer[l], q->len);
				l=l+q->len;
			}
		}
		mem_free(buffer);					//释放内存
		return p;
}

void ethernetif_input(struct netif *netif)
{
    
    
	err_t err;
		struct pbuf *p;
		p=low_level_input(netif);   //调用low_level_input函数接收数据
		if(p==NULL) return;
		err=netif->input(p, netif); //调用netif结构体中的input字段(一个函数)来处理数据包
		if(err!=ERR_OK)
		{
    
    
			LWIP_DEBUGF(NETIF_DEBUG,("ethernetif_input: IP input error\n"));
			pbuf_free(p);
			p = NULL;
		}
		return;
}

err_t ethernetif_init(struct netif *netif)
{
    
    
	LWIP_ASSERT("netif!=NULL",(netif!=NULL));
#if LWIP_NETIF_HOSTNAME			//LWIP_NETIF_HOSTNAME
	netif->hostname="lwip";  	//初始化名称
#endif
	netif->name[0]=IFNAME0; 	//初始化变量netif的name字段
	netif->name[1]=IFNAME1; 	//在文件外定义这里不用关心具体值
	netif->output=etharp_output;//IP层发送数据包函数
	netif->linkoutput=low_level_output;//ARP模块发送数据包函数
	low_level_init(netif); 		//底层硬件初始化函数
	return ERR_OK;
}
void ethernetif_update_config(struct netif *netif)
{
    
    
	//定义的空函数,lwip.c中有对该函数的调用,避免编译报错
}
u32_t sys_jiffies(void)
{
    
    
  return HAL_GetTick();
}

u32_t sys_now(void)
{
    
    
  return HAL_GetTick();
}

​  最后是对main.c文件以及一些相关变量的调整,当ENC28J60模块接收到报文时,可采用两种方式读取数据:轮询方式和中断方式,在本移植中采用中断方式来接收报文。首先在main.c中声明四个外部函数:

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
extern void MX_LWIP_Init(void);
extern void MX_LWIP_Process(void);
extern unsigned char  ENC28J60_Init(void);
extern void sys_check_timeouts(void);
/* USER CODE END 0 */

再在main()函数while循环的前面添加ENC28J60_Init()和MX_LWIP_Init()两个初始化函数,在while循环内添加sys_check_timeouts()函数:

  /* USER CODE BEGIN WHILE */
	ENC28J60_Init();
	MX_LWIP_Init();
  while (1)
  {
    
    
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	sys_check_timeouts();
  }
  /* USER CODE END 3 */

另外需要在lwip.h头文件注释掉“extern ETH_HandleTypeDef heth;”,已防编译报错,这个变量是在临时项目中IDE自动生成的以太网控制器的类型声明,在本项目中不需要。
​  至此,全部的代码修改已完成,IP地址的定义在lwip.c文件中的MX_LWIP_Init()函数内,如需修改IP地址,请在此处进行。编译下载后就可进行验证测试了,用网线连接开发板和PC机网口,注意首先要关闭windows的防火墙,不然会出现ping不通的现象,配置PC上网卡的IP地址为:192.168.1.20,掩码:255.255.255.0,网关:192.168.1.1,完成后从电脑上ping开发板地址192.168.1.100,结果如下:
 
在这里插入图片描述

图11: ping测试结果
 

​  能ping通,则说明lwIP内部的协议栈运行正常。本次lwIP移植为无操作系统实验性质的移植,如需要RTOS的支持,可采用类似的方式进行,在有以太网控制器的芯片上建立临时项目,配置好lwIP和RTOS选项后,拷贝相关的内容至STM32F103项目下进行开发。

猜你喜欢

转载自blog.csdn.net/weixin_49364907/article/details/112848375