STM32C8T6裸机LwIP移植
准备说明
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源码的框架结构

在 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, ðernetif_init, ðernet_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, ðernetif_init, ðernet_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);
}
}
测试结果
串口输出:
网口输出:
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)