准备说明

  LwIP 是一款轻型 TCP/IP 协议栈,既可在操作系统中运行,也支持无操作系统(裸机)环境下的移植。本移植项目基于无操作系统(NO_SYS)​ 进行。尽管 LwIP 设计轻量,但其具备完整的 TCP/IP 基本功能,资源占用较少,非常适用于嵌入式系统开发。
本次移植所使用的硬件平台为 STM32F103C8T6,软件层面采用 LwIP-1.4.1​ 版本,以太网模块使用enc28j60,无操作系统裸机移植。
为完成移植,需要准备以下资源:
LwIP-1.4.1 源码:协议栈核心库。
contrib-1.4.1 包:提供与平台相关的移植层(port)文件。
ST 官方 LwIP 示例代码:作为移植的参考实现。

资源下载地址
  官方渠道:

    LwIP-1.4.1 及 contrib-1.4.1:http://download.savannah.gnu.org/releases/lwip/
    ST 官方 LwIP 示例:http://www.st.com/web/en/catalog/tools/FM147/CL1794/SC961/SS1743/PF257862?
s_searchtype=keyword

  网盘备用地址:
    LwIP-1.4.1 源码:https://pan.baidu.com/s/1PAXl_wQIfaFFjvuZ-LfULg?pwd=lwip提取码: lwip
    ST 官方 LwIP 参考实例:https://pan.baidu.com/s/13Fe3FBXbjo8-t9cQ8SghEw?pwd=hs2p提取码: hs2p
    contrib-1.4.1:https://pan.baidu.com/s/1BG-ns-IZt1zrON5Qw-YLxw?pwd=dyk6提取码: dyk6

LwIP源码的框架结构

LwIP目录结构
  在 src目录下,api文件夹提供了基于操作系统的应用层编程接口(如 Netconn API);core文件夹实现了 TCP/IP 协议栈的核心功能(包括 IPv4、IPv6 等);netif文件夹则包含了与网络硬件设备驱动相关的底层接口。这三个核心模块的源文件(.c)均存放在相应文件夹下,其对应的头文件(.h)则统一存放在 include目录中,以便项目管理与编译。此外,项目根目录下还包含 doc(开发文档)和 test(单元测试代码)等重要资源。

移植过程

文件准备

1.将LwIP-1.4.1的src文件夹下所有文件复制到自己工程目录,并将所有.c文件添加到工程中
在这里插入图片描述

在这里插入图片描述

2.将contrib-1.4.1_ 目录下的\contrib-1.4.1\ports\win32\include\arch的所有.h文件复制到Lwip文件夹中的自己创建的arch文件夹中。
在这里插入图片描述
其中 cc.h 包含了 LwIP 对于基本数据类型的定义。sys_arch.h 定义了与系统有关的信号量、邮箱及线程。

3.将contrib-1.4.1_ 目录下\contrib-1.4.1\ports\win32的sys_arch.h以及\contrib-1.4.1\ports\win32\include下的lwipopts.h也复制到创建的arch文件夹中。最终的arch文件夹内容和工程添加如下:

在这里插入图片描述
在这里插入图片描述
  lwipopts.h是移植过程中需要改动的配置文件,其是 LwIP 协议栈的用户配置文件,其核心作用是通过一系列宏定义来覆盖协议栈内部的默认设置,从而实现对协议栈功能、性能和内存占用的精细化裁剪和定制,以适应不同的应用场景和硬件资源限制。
  可以理解为其理解为一个中央控制面板或功能开关板,您通过在此文件中定义(或取消定义)各种宏,来精确地开启/关闭特定功能(如 IPv6、DNS、DHCP 等),并设置关键参数(如内存池大小、TCP 窗口尺寸、缓冲区数量等),最终塑造出一个适合当前项目的 LwIP 协议栈。

4.以太网模块驱动enc28j60准备
  enc28j60是一款经典的独立以太网控制器模块,它通过简单的 SPI 接口为微控制器(如 Arduino、STM32 等)提供完整的以太网接入能力。该模块集成了 MAC 和 PHY,符合 IEEE 802.3​ 标准,支持 10 Mbps​ 网络速率,仅需少量外部元件即可连接网络,极大简化了嵌入式设备的网络功能设计,非常适合物联网、工业控制等场景的联网需求。
  本例程中STM32使用SPI2与enc28j60进行通信,其驱动文件如下:

enc28j60.c

#include "enc28j60.h"
#include "spi.h"
#include "delay.h"
#include <stdio.h>
 
static unsigned char Enc28j60Bank;
static unsigned int NextPacketPtr;


u8 mymac[6]={0x04,0x02,0x35,0x00,0x00,0x01}; //MAC地址


uint8_t ENC_SPI_RW(uint8_t data)
{
    return SPI2_SendRead(data);
}

void ENC28J60_SPI2_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); //开启SPI2时钟
    
    //SPI GPIO配置
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15; //SCK\MOSI
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //SS
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入 
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14; //MISO
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    //其他GPIO口配置
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //RST
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    
    //SPI2初始化
    SPI_InitTypeDef SPI_InitStructure;
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //选择SPI模式(主机还是从机)
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //双线全双工
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //8位或16位数据帧(8位)
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //高位先行或低位先行(高位先行)
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; //波特率预分频器
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //时钟极性,即默认时钟电平值
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //采样边沿,第一个时钟边沿采样
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //采样软件SS模式,使用普通GPIO口模拟SS
    SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC校验多项式
    SPI_Init(SPI2, &SPI_InitStructure);
    
    SPI_Cmd(SPI2, ENABLE); //使能SPI2
    
    ENC28J60_CSH(); //SS默认高电平。初始不通信
    
    ENC28J60_RSTL();
    delay_ms(10);
    ENC28J60_RSTH();
    delay_ms(10);
    
}

//读取ENC28J60寄存器(带操作码) 
//op:操作码
//addr:寄存器地址/参数
//返回值:读到的数据
unsigned char enc28j60ReadOp(unsigned char op, unsigned char address)
{
    unsigned char dat = 0;

    ENC28J60_CSL();

    dat = op | (address & ADDR_MASK);
    ENC_SPI_RW(dat);
    dat = ENC_SPI_RW(0xFF);
    // 如果是读取MAC/MII寄存器,则第二次读到的数据才是正确的,见手册29页
    if(address & 0x80)
    {
        dat = ENC_SPI_RW(0xFF);
    }
    // release CS
    ENC28J60_CSH();
    return dat;
}


//写ENC28J60寄存器(带操作码)
//op:操作码
//addr:寄存器地址
//data:参数
void enc28j60WriteOp(unsigned char op, unsigned char address, unsigned char data)
{
    unsigned char dat = 0;

    ENC28J60_CSL();
    // issue write command
    dat = op | (address & ADDR_MASK);
    ENC_SPI_RW(dat);
    // write data
    dat = data;
    ENC_SPI_RW(dat);
    ENC28J60_CSH();
}


//读取ENC28J60接收缓存数据
//len:要读取的数据长度
//data:输出数据缓存区(末尾自动添加结束符)
void enc28j60ReadBuffer(unsigned int len, unsigned char* data)
{
    ENC28J60_CSL();
    // issue read command
    ENC_SPI_RW(ENC28J60_READ_BUF_MEM);
    while(len)
        {
        len--;
        // read data
        *data = (unsigned char)ENC_SPI_RW(0);
        data++;
        }
    *data='\0';
    ENC28J60_CSH();
}


//向ENC28J60写发送缓存数据
//len:要写入的数据长度
//data:数据缓存区 
void enc28j60WriteBuffer(unsigned int len, unsigned char* data)
{
    ENC28J60_CSL();
    // issue write command
    ENC_SPI_RW(ENC28J60_WRITE_BUF_MEM);

    while(len)
        {
        len--;
        ENC_SPI_RW(*data);
        data++;
        }
    ENC28J60_CSH();
}


