在移植之前我们需要一个基础工程,这里面使用的是正点原子的 例4-1 UCOSIII移植 好的项目工程。
需要使用到 USMART 组件, 添加USMART 组件,参考正点原子,STM32F4开发指南实验 14 USMART 实验。将试验14USMART 实验下的USMART文件夹复制到当前的工程目录,然后将USMART组件添加到工程中。
注释 usmart_config.c 里面的以下内容:
我没有使用LCD,所有相关lcd的头文件,我都删除了。
因为 STM32F407 带有以太网 MAC 模块,因此 ST 也提供了以太网库,我们可以在 ST 官网:http://www.st.com/web/catalog/tools/FM147/CL1794/SC961/SS1743/LN1734/PF257906 下载解压后为名为:STM32F4x7_ETH_LwIP_V1.1.0,
在移植的过程中我们需要两个文件,LWIP 源码和 LWIP 官方的例程,我们可以在 LWIP 官
网:http://download.savannah.gnu.org/releases/lwip/下载这两个文件,如图所示为我们需要下载的两个文件。
其中 lwip-2.1.3.zip 为 LWIP 的官方源码,contrib-2.1.0.zip 包含官方的一些例程和我们移植时所需要的一些头文件。
一共有3个压缩包。
准备好移植所需要的文件,就开始 LWIP 的移植。我们首先将 ST 的以太网库添加到工程中,将STM32F4x7_ETH_LwIP_V1.1.0 文件中 Libraries文件夹下的STM32F4x7_ETH_Driver文件复制到我们基础工程的FWLIB文件夹下。
在STM32F4x7_ETH_Driver文件夹中一共有 3 个文件,stm32f4x7_eth.h、stm32f4x7_eth.c和 stm32f4x7_eth_conf_template.h。stm32f4x7_eth.h 为头文件,这个很好理解,stm32f4x7_eth.c为ST的以太网库,里面有很多关于STM32F4X7的以太网的函数,stm32f4x7_eth_conf_template.h里面定义了一些关于操作PHY芯片的信息,为了方便移植我们将stm32f4x7_eth_conf_template.h重命名为stm32f4x7_eth_conf.h。最后我们将 stm32f4x7_eth.c 添加到我们的工程中,并且添加头文件路径,添加完成后的基础工程如图 所示。
ST 提供给我们的 stm32f4x7_eth_conf.h文件只是一个参考文件,在这个文件是以DP83848为例编写的,而我们的 STM32F407 开发板使用的是 LAN8720,因此我们要根据LAN8720做一定的修改,修改后的 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
在上面代码中我们定义了操作 PHY 芯片时需要用到的延时配置,也定义了 LAN8720 的状态寄存器地址和 LAN8720 中的速度值、连接状态的掩码。通过读取 LAN8720 的状态寄存器并且与这两个掩码做计算即可得到网络的连接速度和双工模式。到这里我们编译一下工程,没有任何错误,如果有错误的话根据错误类型检查和修改工程。
在 stm32f4x7_eth.c 文件中针对不同的平台定义了四个数组:Rx_Buff[]、Tx_Buff[]、DMARxDscrTab[]和 DMATxDscrTab[],这四个数组占用了大量的 RAM。我们在这里将这四个变量屏蔽掉,如图所示。在其他文件中会采用内存管理的方式为这 4 个数组分配内存,这样我们就可以使用外部 SRAM,减少对 STM32F407 内部 RAM 的使用。
打开我们的 LWIP 无操作系统移植实验的 HARDWARE 文件夹,在里面有一个 ETHERNET文件,在这个文件中有lan8720.c和lan8720.h 这两个文件,这两个文件里面包含LAN8720 和STM32F407 自带的 MAC 的驱动程序,大家在移植的时候将 ETHERNET 文件拷贝到自己的HARDWARE 文 件 中。我修改了文件名LAN8720 , 并 且 将lan8720.c添 加 到 工 程 中 。我们将其加入到工程中。添加完lan8720.c 后的工程如图所示。
记得添加路径。
至此网卡驱动添加完成,我们编译一下提示如上图所示错误,这是因为在以太网 DMA接收中断服务函数 ETH_IRQHandler()中调用了lwip_pkt_handle()函数,而这个函数没有在lan8720.c文件中定义,这个错误提示不用管,lwip_pkt_handle()函数后续会讲解。
在工程目录下新建一个LWIP_2.1.3文件夹,在里面添加前面下载的的lwip文件夹lwip-2.1.3文件夹。(准备将 LwIP 源码添加到工程中,但是 LwIP源码太大了,我们不需要那么多东西,所以我们只需要将 LwIP 源码中的 src 文件文件夹添加进去即可,但是我发现 src 表示源码,但是不够明显,无法一眼就知道它是 LwIP 中的源码,那我们直接把文件夹重命名为“LWIP_2.1.3”,这样子就能让别人一目了然知道文件夹是什么内容了。)
我们现在直接添加文件到工程分组中即可,首先我们在工程中创建4个与 LwIP 相关的工程分组,其中三个分别用于存放 LwIP 源码中的三个文件,其对应关系具体见图。
每个分组中都添加对应文件夹下的源码,添加完成后的源码具体见下图
我们上面只是将 LWIP 源文件和以太网的驱动都添加到工程中,要将以太网驱动和 LWIP连接起来还需要一些其他文件,而这些文件非常重要,我们下面就讲解如何添加这些文件。
打开正点原子的网络实验 1 LWIP 无操作系统移植实验的 LWIP 文件夹可以发现有一个 arch 文件夹,将这个文件夹复制到自己工程中,在 arch 中有 5 个文件 cc.h、cpu.h、perf.h、sys_arch.h和 sys_arch.c。根据 sys_arch.txt 中的描述,cc.h 主要完成了协议栈内部使用的数据类型的定义,如果使用操作系统的话还有临界代码区保护等等。
打开正点原子的网络实验 1 LWIP 无操作系统移植实验的HARDWARE 文件timer文件复制过来。
LWIP 内核中有许多的周期性的定时器,相对应的定时处理函数也需要被周期性调用的,因为没有使用操作系统,所有需要我们自己使用定时器来实现。这里我们使用 STM32F407 定时器 3 提供这个系统时钟,定时器 3 定时周期为 10ms,定时器 3 的中断服务函数如下,lwip_localtime 是一个全局变量,在 lwip_comm.c 文件中有定义。 timer.c 添加到 HARDWARE 组下面,并且添加相应的头文件路径。
添加完这些文件后编译会报错,暂时先不管,继续往下看!
打开我们的网络实验 LWIP无操作系统移植实验的LWIP文件夹可以发现有一个lwip_app文件夹,将这个文件夹复制到自己工程中,lwip_app 文件夹用来放我们以后所有实验的代码。在lwip_app下有一个lwip_comm文件夹,这个文件中有lwip_comm.c、lwip_comm.h和lwipopts.h这三个文件,lwip_comm.c和 lwip_comm.h 是将 LWIP 源码和前面的以太网驱动库结合起来的桥梁!这两个文件非常重要,这两个文件有 ALIENTEK 提供。lwipopts.h 是用来裁剪和配置 LWIP的文件,以后我们想要使用 LWIP 的什么功能的话就在这个文件中配置就行了。
同样的,我们在工程中新建一个LWIP_app分组,并将lwip_comm.c文件添加到这个分组中并且添加相应的头文件路径,如图 所示。
打开正点原子的网络实验1LWIP 无操作系统移植实验,LWIP->lwip1.4.1->src->include->netif中我们会发现有个ethernetif.h文件,这个文件在 LWIP 源码中是不存在的,这个文件由ALIENTEK 提供,将ethernetif.h 文件复制到自己工程的相应位置。
在上面添加中间文件完成后,我们还有修改一下 LWIP 的源码,中间文件连接 LWIP 底层驱动和 LWIP,这样或多或少会对 LWIP 的源码做出一点小的改动,下面我们就讲解如何修改LWIP 源码。
我们按路径:lwip-1.4.1->src->core 可以发现在 core 文件下有一个 sys.c 文件,按路径lwip-1.4.1->src->include->lwip 可以发现有一个sys.h 文件。sys.c 和 sys.h 这两个文件和我们的SYSTEM 文件中的 sys.c 和 sys.h 重名,因此我们将 LWIP 中 sys.c 和 sys.h 改为 lwip_sys.c 和lwip_sys.h,然后在工程中将 LWIP 源码里面的#include “sys.h”代码也要改掉,更改为#include“lwip_sys.h”。
ethernetif.c 的文件路径为:LWIP->lwip1.4.1->src->netif。用我们网络实验 1 LWIP 无操作系统移植实验中的ethernetif.c 文件替代 LWIP 源码中的这个文件,在这个文件中有 5 个函数。
#include "netif/ethernetif.h"
#include "lan8720.h"
#include "lwip_comm.h"
#include "netif/etharp.h"
#include "string.h"
//由ethernetif_init()调用用于初始化硬件
//netif:网卡结构体指针
//返回值:ERR_OK,正常
// 其他,失败
static err_t low_level_init(struct netif *netif)
{
#ifdef CHECKSUM_BY_HARDWARE
int i;
#endif
netif->hwaddr_len = ETHARP_HWADDR_LEN; //设置MAC地址长度,为6个字节
//初始化MAC地址,设置什么地址由用户自己设置,但是不能与网络中其他设备MAC地址重复
netif->hwaddr[0]=lwipdev.mac[0];
netif->hwaddr[1]=lwipdev.mac[1];
netif->hwaddr[2]=lwipdev.mac[2];
netif->hwaddr[3]=lwipdev.mac[3];
netif->hwaddr[4]=lwipdev.mac[4];
netif->hwaddr[5]=lwipdev.mac[5];
netif->mtu=1500; //最大允许传输单元,允许该网卡广播和ARP功能
netif->flags = NETIF_FLAG_BROADCAST|NETIF_FLAG_ETHARP|NETIF_FLAG_LINK_UP;
ETH_MACAddressConfig(ETH_MAC_Address0, netif->hwaddr); //向STM32F4的MAC地址寄存器中写入MAC地址
ETH_DMATxDescChainInit(DMATxDscrTab, Tx_Buff, ETH_TXBUFNB);
ETH_DMARxDescChainInit(DMARxDscrTab, Rx_Buff, ETH_RXBUFNB);
#ifdef CHECKSUM_BY_HARDWARE //使用硬件帧校验
for(i=0;i<ETH_TXBUFNB;i++) //使能TCP,UDP和ICMP的发送帧校验,TCP,UDP和ICMP的接收帧校验在DMA中配置了
{
ETH_DMATxDescChecksumInsertionConfig(&DMATxDscrTab[i], ETH_DMATxDesc_ChecksumTCPUDPICMPFull);
}
#endif
ETH_Start(); //开启MAC和DMA
return ERR_OK;
}
//用于发送数据包的最底层函数(lwip通过netif->linkoutput指向该函数)
//netif:网卡结构体指针
//p:pbuf数据结构体指针
//返回值:ERR_OK,发送正常
// ERR_MEM,发送失败
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
u8 res;
struct pbuf *q;
int l = 0;
u8 *buffer=(u8 *)ETH_GetCurrentTxBuffer();
for(q=p;q!=NULL;q=q->next)
{
memcpy((u8_t*)&buffer[l], q->payload, q->len);
l=l+q->len;
}
res=ETH_Tx_Packet(l);
if(res==ETH_ERROR)return ERR_MEM;//返回错误状态
return ERR_OK;
}
///用于接收数据包的最底层函数
//neitif:网卡结构体指针
//返回值:pbuf数据结构体指针
static struct pbuf * low_level_input(struct netif *netif)
{
struct pbuf *p, *q;
u16_t len;
int l =0;
FrameTypeDef frame;
u8 *buffer;
p = NULL;
frame=ETH_Rx_Packet();
len=frame.length;//得到包大小
buffer=(u8 *)frame.buffer;//得到包数据地址
p=pbuf_alloc(PBUF_RAW,len,PBUF_POOL);//pbufs内存池分配pbuf
if(p!=NULL)
{
for(q=p;q!=NULL;q=q->next)
{
memcpy((u8_t*)q->payload,(u8_t*)&buffer[l], q->len);
l=l+q->len;
}
}
frame.descriptor->Status=ETH_DMARxDesc_OWN;//设置Rx描述符OWN位,buffer重归ETH DMA
if((ETH->DMASRÐ_DMASR_RBUS)!=(u32)RESET)//当Rx Buffer不可用位(RBUS)被设置的时候,重置它.恢复传输
{
ETH->DMASR=ETH_DMASR_RBUS;//重置ETH DMA RBUS位
ETH->DMARPDR=0;//恢复DMA接收
}
return p;
}
//网卡接收数据(lwip直接调用)
//netif:网卡结构体指针
//返回值:ERR_OK,发送正常
// ERR_MEM,发送失败
err_t ethernetif_input(struct netif *netif)
{
err_t err;
struct pbuf *p;
p=low_level_input(netif);//liuyao--
if(p==NULL) return ERR_MEM;
err=netif->input(p, netif);//input
if(err!=ERR_OK)
{
LWIP_DEBUGF(NETIF_DEBUG,("ethernetif_input: IP input error\n"));
pbuf_free(p);
p = NULL;
}
return err;
}
//使用low_level_init()函数来初始化网络
//netif:网卡结构体指针
//返回值:ERR_OK,正常
// 其他,失败
err_t ethernetif_init(struct netif *netif)
{
LWIP_ASSERT("netif!=NULL",(netif!=NULL));
#if LWIP_NETIF_HOSTNAME //LWIP_NETIF_HOSTNAME
netif->hostname="lwip"; //初始化名称
#endif
netif->name[0]=IFNAME0; //初始化变量netif的name字段
netif->name[1]=IFNAME1; //在文件外定义这里不用关心具体值
netif->output=etharp_output;//IP层发送数据包函数
netif->linkoutput=low_level_output;//ARP模块发送数据包函数
low_level_init(netif); //底层硬件初始化函数
return ERR_OK;
}