基于STM32F107xx中以太网ETH实现LWIP功能

廉价感情. 提交于 2020-01-29 07:54:28

前言

在需要使用lwip以太网功能时,可以选择ST支持以太网互联型芯片,如107或105;
当然也可以选择103 + 网卡驱动芯片(如:ENC28J60、DM9000等);

该文章只介绍107芯片中以太网ETH模块的配置过程;
ETH框图
在这里插入图片描述
STM32F107xx支持以太网模块的面纱就是这样,那么我们该如何配置里面的PHY、MAC和以太网专用的DMA以实现底层的配置呢?容许我慢慢道来。

1、网卡结构

  网卡是一块被设计用来允许计算机在计算机网络上进行通讯的计算机硬件。由于其拥有MAC地址,因此属于OSI模型的第2层。它使得用户可以通过电缆或无线相互连接。每一个网卡都有一个被称为MAC地址的独一无二的48位串行号,它被写在卡上的一块ROM中。在网络上的每一个计算机都必须拥有一个独一无二的MAC地址。没有任何两块被生产出来的网卡拥有同样的地址。这是因为电气电子工程师协会(IEEE)负责为网络接口控制器(网卡)销售商分配唯一的MAC地址。
以太网卡中数据链路层的芯片一般简称之为MAC控制器,物理层的芯片我们简称之为PHY。
在这里插入图片描述

1.1、MAC 802.3

  该部分不是两言三语就可以说明白的,《STM32中文参考手册》中有详细的介绍;如果还不够喂饱您,那就自己找吃的;反正网卡没它不行;

1.2、PHY

  PHY是物理接口收发器,它实现物理层。包括MII/GMII(介质独立接口)子层、PCS(物理编码子层)、PMA(物理介质附加)子层、PMD(物理介质相关)子层、MDI子层。

  100BaseTX采用4B/5B编码。PHY在发送数据的时候,收到MAC过来的数据(对PHY来说,没有帧的概念,对它来说,都是数据而不管什么地址,数据还是CRC),每4bit就增加1bit的检错码,然后把并行数据转化为串行流数据,再按照物理层的编码规则把数据编码,再变为模拟信号把数据送出去。收数据时的流程反之。

  PHY还有个重要的功能就是实现CSMA/CD的部分功能。它可以检测到网络上是否有数据在传送,如果有数据在传送中就等待,一旦检测到网络空闲,再等待一个随机时间后将送数据出去。如果两个碰巧同时送出了数据,那样必将造成冲突,这时候,冲突检测机构可以检测到冲突,然后各等待一个随机的时间重新发送数据。这个随机时间很有讲究的,并不是一个常数,在不同的时刻计算出来的随机时间都是不同的,而且有多重算法来应付出现概率很低的同两台主机之间的第二次冲突。
  这里说怎么多可能您看不懂,没关系会配就行;

2、配置ETH

void initEthernet()
{	
    setEthPowerOnOff(ENABLE);//打开ETH的时钟
	ethGpioConfiguration();//配置以太网模块引脚
	ethMacRegConfiguration();//配置PHY、MAC、DMA(重头戏)
}

①打开或关闭ETH的时钟

//打开或关闭ETH的时钟
void setEthPowerOnOff(FunctionalState state)
{
	ethPowerState = state;
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_ETH_MAC | RCC_AHBPeriph_ETH_MAC_Tx |
	RCC_AHBPeriph_ETH_MAC_Rx, state);              
}

②配置以太网模块引脚

/*
*以太网RMII引脚定义
*/
#ifdef RMII_MODE

#define RMII_MDIO			PA2
#define RMII_MDC          	PC1
#define RMII_TX_EN        	PB11
#define RMII_TXD0         	PB12
#define RMII_TXD1         	PB13
#define RMII_REF_CLK      	PA1
#define RMII_CRSDV        	PD8
#define RMII_RXD0         	PD9
#define RMII_RXD1         	PD10
#define RMII_MDINTR       	PC5

#endif