//设置ENC28J60寄存器Bank
//ban:要设置的bank
void enc28j60SetBank(unsigned char address)
{
	// set the bank (if needed)
	if((address & BANK_MASK) != Enc28j60Bank)
	{
        // set the bank
        enc28j60WriteOp(ENC28J60_BIT_FIELD_CLR, ECON1, (ECON1_BSEL1|ECON1_BSEL0));
        enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, (address & BANK_MASK)>>5);
        Enc28j60Bank = (address & BANK_MASK);
	}
}


//读取ENC28J60指定寄存器 
//addr:寄存器地址
//返回值:读到的数据
unsigned char enc28j60Read(unsigned char address)
{
	// set the bank
	enc28j60SetBank(address);
	// do the read
	return enc28j60ReadOp(ENC28J60_READ_CTRL_REG, address);
}

//向ENC28J60指定寄存器写数据
//addr:寄存器地址
//data:要写入的数据		 
void enc28j60Write(unsigned char address, unsigned char data)
{
	// set the bank
	enc28j60SetBank(address);
	// do the write
	enc28j60WriteOp(ENC28J60_WRITE_CTRL_REG, address, data);
}


//向ENC28J60的PHY寄存器写入数据
//addr:寄存器地址
//data:要写入的数据		 
void enc28j60PhyWrite(unsigned char address, unsigned int data)
{
	// set the PHY register address
	enc28j60Write(MIREGADR, address);
	// write the PHY data
	enc28j60Write(MIWRL, data);
	enc28j60Write(MIWRH, data>>8);
	// wait until the PHY write completes
	while(enc28j60Read(MISTAT) & MISTAT_BUSY)
	{
		//_nop_();
	}
}

/**
  * @brief  读取ENC28J60的PHY寄存器
  * @param  address: PHY寄存器地址
  * @retval 读取到的16位PHY寄存器值
  */
uint16_t enc28j60PhyRead(uint8_t address)
{
    uint16_t retry = 0;
    uint16_t data = 0;
    
    // 1. 设置要读取的PHY寄存器地址
    enc28j60Write(MIREGADR, address);
    
    // 2. 设置MICMD.MIIRD位,启动读取操作
    enc28j60Write(MICMD, MICMD_MIIRD);
    
    // 3. 等待PHY读取完成(MISTAT.BUSY位清零)
    while(enc28j60Read(MISTAT) & MISTAT_BUSY)
    {
        retry++;
        if(retry > 0x0FFF) // 超时保护
        {
            break;
        }
    }
    
    // 4. 清除MII读取命令
    enc28j60Write(MICMD, 0x00);
    
    // 5. 读取PHY数据(先低字节后高字节)
    data = enc28j60Read(MIRDL);
    data |= (uint16_t)enc28j60Read(MIRDH) << 8;
    
    return data;
}

void enc28j60clkout(unsigned char clk)
{
    //setup clkout: 2 is 12.5MHz:
	enc28j60Write(ECOCON, clk & 0x7);
}


