一、驱动代码
#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(SRAMIN,DMATxDscrTab);//释放内存
myfree(SRAMIN,Rx_Buff); //释放内存
myfree(SRAMIN,Tx_Buff); //释放内存
}
二、硬件连接方式
2.1 STM32以太网接口引脚:
这些引脚是STM32微控制器上用于以太网通信的复用功能(AF11)引脚,下面我将分类详细介绍它们的作用:
引脚 |
信号名称 |
作用描述 |
PB12/PG13 |
ETH_MII_TXD0/ETH_RMII_TXD0 |
发送数据位0(最低有效位) |
PB13/PG14 |
ETH_MII_TXD1/ETH_RMII_TXD1 |
发送数据位1 |
PC2 |
ETH_MII_TXD2 |
MII发送数据位2(仅MII模式使用) |
PB8/PE2 |
ETH_MII_TXD3 |
MII发送数据位3(仅MII模式使用) |
引脚 |
信号名称 |
作用描述 |
PC4 |
ETH_MII_RXD0/ETH_RMII_RXD0 |
接收数据位0(最低有效位) |
PC5 |
ETH_MII_RXD1/ETH_RMII_RXD1 |
接收数据位1 |
PB0/PH6 |
ETH_MII_RXD2 |
MII接收数据位2(仅MII模式使用) |
PB1/PH7 |
ETH_MII_RXD3 |
MII接收数据位3(仅MII模式使用) |
引脚 |
信号名称 |
作用描述 |
PB11/PG11 |
ETH_MII_TX_EN/ETH_RMII_TX_EN |
发送使能信号,高电平表示正在发送数据 |
PA7 |
ETH_MII_RX_DV/ETH_RMII_CRS_DV |
MII模式:接收数据有效<br>RMII模式:载波侦听/接收数据有效 |
PB10/PI10 |
ETH_MII_RX_ER |
接收错误指示,高电平表示接收过程中检测到错误 |
引脚 |
信号名称 |
作用描述 |
PA1 |
ETH_MII_RX_CLK |
接收时钟(由PHY提供,25MHz@100Mbps,2.5MHz@10Mbps) |
PC3 |
ETH_MII_TX_CLK |
发送时钟(由PHY提供,25MHz@100Mbps,2.5MHz@10Mbps) |
引脚 |
信号名称 |
作用描述 |
PA0/PH2 |
ETH_MII_CRS |
载波侦听,表示信道被占用(半双工模式) |
PA3/PH3 |
ETH_MII_COL |
冲突检测,表示检测到冲突(半双工模式) |
引脚 |
信号名称 |
作用描述 |
PA1 |
ETH_RMII_REF_CLK |
50MHz参考时钟(必须由外部PHY或晶振提供) |
引脚 |
信号名称 |
作用描述 |
PA2 |
ETH_MDIO |
管理数据输入输出,用于配置PHY寄存器 |
PC1 |
ETH_MDC |
管理数据时钟,由MAC产生(最高2.5MHz) |
2.2 LAN8720 接线
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*/

2.3 配置顺序
电源和复位配置
电源上电顺序
VDDIO(1.6V–3.6V)、VDD1A/VDD2A(3.0V–3.6V)需在 50 ms 内达到标称电压(±10%容差)
VDDCR(1.2V):
- 若使用内部稳压器(默认):由VDD2A通过内部LDO生成,无需外部供电。
- 若禁止内部稳压器(REGOFF=1):需在VDDIO达到80%后,再提供外部1.2V(±5%)。
复位信号(nRST)
- 复位脉冲宽度:至少保持低电平 100 μs(
trstia
)。
- 释放时机:所有电源稳定后,延迟 25 ms(
tpurstd
)再拉高nRST。
关键配置脚锁存
配置脚在 nRST上升沿 时锁存,需在复位前稳定电平:
PHY地址配置(PHYAD0)
- 引脚:
RXER/PHYAD0
(引脚10)
- 功能:设置SMI管理接口的PHY地址(0或1)。
- 配置方式:
- 拉低(下拉电阻):PHY地址=0(默认)。
- 拉高(上拉电阻至VDDIO):PHY地址=1。
工作模式(MODE[2:0])
- 引脚:
RXD0/MODE0
(引脚8)
RXD1/MODE1
(引脚7)
CRS_DV/MODE2
(引脚11)
- 配置方式:通过外部电阻组合设置(见下表):
MODE[2:0] |
模式描述 |
典型应用 |
000 |
10BASE-T半双工,禁止自动协商 |
固定10M半双工 |
111 |
自动协商全功能(默认) |
支持10/100M全/半双工 |
110 |
掉电模式 |
低功耗待机 |
时钟配置
晶振模式(推荐)
- 连接:
XTAL1
(引脚5):接25 MHz晶振。
XTAL2
(引脚4):接晶振另一端,并联负载电容(如20 pF)。
- 注意:晶振需满足±50 ppm精度。
外部时钟模式
- 连接:
XTAL1/CLKIN
(引脚5):输入25 MHz(REF_CLK输出模式)或50 MHz(REF_CLK输入模式)时钟。
XTAL2
(引脚4):悬空。
RMII接口配置
- REF_CLK模式选择:
- 输入模式(nINTSEL=1):MAC提供50 MHz时钟至
XTAL1/CLKIN
。
- 输出模式(nINTSEL=0):PHY生成50 MHz时钟至
REFCLKO
(需连接MAC的CLK输入)。
- 信号连接:
TXD[1:0]
(引脚17-18)、TXEN
(引脚16):MAC发送数据。
RXD[1:0]
(引脚7-8)、CRS_DV
(引脚11):MAC接收数据。
配置顺序总结
- 电源上电:确保VDDIO、VDD1A/VDD2A、VDDCR(若外部)稳定。
- 配置脚设置:在nRST拉低前,通过电阻设置PHYAD0、MODE[2:0]、REGOFF、nINTSEL。
- 复位释放:保持nRST低电平≥100 μs后拉高,锁存配置。
- 时钟初始化:晶振/外部时钟需在复位完成后稳定运行。
- RMII接口:根据nINTSEL选择时钟模式,连接MAC接口。