//配置以太网模块引脚
void ethGpioConfiguration(void)
{
	//GPIO_InitTypeDef GPIO_InitStructure;
#ifdef MII_MODE

#elif defined RMII_MODE//使用RMII接口时
	GPIO_Initialize(GpioType(RMII_MDIO), GpioIndex(RMII_MDIO), GPIO_Mode_AF_PP);
	GPIO_Initialize(GpioType(RMII_MDC), GpioIndex(RMII_MDC), GPIO_Mode_AF_PP);
	GPIO_Initialize(GpioType(RMII_TX_EN), GpioIndex(RMII_TX_EN), GPIO_Mode_AF_PP);
	GPIO_Initialize(GpioType(RMII_TXD0), GpioIndex(RMII_TXD0), GPIO_Mode_AF_PP);
	GPIO_Initialize(GpioType(RMII_TXD1), GpioIndex(RMII_TXD1), GPIO_Mode_AF_PP);
	GPIO_Initialize(GpioType(RMII_REF_CLK), GpioIndex(RMII_REF_CLK), GPIO_Mode_IN_FLOATING);
	GPIO_Initialize(GpioType(RMII_MDINTR), GpioIndex(RMII_MDINTR), GPIO_Mode_IPU);
	//GPIO_Initialize(GpioType(MCO), GpioIndex(MCO), GPIO_Mode_AF_PP);
	GPIO_PinRemapConfig(GPIO_Remap_ETH, ENABLE);
	GPIO_Initialize(GpioType(RMII_CRSDV), GpioIndex(RMII_CRSDV), GPIO_Mode_IN_FLOATING);
	GPIO_Initialize(GpioType(RMII_RXD0), GpioIndex(RMII_RXD0),GPIO_Mode_IN_FLOATING);
	GPIO_Initialize(GpioType(RMII_RXD1), GpioIndex(RMII_RXD1),GPIO_Mode_IN_FLOATING);
#endif
}

③配置PHY、MAC、DMA(重头戏)

//配置PHY、MAC、DMA(重头戏)
void ethMacRegConfiguration(void)
{   
	ETH_InitTypeDef ETH_InitStructure;
	u32_t delayTime=0;
    	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_ETH_MAC | RCC_AHBPeriph_ETH_MAC_Tx |RCC_AHBPeriph_ETH_MAC_Rx, ENABLE);
	ethPowerState=ENABLE;
#ifdef MII_MODE 
	GPIO_ETH_MediaInterfaceConfig(GPIO_ETH_MediaInterface_MII);
	RCC_MCOConfig(RCC_MCO_HSE);
#elif defined RMII_MODE  
    GPIO_ETH_MediaInterfaceConfig(GPIO_ETH_MediaInterface_RMII);//选择精简的独立于介质的接口(RMII)
#if 0
	RCC_PLL3Config(RCC_PLL3Mul_10);
	RCC_PLL3Cmd(ENABLE);
	while ((RCC_GetFlagStatus(RCC_FLAG_PLL3RDY) == RESET)&&(delayTime<0x4ffff))
    {
		delayTime++;
	}
	if(delayTime==0x4ffff)
		return ;
	RCC_MCOConfig(RCC_MCO_PLL3CLK);
#endif
#endif
	ETH_DeInit();
    ETH_SoftwareReset(); 
	while ((ETH_GetSoftwareResetStatus() == SET)&&(delayTime<0xffff))
	{
		delayTime++;
	}
	if(delayTime==0xffff)
		return ;
	ETH_StructInit(&ETH_InitStructure);
	
	 /*------------------------ ETH_MACCCR-----------------------------------*/
	ETH_InitStructure.ETH_AutoNegotiation = ETH_AutoNegotiation_Enable  ; //(不一样)参数是否自动配置,选择disable时需要自动配置默认参数	
	ETH_InitStructure.ETH_RetryTransmission = ETH_RetryTransmission_Disable;//(不一样)
	ETH_InitStructure.ETH_AutomaticPadCRCStrip = ETH_AutomaticPadCRCStrip_Disable; /*自动填充/CRC剥离处理不执行,转发所有帧*/
	ETH_InitStructure.ETH_LoopbackMode = ETH_LoopbackMode_Disable;						//不启用自循环模式
	
	 /*------------------------ ETH_MACFFR----------------------------------*/
	ETH_InitStructure.ETH_BroadcastFramesReception = ETH_BroadcastFramesReception_Enable;//(不一样)接收广播帧
	ETH_InitStructure.ETH_ReceiveAll = ETH_ReceiveAll_Disable;//MAC过滤只接受通过源目的地址的数据
	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头文件硬件校验