//初始化ENC28J60
//macaddr:MAC地址
//返回值:0,初始化成功;
//       1,初始化失败;
uint8_t enc28j60Init(uint8_t * macaddr)
{
    u16 retry=0;		  
    
      ENC28J60_RSTL();
    delay_ms(10);
    ENC28J60_RSTH();
    delay_ms(10);
    
    /*将ENC28J60的SPI NSS信号置高*/
    ENC28J60_CSH();
 
	/*软件复位ENC28J60*/
	enc28j60WriteOp(ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET);
    
    while(!(enc28j60Read(ESTAT)&ESTAT_CLKRDY)&&retry<500)//等待时钟稳定
	{
		retry++;
		delay_ms(1);
	};
	if(retry>=500) return 1;//ENC28J60初始化失败
 
	//设置接收缓冲区地址  8K字节容量
	NextPacketPtr = RXSTART_INIT;
 
	//接收缓冲器由一个硬件管理的循环FIFO 缓冲器构成。寄存器对ERXSTH:ERXSTL 和ERXNDH:ERXNDL 作为指针,定义
	//缓冲器的容量和其在存储器中的位置。ERXST和ERXND指向的字节均包含在FIFO缓冲器内。当从以太网接口接收数据
	//字节时,这些字节被顺序写入接收缓冲器。 但是当写入由ERXND 指向的存储单元后,硬件会自动将接收的下一
	//字节写入由ERXST 指向的存储单元。 因此接收硬件将不会写入FIFO 以外的单元。
 
	enc28j60Write(ERXSTL, RXSTART_INIT&0xFF);
	enc28j60Write(ERXSTH, RXSTART_INIT>>8);
 
    // set receive pointer address
    //ERXWRPTH:ERXWRPTL 寄存器定义硬件向FIFO 中的哪个位置写入其接收到的字节。 指针是只读的,在成
    //功接收到一个数据包后,硬件会自动更新指针。 指针可用于判断FIFO 内剩余空间的大小  8K-1500。
	enc28j60Write(ERXRDPTL, RXSTART_INIT&0xFF);
	enc28j60Write(ERXRDPTH, RXSTART_INIT>>8);
	// RX end
	enc28j60Write(ERXNDL, RXSTOP_INIT&0xFF);
	enc28j60Write(ERXNDH, RXSTOP_INIT>>8);
	// TX start	  1500
	enc28j60Write(ETXSTL, TXSTART_INIT&0xFF);
	enc28j60Write(ETXSTH, TXSTART_INIT>>8);
	// TX end
	enc28j60Write(ETXNDL, TXSTOP_INIT&0xFF);
	enc28j60Write(ETXNDH, TXSTOP_INIT>>8);
	// do bank 1 stuff, packet filter:
        // For broadcast packets we allow only ARP packtets
        // All other packets should be unicast only for our mac (MAADR)
        //
        // The pattern to match on is therefore
        // Type     ETH.DST
        // ARP      BROADCAST
        // 06 08 -- ff ff ff ff ff ff -> ip checksum for theses bytes=f7f9
        // in binary these poitions are:11 0000 0011 1111
        // This is hex 303F->EPMM0=0x3f,EPMM1=0x30
    //接收过滤器
	//UCEN:单播过滤器使能位
    // 当ANDOR = 1 时:
	// 1= 目标地址与本地MAC 地址不匹配的数据包将被丢弃
	// 0= 禁止过滤器
	// 当ANDOR = 0 时:
	// 1= 目标地址与本地MAC 地址匹配的数据包会被接受
	// 0 = 禁止过滤器
 
    //CRCEN:后过滤器CRC 校验使能位
	// 1 = 所有CRC 无效的数据包都将被丢弃
	// 0 = 不考虑CRC 是否有效
 
	// PMEN:格式匹配过滤器使能位
	// 当ANDOR = 1 时:
	// 1 = 数据包必须符合格式匹配条件,否则将被丢弃
	// 0 = 禁止过滤器
	// 当ANDOR = 0 时:
	// 1 = 符合格式匹配条件的数据包将被接受
	// 0 = 禁止过滤器
	enc28j60Write(ERXFCON, ERXFCON_UCEN|ERXFCON_CRCEN|ERXFCON_PMEN);
	
    //enc28j60Write(ERXFCON,0x00);
    
    enc28j60Write(EPMM0, 0x3f);
	enc28j60Write(EPMM1, 0x30);
	enc28j60Write(EPMCSL, 0xf9);
	enc28j60Write(EPMCSH, 0xf7);
	// do bank 2 stuff
	// enable MAC receive
	//bit 0 MARXEN:MAC 接收使能位
		// 1= 允许MAC 接收数据包
		// 0 = 禁止数据包接收
	//bit  3 TXPAUS:暂停控制帧发送使能位
		// 1= 允许MAC 发送暂停控制帧(用于全双工模式下的流量控制)
		//0 = 禁止暂停帧发送
	//bit 2 RXPAUS:暂停控制帧接收使能位
		// 1 = 当接收到暂停控制帧时,禁止发送(正常操作)
		// 0 = 忽略接收到的暂停控制帧
	enc28j60Write(MACON1, MACON1_MARXEN|MACON1_TXPAUS|MACON1_RXPAUS);
	// bring MAC out of reset
	//将MACON2 中的MARST 位清零,使MAC 退出复位状态。
	enc28j60Write(MACON2, 0x00);
	// enable automatic padding to 60bytes and CRC operations
	//bit 7-5 PADCFG2:PACDFG0:自动填充和CRC 配置位
		//111 = 用0 填充所有短帧至64 字节长,并追加一个有效的CRC
		//110 = 不自动填充短帧
		//101 = MAC 自动检测具有8100h 类型字段的VLAN 协议帧,并自动填充到64 字节长。如果不
		//是VLAN 帧,则填充至60 字节长。填充后还要追加一个有效的CRC
		//100 = 不自动填充短帧
		//011 = 用0 填充所有短帧至64 字节长,并追加一个有效的CRC
		//010 = 不自动填充短帧
		//001 = 用0 填充所有短帧至60 字节长,并追加一个有效的CRC
		//000 = 不自动填充短帧
	//bit 4 TXCRCEN:发送CRC 使能位
	  	// 1= 不管PADCFG如何,MAC都会在发送帧的末尾追加一个有效的CRC。 如果PADCFG规定要
		//追加有效的CRC,则必须将TXCRCEN 置1。
		// 0 = MAC不会追加CRC。 检查最后4 个字节,如果不是有效的CRC 则报告给发送状态向量。
	//bit 0 FULDPX:MAC 全双工使能位
		// 1= MAC工作在全双工模式下。 PHCON1.PDPXMD 位必须置1。
		// 0 = MAC工作在半双工模式下。 PHCON1.PDPXMD 位必须清零。
 
	enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, MACON3, MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN|MACON3_FULDPX);
    delay_ms(1000); // 延迟1秒
	// set inter-frame gap (non-back-to-back)
	//配置非背对背包间间隔寄存器的低字节MAIPGL。 大多数应用使用12h 编程该寄存器。
	//如果使用半双工模式,应编程非背对背包间间隔寄存器的高字节MAIPGH。 大多数应用使用0Ch
	//编程该寄存器。
	enc28j60Write(MAIPGL, 0x12);
	enc28j60Write(MAIPGH, 0x0C);
	// set inter-frame gap (back-to-back)
	//配置背对背包间间隔寄存器MABBIPG。当使用全双工模式时,大多数应用使用15h 编程该寄存
	//器,而使用半双工模式时则使用12h 进行编程。
	enc28j60Write(MABBIPG, 0x15);
 
	// Set the maximum packet size which the controller will accept
    // Do not send packets longer than MAX_FRAMELEN:
	// 最大帧长度  1500
	enc28j60Write(MAMXFLL, MAX_FRAMELEN&0xFF);
	enc28j60Write(MAMXFLH, MAX_FRAMELEN>>8);
 
	// write MAC address
	// NOTE: MAC address in ENC28J60 is byte-backward
	enc28j60Write(MAADR5, macaddr[0]);
	enc28j60Write(MAADR4, macaddr[1]);
	enc28j60Write(MAADR3, macaddr[2]);
	enc28j60Write(MAADR2, macaddr[3]);
	enc28j60Write(MAADR1, macaddr[4]);
	enc28j60Write(MAADR0, macaddr[5]);
 
	//配置PHY为全双工  LEDB为拉电流
	enc28j60PhyWrite(PHCON1, PHCON1_PDPXMD);
 
	// no loopback of transmitted frames	 禁止环回
    //HDLDIS:PHY 半双工环回禁止位
		//当PHCON1.PDPXMD = 1 或PHCON1.PLOOPBK = 1 时:
		//此位可被忽略。
		//当PHCON1.PDPXMD = 0 且PHCON1.PLOOPBK = 0 时:
	    // 1 =  要发送的数据仅通过双绞线接口发出
		// 0 = 要发送的数据会环回到MAC 并通过双绞线接口发出
 
	enc28j60PhyWrite(PHCON2, PHCON2_HDLDIS);
	// switch to bank 0
	//ECON1 寄存器
		//寄存器3-1 所示为ECON1 寄存器,它用于控制
		//ENC28J60 的主要功能。 ECON1 中包含接收使能、发
		//送请求、DMA 控制和存储区选择位。
	enc28j60SetBank(ECON1);
	// enable interrutps
	//EIE: 以太网中断允许寄存器
	//bit 7 INTIE: 全局INT 中断允许位
		// 1 =  允许中断事件驱动INT 引脚
		// 0 = 禁止所有INT 引脚的活动(引脚始终被驱动为高电平)
	//bit 6 PKTIE: 接收数据包待处理中断允许位
		// 1  =  允许接收数据包待处理中断
		// 0 = 禁止接收数据包待处理中断
 
	enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, EIE, EIE_INTIE|EIE_PKTIE);
	// enable packet reception
	//bit 2 RXEN:接收使能位
	    // 1  = 通过当前过滤器的数据包将被写入接收缓冲器
		//0 = 忽略所有接收的数据包
	enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);
    
    
    if(enc28j60Read(MAADR5)== macaddr[0])return 0;//初始化成功
	else return 1; 	  
 
    //指示灯状态:0x476 is PHLCON LEDA(绿)=links status, LEDB(红)=receive/transmit
    //enc28j60PhyWrite(PHLCON,0x7a4);
	//PHLCON:PHY 模块LED 控制寄存器
	enc28j60PhyWrite(PHLCON,0x0476);
 
	enc28j60clkout(2); // change clkout from 6.25MHz to 12.5MHz
}


//读取EREVID
// read the revision of the chip:
unsigned char enc28j60getrev(void)
{
	//在EREVID 内也存储了版本信息。 EREVID 是一个只读控
	//制寄存器,包含一个5 位标识符,用来标识器件特定硅片
	//的版本号
	return(enc28j60Read(EREVID));
}


//通过ENC28J60发送数据包到网络
//len:数据包大小
//packet:数据包
void enc28j60PacketSend(unsigned int len, unsigned char* packet)
{
	// Set the write pointer to start of transmit buffer area
	enc28j60Write(EWRPTL, TXSTART_INIT&0xFF);
	enc28j60Write(EWRPTH, TXSTART_INIT>>8);
 
	// Set the TXND pointer to correspond to the packet size given
	enc28j60Write(ETXNDL, (TXSTART_INIT+len)&0xFF);
	enc28j60Write(ETXNDH, (TXSTART_INIT+len)>>8);
 
	// write per-packet control byte (0x00 means use macon3 settings)
	enc28j60WriteOp(ENC28J60_WRITE_BUF_MEM, 0, 0x00);
 
	// copy the packet into the transmit buffer
	enc28j60WriteBuffer(len, packet);
 
	// send the contents of the transmit buffer onto the network
	enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS);
 
    // Reset the transmit logic problem. See Rev. B4 Silicon Errata point 12.
	if( (enc28j60Read(EIR) & EIR_TXERIF) )
	{
        enc28j60WriteOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRTS);
    }
}



