移植平台使用STM32F407为核心芯片的正点原子开发平台,网络芯片使用LAN8720。LWIP使用官方1.4.1版本。本文章只说移植,详细说明请参考正点原子《STM32F4 LWIP开发手册》
0、环境搭建
需要使用到空间开辟函数,我使用的是正点原子。正点原子空间开辟函数移植后可以直接使用。
1、准备工作
(1)lwip下载
官方下载地址:http://download.savannah.nongnu.org/releases/lwip/
lwip-1.4.1.zip为官方原码
contrib-1.4.1.zip包含官方例程和移植时所需要的一些头文件
(2)st以太网库
F407带有以太网MAC模块,ST提供了以太网库
官方下载地址:https://www.st.com/en/embedded-software/stsw-stm32070.html
2、添加修改以太网库
(1)添加以太网
解压后找到所需文件夹STM32F4x7_ETH_Driver,并修改stm32f4x7_eth_conf_template.h文件改为stm32f4x7_eth_conf.h。
将文件夹中的三个文件stm32f4x7_eth.c、stm32f4x7_eth.h、stm32f4x7_eth_conf.h添加到工程中
(2)修改stm32f4x7_eth_conf.h
#ifndef __STM32F4x7_ETH_CONF_H
#define __STM32F4x7_ETH_CONF_H
#include "stm32f4xx.h"
#define USE_ENHANCED_DMA_DESCRIPTORS
//如果使用自己定义的延时函数的话就注销掉下面一行代码,否则使用
//默认的低精度延时函数
//#define USE_Delay //使用默认延时函数,因此注销掉
#ifdef USE_Delay
#include "main.h"
#define _eth_delay_ Delay //Delay为用户自己提供的高精度延时函数
#else
#define _eth_delay_ ETH_Delay //默认的_eth_delay功能函数延时精度差
#endif
#ifdef CUSTOM_DRIVER_BUFFERS_CONFIG
//重新定义以太网接收和发送缓冲区的大小和数量
#define ETH_RX_BUF_SIZE ETH_MAX_PACKET_SIZE //接收缓冲区的大小
#define ETH_TX_BUF_SIZE ETH_MAX_PACKET_SIZE //发送缓冲区的大小
#define ETH_RXBUFNB 20 //接收缓冲区数量
#define ETH_TXBUFNB 5 //发送缓冲区数量
#endif
//*******************PHY配置块*******************
#ifdef USE_Delay
#define PHY_RESET_DELAY ((uint32_t)0x000000FF) //PHY复位延时
#define PHY_CONFIG_DELAY ((uint32_t)0x00000FFF) //PHY配置延时
#define ETH_REG_WRITE_DELAY ((uint32_t)0x00000001) //向以太网寄存器写数据时的延时
#else
#define PHY_RESET_DELAY ((uint32_t)0x000FFFFF) //PHY复位延时
#define PHY_CONFIG_DELAY ((uint32_t)0x00FFFFFF) //PHY配置延时
#define ETH_REG_WRITE_DELAY ((uint32_t)0x0000FFFF) //向以太网寄存器写数据时的延时
#endif
//LAN8720 PHY芯片的状态寄存器
#define PHY_SR ((uint16_t)31) //LAN8720的PHY状态寄存器地址
#define PHY_SPEED_STATUS ((uint16_t)0x0004) //LAN8720 PHY速度值掩码
#define PHY_DUPLEX_STATUS ((uint16_t)0x00010) //LAN8720 PHY连接状态值掩码
#endif
相比例程无需改动太多,原码中最后的部分为dp83848的状态寄存器设置,需要改为lan8720状态寄存器设置
(3)修改stm32f4x7_eth.c文件
//#if defined (__CC_ARM) /*!< ARM Compiler */
//__align(4)
//ETH_DMADESCTypeDef DMARxDscrTab[ETH_RXBUFNB];/* Ethernet Rx MA Descriptor */
//__align(4)
//ETH_DMADESCTypeDef DMATxDscrTab[ETH_TXBUFNB];/* Ethernet Tx DMA Descriptor */
//__align(4)
//uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE]; /* Ethernet Receive Buffer */
//__align(4)
//uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE]; /* Ethernet Transmit Buffer */
//#elif defined ( __ICCARM__ ) /*!< IAR Compiler */
//#pragma data_alignment=4
//ETH_DMADESCTypeDef DMARxDscrTab[ETH_RXBUFNB];/* Ethernet Rx MA Descriptor */
//#pragma data_alignment=4
//ETH_DMADESCTypeDef DMATxDscrTab[ETH_TXBUFNB];/* Ethernet Tx DMA Descriptor */
//#pragma data_alignment=4
//uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE]; /* Ethernet Receive Buffer */
//#pragma data_alignment=4
//uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE]; /* Ethernet Transmit Buffer */
//#elif defined (__GNUC__) /*!< GNU Compiler */
//ETH_DMADESCTypeDef DMARxDscrTab[ETH_RXBUFNB] __attribute__ ((aligned (4))); /* Ethernet Rx DMA Descriptor */
//ETH_DMADESCTypeDef DMATxDscrTab[ETH_TXBUFNB] __attribute__ ((aligned (4))); /* Ethernet Tx DMA Descriptor */
//uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE] __attribute__ ((aligned (4))); /* Ethernet Receive Buffer */
//uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE] __attribute__ ((aligned (4))); /* Ethernet Transmit Buffer */
//#elif defined (__TASKING__) /*!< TASKING Compiler */
//__align(4)
//ETH_DMADESCTypeDef DMARxDscrTab[ETH_RXBUFNB];/* Ethernet Rx MA Descriptor */
//__align(4)
//ETH_DMADESCTypeDef DMATxDscrTab[ETH_TXBUFNB];/* Ethernet Tx DMA Descriptor */
//__align(4)
//uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE]; /* Ethernet Receive Buffer */
//__align(4)
//uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE]; /* Ethernet Transmit Buffer */
//#endif /* __CC_ARM */
3、添加网卡驱动
添加lan8720.c和lan8720.h到工程,在lan8720.c中对上述屏蔽的四个数组使用动态开闭进行重定义,并添加官方外设库stm32f4xx_syscfg.c
4、添加LWIP原码
工程中添加三个分组并添加头文件
LWIP-NETIF:lwip-1.4.1->src->netif路径下etharp.c和ethernetif.c文件
LWIP-CORE:lwip-1.4.1->src->core->ipv4路径下所有文件
lwip-1.4.1->src->core路径下所有.c文件
LWIP-API: lwip-1.4.1->src->api路径下所有文件
5、arch文件夹
arch文件夹中包含5个文件:
cc.h:下列为不含操作系统的定义
#ifndef __CC_H__
#define __CC_H__
#include "cpu.h"
#include "stdio.h"
//定义与平台无关的数据类型
typedef unsigned char u8_t;
typedef signed char s8_t;
typedef unsigned short u16_t;
typedef signed short s16_t;
typedef unsigned long u32_t;
typedef signed long s32_t;
typedef u32_t mem_ptr_t; //内存地址型数据
typedef int sys_prot_t; //临界保护型数据
//根据不同的编译器定义了一些符号
/* define compiler specific symbols */
#if defined (__ICCARM__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#define PACK_STRUCT_USE_INCLUDES
#elif defined (__CC_ARM)
#define PACK_STRUCT_BEGIN __packed
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#elif defined (__GNUC__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT __attribute__ ((__packed__))
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#elif defined (__TASKING__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#endif
//lwip用printf调试时使用到的一些类型
/*---define (sn)printf formatters for these lwip types, for lwip DEBUG/STATS--*/
#define U16_F "4d"
#define S16_F "4d"
#define X16_F "4x"
#define U32_F "8ld"
#define S32_F "8ld"
#define X32_F "8lx"
//宏定义
#ifndef LWIP_PLATFORM_ASSERT
#define LWIP_PLATFORM_ASSERT(x) \
do { printf("Assertion \"%s\" failed at line %d in %s\r\n", x, __LINE__, __FILE__); \
} while(0)
#endif
#ifndef LWIP_PLATFORM_DIAG
#define LWIP_PLATFORM_DIAG(x) do {printf x;} while(0)
#endif
#endif
若使用操作系统则添加下列代码
//使用操作系统时的临界区保护--ucosii为例子
//当定义了OS_CRITICAL_METHOD时说明使用了ucosii
/*-------------critical region protection (depends on uC/OS-II setting)-------*/
#if OS_CRITICAL_METHOD == 1
#define SYS_ARCH_DECL_PROTECT(lev)
#define SYS_ARCH_PROTECT(lev) CPU_INT_DIS()
#define SYS_ARCH_UNPROTECT(lev) CPU_INT_EN()
#endif
#if OS_CRITICAL_METHOD == 3 //method 3 is used in this port.
#define SYS_ARCH_DECL_PROTECT(lev) u32_t lev
#define SYS_ARCH_PROTECT(lev) lev = OS_CPU_SR_Save()
#define SYS_ARCH_UNPROTECT(lev) OS_CPU_SR_Restore(lev)
#endif
cpu.h:定义了cpu大小端模式,stm32为小端模式
#ifndef __CPU_H__
#define __CPU_H__
#define BYTE_ORDER LITTLE_ENDIAN //小端模式
#endif /* __CPU_H__ */
perf.h:我们不做任何测量和统计,因此两个宏定义为空
#ifndef __PERF_H__
#define __PERF_H__
#define PERF_START //空定义
#define PERF_STOP(x) //空定义
#endif /* __PERF_H__ */
sys_arch.c:由sys_arch.txt中可知,文件中主要定义了信号量、邮箱等操作系统用到的函数。无操作系统时只是获取了系统时间为lwip提供计时。
extern uint32_t lwip_localtime;//lwip本地时间计数器,单位:ms
u32 sys_now(void){
return lwip_localtime;
}
sys_arch.h:函数声明
6、添加通用文件:
工程创建两个分类:
lwip-arch:添加sys_arch.c
lwip-com:添加lwip-comm.c。文件为正点原子代码,不再展示,无需修改
7、LWIP原码文件修改
(1)LWIP原码文件名
正点原子自定义的sys.c和sys.h与LWIP源码中的的文件重名,因此需要修改其中一个
(2)ethernetif.c文件修改
文件中有五个函数,后三个与网卡密切相关,函数内容参考正点原子代码不再展示,说明如下:
//对low_level_init()函数简单封装,并且初始化了netif的相关字段
err_t ethernetif_init(struct netif *netif);
//对low_level_input()函数进行封装,然后将接受的数据送入制定网卡结构
err_t ethernetif_input(struct netif *netif);
//主要完成网卡的复位、协议栈网络接口管理结构体netif中相关字节的初始化、发送和接受DMA描述符链表的初始化,硬件帧校验的开启并且打开以太网
static err_t low_level_init(struct netif *netif);
//用于发送数据
static err_t low_level_output(struct netif *netif, struct pbuf *p);
//从网卡中提取数据,并将数据封装在pbuf结构体中供LWIP使用
static struct pbuf * low_level_input(struct netif *netif);
lwip源码中缺少ethernetif.h文件,需要自写
////网卡的名字
#define IFNAME0 'e'
#define IFNAME1 'n'
err_t ethernetif_init(struct netif *netif);
err_t ethernetif_input(struct netif *netif);
static err_t low_level_init(struct netif *netif);
static err_t low_level_output(struct netif *netif, struct pbuf *p);
static struct pbuf * low_level_input(struct netif *netif);
#endif
(3)修改mem.c和memp.c文件
lwip中的内存池和内存堆使用大数组,将其定义屏蔽掉并使用指针定义
并在memp.c文件330行左右添加如下代码
//得到memp_memory数组大小
u32_t memp_get_memorysize(void)
{
u32_t length=0;
length=(
MEM_ALIGNMENT-1 //全局型数组 为所有POOL分配的内存空间
//MEMP_SIZE表示需要在每个POOL头部预留的空间 MEMP_SIZE = 0
#define LWIP_MEMPOOL(name,num,size,desc)+((num)*(MEMP_SIZE+MEMP_ALIGN_SIZE(size)))
#include "lwip/memp_std.h"
);
return length;
}
(4)修改icmp.c文件
修改icmp.c使其支持硬件帧校验。在195行左右更改如下
//屏蔽代码
//#if CHECKSUM_GEN_ICMP
// /* adjust the checksum */
// if (iecho->chksum >= PP_HTONS(0xffffU - (ICMP_ECHO << 8))) {
// iecho->chksum += PP_HTONS(ICMP_ECHO << 8) + 1;
// } else {
// iecho->chksum += PP_HTONS(ICMP_ECHO << 8);
// }
//#else /* CHECKSUM_GEN_ICMP */
// iecho->chksum = 0;
//#endif /* CHECKSUM_GEN_ICMP */
//添加代码
#ifdef CHECKSUM_BY_HARDWARE
iecho->chksum = 0;
#else
/* adjust the checksum */
if (iecho->chksum >= htons(0xffff - (ICMP_ECHO << 8))) {
iecho->chksum += htons(ICMP_ECHO << 8) + 1;
} else {
iecho->chksum += htons(ICMP_ECHO << 8);
}
#endif
8、LWIP裁剪与配置
lwip源码中有个opt.h文件,为裁剪配置文件,但一般我们在lwipopt.h文件中对其进行配置,不在详细说明
9、主函数设计
int main(void)
{
uint8_t err;
SysLED_Init();
SysTick_init();
USART_Config();
usmart_dev.init(168);
FSMC_SRAM_Init();
my_mem_init(SRAMIN); //初始化内部内存池
my_mem_init(SRAMEX); //初始化外部内存池
my_mem_init(SRAMCCM); //初始化CCM内存池
lwip_comm_init();
printf("初始化完成\r\n");
usmartled(1);
while(1)
{
if(sysledrun[0]>=sysledrun[1])
{
sysledrun[0] = 0;
SysLed();
}
lwip_periodic_handle(); //LWIP内核需要定时处理的函数
}
}