#endif
	/*------------------------   DMA   -----------------------------------*/  
	ETH_InitStructure.ETH_DropTCPIPChecksumErrorFrame = ETH_DropTCPIPChecksumErrorFrame_Enable; /*(不一样)丢弃校验错误帧不执行(因为未进行硬件校验)*/
	ETH_InitStructure.ETH_ReceiveStoreForward = ETH_ReceiveStoreForward_Enable;	 //接收数据超过阈值转发	 
	ETH_InitStructure.ETH_TransmitStoreForward = ETH_TransmitStoreForward_Enable;	//发送数据完整帧转发  
	ETH_InitStructure.ETH_ForwardErrorFrames = ETH_ForwardErrorFrames_Disable;  //接收FIFO丢弃所有错误帧	  
	ETH_InitStructure.ETH_ForwardUndersizedGoodFrames = ETH_ForwardUndersizedGoodFrames_Disable;	 /*接收FIFO上传长度不够的好帧*/
	ETH_InitStructure.ETH_SecondFrameOperate = ETH_SecondFrameOperate_Enable;//(不一样)DMA直接发送第二个帧,不需要之前帧回复	
	
	ETH_InitStructure.ETH_AddressAlignedBeats = ETH_AddressAlignedBeats_Enable;	     //传输地址对齐
	ETH_InitStructure.ETH_FixedBurst = ETH_FixedBurst_Enable;	 					//(不一样)固定的突发			  
	ETH_InitStructure.ETH_RxDMABurstLength = ETH_RxDMABurstLength_32Beat;			
	ETH_InitStructure.ETH_TxDMABurstLength = ETH_TxDMABurstLength_32Beat;																   
	ETH_InitStructure.ETH_DMAArbitration = ETH_DMAArbitration_RoundRobin_RxTx_2_1;//(不一样)接收和发送比例 2:1
	assert_param(IS_ETH_AUTONEGOTIATION(ETH_InitStructure->ETH_AutoNegotiation));
	
	if(ETH_InitStructure.ETH_AutoNegotiation != ETH_AutoNegotiation_Disable)
	{
		AutoNetgotiation_flags=1;//参数是自动配置
	}
	else
	{
	   	AutoNetgotiation_flags=0;
	}
	
	phyRegConfiguration(PHY_ADDRESS);
    if((ModeSpeed & ETH_Mode_FullDuplex)!= RESET)
	{
      		ETH_InitStructure.ETH_Mode=ETH_Mode_FullDuplex;
  	}
  	else
  	{
      		ETH_InitStructure.ETH_Mode=ETH_Mode_HalfDuplex;
  	}
  	
  	if((ModeSpeed & ETH_Speed_100M)!= RESET)
  	{   
      		ETH_InitStructure.ETH_Speed=ETH_Speed_100M;
  	}
  	else
  	{
      		ETH_InitStructure.ETH_Speed=ETH_Speed_10M;
  	}
  	
	ETH_Init(&ETH_InitStructure,PHY_ADDRESS);
    ETH_DMAITConfig(ETH_DMA_IT_NIS | ETH_DMA_IT_R, ENABLE);
}
#endif

对于PHY的配置:

static u32_t phyRegConfiguration(u16_t PHYAddress)
{
	__IO u32_t timeout = 0;
  	u8_t temp;
  /* Put the PHY in reset mode */
  	Ethernet_MDIO_Config();
  	//1、重启PHY,并延时一段时间
  	if(!(ETH_WritePHYRegister(PHYAddress, PHY_BCR, PHY_Reset)))
  	{
    		return 0;
  	}
  	
	do
	{
		timeout++;
	}while(timeout<PHY_ResetDelay);
	
	timeout=0;

	//2、重启后,判断是否建立有效连接;
	do
   	 {
      		timeout++;
	  	temp=getPhyStatus(PHYAddress);
    	}while ((!temp) && (timeout < PHY_READ_TO));
    	
	if(timeout == PHY_READ_TO)
   	{												
      		return 0;//不会从这里返回
    	}

    	//3、配置PHY_REG_21寄存器,
	if(!(ETH_WritePHYRegister(PHYAddress,PHY_REG_21,0x0c00)))
	{
    		return 0;
	}

	//4、重启后,继续配置PHY自适应模式,并读取自适应模式后PHY的模式(半双or全双)和速度(10M or 100M);
  	if(phyGetModeSpeed(PHY_ADDRESS))
    	return 1;
	else
		return 0;
}