//从网络获取一个数据包内容
//maxlen:数据包最大允许接收长度
//packet:数据包缓存区
//返回值:收到的数据包长度(字节)									  
// Gets a packet from the network receive buffer, if one is available.
// The packet will by headed by an ethernet header.
//      maxlen  The maximum acceptable length of a retrieved packet.
//      packet  Pointer where packet data should be stored.
// Returns: Packet length in bytes if a packet was retrieved, zero otherwise.
unsigned int enc28j60PacketReceive(unsigned int maxlen, unsigned char* packet)
{
	unsigned int rxstat;
	unsigned int len;
 
	// check if a packet has been received and buffered
        // The above does not work. See Rev. B4 Silicon Errata point 6.
	if( enc28j60Read(EPKTCNT) ==0 )  //收到的以太网数据包长度
	{
		return(0);
    }
 
	// Set the read pointer to the start of the received packet		 缓冲器读指针
	enc28j60Write(ERDPTL, (NextPacketPtr));
	enc28j60Write(ERDPTH, (NextPacketPtr)>>8);
 
	// read the next packet pointer
	NextPacketPtr  = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
	NextPacketPtr |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8;
 
	// read the packet length (see datasheet page 43)
	len  = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
	len |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8;
 
    len-=4; //remove the CRC count
 
	// read the receive status (see datasheet page 43)
	rxstat  = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);
	rxstat |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8;
 
	// limit retrieve length
    if (len>maxlen-1)
	{
        len=maxlen-1;
    }
 
    // check CRC and symbol errors (see datasheet page 44, table 7-3):
    // The ERXFCON.CRCEN is set by default. Normally we should not
    // need to check this.
    if ((rxstat & 0x80)==0)
	{
		// invalid
		len=0;
    }
	else
	{
        // copy the packet from the receive buffer
        enc28j60ReadBuffer(len, packet);
    }
 
	// Move the RX read pointer to the start of the next received packet
	// This frees the memory we just read out
	enc28j60Write(ERXRDPTL, (NextPacketPtr));
	enc28j60Write(ERXRDPTH, (NextPacketPtr)>>8);
 
	// decrement the packet counter indicate we are done with this packet
	enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC);
 
    return(len);
}

enc28j60.h

#ifndef __ENC28J60_H
#define __ENC28J60_H

#include "stm32f10x.h"                  // Device header

 
// ENC28J60 Control Registers
// Control register definitions are a combination of address,
// bank number, and Ethernet/MAC/PHY indicator bits.
// - Register address        (bits 0-4)
// - Bank number        (bits 5-6)
// - MAC/PHY indicator        (bit 7)
#define ADDR_MASK        0x1F
#define BANK_MASK        0x60
#define SPRD_MASK        0x80
// All-bank registers
#define EIE              0x1B
#define EIR              0x1C
#define ESTAT            0x1D
#define ECON2            0x1E
#define ECON1            0x1F
// Bank 0 registers
#define ERDPTL           (0x00|0x00)
#define ERDPTH           (0x01|0x00)
#define EWRPTL           (0x02|0x00)
#define EWRPTH           (0x03|0x00)
#define ETXSTL           (0x04|0x00)
#define ETXSTH           (0x05|0x00)
#define ETXNDL           (0x06|0x00)
#define ETXNDH           (0x07|0x00)
#define ERXSTL           (0x08|0x00)
#define ERXSTH           (0x09|0x00)
#define ERXNDL           (0x0A|0x00)
#define ERXNDH           (0x0B|0x00)
//ERXWRPTH:ERXWRPTL 寄存器定义硬件向FIFO 中
    //的哪个位置写入其接收到的字节。 指针是只读的,在成
    //功接收到一个数据包后,硬件会自动更新指针。 指针可
    //用于判断FIFO 内剩余空间的大小。
#define ERXRDPTL         (0x0C|0x00)
#define ERXRDPTH         (0x0D|0x00)
#define ERXWRPTL         (0x0E|0x00)
#define ERXWRPTH         (0x0F|0x00)
#define EDMASTL          (0x10|0x00)
#define EDMASTH          (0x11|0x00)
#define EDMANDL          (0x12|0x00)
#define EDMANDH          (0x13|0x00)
#define EDMADSTL         (0x14|0x00)
#define EDMADSTH         (0x15|0x00)
#define EDMACSL          (0x16|0x00)
#define EDMACSH          (0x17|0x00)
// Bank 1 registers
#define EHT0             (0x00|0x20)
#define EHT1             (0x01|0x20)
#define EHT2             (0x02|0x20)
#define EHT3             (0x03|0x20)
#define EHT4             (0x04|0x20)
#define EHT5             (0x05|0x20)
#define EHT6             (0x06|0x20)
#define EHT7             (0x07|0x20)
#define EPMM0            (0x08|0x20)
#define EPMM1            (0x09|0x20)
#define EPMM2            (0x0A|0x20)
#define EPMM3            (0x0B|0x20)
#define EPMM4            (0x0C|0x20)
#define EPMM5            (0x0D|0x20)
#define EPMM6            (0x0E|0x20)
#define EPMM7            (0x0F|0x20)
#define EPMCSL           (0x10|0x20)
#define EPMCSH           (0x11|0x20)
#define EPMOL            (0x14|0x20)
#define EPMOH            (0x15|0x20)
#define EWOLIE           (0x16|0x20)
#define EWOLIR           (0x17|0x20)
#define ERXFCON          (0x18|0x20)
#define EPKTCNT          (0x19|0x20)
// Bank 2 registers
#define MACON1           (0x00|0x40|0x80)
#define MACON2           (0x01|0x40|0x80)
#define MACON3           (0x02|0x40|0x80)
#define MACON4           (0x03|0x40|0x80)
#define MABBIPG          (0x04|0x40|0x80)
#define MAIPGL           (0x06|0x40|0x80)
#define MAIPGH           (0x07|0x40|0x80)
#define MACLCON1         (0x08|0x40|0x80)
#define MACLCON2         (0x09|0x40|0x80)
#define MAMXFLL          (0x0A|0x40|0x80)
#define MAMXFLH          (0x0B|0x40|0x80)
#define MAPHSUP          (0x0D|0x40|0x80)
#define MICON            (0x11|0x40|0x80)
#define MICMD            (0x12|0x40|0x80)
#define MIREGADR         (0x14|0x40|0x80)
#define MIWRL            (0x16|0x40|0x80)
#define MIWRH            (0x17|0x40|0x80)
#define MIRDL            (0x18|0x40|0x80)
#define MIRDH            (0x19|0x40|0x80)
// Bank 3 registers
#define MAADR1           (0x00|0x60|0x80)
#define MAADR0           (0x01|0x60|0x80)
#define MAADR3           (0x02|0x60|0x80)
#define MAADR2           (0x03|0x60|0x80)
#define MAADR5           (0x04|0x60|0x80)
#define MAADR4           (0x05|0x60|0x80)
#define EBSTSD           (0x06|0x60)
#define EBSTCON          (0x07|0x60)
#define EBSTCSL          (0x08|0x60)
#define EBSTCSH          (0x09|0x60)
#define MISTAT           (0x0A|0x60|0x80)
#define EREVID           (0x12|0x60)
#define ECOCON           (0x15|0x60)
#define EFLOCON          (0x17|0x60)
#define EPAUSL           (0x18|0x60)
#define EPAUSH           (0x19|0x60)
// PHY registers
#define PHCON1           0x00
#define PHSTAT1          0x01
#define PHHID1           0x02
#define PHHID2           0x03
#define PHCON2           0x10
#define PHSTAT2          0x11
#define PHIE             0x12
#define PHIR             0x13
#define PHLCON           0x14
 
