最近在看硬汉写的RL-TCPnet教程的时候,感觉硬汉对底层驱动移植那一块讲得不是很清楚,看了原子的也是差不多,只是大概的讲了一下,对于刚学的人来说可能会有点不理解,跟教程写把程序写了出来,能用,但是不知道为什么这样写。自己研究了一下,大概弄懂了一些,在这里记录一下吧。我使用的开发平台是stm32F4,HAL库,PHY为LAN8720A.
在移植之前先要对STM32F4的以太网控制部分有一个了解,从官方文档中可以查到:STM32F407芯片自带以太网模块,该模包括带专用DMA控制器的MAC802.3(介质访问控制)控制器,支持介质独立接口(MII)和简化介质独立接口(RMII),并自带了一个用于外部PHY通信的SMI接口,通过一组配置寄存器,用户可以为MAC控制器和DMA控制器选择所需模式和功能。
从这段话中可以看到,F4的以太网控制模块是带专用DMA控制器的,而DMA主要是用来转移数据的,因此我们应该主要理解DMA在这里面起得是什么作用。
配置引脚的话没有什么好讲的,这个按照手册要未去配置就行,记得打开时钟以及引脚的复用功能。简单点的话可以使用STM32CUBE,非常简单,配置代码如下:
void bsp_lan_GPIOConfig(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
/* 打开外设时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_ETH_CLK_ENABLE();
__ETH_CLK_ENABLE() ;
GPIO_InitStruct.Pin = GPIO_PIN_1|GPIO_PIN_4|GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF11_ETH;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF11_ETH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF11_ETH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_7; /* 该引脚为PHY复位引脚 */
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
}
void ETH_NVICConfig(void)
{
HAL_NVIC_SetPriority(ETH_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(ETH_IRQn);
}
从图中可以看到32的以太网DMA有两个FIFO,一个管发送,一个管接收,同时还有DMA控制与状态寄存器,在程序内部刚将这些组织为一个结构体,叫做DMA描述符,其定义如下:
typedef struct
{
__IO uint32_t Status; /*!< Status */
uint32_t ControlBufferSize; /*!< Control and Buffer1, Buffer2 lengths */
uint32_t Buffer1Addr; /*!< Buffer1 address pointer */
uint32_t Buffer2NextDescAddr; /*!< Buffer2 or next descriptor address pointer */
/*!< Enhanced ETHERNET DMA PTP Descriptors */
uint32_t ExtendedStatus; /*!< Extended status for PTP receive descriptor */
uint32_t Reserved1; /*!< Reserved */
uint32_t TimeStampLow; /*!< Time Stamp Low value for transmit and receive */
uint32_t TimeStampHigh; /*!< Time Stamp High value for transmit and receive */
} ETH_DMADescTypeDef;
看起来好像很复杂,其实每个成员的基本上都是处注释的,看名字就知道他的意义,我再分别说一下吧。
Status:这个变量其实就是上面面框图里的状态寄存器,指示着DMA描述符的状态,比如说收到的数据是不是该描述符的。
ControlBufferSize:这个变量指示这个描述符里面数据的大小。
Buffer1Addr:这个变量存储着接收到的数据的基地址,我们读以太网的数据的话就是从这里读的。
Buffer2NextDescAddr:这个变量指示着下一个DMA描述符的地址,官方将这个DMA描述符组织成一个链表,便于使用和管理。
知道了DMA描述符,我们就接着配置剩下的。我们先声明两个DMA描述符指针,用于构成DMA描述符链表,另外再声明两个uint8_t型指针,一个用于接收数据,一个用于发送数据,上面有说到的Buffer1Addr其实只是一个地址,并不能存储数据,因此需要在这里另外申请空间,并将DMA描述符的Buffer1Addr指向这里,需要注意的是,这里声明的都是指针,需要给他们分配空间,不然会使用出错,当然你也可以直接声明数组,这样就不需要为他们申请空间了。
ETH_HandleTypeDef ETH_Handler;
ETH_DMADescTypeDef *DMARxDescTab; //发送DMA描述符
ETH_DMADescTypeDef *DMATxDescTab; //接收DMA描述符
uint8_t *Rx_Buff; //数据接收Buffer
uint8_t *Tx_Buff; //数据发送Buffer
uint8_t ETH_Malloc(void)
{
DMARxDescTab = bsp_mem_Malloc(SRAMIN, ETH_RXBUFNB * sizeof(ETH_DMADescTypeDef));
DMATxDescTab = bsp_mem_Malloc(SRAMIN, ETH_TXBUFNB * sizeof(ETH_DMADescTypeDef));
Rx_Buff = bsp_mem_Malloc(SRAMIN, ETH_RX_BUF_SIZE * ETH_RXBUFNB);
Tx_Buff = bsp_mem_Malloc(SRAMIN, ETH_TX_BUF_SIZE * ETH_TXBUFNB);
if(!DMARxDescTab || !DMATxDescTab || !Rx_Buff || !Tx_Buff)
{
ETH_Free();
return 1;
}
return 0;
}
这里的话我自己有实现内存管理函数,你也可以使用其他内存管理方式。
MAC[0] = own_hw_adr[0];
MAC[1] = own_hw_adr[1];
MAC[2] = own_hw_adr[2];
MAC[3] = own_hw_adr[3];
MAC[4] = own_hw_adr[4];
MAC[5] = own_hw_adr[5];
ETH_Handler.Instance = ETH;
ETH_Handler.Init.AutoNegotiation = ETH_AUTONEGOTIATION_ENABLE;
// ETH_Handler.Init.Speed = ETH_SPEED_100M;
// ETH_Handler.Init.DuplexMode = ETH_MODE_FULLDUPLEX;
ETH_Handler.Init.PhyAddress = LAN_PHY_ADDR;
ETH_Handler.Init.MACAddr = &MAC[0];
ETH_Handler.Init.RxMode = ETH_RXINTERRUPT_MODE;
ETH_Handler.Init.ChecksumMode = ETH_CHECKSUM_BY_SOFTWARE;
ETH_Handler.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII;
HAL_ETH_DeInit(Ð_Handler); /* 配置之前先复位 */
/* 开启发送完成中断以及异常错误总中断 */
__HAL_ETH_DMA_ENABLE_IT(Ð_Handler, ETH_DMA_IT_NIS | ETH_DMA_IT_T | ETH_DMA_IT_AIS | ETH_DMA_IT_FBE);
/* 正常初始化返回HAL_OK,否则返回错误消息,比如没有插网线则返回超时 */
eth_err = HAL_ETH_Init(Ð_Handler);
接下来就是将DMA描述符构成链表了,调用库函数就行:
/* 构造DMA发送和接收描述符为链表 */
HAL_ETH_DMATxDescListInit(Ð_Handler, DMATxDescTab, (uint8_t *)Tx_Buff, ETH_TXBUFNB);
HAL_ETH_DMARxDescListInit(Ð_Handler, DMARxDescTab, (uint8_t *)Rx_Buff, ETH_RXBUFNB);
最后启动以太网就OK了:
HAL_ETH_Start(Ð_Handler);
上面的都配置好后,我们就需要编写中断函数了,因为我们开启了中断,中断函数如下:
extern void RL_TCPnet_ptk_Handler(void);
void ETH_IRQHandler(void)
{
while(ETH_GetRxPktSize(ETH_Handler.RxDesc))
{
RL_TCPnet_ptk_Handler();//调用中断处理函数
}
//消除中断标志及错误标志
__HAL_ETH_DMA_CLEAR_IT(Ð_Handler,ETH_DMA_IT_R);
__HAL_ETH_DMA_CLEAR_IT(Ð_Handler,ETH_DMA_IT_NIS);
}
uint32_t bsp_lan_ReadPHY(uint16_t reg)
{
uint32_t regval;
HAL_ETH_ReadPHYRegister(Ð_Handler,reg,®val);
return regval;
}
void bsp_lan_WritePHY(uint16_t reg, uint16_t value)
{
uint32_t regval = value;
HAL_ETH_ReadPHYRegister(Ð_Handler, reg, ®val);
}
其实LAN8720A驱动的编写也是比较简单的,特别是官方的库里面都做好了封装,实质上不懂得怎么编写还是对库的不熟悉,也就是对库组织以太网这一部分的方式不理解,我觉得难点应该在对DMA那一块的理解,我已经尽我最大的表述能力来描述了,如果有不理解的地方可以私信我。如果有错误的地方也希望各位能够批评指正,第一次写博客。最后给出整个代码:
# include "bsp_lan.h"
//# include "lwip_comm.h"
# include "app_sys.h"
# include "includes.h"
#include "ETH_STM32F4xx.h"
ETH_HandleTypeDef ETH_Handler;
ETH_DMADescTypeDef *DMARxDescTab; //ÒÔÌ«ÍøDMA½ÓÊÕÃèÊö·ûÊý¾Ý½á¹¹ÌåÖ¸Õë
ETH_DMADescTypeDef *DMATxDescTab; //ÒÔÌ«ÍøDMA·¢ËÍÃèÊö·ûÊý¾Ý½á¹¹ÌåÖ¸Õë
uint8_t *Rx_Buff; //ÒÔÌ«Íøµ×²ãÇý¶¯½ÓÊÕbuffersÖ¸Õë
uint8_t *Tx_Buff; //ÒÔÌ«Íøµ×²ãÇý¶¯·¢ËÍbuffersÖ¸Õë
//extern lwip_dev lwip_info;
/*ÍøÂçÒý½ÅÉèÖà 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 -------------------> PB11
ETH_RMII_TXD0 --------------------> PB12
ETH_RMII_TXD1 --------------------> PB13
ETH_RESET-------------------------> PC7*/
/*
*********************************************************************************************************
*
*
* Description:
*
* Arguments :
*
* Note(s) :
*********************************************************************************************************
*/
void ETH_NVICConfig(void)
{
HAL_NVIC_SetPriority(ETH_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(ETH_IRQn);
}
void bsp_lan_GPIOConfig(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
/* ´ò¿ªÍâÉèʱÖÓ */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_ETH_CLK_ENABLE();
__ETH_CLK_ENABLE() ;
GPIO_InitStruct.Pin = GPIO_PIN_1|GPIO_PIN_4|GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF11_ETH;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF11_ETH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF11_ETH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_7; /* ¸ÃÒý½ÅΪPHYµÄ¸´Î»Òý½Å */
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
}
/*
*********************************************************************************************************
*
*
* Description:
*
* Arguments :
*
* Note(s) :
*********************************************************************************************************
*/
extern uint8_t own_hw_adr[];
uint8_t bsp_lan_Config(void)
{
uint8_t MAC[6];
uint32_t phyreg = 0x00;
HAL_StatusTypeDef eth_err;
ETH_Malloc();
bsp_lan_GPIOConfig();
ETH_NVICConfig();
DISABLE_INT();
LAN_RST = 0;
bsp_tim_DelayMs(100);
LAN_RST = 1;
ENABLE_INT();
MAC[0] = own_hw_adr[0];
MAC[1] = own_hw_adr[1];
MAC[2] = own_hw_adr[2];
MAC[3] = own_hw_adr[3];
MAC[4] = own_hw_adr[4];
MAC[5] = own_hw_adr[5];
ETH_Handler.Instance = ETH;
ETH_Handler.Init.AutoNegotiation = ETH_AUTONEGOTIATION_ENABLE;
// ETH_Handler.Init.Speed = ETH_SPEED_100M;
// ETH_Handler.Init.DuplexMode = ETH_MODE_FULLDUPLEX;
ETH_Handler.Init.PhyAddress = LAN_PHY_ADDR;
ETH_Handler.Init.MACAddr = &MAC[0];
ETH_Handler.Init.RxMode = ETH_RXINTERRUPT_MODE;
ETH_Handler.Init.ChecksumMode = ETH_CHECKSUM_BY_SOFTWARE;
ETH_Handler.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII;
HAL_ETH_DeInit(Ð_Handler); /* ³õʼ»¯Ö®Ç°Ïȸ´Î» */
/* ¿ªÆô·¢ËÍÍê³ÉÖжϺÍÒì³£´íÎó×ÜÖÐ¶Ï */
__HAL_ETH_DMA_ENABLE_IT(Ð_Handler, ETH_DMA_IT_NIS | ETH_DMA_IT_T | ETH_DMA_IT_AIS | ETH_DMA_IT_FBE);
/* Õý³£³õʼ»¯·µ»ØHAL_OK£¬Èç¹ûû²åÍøÏßÔò·µ»Ø³¬Ê± */
eth_err = HAL_ETH_Init(Ð_Handler);
/* ¹¹ÔìDMA·¢ËͺͽÓÊÕÃèÊö·û³ÉΪÁ´±í·½Ê½ */
HAL_ETH_DMATxDescListInit(Ð_Handler, DMATxDescTab, (uint8_t *)Tx_Buff, ETH_TXBUFNB);
HAL_ETH_DMARxDescListInit(Ð_Handler, DMARxDescTab, (uint8_t *)Rx_Buff, ETH_RXBUFNB);
// tx_descr_init();
// rx_descr_init();
/* Æô¶¯ETH */
HAL_ETH_Start(Ð_Handler);
/* ¼ì²éÊÇ·ñÁ¬½ÓÁËÍøÏß*/
HAL_ETH_ReadPHYRegister(Ð_Handler, PHY_BSR, &phyreg);
Sys_Info.LinkStatus = (phyreg & PHY_LINKED_STATUS);
return eth_err;
}
/*
*********************************************************************************************************
*
*
* Description:
*
* Arguments :
*
* Reutrn :
*
* Note(s) :
*********************************************************************************************************
*/
void bsp_lan_WritePHY(uint16_t reg, uint16_t value)
{
uint32_t regval = value;
HAL_ETH_ReadPHYRegister(Ð_Handler, reg, ®val);
}
/*
*********************************************************************************************************
*
*
* Description:
*
* Arguments :
*
* Reutrn :
*
* Note(s) :
*********************************************************************************************************
*/
uint32_t bsp_lan_ReadPHY(uint16_t reg)
{
uint32_t regval;
HAL_ETH_ReadPHYRegister(Ð_Handler,reg,®val);
return regval;
}
/*
*********************************************************************************************************
*
*
* Description:
*
* Arguments :
*
* Note(s) :
*********************************************************************************************************
*/
uint8_t bsp_lan_GetSpeed(void)
{
return ((bsp_lan_ReadPHY(31) & 0x1c) >> 2);
}
/*
*********************************************************************************************************
*
*
* Description:
*
* Arguments :
*
* Note(s) :
*********************************************************************************************************
*/
uint8_t ETH_Malloc(void)
{
DMARxDescTab = bsp_mem_Malloc(SRAMIN, ETH_RXBUFNB * sizeof(ETH_DMADescTypeDef));
DMATxDescTab = bsp_mem_Malloc(SRAMIN, ETH_TXBUFNB * sizeof(ETH_DMADescTypeDef));
Rx_Buff = bsp_mem_Malloc(SRAMIN, ETH_RX_BUF_SIZE * ETH_RXBUFNB);
Tx_Buff = bsp_mem_Malloc(SRAMIN, ETH_TX_BUF_SIZE * ETH_TXBUFNB);
if(!DMARxDescTab || !DMATxDescTab || !Rx_Buff || !Tx_Buff)
{
ETH_Free();
return 1;
}
return 0;
}
/*
*********************************************************************************************************
*
*
* Description:
*
* Arguments :
*
* Note(s) :
*********************************************************************************************************
*/
void ETH_Free(void)
{
bsp_mem_Free(SRAMIN, DMARxDescTab);
bsp_mem_Free(SRAMIN, DMATxDescTab);
bsp_mem_Free(SRAMIN, Rx_Buff);
bsp_mem_Free(SRAMIN, Tx_Buff);
}
/*
*********************************************************************************************************
*
*
* Description:
*
* Arguments :
*
* Note(s) :
*********************************************************************************************************
*/
extern void RL_TCPnet_ptk_Handler(void);
void ETH_IRQHandler(void)
{
OSIntEnter();
while(ETH_GetRxPktSize(ETH_Handler.RxDesc))
{
RL_TCPnet_ptk_Handler();//´¦ÀíÒÔÌ«ÍøÊý¾Ý
}
//Çå³ýÖжϱê־λ
__HAL_ETH_DMA_CLEAR_IT(Ð_Handler,ETH_DMA_IT_R);
__HAL_ETH_DMA_CLEAR_IT(Ð_Handler,ETH_DMA_IT_NIS);
OSIntExit();
}
/*
*********************************************************************************************************
*
*
* Description:
*
* Arguments :
*
* Reutrn :
*
* Note(s) :
*********************************************************************************************************
*/
uint32_t ETH_GetRxPktSize(ETH_DMADescTypeDef *DMARxDesc)
{
uint32_t frameLength = 0;
if(((DMARxDesc->StatusÐ_DMARXDESC_OWN)==(uint32_t)RESET) &&
((DMARxDesc->StatusÐ_DMARXDESC_ES)==(uint32_t)RESET) &&
((DMARxDesc->StatusÐ_DMARXDESC_LS)!=(uint32_t)RESET))
{
frameLength=((DMARxDesc->StatusÐ_DMARXDESC_FL)>>ETH_DMARXDESC_FRAME_LENGTHSHIFT);
}
return frameLength;
}