“重头戏”就是这样了,里面有一些函数是stm32_eth.c中本来就有的,如ETH_StructInit(); ETH_Init(); ETH_DMAITConfig();等,这里就不给出他们函数体了,自己根据原文件慢慢看;并且库函数中还有对PHY寄存器读写的API函数,如ETH_ReadPHYRegister(); ETH_WritePHYRegister();等进行配置;

  • 暂时写到这里,自己琢磨后如有不是很清楚的请留言大家一起共同探讨,又或者您有更好的说明配置也希望可以拿出来大家一起分享;后面如有更新说明的,会持续更新。

更新V1.0
内容:获取PHY(物理层)对网口的连接状态。

typedef enum 
{
   PHY_UNLINK=1,
   PHY_LINKED,
   PHY_BUSY,
}PhyLinkState;//定义一个连接状态枚举类型

PhyLinkState getPhyLinkStatusByPin(PhyLinkState lastState)
{
	u8_t PinValue=0xff;
	u16_t value=0;
	PhyLinkState Status=lastState;
  	if(ethPowerState)
  	{
    		PinValue = GPIO_ReadInputDataBit(GpioType(RMII_MDINTR), GpioIndex(RMII_MDINTR));
	     	if(!PinValue)
	    	{ 
				Status = getPhyStatusQuick(PHY_ADDRESS);//先获取PHY连接状态;
	        	value = ETH_ReadPHYRegister(PHY_ADDRESS,PHY_REG_21);//
			if(value & (0x0005))//判断这个是?????
			{
				Status = getPhyStatusQuick(PHY_ADDRESS);//再次获取PHY连接状态;’
				if(Status == PHY_LINKED)//连接后,
				{
					phyGetModeSpeed(PHY_ADDRESS);//获取PHY自适应的速度和模式;
					ethSetModeSpeed(ModeSpeed);//把获取到的PHY速度和模式,配置给MAC
				}
			}
			else
			{
				//如不满足,返回之前的连接状态;
				Status = lastState;	
			}	
	    	}
  	}
  	return Status;
}

里面有一函数getPhyStatusQuick()该函数主要是对寄存器ETH->MACMIIAR和ETH->MACMIIDR操作(因为获取连接状态是通过判断MII不忙的情况下,读取MACMIIDR寄存的值来获取的),手册有对相应寄存器有描述。函数体如下:

static PhyLinkState getPhyStatusQuick(u16_t PHYAddr)
{
	PhyLinkState ret=PHY_UNLINK;
   	uint32_t timeout=0;
   	uint32_t temp=0;
   	assert_param(IS_ETH_PHY_ADDRESS(PHYAddr));
   	
   	temp=ETH->MACMIIAR;//
   	temp &= ~MACMIIAR_CR_MASK;
   	temp |=(((uint32_t)PHYAddr << 11) & ETH_MACMIIAR_PA);//表示了32个可能的PHY地址中, MII想要访问的PHY地址
   	temp |=(((uint32_t)0x1 << 6) & ETH_MACMIIAR_MR);//选择了要访问PHY的第6个MII寄存器
   	temp &= ~ETH_MACMIIAR_MW;//表示对PHY进行读操作,数据在MII的数据寄存器中。
   	temp |= ETH_MACMIIAR_MB;//先给MII定为"忙"
   	ETH->MACMIIAR=temp;

   	//在一定时间内,等待MII不忙;
   	do{
    	timeout++;
		temp =ETH->MACMIIAR;
   	}while((temp & ETH_MACMIIAR_MB) && (timeout < (uint32_t)0xff));

   	if(timeout == 0xff)
   	{
    	ret = PHY_BUSY;
	 	return ret;
   	}
   	
   	//不忙时,再获取PHY的连接状态;
   	ret = (((uint16_t)(ETH->MACMIIDR)) & PHY_Linked_Status)? PHY_LINKED : PHY_UNLINK;

   	return ret;  
}

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!