// ENC28J60 ERXFCON Register Bit Definitions
#define ERXFCON_UCEN     0x80
#define ERXFCON_ANDOR    0x40
#define ERXFCON_CRCEN    0x20
#define ERXFCON_PMEN     0x10
#define ERXFCON_MPEN     0x08
#define ERXFCON_HTEN     0x04
#define ERXFCON_MCEN     0x02
#define ERXFCON_BCEN     0x01
// ENC28J60 EIE Register Bit Definitions
#define EIE_INTIE        0x80
#define EIE_PKTIE        0x40
#define EIE_DMAIE        0x20
#define EIE_LINKIE       0x10
#define EIE_TXIE         0x08
#define EIE_WOLIE        0x04
#define EIE_TXERIE       0x02
#define EIE_RXERIE       0x01
// ENC28J60 EIR Register Bit Definitions
#define EIR_PKTIF        0x40
#define EIR_DMAIF        0x20
#define EIR_LINKIF       0x10
#define EIR_TXIF         0x08
#define EIR_WOLIF        0x04
#define EIR_TXERIF       0x02
#define EIR_RXERIF       0x01
// ENC28J60 ESTAT Register Bit Definitions
#define ESTAT_INT        0x80
#define ESTAT_LATECOL    0x10
#define ESTAT_RXBUSY     0x04
#define ESTAT_TXABRT     0x02
#define ESTAT_CLKRDY     0x01
// ENC28J60 ECON2 Register Bit Definitions
#define ECON2_AUTOINC    0x80
#define ECON2_PKTDEC     0x40
#define ECON2_PWRSV      0x20
#define ECON2_VRPS       0x08
// ENC28J60 ECON1 Register Bit Definitions
#define ECON1_TXRST      0x80
#define ECON1_RXRST      0x40
#define ECON1_DMAST      0x20
#define ECON1_CSUMEN     0x10
#define ECON1_TXRTS      0x08
#define ECON1_RXEN       0x04
#define ECON1_BSEL1      0x02
#define ECON1_BSEL0      0x01
// ENC28J60 MACON1 Register Bit Definitions
#define MACON1_LOOPBK    0x10
#define MACON1_TXPAUS    0x08
#define MACON1_RXPAUS    0x04
#define MACON1_PASSALL   0x02
#define MACON1_MARXEN    0x01
// ENC28J60 MACON2 Register Bit Definitions
#define MACON2_MARST     0x80
#define MACON2_RNDRST    0x40
#define MACON2_MARXRST   0x08
#define MACON2_RFUNRST   0x04
#define MACON2_MATXRST   0x02
#define MACON2_TFUNRST   0x01
// ENC28J60 MACON3 Register Bit Definitions
#define MACON3_PADCFG2   0x80
#define MACON3_PADCFG1   0x40
#define MACON3_PADCFG0   0x20
#define MACON3_TXCRCEN   0x10
#define MACON3_PHDRLEN   0x08
#define MACON3_HFRMLEN   0x04
#define MACON3_FRMLNEN   0x02
#define MACON3_FULDPX    0x01
// ENC28J60 MICMD Register Bit Definitions
#define MICMD_MIISCAN    0x02
#define MICMD_MIIRD      0x01
// ENC28J60 MISTAT Register Bit Definitions
#define MISTAT_NVALID    0x04
#define MISTAT_SCAN      0x02
#define MISTAT_BUSY      0x01
// ENC28J60 PHY PHCON1 Register Bit Definitions
#define PHCON1_PRST      0x8000
#define PHCON1_PLOOPBK   0x4000
#define PHCON1_PPWRSV    0x0800
#define PHCON1_PDPXMD    0x0100
// ENC28J60 PHY PHSTAT1 Register Bit Definitions
#define PHSTAT1_PFDPX    0x1000
#define PHSTAT1_PHDPX    0x0800
#define PHSTAT1_LLSTAT   0x0004
#define PHSTAT1_JBSTAT   0x0002
// ENC28J60 PHY PHCON2 Register Bit Definitions
#define PHCON2_FRCLINK   0x4000
#define PHCON2_TXDIS     0x2000
#define PHCON2_JABBER    0x0400
#define PHCON2_HDLDIS    0x0100
 
// ENC28J60 Packet Control Byte Bit Definitions
#define PKTCTRL_PHUGEEN  0x08
#define PKTCTRL_PPADEN   0x04
#define PKTCTRL_PCRCEN   0x02
#define PKTCTRL_POVERRIDE 0x01
 
// SPI operation codes
#define ENC28J60_READ_CTRL_REG       0x00
#define ENC28J60_READ_BUF_MEM        0x3A
#define ENC28J60_WRITE_CTRL_REG      0x40
#define ENC28J60_WRITE_BUF_MEM       0x7A
#define ENC28J60_BIT_FIELD_SET       0x80
#define ENC28J60_BIT_FIELD_CLR       0xA0
#define ENC28J60_SOFT_RESET          0xFF
 
// The RXSTART_INIT should be zero. See Rev. B4 Silicon Errata
// buffer boundaries applied to internal 8K ram
// the entire available packet buffer space is allocated
//
// start with recbuf at 0/
#define RXSTART_INIT     0x0
// receive buffer end
#define RXSTOP_INIT      (0x1FFF-0x0600-1)
// start TX buffer at 0x1FFF-0x0600, pace for one full ethernet frame (~1500 bytes)
#define TXSTART_INIT     (0x1FFF-0x0600)
// stp TX buffer at end of mem
#define TXSTOP_INIT      0x1FFF
//
// max frame length which the conroller will accept:
#define        MAX_FRAMELEN        1500        // (note: maximum ethernet frame length would be 1518)
//#define MAX_FRAMELEN     600
 
#define ENC28J60_CSH()   GPIO_WriteBit(GPIOB, GPIO_Pin_12, Bit_SET)
#define ENC28J60_CSL()   GPIO_WriteBit(GPIOB, GPIO_Pin_12, Bit_RESET)

#define ENC28J60_RSTH()   GPIO_WriteBit(GPIOA, GPIO_Pin_8, Bit_SET)
#define ENC28J60_RSTL()   GPIO_WriteBit(GPIOA, GPIO_Pin_8, Bit_RESET)


extern u8 mymac[6]; //MAC地址


//SPI1初始化
//void	ENC28J60_Init(void);
void ENC28J60_SPI2_Init(void);
unsigned char enc28j60ReadOp(unsigned char op, unsigned char address);
void 	enc28j60WriteOp(unsigned char op, unsigned char address, unsigned char data);
void 	enc28j60ReadBuffer(unsigned int len, unsigned char* data);
void 	enc28j60WriteBuffer(unsigned int len, unsigned char* data);
void 	enc28j60SetBank(unsigned char address);
unsigned char enc28j60Read(unsigned char address);
void 	enc28j60Write(unsigned char address, unsigned char data);
void 	enc28j60PhyWrite(unsigned char address, unsigned int data);
uint16_t enc28j60PhyRead(uint8_t address);
void 	enc28j60clkout(unsigned char clk);
uint8_t 	enc28j60Init(unsigned char* macaddr);
unsigned char enc28j60getrev(void);
void 	enc28j60PacketSend(unsigned int len, unsigned char* packet);
unsigned int enc28j60PacketReceive(unsigned int maxlen, unsigned char* packet);
 
 
//SPI1读写一字节数据
//INT8U	ENC28J60_ReadWrite(INT8U writedat);
 
#endif

  在代码中置换数据的函数ENC_SPI_RW直接调用了spi驱动实现的SPI2_SendRead函数,其内容如下:

uint8_t SPI2_SendRead(uint8_t SendData)
{
    while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);
    
    SPI_I2S_SendData(SPI2, SendData);
    
    while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET);
    
    return SPI_I2S_ReceiveData(SPI2);
}

  其余驱动代码,例如串口等,这里不做展示,使用自己实现的,或者下载文章底部的示例工程

LwIP与网口驱动关联

  LwIP 协议栈为底层网络驱动提供了一套标准接口,主要包括网卡初始化、数据包发送与接收等基础操作。这些接口的定义位于 \lwip-1.4.1\src\netif目录下的 ethernetif.c文件中,该文件在项目工程中已被包含。
  由于具体网络硬件(如以太网控制器、PHY芯片)的差异,这些接口的具体实现需要开发者根据所选用的硬件平台来完成。
  移植工作的核心,就是基于 LwIP 官方源码中 ethernetif.c文件提供的模板,参照意法半导体(ST)等芯片厂商提供的示例,针对自己的硬件实现这些函数功能。其中,最需要关注并完成具体实现的是以下三个底层函数:
  low_level_init:负责初始化特定的网络硬件
  low_level_output:实现将数据包通过硬件实际发送出去的功能
  low_level_input:实现从硬件接收数据包的功能

  1) 在low_level_init函数主要修改设置MAC地址部分,其中mymac在enc28j60.c中定义
在这里插入图片描述

