目录
前言
本文主要对LWIP简单介绍,并对LWIP进行基于UCOS操作系统的移植,以及对LWIP相关编程接口作简单介绍
一、以太网协议简介
1.1 TCP/IP协议简介
TCP/IP 中文名为传输控制协议/因特网互联协议,又名网络通讯协议,是 Internet 最基本的协议、Internet 国际互联网络的基础,由网络层的 IP 协议和传输层的 TCP 协议组成。TCP/IP 协议不是 TCP 和 IP 这两个协议的合称,而是指因特网整个 TCP/IP 协议族。从协议分层模型方面来讲,TCP/IP 由四个层次组成:网络接口层、网络层、传输层、应用层。OSI 是传统的开放式系统互连参考模型,该模型将 TCP/IP 分为七层:物理层、数据链路层(网络接口层)、网络层(网络层)、传输层(传输层)、会话层、表示层和应用层(应用层)。TCP/IP 模型与 OSI模型对比如表所示
在LWIP实验中 PHY 层芯片 LAN8720 相当于物理层,STM32F407 自带的 MAC 层相当于数据链路层,而 LWIP 提供的就是网络层、传输层的功能,应用层是需要用户自己根据自己想要的功能去实现的。
1.2 STM32的ETH外设
STM32F407 芯片自带以太网模块,该模块包括带专用 DMA 控制器的 MAC 802.3(介质访问控制)控制器,支持介质独立接口 (MII) 和简化介质独立接口 (RMII),并自带了一个用于外部 PHY 通信的 SMI 接口,通过一组配置寄存器,用户可以为 MAC 控制器和 DMA 控制器选择所需模式和功能。
1.2.1 MAC子层
MAC 子层是属于数据链路层的下半部分,它主要负责与物理层进行数据交接,如是否可以发送数据,发送的数据是否正确,对数据流进行控制等。它自动对来自上层的数据包加上一些控制信号,交给物理层。接收方得到正常数据时,自动去除 MAC 控制信号,把该数据包交给上层
IEEE 对以太网上传输的数据包格式也进行了统一规定,MAC 数据包由前导字段、帧起始定界符、目标地址、源地址、数据包类型、数据域、填充域、校验和域组成。
1.2.2 SMI站管理接口
站管理接口(SMI) 允许应用程序通过 2 条线:时钟(MDC)和数据线(MDIO)访问任意 PHY 寄存器。该接口支持访问多达 32 个 PHY。应用程序可以从 32 个 PHY 中选择一个 PHY,然后从任意 PHY 包含的 32 个寄存器中选择一个寄存器,发送控制数据或接收状态信息。任意给定时间内只能对一个 PHY 中的一个寄存器进行寻址。在 MAC 对 PHY 进行读写操作的时候,应用程序不能修改 MII 的地址寄存器和 MII 的数据寄存器。在此期间对 MII 地址寄存器或 MII 数据寄存器执行的写操作将会被忽略。
1.2.3 MII和RMII接口
介质独立接口(MII)用于MAC层与PHY层进行数据传输。而精简介质独立接口(RMII) 规范降低 10/100 Mbit/s 下微控制器以太网外设与外部 PHY 间的引脚数。根据 IEEE 802.3u 标准,MII 包括 16 个数据和控制信号的引脚。RMII 规范将引脚数减少为 7 个。
因为 RMII 相比 MII,其发送和接收都少了两条线。因此要达到 10Mbit/s 的速度,其时钟频率应为 5MHZ,同理要达到100Mbit/s 的速度其时钟频率应为 50MHz,而 MII 接口分别为 2.5MHZ 和 25MHZ。STM32F407 开发板采用 RMII 接口连接 LAN8720示意图:
1.3 外部PHY芯片LAN8720
LAN8720 是低功耗的 10/100M 以太网 PHY 层芯片,I/O 引脚电压符合 IEEE802.3-2005 标准。LAN8720 支持通过 RMII 接口与以太网 MAC 层通信,内置 10-BASE-T/100BASE-TX 全双工传输模块,支持 10Mbps 和 100Mbps。LAN8720 可以通过自协商的方式与目的主机最佳的连接方式(速度和双工模式)。支持 HP Auto-MDIX 自动翻转功能,无需更换网线即可将连接更改为直连或交叉连接。LAN8720 功能框图如图所示:
1.3.1 LAN8720 中断管理
当一个中断事件发生并且相应事件的中断位使能,LAN8720 就会在 nINT(14 脚)产生一个低电平有效的中断信号。LAN8720的中断系统提供两种中断模式:主中断模式和复用中断模式。主中断模式是默认中断模式,LAN8720 上电或复位后就工作在主中断模式,当模式控制/状态寄存器(十进制地址为 17)的ALTINT 为 0 是 LAN8720 工作在主模式,当 ALTINT 为 1 时工作在复用中断模式。可以不使用用到中断功能
1.3.2 PHY 地址设置
MAC 层通过 SMI 总线对 PHY 进行读写操作,SMI 可以控制 32 个 PHY 芯片,通过不同的PHY 芯片地址来对不同的 PHY 操作。LAN8720 通过设置 RXER/PHYAD0 引脚来设置其 PHY地址,默认情况下为 0,在系统上电时会检测该引脚获取得到 LAN8720A的地址为 0 或者 1,并保存在特殊模式寄存器(R18)的 PHYAD 位中,该寄存器的 PHYAD有 5 个位,在需要超过 2 个 LAN8720A 时可以通过软件设置不同 SMI 通信地址。PHYAD[0]是与 RXER 引脚共用。
1.3.3 nINT/REFCLKO 配置
nINTSEL引脚(2 号引脚)用于设置 nINT/REFCLKO 引脚(14 号引脚)的功能。当工作在 REF_CLK In 模式时,50MHz 的外部时钟信号应接到 LAN8720 的 XTAL1/CKIN引脚(5 号引脚)和 STM32F407 的 RMII_REF_CLK(PA1)引脚上;为了降低成本,LAN8720 可以从外部的 25MHz 的晶振中产生 REF_CLK 时钟。到要使用此功能时应工作在 REF_CLK Out 模式时。
1.3.4 LAN8720 内部寄存器
PHY 是由 IEEE 802.3 定义的,一般通过 SMI 对 PHY 进行管理和控制,也就是读写 PHY 内部寄存器。PHY 寄存器的地址空间为 5 位,可以定义 0 ~ 31 共 32 个寄存器,IEEE 802.3 定义了 0~ 15 这 16 个寄存器的功能,16~31 寄存器由芯片制造商自由定义。但是随之 PHY 芯片功能的增加,很多 PHY 芯片采用分页技术来扩展地址空间,定义更多的寄存器,在这里我们只介绍几个用到的寄存器(括号内为寄存器地址,此处使用十进制表示):BCR(0),BSR(1),PHY 特殊功能寄存器(31)这三个寄存器。
-
BCR寄存器
我们配置 LAN8720 其实就是配置 BCR 寄存器,我们通过调用 ST 官方的以太网库的ETH_ReadPHYRegister 和 ETH_WritePHYRegister 函数来完成对 PHY 芯片寄存器的读写操作。在 ST 官方以太网库的 stm32f4x7_eth.h 中已经定义了 BCR 和 BSR 寄存器。在以后要讲的 LAN8720 初始化的中并没有看到我们直接操作 PHY 的寄存器,原因就在这里当我们调用 ETH_Init()函数以后就会根据我们输入的参数配置 LAN8720 的相应寄存器。 -
BSR 寄存器
-
LAN8720 特殊功能寄存器
特殊功能寄存器中我们关心的是 bit2~bit4 这三位,因为通过这 3 位来确定连接的状态和速度。在 ETH_Init 函数中通过读取这 3 位的值来设置 BCR 寄存器的 bit8 和 bit13。由于特殊功能寄存器不属于 IEEE802.3 规定的前 16 个寄存器,所以每个厂家的可能不同,这个需要用户根自己实际使用的 PHY 芯片去修改,在 stm32f4x7_eth_conf.h 文件中定义
1.4 LWIP 简介
LwIP全名:Light weight IP,意思是轻量化的 TCP/IP 协议,是瑞典计算机科学院(SICS)的 Adam Dunkels 开发的一个小型开源的 TCP/IP 协议栈。LwIP 的设计初衷是:用少量的资源消耗实现一个较为完整的 TCP/IP 协议栈,其中“完整”主要指的是 TCP 协议的完整性,实现的重点是在保持 TCP 协议主要功能的基础上减少对 RAM 的占用。此外 LwIP既可以移植到操作系统上运行,也可以在无操作系统的情况下独立运行。其与TCP/IP的区别如下:
LwIP下载网址:http://download-mirror.savannah.gnu.org/releases/lwip/
下载两个包:lwip-2.1.2.zip(源码包)和 contrib-2.1.0.zip(contrib 包)。解压以后会得到两个文件夹。打开LWIP1.4.1 其中包括 doc,src 和 test 三个文件夹和 5 个其他文件。doc 文件夹下包含了几个与协议栈使用相关的文本文档,doc 文件夹里面有两个比较重要的文档:rawapi.txt 和 sys_arch.txt。
rawapi.txt 告诉读者怎么使用 raw/callback API 进行编程,sys_arch.txt 包含了移植说明,在移植的时候会用到。src 文件夹是我们的重点,里面包含了 LWIP 的源码。test 是 LWIP 提供的一些测试程序。src 文件夹由 4 个文件夹组成:api、core、include、netif 四个文件夹。api 文件夹里面是 LWIP的 sequential API(Netconn)和 socket API 两种接口函数的源码,要使用这两种 API 需要操作系统支持。 core 文件夹是 LWIP 内核源码,include 文件夹里面是 LWIP 使用到的头文件,netif 文件夹里面是与网络底层接口有关的文件。
LwIP 提供了三种编程接口,分别为 RAW/Callback API、NETCONN API、SOCKETAPI。它们的易用性从左到右依次提高,而执行效率从左到右依次降低,用户可以根据实际情况,平衡利弊,选择合适的 API 进行网络应用程序的开发。以下内容将分别介绍这三种 API。
二、带操作系统的移植
首先LWIP也可以实现无操作系统移植,不过无操作系统只能使用RAW API 编程接口,虽然可以带来更高的运行效率,但是学习成本增大,且很多应用场景并不适合
2.1 移植准备工作
需要三个官方文件lwip-1.4.1、contrib-1.4.1和STM32F4x7_ETH_LwIP_V1.1.0,以及一个移植好的UCOSIII工程。其中lwip文件夹是关于以太网协议的实现,contrib是以太网顶层应用层的实现,STM32F4x7_ETH_LwIP文件是官方驱动实现,LWIP所需文件下载见附件。最后UCOS的工程移植不介绍,具体见下链接:https://blog.csdn.net/weixin_44567668/article/details/131056991
当然也可以在其他操作系统上移植,本文不做过多介绍
2.2 文件移植与工程添加
-
官方驱动移植
将STM32F4x7_ETH_LwIP_V1.1.0\Libraries里的STM32F4x7_ETH_Driver文件夹复制进工程里的FWLIB中
-
LWIP文件移植
在工程里新建LWIP文件夹,然后将lwip-1.4.1\src里的文件复制进来,然后在LWIP文件夹里建立arch与apps文件夹
-
arch框架移植
将contrib-1.4.1\contrib-1.4.1\ports\unix文件夹里的cc.h、cpu.h、lwipopts.h、perf.h、sys_arch.c、sys_arch.h这6个文件复制进上面新建的arch文件夹里
-
在keil工程里添加,结果如下
2.3 修改以太网驱动
2.3.1 stm32f4x7_eth_conf.h修改
将stm32f4x7_eth_conf_template.h重命名为stm32f4x7_eth_conf.h,里面定义了一些关于操作 PHY 芯片的信息,根据 LAN8720 做一定的修改
#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
2.3.2 修改 stm32f4x7_eth.c 文件
将下面四个数组:Rx_Buff[]、Tx_Buff[]、DMARxDscrTab[]和 DMATxDscrTab[]屏蔽,这四个数组占用了大量的 RAM,后面采用动态内存分配
//#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 */
2.3.3 添加 LAN8720 和 MAC /DMA 驱动
- lan8720.h头文件
#ifndef __LAN8720_H
#define __LAN8720_H
#include "sys.h"
#include "stm32f4x7_eth.h"
#define LAN8720_PHY_ADDRESS 0x00 //LAN8720 PHY芯片地址.
#define LAN8720_RST PDout(3) //LAN8720复位引脚
extern ETH_DMADESCTypeDef *DMARxDscrTab; //以太网DMA接收描述符数据结构体指针
extern ETH_DMADESCTypeDef *DMATxDscrTab; //以太网DMA发送描述符数据结构体指针
extern uint8_t *Rx_Buff; //以太网底层驱动接收buffers指针
extern uint8_t *Tx_Buff; //以太网底层驱动发送buffers指针
extern ETH_DMADESCTypeDef *DMATxDescToSet; //DMA发送描述符追踪指针
extern ETH_DMADESCTypeDef *DMARxDescToGet; //DMA接收描述符追踪指针
extern ETH_DMA_Rx_Frame_infos *DMA_RX_FRAME_infos; //DMA最后接收到的帧信息指针
u8 LAN8720_Init(void);
u8 LAN8720_Get_Speed(void);
u8 ETH_MACDMA_Config(void);
FrameTypeDef ETH_Rx_Packet(void);
u8 ETH_Tx_Packet(u16 FrameLength);
u32 ETH_GetCurrentTxBuffer(void);
u8 ETH_Mem_Malloc(void);
void ETH_Mem_Free(void);
#endif
- lan8720.c文件
#include "lan8720.h"
#include "stm32f4x7_eth.h"
#include "usart.h"
#include "delay.h"
#include "malloc.h"
ETH_DMADESCTypeDef *DMARxDscrTab; //以太网DMA接收描述符数据结构体指针
ETH_DMADESCTypeDef *DMATxDscrTab; //以太网DMA发送描述符数据结构体指针
uint8_t *Rx_Buff; //以太网底层驱动接收buffers指针
uint8_t *Tx_Buff; //以太网底层驱动发送buffers指针
static void ETHERNET_NVICConfiguration(void);
//LAN8720初始化
//返回值:0,成功;
// 其他,失败
u8 LAN8720_Init(void)
{
u8 rval=0;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOC|RCC_AHB1Periph_GPIOG|RCC_AHB1Periph_GPIOD, ENABLE);//使能GPIO时钟 RMII接口
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); //使能SYSCFG时钟
SYSCFG_ETH_MediaInterfaceConfig(SYSCFG_ETH_MediaInterface_RMII); //MAC和PHY之间使用RMII接口
/*网络引脚设置 RMII接口
ETH_MDIO -------------------------> PA2
ETH_MDC --------------------------> PC1
ETH_RMII_REF_CLK------------------> PA1
ETH_RMII_CRS_DV ------------------> PA7
ETH_RMII_RXD0 --------------------> PC4
ETH_RMII_RXD1 --------------------> PC5
ETH_RMII_TX_EN -------------------> PG11
ETH_RMII_TXD0 --------------------> PG13
ETH_RMII_TXD1 --------------------> PG14
ETH_RESET-------------------------> PD3*/
//配置PA1 PA2 PA7
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_ETH); //引脚复用到网络接口上
GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_ETH);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_ETH);
//配置PC1,PC4 and PC5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_4 | GPIO_Pin_5;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource1, GPIO_AF_ETH); //引脚复用到网络接口上
GPIO_PinAFConfig(GPIOC, GPIO_PinSource4, GPIO_AF_ETH);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource5, GPIO_AF_ETH);
//配置PG11, PG14 and PG13
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_13 | GPIO_Pin_14;
GPIO_Init(GPIOG, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOG, GPIO_PinSource11, GPIO_AF_ETH);
GPIO_PinAFConfig(GPIOG, GPIO_PinSource13, GPIO_AF_ETH);
GPIO_PinAFConfig(GPIOG, GPIO_PinSource14, GPIO_AF_ETH);
//配置PD3为推完输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推完输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
GPIO_Init(GPIOD, &GPIO_InitStructure);
LAN8720_RST=0; //硬件复位LAN8720
delay_ms(50);
LAN8720_RST=1; //复位结束
ETHERNET_NVICConfiguration(); //设置中断优先级
rval=ETH_MACDMA_Config(); //配置MAC及DMA
return !rval; //ETH的规则为:0,失败;1,成功;所以要取反一下
}
//以太网中断分组配置
void ETHERNET_NVICConfiguration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = ETH_IRQn; //以太网中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0X00; //中断寄存器组2最高优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0X00;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
//得到8720的速度模式
//返回值:
//001:10M半双工
//101:10M全双工
//010:100M半双工
//110:100M全双工
//其他:错误.
u8 LAN8720_Get_Speed(void)
{
u8 speed;
speed=((ETH_ReadPHYRegister(0x00,31)&0x1C)>>2); //从LAN8720的31号寄存器中读取网络速度和双工模式
return speed;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
//以下部分为STM32F407网卡配置/接口函数.
//初始化ETH MAC层及DMA配置
//返回值:ETH_ERROR,发送失败(0)
// ETH_SUCCESS,发送成功(1)
u8 ETH_MACDMA_Config(void)
{
u8 rval;
ETH_InitTypeDef ETH_InitStructure;
//使能以太网MAC以及MAC接收和发送时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_ETH_MAC | RCC_AHB1Periph_ETH_MAC_Tx |RCC_AHB1Periph_ETH_MAC_Rx, ENABLE);
ETH_DeInit(); //AHB总线重启以太网
ETH_SoftwareReset(); //软件重启网络
while (ETH_GetSoftwareResetStatus() == SET);//等待软件重启网络完成
ETH_StructInit(Ð_InitStructure); //初始化网络为默认值
///网络MAC参数设置
ETH_InitStructure.ETH_AutoNegotiation = ETH_AutoNegotiation_Enable; //开启网络自适应功能
ETH_InitStructure.ETH_LoopbackMode = ETH_LoopbackMode_Disable; //关闭反馈
ETH_InitStructure.ETH_RetryTransmission = ETH_RetryTransmission_Disable; //关闭重传功能
ETH_InitStructure.ETH_AutomaticPadCRCStrip = ETH_AutomaticPadCRCStrip_Disable; //关闭自动去除PDA/CRC功能
ETH_InitStructure.ETH_ReceiveAll = ETH_ReceiveAll_Disable; //关闭接收所有的帧
ETH_InitStructure.ETH_BroadcastFramesReception = ETH_BroadcastFramesReception_Enable;//允许接收所有广播帧
ETH_InitStructure.ETH_PromiscuousMode = ETH_PromiscuousMode_Disable; //关闭混合模式的地址过滤
ETH_InitStructure.ETH_MulticastFramesFilter = ETH_MulticastFramesFilter_Perfect;//对于组播地址使用完美地址过滤
ETH_InitStructure.ETH_UnicastFramesFilter = ETH_UnicastFramesFilter_Perfect; //对单播地址使用完美地址过滤
#ifdef CHECKSUM_BY_HARDWARE
ETH_InitStructure.ETH_ChecksumOffload = ETH_ChecksumOffload_Enable; //开启ipv4和TCP/UDP/ICMP的帧校验和卸载
#endif
//当我们使用帧校验和卸载功能的时候,一定要使能存储转发模式,存储转发模式中要保证整个帧存储在FIFO中,
//这样MAC能插入/识别出帧校验值,当真校验正确的时候DMA就可以处理帧,否则就丢弃掉该帧
ETH_InitStructure.ETH_DropTCPIPChecksumErrorFrame = ETH_DropTCPIPChecksumErrorFrame_Enable; //开启丢弃TCP/IP错误帧
ETH_InitStructure.ETH_ReceiveStoreForward = ETH_ReceiveStoreForward_Enable; //开启接收数据的存储转发模式
ETH_InitStructure.ETH_TransmitStoreForward = ETH_TransmitStoreForward_Enable; //开启发送数据的存储转发模式
ETH_InitStructure.ETH_ForwardErrorFrames = ETH_ForwardErrorFrames_Disable; //禁止转发错误帧
ETH_InitStructure.ETH_ForwardUndersizedGoodFrames = ETH_ForwardUndersizedGoodFrames_Disable; //不转发过小的好帧
ETH_InitStructure.ETH_SecondFrameOperate = ETH_SecondFrameOperate_Enable; //打开处理第二帧功能
ETH_InitStructure.ETH_AddressAlignedBeats = ETH_AddressAlignedBeats_Enable; //开启DMA传输的地址对齐功能
ETH_InitStructure.ETH_FixedBurst = ETH_FixedBurst_Enable; //开启固定突发功能
ETH_InitStructure.ETH_RxDMABurstLength = ETH_RxDMABurstLength_32Beat; //DMA发送的最大突发长度为32个节拍
ETH_InitStructure.ETH_TxDMABurstLength = ETH_TxDMABurstLength_32Beat; //DMA接收的最大突发长度为32个节拍
ETH_InitStructure.ETH_DMAArbitration = ETH_DMAArbitration_RoundRobin_RxTx_2_1;
rval=ETH_Init(Ð_InitStructure,LAN8720_PHY_ADDRESS); //配置ETH
if(rval==ETH_SUCCESS)//配置成功
{
ETH_DMAITConfig(ETH_DMA_IT_NIS|ETH_DMA_IT_R,ENABLE); //使能以太网接收中断
}
return rval;
}
extern void lwip_pkt_handle(void); //在lwip_comm.c里面定义
//以太网DMA接收中断服务函数
void ETH_IRQHandler(void)
{
while(ETH_GetRxPktSize(DMARxDescToGet)!=0) //检测是否收到数据包
{
lwip_pkt_handle();
}
ETH_DMAClearITPendingBit(ETH_DMA_IT_R); //清除DMA中断标志位
ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS); //清除DMA接收中断标志位
}
//接收一个网卡数据包
//返回值:网络数据包帧结构体
FrameTypeDef ETH_Rx_Packet(void)
{
u32 framelength=0;
FrameTypeDef frame={
0,0};
//检查当前描述符,是否属于ETHERNET DMA(设置的时候)/CPU(复位的时候)
if((DMARxDescToGet->StatusÐ_DMARxDesc_OWN)!=(u32)RESET)
{
frame.length=ETH_ERROR;
if ((ETH->DMASRÐ_DMASR_RBUS)!=(u32)RESET)
{
ETH->DMASR = ETH_DMASR_RBUS;//清除ETH DMA的RBUS位
ETH->DMARPDR=0;//恢复DMA接收
}
return frame;//错误,OWN位被设置了
}
if(((DMARxDescToGet->StatusÐ_DMARxDesc_ES)==(u32)RESET)&&
((DMARxDescToGet->Status & ETH_DMARxDesc_LS)!=(u32)RESET)&&
((DMARxDescToGet->Status & ETH_DMARxDesc_FS)!=(u32)RESET))
{
framelength=((DMARxDescToGet->StatusÐ_DMARxDesc_FL)>>ETH_DMARxDesc_FrameLengthShift)-4;//得到接收包帧长度(不包含4字节CRC)
frame.buffer = DMARxDescToGet->Buffer1Addr;//得到包数据所在的位置
}else framelength=ETH_ERROR;//错误
frame.length=framelength;
frame.descriptor=DMARxDescToGet;
//更新ETH DMA全局Rx描述符为下一个Rx描述符
//为下一次buffer读取设置下一个DMA Rx描述符
DMARxDescToGet=(ETH_DMADESCTypeDef*)(DMARxDescToGet->Buffer2NextDescAddr);
return frame;
}
//发送一个网卡数据包
//FrameLength:数据包长度
//返回值:ETH_ERROR,发送失败(0)
// ETH_SUCCESS,发送成功(1)
u8 ETH_Tx_Packet(u16 FrameLength)
{
//检查当前描述符,是否属于ETHERNET DMA(设置的时候)/CPU(复位的时候)
if((DMATxDescToSet->StatusÐ_DMATxDesc_OWN)!=(u32)RESET)return ETH_ERROR;//错误,OWN位被设置了
DMATxDescToSet->ControlBufferSize=(FrameLengthÐ_DMATxDesc_TBS1);//设置帧长度,bits[12:0]
DMATxDescToSet->Status|=ETH_DMATxDesc_LS|ETH_DMATxDesc_FS;//设置最后一个和第一个位段置位(1个描述符传输一帧)
DMATxDescToSet->Status|=ETH_DMATxDesc_OWN;//设置Tx描述符的OWN位,buffer重归ETH DMA
if((ETH->DMASRÐ_DMASR_TBUS)!=(u32)RESET)//当Tx Buffer不可用位(TBUS)被设置的时候,重置它.恢复传输
{
ETH->DMASR=ETH_DMASR_TBUS;//重置ETH DMA TBUS位
ETH->DMATPDR=0;//恢复DMA发送
}
//更新ETH DMA全局Tx描述符为下一个Tx描述符
//为下一次buffer发送设置下一个DMA Tx描述符
DMATxDescToSet=(ETH_DMADESCTypeDef*)(DMATxDescToSet->Buffer2NextDescAddr);
return ETH_SUCCESS;
}
//得到当前描述符的Tx buffer地址
//返回值:Tx buffer地址
u32 ETH_GetCurrentTxBuffer(void)
{
return DMATxDescToSet->Buffer1Addr;//返回Tx buffer地址
}
//为ETH底层驱动申请内存
//返回值:0,正常
// 其他,失败
u8 ETH_Mem_Malloc(void)
{
DMARxDscrTab=mymalloc(SRAMIN,ETH_RXBUFNB*sizeof(ETH_DMADESCTypeDef));//申请内存
DMATxDscrTab=mymalloc(SRAMIN,ETH_TXBUFNB*sizeof(ETH_DMADESCTypeDef));//申请内存
Rx_Buff=mymalloc(SRAMIN,ETH_RX_BUF_SIZE*ETH_RXBUFNB); //申请内存
Tx_Buff=mymalloc(SRAMIN,ETH_TX_BUF_SIZE*ETH_TXBUFNB); //申请内存
if(!DMARxDscrTab||!DMATxDscrTab||!Rx_Buff||!Tx_Buff)
{
ETH_Mem_Free();
return 1; //申请失败
}
return 0; //申请成功
}
//释放ETH 底层驱动申请的内存
void ETH_Mem_Free(void)
{
myfree(SRAMIN,DMARxDscrTab);//释放内存
myfree