static void
low_level_init(struct netif *netif)
{
  struct ethernetif *ethernetif = netif->state;
  
  /* set MAC hardware address length */
  netif->hwaddr_len = ETHARP_HWADDR_LEN;

  /* set MAC hardware address */
  netif->hwaddr[0] = mymac[0];
  netif->hwaddr[1] = mymac[1];
  netif->hwaddr[2] = mymac[2];
  netif->hwaddr[3] = mymac[3];
  netif->hwaddr[4] = mymac[4];
  netif->hwaddr[5] = mymac[5];

  /* maximum transfer unit */
  netif->mtu = 1500;
  
  /* device capabilities */
  /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
  netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;
 
  /* Do whatever else is needed to initialize interface. */  
}

  2) 在low_level_output修改发送数据代码
在这里插入图片描述

static err_t
low_level_output(struct netif *netif, struct pbuf *p)
{
  struct ethernetif *ethernetif = netif->state;
  struct pbuf *q;

  //initiate transfer();
  
#if ETH_PAD_SIZE
  pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */
#endif

  for(q = p; q != NULL; q = q->next) {
    /* Send the data from the pbuf to the interface, one pbuf at a
       time. The size of the data in each pbuf is kept in the ->len
       variable. */
      enc28j60PacketSend(q->len, q->payload);
    //send data from(q->payload, q->len);
  }

  //signal that packet should be sent();

#if ETH_PAD_SIZE
  pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endif
  
  LINK_STATS_INC(link.xmit);

  return ERR_OK;
}

  3) 在low_level_input主要修改两个地方,第一个为获取数据长度,第二个为接收,对于enc28j60驱动直接在获取长度的首接收数据,故需要定义数据接收buf,即RecvDataBuf。
在这里插入图片描述
在这里插入图片描述

uint8_t RecvDataBuf[MAX_FRAMELEN + 20];

static struct pbuf *
low_level_input(struct netif *netif)
{
  struct ethernetif *ethernetif = netif->state;
  struct pbuf *p, *q;
  u16_t len;

  /* Obtain the size of the packet and put it into the "len"
     variable. */
  len = enc28j60PacketReceive(MAX_FRAMELEN, RecvDataBuf);

#if ETH_PAD_SIZE
  len += ETH_PAD_SIZE; /* allow room for Ethernet padding */
#endif

  /* We allocate a pbuf chain of pbufs from the pool. */
  p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
  
  if (p != NULL) {

#if ETH_PAD_SIZE
    pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */
#endif

    /* We iterate over the pbuf chain until we have read the entire
     * packet into the pbuf. */
     uint16_t i = 0;
    for(q = p; q != NULL; q = q->next) {
      /* Read enough bytes to fill this pbuf in the chain. The
       * available data in the pbuf is given by the q->len
       * variable.
       * This does not necessarily have to be a memcpy, you can also preallocate
       * pbufs for a DMA-enabled MAC and after receiving truncate it to the
       * actually received size. In this case, ensure the tot_len member of the
       * pbuf is the sum of the chained pbuf len members.
       */
        memcpy((u8_t*)q->payload, (u8_t*)&RecvDataBuf[i], q->len);//将接收到的数据分配到p指向的pbuf链表中
         i = i + q->len;
      //read data into(q->payload, q->len);
    }
    //acknowledge that packet has been read();
    
    if( i != p->tot_len ){ return 0;}  //相等的时候,表明到了数据尾

#if ETH_PAD_SIZE
    pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endif

    LINK_STATS_INC(link.recv);
  } else {
    //drop packet();
    LINK_STATS_INC(link.memerr);
    LINK_STATS_INC(link.drop);
  }

  return p;
}

配置文件lwipopts.h

lwipopts.h文件的配置可以覆盖opt.h文件中的默认配置,使得配置符合对应控制器使用。其初始版本来自于contrib-1.4.1。
针对STM32C8T6,由于其RAM只有20k,故需要对需内存的参数进行合理的修改,最终修改如版本如下:

/*
 * Copyright (c) 2001-2003 Swedish Institute of Computer Science.
 * All rights reserved. 
 * 
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
 * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
 * OF SUCH DAMAGE.
 *
 * This file is part of the lwIP TCP/IP stack.
 * 
 * Author: Adam Dunkels <adam@sics.se>
 *
 */
#ifndef __LWIPOPTS_H__
#define __LWIPOPTS_H__

#define NO_SYS                     1
#define LWIP_SOCKET               (NO_SYS==0)
#define LWIP_NETCONN              (NO_SYS==0)

#define LWIP_IGMP                  1
#define LWIP_ICMP                  1
#define LWIP_SNMP                  0

#define LWIP_DNS                   1

#define LWIP_HAVE_LOOPIF           1
#define LWIP_NETIF_LOOPBACK        1
#define LWIP_LOOPBACK_MAX_PBUFS    10

#define TCP_LISTEN_BACKLOG         0

#define LWIP_COMPAT_SOCKETS        1
#define LWIP_SO_RCVTIMEO           1
#define LWIP_SO_RCVBUF             1

#define LWIP_TCPIP_CORE_LOCKING    0

#define LWIP_NETIF_LINK_CALLBACK   1
#define LWIP_NETIF_STATUS_CALLBACK 1

#ifdef LWIP_DEBUG

#define LWIP_DBG_MIN_LEVEL         0
#define PPP_DEBUG                  LWIP_DBG_OFF
#define MEM_DEBUG                  LWIP_DBG_OFF
#define MEMP_DEBUG                 LWIP_DBG_OFF
#define PBUF_DEBUG                 LWIP_DBG_OFF
#define API_LIB_DEBUG              LWIP_DBG_OFF
#define API_MSG_DEBUG              LWIP_DBG_OFF
#define TCPIP_DEBUG                LWIP_DBG_OFF
#define NETIF_DEBUG                LWIP_DBG_OFF
#define SOCKETS_DEBUG              LWIP_DBG_OFF
#define DNS_DEBUG                  LWIP_DBG_OFF
#define AUTOIP_DEBUG               LWIP_DBG_OFF
#define DHCP_DEBUG                 LWIP_DBG_OFF
#define IP_DEBUG                   LWIP_DBG_OFF
#define IP_REASS_DEBUG             LWIP_DBG_OFF
#define ICMP_DEBUG                 LWIP_DBG_OFF
#define IGMP_DEBUG                 LWIP_DBG_OFF
#define UDP_DEBUG                  LWIP_DBG_OFF
#define TCP_DEBUG                  LWIP_DBG_OFF
#define TCP_INPUT_DEBUG            LWIP_DBG_OFF
#define TCP_OUTPUT_DEBUG           LWIP_DBG_OFF
#define TCP_RTO_DEBUG              LWIP_DBG_OFF
#define TCP_CWND_DEBUG             LWIP_DBG_OFF
#define TCP_WND_DEBUG              LWIP_DBG_OFF
#define TCP_FR_DEBUG               LWIP_DBG_OFF
#define TCP_QLEN_DEBUG             LWIP_DBG_OFF
#define TCP_RST_DEBUG              LWIP_DBG_OFF
#endif

#define LWIP_DBG_TYPES_ON         (LWIP_DBG_ON|LWIP_DBG_TRACE|LWIP_DBG_STATE|LWIP_DBG_FRESH|LWIP_DBG_HALT)


/* ---------- Memory options ---------- */
/* MEM_ALIGNMENT: should be set to the alignment of the CPU for which
   lwIP is compiled. 4 byte alignment -> define MEM_ALIGNMENT to 4, 2
   byte alignment -> define MEM_ALIGNMENT to 2. */
/* MSVC port: intel processors don't need 4-byte alignment,
   but are faster that way! */
#define MEM_ALIGNMENT           4

/* MEM_SIZE: the size of the heap memory. If the application will send
a lot of data that needs to be copied, this should be set high. */
#define MEM_SIZE               (1024 * 6)

/* MEMP_NUM_PBUF: the number of memp struct pbufs. If the application
   sends a lot of data out of ROM (or other static memory), this
   should be set high. */
#define MEMP_NUM_PBUF           12 //16
/* MEMP_NUM_RAW_PCB: the number of UDP protocol control blocks. One
   per active RAW "connection". */
#define MEMP_NUM_RAW_PCB        3
/* MEMP_NUM_UDP_PCB: the number of UDP protocol control blocks. One
   per active UDP "connection". */
#define MEMP_NUM_UDP_PCB        4
/* MEMP_NUM_TCP_PCB: the number of simulatenously active TCP
   connections. */
#define MEMP_NUM_TCP_PCB        5
/* MEMP_NUM_TCP_PCB_LISTEN: the number of listening TCP
   connections. */
#define MEMP_NUM_TCP_PCB_LISTEN 8
/* MEMP_NUM_TCP_SEG: the number of simultaneously queued TCP
   segments. */
#define MEMP_NUM_TCP_SEG        16
/* MEMP_NUM_SYS_TIMEOUT: the number of simulateously active
   timeouts. */
#define MEMP_NUM_SYS_TIMEOUT    15

/* The following four are used only with the sequential API and can be
   set to 0 if the application only will use the raw API. */
/* MEMP_NUM_NETBUF: the number of struct netbufs. */
#define MEMP_NUM_NETBUF         2
/* MEMP_NUM_NETCONN: the number of struct netconns. */
#define MEMP_NUM_NETCONN        10
/* MEMP_NUM_TCPIP_MSG_*: the number of struct tcpip_msg, which is used
   for sequential API communication and incoming packets. Used in
   src/api/tcpip.c. */
#define MEMP_NUM_TCPIP_MSG_API   16
#define MEMP_NUM_TCPIP_MSG_INPKT 16


/* ---------- Pbuf options ---------- */
/* PBUF_POOL_SIZE: the number of buffers in the pbuf pool. */
#define PBUF_POOL_SIZE          4 //120

/* PBUF_POOL_BUFSIZE: the size of each pbuf in the pbuf pool. */
#define PBUF_POOL_BUFSIZE       1500 //128

/* PBUF_LINK_HLEN: the number of bytes that should be allocated for a
   link level header. */
#define PBUF_LINK_HLEN          16

/** SYS_LIGHTWEIGHT_PROT
 * define SYS_LIGHTWEIGHT_PROT in lwipopts.h if you want inter-task protection
 * for certain critical regions during buffer allocation, deallocation and memory
 * allocation and deallocation.
 */
#define SYS_LIGHTWEIGHT_PROT    (NO_SYS==0)


/* ---------- TCP options ---------- */
#define LWIP_TCP                1
#define TCP_TTL                 255

/* Controls if TCP should queue segments that arrive out of
   order. Define to 0 if your device is low on memory. */
#define TCP_QUEUE_OOSEQ         0

/* TCP Maximum segment size. */
#define TCP_MSS                 (1500 - 40)

/* TCP sender buffer space (bytes). */
#define TCP_SND_BUF             (2 * TCP_MSS)

/* TCP sender buffer space (pbufs). This must be at least = 2 *
   TCP_SND_BUF/TCP_MSS for things to work. */
#define TCP_SND_QUEUELEN       (4 * TCP_SND_BUF/TCP_MSS)

/* TCP writable space (bytes). This must be less than or equal
   to TCP_SND_BUF. It is the amount of space which must be
   available in the tcp snd_buf for select to return writable */
#define TCP_SNDLOWAT           (TCP_SND_BUF/2)

/* TCP receive window. */
#define TCP_WND                 (2 * TCP_MSS)

/* Maximum number of retransmissions of data segments. */
#define TCP_MAXRTX              12

/* Maximum number of retransmissions of SYN segments. */
#define TCP_SYNMAXRTX           4


/* ---------- ARP options ---------- */
#define LWIP_ARP                1
#define ARP_TABLE_SIZE          10
#define ARP_QUEUEING            1


/* ---------- IP options ---------- */
/* Define IP_FORWARD to 1 if you wish to have the ability to forward
   IP packets across network interfaces. If you are going to run lwIP
   on a device with only one network interface, define this to 0. */
#define IP_FORWARD              1

/* IP reassembly and segmentation.These are orthogonal even
 * if they both deal with IP fragments */
#define IP_REASSEMBLY           1
#define IP_REASS_MAX_PBUFS      10
#define MEMP_NUM_REASSDATA      10
#define IP_FRAG                 1


/* ---------- ICMP options ---------- */
#define ICMP_TTL                255


/* ---------- DHCP options ---------- */
/* Define LWIP_DHCP to 1 if you want DHCP configuration of
   interfaces. */
#define LWIP_DHCP               0

/* 1 if you want to do an ARP check on the offered address
   (recommended). */
#define DHCP_DOES_ARP_CHECK    (LWIP_DHCP)


/* ---------- AUTOIP options ------- */
#define LWIP_AUTOIP             0
#define LWIP_DHCP_AUTOIP_COOP  (LWIP_DHCP && LWIP_AUTOIP)


/* ---------- UDP options ---------- */
#define LWIP_UDP                1
#define LWIP_UDPLITE            1
#define UDP_TTL                 255


/* ---------- Statistics options ---------- */

#define LWIP_STATS              1
#define LWIP_STATS_DISPLAY      1

#if LWIP_STATS
#define LINK_STATS              1
#define IP_STATS                1
#define ICMP_STATS              1
#define IGMP_STATS              1
#define IPFRAG_STATS            1
#define UDP_STATS               1
#define TCP_STATS               1
#define MEM_STATS               1
#define MEMP_STATS              1
#define PBUF_STATS              1
#define SYS_STATS               1
#endif /* LWIP_STATS */


/* ---------- PPP options ---------- */

#define PPP_SUPPORT             0      /* Set > 0 for PPP */

#if PPP_SUPPORT

#define NUM_PPP                 1      /* Max PPP sessions. */


/* Select modules to enable.  Ideally these would be set in the makefile but
 * we're limited by the command line length so you need to modify the settings
 * in this file.
 */
#define PPPOE_SUPPORT           1
#define PPPOS_SUPPORT           1

#define PAP_SUPPORT             1      /* Set > 0 for PAP. */
#define CHAP_SUPPORT            1      /* Set > 0 for CHAP. */
#define MSCHAP_SUPPORT          0      /* Set > 0 for MSCHAP (NOT FUNCTIONAL!) */
#define CBCP_SUPPORT            0      /* Set > 0 for CBCP (NOT FUNCTIONAL!) */
#define CCP_SUPPORT             0      /* Set > 0 for CCP (NOT FUNCTIONAL!) */
#define VJ_SUPPORT              1      /* Set > 0 for VJ header compression. */
#define MD5_SUPPORT             1      /* Set > 0 for MD5 (see also CHAP) */

#endif /* PPP_SUPPORT */

#endif /* __LWIPOPTS_H__ */

   由于本例程只保留了测试LwIP的内容和串口,如果最终编译发现还是空间不足,可以对内存相关参数进行修改。

定时器计时

  由于LwIP协议许多地方需要时间基准和时间间隔,故采用定时器完成时间计时,采用1ms的时间基准
timer.c

#include "timer.h"

void TIMER_Init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);//开启TIM4使能
    
    TIM_InternalClockConfig(TIM4); //配置为启用内部时钟
    
    //配置时基单元
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //滤波分频(暂时无用)
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
    TIM_TimeBaseInitStructure.TIM_Period = 1000 - 1; //ARR
    TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //PSC
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStructure);
    
    TIM_ClearFlag(TIM4, TIM_FLAG_Update); //配置时基单元函数最后为了使配置立马生效会软件触发更新事件,在此消除更新事件
    
    //TIM_GenerateEvent(TIM4, TIM_EventSource_Update); //软件产生事件
    //TIM_SelectOutputTrigger(TIM4, TIM_TRGOSource_Update); //配置更新输出事件
    
    
    //使能中断,即中断控制器
    TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
    
    //配置NVIC
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitStructure);
    
    //启动定时器
    TIM_Cmd(TIM4, ENABLE);
}

extern uint32_t lwip_time_cnt;
extern uint32_t LocalTime;

//1ms
void TIM4_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM4, TIM_IT_Update) == SET)
    {
        lwip_time_cnt++;
        LocalTime++;
        TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
    }
}


  修改sys_arch.c,由于使用的是无操作系统移植,只需要保留sys_now函数,最终版本如下:

#include <stdlib.h>
#include <stdio.h> /* sprintf() for task names */

#include <time.h>

#include <lwip/opt.h>
#include <lwip/arch.h>
#include <lwip/stats.h>
#include <lwip/debug.h>
#include <lwip/sys.h>

u32_t lwip_time_cnt = 0; 
u32_t sys_now()
{
  return lwip_time_cnt; //在定时器中断完成自增(ms)
}


移植结果测试

LWIP初始化函数 LwIP_Init以及轮询函数LwIP_Periodic_Handle的实现

  该实现参考ST例程的stsw-stm32026\STM32F107_ETH_LwIP_V1.0.0\Project\src目录下的netconf.c源文件,并去掉了设置MAC地址部分,因为在enc28j60硬件初始化时已经设置好了。
  首先将netconf.c和netconf.h复制进工程。修改LwIP_Init函数如下:

void LwIP_Init(void)
{
  struct ip_addr ipaddr;
  struct ip_addr netmask;
  struct ip_addr gw;
  //uint8_t macaddress[6]={0,0,0,0,0,1};

  /* Initializes the dynamic memory heap defined by MEM_SIZE.*/
  //lwip_init();
  
  mem_init();

  /* Initializes the memory pools defined by MEMP_NUM_x.*/
  memp_init();


#if LWIP_DHCP
  ipaddr.addr = 0;
  netmask.addr = 0;
  gw.addr = 0;

#else
  IP4_ADDR(&ipaddr, 192, 168, 1, 8);
  IP4_ADDR(&netmask, 255, 255, 255, 0);
  IP4_ADDR(&gw, 192, 168, 1, 1);
#endif

  //Set_MAC_Address(macaddress);

  /* - netif_add(struct netif *netif, struct ip_addr *ipaddr,
            struct ip_addr *netmask, struct ip_addr *gw,
            void *state, err_t (* init)(struct netif *netif),
            err_t (* input)(struct pbuf *p, struct netif *netif))
    
   Adds your network interface to the netif_list. Allocate a struct
  netif and pass a pointer to this structure as the first argument.
  Give pointers to cleared ip_addr structures when using DHCP,
  or fill them with sane numbers otherwise. The state pointer may be NULL.

  The init function pointer must point to a initialization function for
  your ethernet netif interface. The following code illustrates it's use.*/
  netif_add(&netif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &ethernet_input);

  /*  Registers the default network interface.*/
  netif_set_default(&netif);


#if LWIP_DHCP
  /*  Creates a new DHCP client for this interface on the first call.
  Note: you must call dhcp_fine_tmr() and dhcp_coarse_tmr() at
  the predefined regular intervals after starting the client.
  You can peek in the netif->dhcp struct for the actual DHCP status.*/
  dhcp_start(&netif);
#endif

  /*  When the netif is fully configured this function must be called.*/
  netif_set_up(&netif);

}

  可以看到netif_add(&netif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &ethernet_input);这一行包含了对ethernetif_init和ethernet_input函数的注册,故需要实现ethernetif.h文件。其中void ethernetif_input(struct netif *netif);函数在ethernetif.c定位为static静态函数,需要在ethernetif.c也去掉static静态修饰,才可以在外部调用

#ifndef __ETHERNETIF_H__
#define __ETHERNETIF_H__


#include "lwip/err.h"
#include "lwip/netif.h"

err_t ethernetif_init(struct netif *netif);
void ethernetif_input(struct netif *netif);

#endif 

  修改LwIP_Periodic_Handle函数如下:

void LwIP_Periodic_Handle(__IO uint32_t localtime)
{

  /* TCP periodic process every 250 ms */
  if (localtime - TCPTimer >= TCP_TMR_INTERVAL)
  {
    TCPTimer =  localtime;
    tcp_tmr();
  }
  /* ARP periodic process every 5s */
  if (localtime - ARPTimer >= ARP_TMR_INTERVAL)
  {
    ARPTimer =  localtime;
    etharp_tmr();
  }

#if LWIP_DHCP
  /* Fine DHCP periodic process every 500ms */
  if (localtime - DHCPfineTimer >= DHCP_FINE_TIMER_MSECS)
  {
    DHCPfineTimer =  localtime;
    dhcp_fine_tmr();
  }
  /* DHCP Coarse periodic process every 60s */
  if (localtime - DHCPcoarseTimer >= DHCP_COARSE_TIMER_MSECS)
  {
    DHCPcoarseTimer =  localtime;
    dhcp_coarse_tmr();
  }
#endif

}

TCP服务端测试

  至此移植所有修改完成,采用TCP服务端测试移植是否成功。代码如下:

  TCP初始化函数TcpTest_init

void TcpTest_init(void)
{
  struct tcp_pcb *pcb;
  pcb = tcp_new();      //建立通信的TCP控制块
  tcp_bind(pcb, IP_ADDR_ANY,23); //绑定端口号为23,绑定本地IP。因为只有一个网络接口,不需要指定IP地址。
  pcb = tcp_listen(pcb); //进入监听状态
  tcp_accept(pcb,TcpTest_accept); //设置有请求连接时候的回调函数,当有连接的时候,LWIP就会调用TcpTest_accept函数。
}

  TcpTest_accept函数,其中 #define INTRODUCT “TCP TEST” 在文件开始定义

static err_t TcpTest_accept(void *arg,struct tcp_pcb *pcb,err_t err)
{
  tcp_arg(pcb,NULL);
  tcp_recv(pcb,TcpTest_recv); //设置接收到数据后的回调函数;在建立连接后,当接收到数据之后,就会调用TcpTest_recv函数。
  tcp_write(pcb, INTRODUCT, strlen(INTRODUCT),1); //在建立连接的时候,向客户端发送INTRDUCT(宏)的字符串
  return  ERR_OK;
};

TcpTest_recv函数

// TcpTest_recv在接收到数据之后,将所接收到的数据回写给客户端同时通过串口打印出来。
static err_t TcpTest_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
    if (p == NULL) {
        // 连接关闭
        UART_Printf(USART1, "TCP connection closed\n");
        return tcp_close(tpcb);
    }
    
    if (err != ERR_OK) {
        pbuf_free(p);
        return err;
    }
    
    // 更新TCP接收窗口
    tcp_recved(tpcb, p->tot_len);
    
    UART_Printf(USART1, "Received %d bytes: ", p->tot_len);
    
    // 直接遍历pbuf链打印数据
    struct pbuf *q = p;
    while (q != NULL) {
        int i;
        for (i = 0; i < q->len; i++) {
            char data_char = ((char*)q->payload)[i];
                UART_Printf(USART1, "%c", data_char);
        }
        q = q->next;
    }
    UART_Printf(USART1, "\n");
    
    // 回声:将接收到的数据原样发回
    err_t write_err = tcp_write(tpcb, p->payload, p->tot_len, TCP_WRITE_FLAG_COPY);
    if (write_err == ERR_OK) {
        UART_Printf(USART1, "Echoed data back to client\n");
    }
    
    pbuf_free(p);
    return ERR_OK;
}

  最终main.c编写

#include "stm32f10x.h"

#include "delay.h"
#include "serial.h"
#include "enc28j60.h"
#include "lwip_config.h"

#include "timer.h"

uint32_t LocalTime = 0;

int main(void)
{
    delay_init();
    UART1_Init(115200);
    ENC28J60_SPI2_Init();
    
    while(enc28j60Init(mymac))
    {
        UART_Printf(USART1, "enc28j60Init error\n");
    }
    
    UART_Printf(USART1, "enc28j60Init s\n");
    
    LwIP_Init();
    TIMER_Init();
    
    TcpTest_init(); //建立TCP服务器
    
    while (1)
    {
        LwIP_Pkt_Handle();
        LwIP_Periodic_Handle(LocalTime);
    }
}


测试结果

  串口输出:
在这里插入图片描述
  网口输出:
在这里插入图片描述

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