第6章 RL-TCPnet底层驱动说明
本章节为大家讲解RL-TCPnet的底层驱动,主要是STM32自带MAC的驱动实现和PHY的驱动实现。
6.1 初学者重要提示
6.2 KEIL提供的底层驱动文件
6.3 DM9161和DM9162的区别
6.4 底层驱动实现说明
6.5 总结
6.1 初学者重要提示
1、学习本章节前,务必学习STM32参考手册中MAC章节的基础知识讲解,非常重要。
2、DM9161和DM9162的手册可以在官网地址下载,本章节需要用到部分寄存器:
http://www.davicom.com.tw/page1.aspx?no=143762 。
3、早期STM32F407开发板使用的PHY芯片是DM9161,不过现在基本已经停产了,当前F407和F429开发板统一使用DM9162。底层代码对这两个芯片都可以正确驱动。
4、如果大家要驱动其它的PHY芯片,需要修改底层驱动函数中以下三个地方:
(1)根据使用的MII或者RMII接口方式,配置实际使用的引脚。
(2)所有PHY芯片的基本寄存器地址都是一样的,只有扩展寄存器不同,用户需要根据实际情况做修改。
(3)如果要使用PHY芯片的中断触发功能,也要根据实际使用的引脚重新配置。
6.2 KEIL提供的底层驱动文件
在MDK4.74的安装路径C:\Keil_v474\ARM\RL\TCPnet\Drivers已经包含了大量制作好的驱动文件,下面是部分驱动文件的截图:
这些驱动文件主要分为三类:
1、以太网驱动(Ethernet Driver)
这种类型的驱动文件也分为三类。
(1)用芯片自带的MAC驱动外置PHY芯片,比如STM32F407,STM32F429就是这种的。
(2)芯片自带MAC+PHY,这样就不需要外置PHY了,比如LM3S。
(3)驱动外置的以太网控制器,这种控制器自带MAC+PHY,比如LAN91C111。
2、调制解调器驱动(Modem Driver)
这种主要是通过PPP或者SLIP方式的网络接口驱动调制解调器。驱动里面提供了Null_Modem.c和Std_Modem.c两种驱动文件。
3、串行驱动(Serial Driver)
这种也是采用的PPP或者SLIP方式的驱动,只是驱动接口采用的串口。驱动里面也提供了很多相关的驱动文件,比如Serial_LPC23xx.c,Serial_S3C44B0X.c和Serial_STM32x.c等。
6.3 DM9161和DM9162的区别
早期我们发布的STM32F407开发板的PHY芯片使用的是DM9161,现在这个芯片基本已经停产,所以已经统一改成使用DM9162,这两个型号主要在以下两个地方有区别,其它基本都一样。
1、两个PHY芯片的的ID不一样,DM9161的ID是0x0181B8B1,DM9162的ID是0x0181B8A0。
2、系统刚上电时,DM9161的ID寄存器支持立即读取,但是DM9162不支持,这一点用户在使用的时候要特别注意。但是DM9161和DM9162都支持立即写寄存器BMCR,所以当前的操作就是直接对寄存器BMCR发复位命令,然后再进行相关设置。
对于这两个芯片,了解这两点区别就可以了。另外,这两个芯片的手册和其它的相关知识在这个帖子里面进行了简单的汇总:http://bbs.armfly.com/read.php?tid=19577 。
6.4 底层驱动实现说明
当前教程配套的开发板STM32F407和STM32F429都是采用的RMII接口,即下面这种硬件接口方式:
RMII接口降低了 10/100Mbps下微控制器以太网外设与外部PHY间的引脚数。根据IEEE 802.3u标准, MII包括16个数据和控制信号的引脚。RMII规范将引脚数减少为7个(引脚数减少62.5%)。RMII具有以下特性:
(1)支持10Mbps和100Mbps的运行速率。
(2)参考时钟必须是 50 MHz。
(3)相同的参考时钟必须从外部提供给 MAC 和外部以太网 PHY。
(4)它提供了独立的2位宽(双位)的发送和接收数据路径,即发生和接收都是占用了两个引脚。
根据上面的硬件设计,我们需要实现RMII接口用到的引脚配置,STM32的MAC配置以及用到的PHY芯片DM9161/9162的配置。
6.4.1 STM32F407和STM32F429开发板底层驱动区别
STM32F407和STM32F429开发板的底层驱动仅有一个引脚配置不同,其它所有的驱动代码都一样。STM32F407开发板使用的引脚如下:
/* PA1/ETH_RMII_RX_CLK PA2/ETH_MDIO PA7/RMII_CRS_DV PC1/ETH_MDC PC4/ETH_RMII_RX_D0 PC5/ETH_RMII_RX_D1 PG11/ETH_RMII_TX_EN PG13/FSMC_A24/ETH_RMII_TXD0 PG14/ETH_RMII_TXD1 PH6/MII_INT ----- 中断引脚,这里将其用于网线断开或者连接的状态触发 */
STM32F429开发板使用的引脚如下:
/* PA1/ETH_RMII_RX_CLK PA2/ETH_MDIO PA7/RMII_CRS_DV PC1/ETH_MDC PC4/ETH_RMII_RX_D0 PC5/ETH_RMII_RX_D1 PG11/ETH_RMII_TX_EN PG13/FSMC_A24/ETH_RMII_TXD0 PB13/ETH_RMII_TXD1 PH6/MII_INT ----- 中断引脚,这里将其用于网线断开或者连接的状态触发 */
从两者的引脚可以看出F407开发板的TXD1引脚用的是PG14,F429开发板的TXD1引脚用的是PB13。除了这点不同,底层驱动的其它地方都是相同的。
6.4.2 中断方式和查询方式接口函数
RL-TCPnet的底层提供了中断和查询两种方式的接口函数。
1、 查询方式需要提供如下几个函数的实现:
(1)void init_ethernet ()
初始化以太网控制器。
(2)void send_frame (OS_FRAME *frame)
发送数据包给以太网控制器。
(3)void poll_ethernet (void)
从以太网控制器缓冲中读取数据包。
2、 中断方式需要提供以下几个函数的实现:
(1)void init_ethernet ()
初始化以太网控制器。
(2)void send_frame (OS_FRAME *frame)
发送数据包给以太网控制器。
(3)void int_enable_eth ()
使能以太网控制器中断。
(4)void int_disable_eth ()
关闭以太网控制器中断。
(5)interrupt function
中断函数,主要用于数据包的接收。
教程配套例子是采用的中断方式,需要用户提供中断接口函数的实现,当前的实现是在KEIL官方ETH_STM32F4xx.c文件的基础上修改而来的。官方提供的驱动是基于DP83848C实现的,现在将其修改为DM9161和DM9162的驱动,并增加PHY芯片的中断触发功能,这样可以实时监测到网线的插拔状态。
6.4.3 用于调试和配置的宏定义
底层驱动文件ETH_STM32F4xx.c文件里面提供了三个宏定义,分别如下:
1. 用于驱动调试的宏定义
/* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #if 1 #define printf_eth printf #else #define printf_eth(...) #endif
在底层驱动比较关键的地方都加上了函数printf_eth,用于驱动代码的调试,如果不想使用这个功能,将条件编译#if 1改成#if 0就可以了。如果PHY芯片正确驱动了,串口打印出来的效果就是如下这个样子的:
2. 用于选择10Mbps,100Mbps或者Auto-Negotiation功能
/* 默认情况下,我们选择是自动识别,即使用PHY芯片支持的Auto-Negotiation实现自适应10Mbps网络或者100Mbps网络 但是这种时间稍长,如果用户确定了使用的网络是10Mbps还是100Mbps,直接通过下面的宏定义选择即可,如果使用的 自适应,两个都不需要选择。 */ //#define _10MBIT_ //#define _100MBIT_
3. 用于网线插拔消息实时打印
为了检查网线插拔是否正确识别,这里专门做了一个宏定义,方便串口打印,具体使用和说明看本章节6.4.8小节的讲解,下面是宏定义:
#define ETH_CONSTATUS #define ETH_CONNECT "ETH_LINK Connect\r\n" #define ETH_DISCONNECT "ETH_LINK Disconnect\r\n"
6.4.4 初始化函数init_ethernet
初始化函数主要是实现以太网RMII方式的引脚配置,PHY芯片DM9161/9162的配置,MAC配置及其DMA方式配置。具体实现的代码如下:
/* ********************************************************************************************************* * 函 数 名: init_ethernet * 功能说明: 初始化以太网RMII方式引脚,驱动PHY,配置MAC及其DMA方式。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void init_ethernet (void) { U32 regv,tout,conn; /* 关闭PHY中断触发引脚 */ NVIC_DisableIRQ(EXTI9_5_IRQn); //--------------(1) /* 使能系统配置控制器时钟 */ RCC->APB2ENR |= (1 << 14); /* 复位以太网MAC */ RCC->AHB1RSTR |= 0x02000000; /* 选择RMII接口,必须在 MAC 处于复位状态且在使能 MAC 时钟之前完成此配置 */ SYSCFG->PMC |= (1 << 23); /* 停止复位以太网MAC */ RCC->AHB1RSTR &= ~0x02000000; /* 使能以太网时钟,GPIOA,GPIOB,GPIOC,GPIOG时钟 */ RCC->AHB1ENR |= 0x1E000047; /* 原始驱动还配置了PA8,用于给PHY芯片提供时钟,V6开发板外置有源晶振,无需配置PA8 */ //-------(2) /* 配置PA1,PA2和PA7,复用功能,推挽模式,100MHz,无上拉下拉,复用到AF11 (Ethernet) */ GPIOA->MODER &= ~0x0000C03C; GPIOA->MODER |= 0x00008028; GPIOA->OTYPER &= ~0x00000086; GPIOA->OSPEEDR |= 0x0003C03C; GPIOA->PUPDR &= ~0x0003C03C; GPIOA->AFR[0] &= ~0xF0000FF0; GPIOA->AFR[0] |= 0xB0000BB0; /* 配置PC1,PC4和PC5,复用功能,推挽模式,100MHz,无上拉下拉,复用到AF11 (Ethernet) */ GPIOC->MODER &= ~0x00000F0C; GPIOC->MODER |= 0x00000A08; GPIOC->OTYPER &= ~0x00000032; GPIOC->OSPEEDR |= 0x00000F0C; GPIOC->PUPDR &= ~0x00000F0C; GPIOC->AFR[0] &= ~0x00FF00F0; GPIOC->AFR[0] |= 0x00BB00B0; /* 配置PG11,PG13,复用功能,推挽模式,100MHz,无上拉下拉,复用到AF11 (Ethernet) */ GPIOG->MODER &= ~0x0CC00000; GPIOG->MODER |= 0x08800000; GPIOG->OTYPER &= ~0x00002800; GPIOG->OSPEEDR |= 0x0CC00000; GPIOG->PUPDR &= ~0x0CC00000; GPIOG->AFR[1] &= ~0x00F0F000; GPIOG->AFR[1] |= 0x00B0B000; /* 配置PB13,复用功能,推挽模式,100MHz,无上拉下拉,复用到AF11 (Ethernet) */ GPIOB->MODER &= ~0x0C000000; GPIOB->MODER |= 0x08000000; GPIOB->OTYPER &= ~0x00002000; GPIOB->OSPEEDR |= 0x0C000000; GPIOB->PUPDR &= ~0x0C000000; GPIOB->AFR[1] &= ~0x00F00000; GPIOB->AFR[1] |= 0x00B00000; /* 寄存器ETH->DMABMR的SR位置1后,MAC DMA控制器会复位所有MAC子系统的内部寄存器和逻辑。在所有内 核时钟域完成复位操作后,该位自动清零。重新编程任何内核寄存器之前,在该位中读取0 值。 */ ETH->DMABMR |= DBMR_SR; while (ETH->DMABMR & DBMR_SR); conn = 0; /* HCLK的时钟是168MHz,这里选项CR位为100,CR占用寄存器ETH->MACMIIAR的bit4,bit3和bit2。 CR 时钟范围选项可确定 HCLK 频率并用于决定 MDC 时钟频率: 选项 HCLK MDC 时钟 000 60-100MHz HCLK/42 001 100-150MHz HCLK/62 010 20-35MHz HCLK/16 011 35-60MHz HCLK/26 100 150-168MHz HCLK/102 101、110、111 保留 */ ETH->MACMIIAR = 0x00000010; /* 注意事项:DM9161可以上电后就读取其ID寄存器,但是DM9162不行,需要延迟一段时间这里为了方便起见, 直接将其复位,发送复位指令可以立即执行。 */ /* 第1步:复位DM9161/9162 ***********************************************************/ printf_eth("===============================================================\r\n"); printf_eth("下面是DM9161/9162的硬件初始化:\r\n"); printf_eth("1. Start PHY_ID_DM9161/9162 Init\r\n"); /* 发送复位命令 */ write_PHY (PHY_REG_BMCR, 0x8000); //--------------(3) /* 等待复位完成 */ for (tout = 0; tout < 0x10000; tout++) { regv = read_PHY (PHY_REG_BMCR); if (!(regv & 0x8800)) { /* 复位完成 */ printf_eth("2. Reset Complete\r\n"); break; } } /* 第2步:配置DM9161/9162 ***********************************************************/ #if defined (_10MBIT_) //--------------(4) write_PHY (PHY_REG_BMCR, PHY_FULLD_10M); /* 连接到10Mbps的网络 */ #elif defined (_100MBIT_) write_PHY (PHY_REG_BMCR, PHY_FULLD_100M); /* 连接到100Mbps的网络 */ #else /* 通过Auto-Negotiation实现自适应10Mbps网络或者100Mbps网络 */ write_PHY (PHY_REG_BMCR, PHY_AUTO_NEG); //--------------(5) /* 等待完成Auto-Negotiation */ for (tout = 0; tout < 0x100000; tout++) { regv = read_PHY (PHY_REG_BMSR); if (regv & 0x0020) { /* 完成Auto-Negotiation */ printf_eth("3. Auto-Negotiation Complete\r\n"); break; } } #endif /* 第3步:检测连接状态 ***********************************************************/ for (tout = 0; tout < 0x10000; tout++) { regv = read_PHY (PHY_REG_BMSR); if (regv & (1 << 2)) //--------------(6) { printf_eth("4. Connection Succeeded\r\n"); /* PHY已经连接上网络 */ g_ucEthLinkStatus = 1; /* 获取连接信息 */ regv = read_PHY (PHY_REG_DSCSR); if ((regv & (1 << 15))|(regv & (1 << 13))) //--------------(7) { /* 全双工 */ printf_eth("5. Full-duplex connection\r\n"); conn |= PHY_CON_SET_FULLD; } if ((regv & (1 << 15))|(regv & (1 << 14))) //--------------(8) { /* 速度100Mbps的网络 */ printf_eth("6. 100Mbps Mode\r\n"); conn |= PHY_CON_SET_100M; } break; } else { printf_eth("4. Connection failed\r\n"); /* 未连接上 */ g_ucEthLinkStatus = 0; } } /* 第4步:使能DM9161/9162中断 ***********************************************************/ /* 使能DM9161/9162的连接中断 */ write_PHY (PHY_REG_INTERRUPT, 1<<12); /* 配置引脚PH6来接收中断信号 */ Eth_Link_EXTIConfig(); /* 第5步:使能DM9161/9162中断 ***********************************************************/ /* 初始化MAC配置寄存器 (1)当该位MCR_ROD置1时,MAC禁止在半双工模式下接收帧。 (2)当该位MCR_ROD清0时,MAC接收PHY发送的所有数据包。 (3)如果MAC在全双工模式下工作,该位不适用。 */ ETH->MACCR = MCR_ROD; /* 设置MAC工作在全双工模式 */ if (conn & PHY_CON_SET_FULLD) { /* 使能全双工 */ ETH->MACCR |= MCR_DM; } /* 通过位MCR_FES配置MAC通信速度 (1)0表示10Mbps (2)1表示100Mbps */ if (conn & PHY_CON_SET_100M) { /* 配置为100Mbps */ ETH->MACCR |= MCR_FES; } /* MACFFR 以太网帧过滤寄存器,配置可接收所有MAC组播包,即MAC地址第一个字节的bit0 = 1 */ ETH->MACFFR = MFFR_HPF | MFFR_PAM; /* MACFCR 以太网流控制寄存器,ZQPD零时间片暂停禁止 */ ETH->MACFCR = MFCR_ZQPD; /* 设置以太网MAC地址寄存器 */ ETH->MACA0HR = ((U32)own_hw_adr[5] << 8) | (U32)own_hw_adr[4]; //--------------(9) ETH->MACA0LR = ((U32)own_hw_adr[3] << 24) | (U32)own_hw_adr[2] << 16 | ((U32)own_hw_adr[1] << 8) | (U32)own_hw_adr[0]; /* 初始化DMA发送和接收描述符 */ rx_descr_init (); //--------------(10) tx_descr_init (); //--------------(11) /* 刷新FIFO,启动DMA发送和接收功能 DMAOMR 工作模式寄存器 位20 DOMR_FTF:刷新发送 FIFO (Flush transmit FIFO): 该位置1时,发送FIFO控制器逻辑会复位为默认值,因此,TX FIFO中的所有数据均会 丢失/刷新。刷新操作结束时该位在内部清零。此位清零之前不得对工作模式寄存器执 行写操作。 位13 DOMR_ST:启动/停止发送 (Start/stop transmission) 该位置1时,启动发送,DMA会检查当前位置的发送列表来查找待发送的帧。 位1 DOMR_SR:启动/停止接收 (Start/stop receive) 该位置1时,启动接收,DMA尝试从接收列表中获取描述符并处理传入帧。 */ ETH->DMAOMR = DOMR_FTF | DOMR_ST | DOMR_SR; //--------------(12) /* 使能发送和接收 */ ETH->MACCR |= MCR_TE | MCR_RE; /* 复位所有MAC中断 */ ETH->DMASR = 0xFFFFFFFF; /* 使能发送和接收中断 DMAIER 中断使能寄存器 位16 NISE:使能所有正常中断(Normal interrupt summary enable) 位15 AISE:使能所有异常中断(Abnormal interrupt summary enable) 位7 RBUIE:接收缓冲区不可用中断使能(Receive buffer unavailable interrupt enable) 当该位和AISE位都置1后,可使能接收缓冲区不可用中断。该位清零时,会禁止接 收缓冲区不可用中断。 位6 RIE:接收中断使能 (Receive interrupt enable) 当该位和AISE都置1后,可使能接收中断。该位清零时,会禁止接收中断。 */ ETH->DMAIER = ETH_DMAIER_NISE | ETH_DMAIER_AISE | ETH_DMAIER_RBUIE | ETH_DMAIER_RIE; /* 设置为最高优先级,仅调用NVIC->ISER设置的默认优先级也是最高优先级0 */ NVIC_SetPriority(ETH_IRQn, 0); printf_eth("===============================================================\r\n"); }
1. 这里通过函数NVIC_DisableIRQ(EXTI9_5_IRQn)关闭PHY芯片触发STM32的PH6引脚中断,防止PHY芯片初始化的过程中造成误触发。
2. 初始化RMII接口用到的引脚:
/* PA1/ETH_RMII_RX_CLK PA2/ETH_MDIO PA7/RMII_CRS_DV PC1/ETH_MDC PC4/ETH_RMII_RX_D0 PC5/ETH_RMII_RX_D1 PG11/ETH_RMII_TX_EN PG13/FSMC_A24/ETH_RMII_TXD0 PB13/ETH_RMII_TXD1 PH6/MII_INT ----- 中断引脚,这里将其用于网线断开或者连接的状态触发 */
3. 对PHY芯片的BMCR寄存器bit15置1可以实现对PHY芯片的软件复位操作。PHY芯片能否正确复位是建立在前面RMII接口引脚正确配置,而且PHY芯片的硬件电路设计没问题的基础上。间接的,我们也就可以通过判断芯片是否能够正常复位来判断RMII接口引脚配置是否正确,PHY芯片的引脚电路设计是否正确。
给PHY芯片发送了复位命令后,要等待复位完成,也就是继续查询此寄存器的bit15或者bit11,任何一个被清零了,都表示系统正常复位了。也就是下面调用函数:
regv = read_PHY (PHY_REG_BMCR);
进行不断的查询,直到bit15或者bit11任何一个bit被清零,表示PHY芯片正常复位了。
4. 配置PHY工作在10Mbps或者100Mbps状态, 也可以通过使用PHY芯片支持的Auto-Negotiation实现自适应10Mbps网络或者100Mbps网络,但是这种时间稍长,如果用户确定了使用的网络是10Mbps还是100Mbps,直接通过下面的宏定义选择即可,如果使用的自适应,两个都不需要选择。
#define _10MBIT_
#define _100MBIT_
5. 通过配置PHY芯片的BMCR寄存器的bit12使能Auto-Negotiation功能,从而可以根据实际的网络环境是10Mbps还是100Mbps实现自适应(这里自适应的意思是PHY芯片根据所处的网络环境来自行配置10Mbps或者100Mbps)。配置完毕后,不断查询BMSR寄存器的bit5来判断自适应是否完成,这个判别过程时间稍长。
6. 通过读取PHY芯片的BMSR寄存器bit2来获取连接状态,即PHY芯片是否和外部网络建立了10Mbps或者100Mbps的网络连接,如果返回1表示有效的连接已经建立,否则反之。同时设置全局变量g_ucEthLinkStatus来表示连接状态,方便查询。
7. 通过读取PHY芯片DSCSR寄存器bit15和bit13来获取PHY芯片是否工作在全双工模式。可以读取这个寄存器是建立在用户使能了Auto-Negotiation功能的基础上。
BIT15 : 100Mbps全双工模式判别。
Auto-Negotiation功能完成后,如果此位是1表示PHY芯片工作在100Mpbs全双工模式。
BIT13:10Mbps全双工模式判别。
Auto-Negotiation功能完成后,如果此位是1表示PHY芯片工作在10Mpbs全双工模式。
8. 通过读取PHY芯片DSCSR寄存器bit15和bit14来获取PHY芯片是否工作在100Mbps。可以读取这个寄存器是建立在用户使能了Auto-Negotiation功能的基础上。
BIT15 : 100Mbps全双工模式判别。
Auto-Negotiation功能完成后,如果此位是1表示PHY芯片工作在100Mpbs全双工模式。
BIT14:10Mbps半双工模式判别。
Auto-Negotiation功能完成后,如果此位是1表示PHY芯片工作在10Mpbs半双工模式。
9. 配置MAC地址,地址的设置是在配置向导文件Net_Config.c文件里面:
10. 配置MAC的DMA接收描述符,具体实现代码如下:
/* ********************************************************************************************************* * 函 数 名: rx_descr_init * 功能说明: MAC DMA接收描述符初始化。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void rx_descr_init (void) { U32 i,next; /* 1. RDES0:接收描述符字0,对应Rx_Desc[i].Stat 位31 OWN:所有关系位 (Own bit) 该位置1时,指示描述符由MAC子系统的DMA所拥有。 该位清零时,指示描述符由主机所拥有,即CPU。 DMA在帧接收完成或此描述符的关联缓冲区已满时将该位清零。 2. RDES1:接收描述符字1,对应Rx_Desc[i].Ctrl 位14 RCH: 链接的第二个地址 (Second address chained) 该位置1时,表示描述符中的第二个地址是下一个描述符地址,而非第二个缓冲区地址。该 位置1时,RBS2(RDES1[28:16])为无关值。RDES1[15]比RDES1[14]优先处理。 位12:0 RBS1:接收缓冲区1大小 (Receive buffer 1 size) 第一个数据缓冲区的大小以字节为单位。即使RDES2(缓冲区1地址指针)的值未对齐,缓 冲区大小也必须为4、8或16的倍数,具体取决于总线宽度32、64或128。如果缓冲区大小不 是4、8或16的倍数,这种情况的结果是未定义。如果该字段为0,则DMA会忽略该缓冲区并 使用缓冲区2或下一个描述符,具体取决于RCH(位14)的值。 3. RDES2:接收描述符字2,对应Rx_Desc[i].Addr 位31:0 RBAP1/RTSL:接收缓冲区1地址指针/接收帧时间戳低位 Receive buffer 1 address pointer Receive frame time stamp low 4. RDES3:接收描述符字3,对应Rx_Desc[i].Next 位31:0 RBAP2/RTSH:接收缓冲区2地址指针(下一个描述符地址)/ 接收帧时间戳高位 Receive buffer 2 address pointer (next descriptor address) Receive frame time stamp high */ RxBufIndex = 0; for (i = 0, next = 0; i < NUM_RX_BUF; i++) { if (++next == NUM_RX_BUF) next = 0; Rx_Desc[i].Stat = DMA_RX_OWN; Rx_Desc[i].Ctrl = DMA_RX_RCH | ETH_BUF_SIZE; Rx_Desc[i].Addr = (U32)&rx_buf[i]; Rx_Desc[i].Next = (U32)&Rx_Desc[next]; } /* 接收描述符列表地址寄存器指向接收描述符列表的起始处 */ ETH->DMARDLAR = (U32)&Rx_Desc[0]; }
这里是将接收描述符做成了环形队列进行初始化,通过DMA接收描述符结构体成员Next指向下一个描述符的地址,从而组成一个环形队列。这样DMA方式数据接收的时候就可以做成FIFO的形式,提升DMA接收效率。DMA接收描述符定义和DMA缓冲定义:
#define NUM_RX_BUF 4 /* 接收缓冲个数 (4*1536=6K) */ #define NUM_TX_BUF 2 /* 发送缓冲个数 (2*1536=3K) */ #define ETH_BUF_SIZE 1536 /* 发送/接收缓冲大小定义 */ /* DMA 接收描述符定义 */ typedef struct { U32 volatile Stat; U32 Ctrl; U32 Addr; U32 Next; } RX_Desc; static RX_Desc Rx_Desc[NUM_RX_BUF]; /* DMA接收描述符 */ static U32 rx_buf[NUM_RX_BUF][ETH_BUF_SIZE>>2]; /* DMA接收描述符缓冲 */
11. 配置MAC的DMA发送描述符,具体实现代码如下:
/* ********************************************************************************************************* * 函 数 名: tx_descr_init * 功能说明: MAC DMA发送描述符初始化 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void tx_descr_init (void) { U32 i,next; /* 1. TDES0:发送描述符字0,对应Tx_Desc[i].CtrlStat 位29 LS :末段 (Last segment) 该位置1时,指示缓冲区中包含帧的末段。 位28 FS :首段 (First segment) 该位置1时,指示缓冲区中包含帧的首段 位20 TCH:链接的第二个地址 (Second address chained) 该位置1时,表示描述符中的第二个地址是下一个描述符地址,而非第二个缓冲区地址。 TDES0[20]置1时,TBS2(TDES1[28:16])为无关值。TDES0[21]比TDES0[20]优先处理。 2. TDES1:发送描述符字1,对应Tx_Desc[i].Size 3. TDES2:发送描述符字2,对应Tx_Desc[i].Addr 位31:0 TBAP1:发送缓冲区1地址指针/发送帧时间戳低位 Transmit buffer 1 address pointer / Transmitframe time stamp low 4. TDES3:发送描述符字3,对应Tx_Desc[i].Next 位 1:0 TBAP2:发送缓冲区2地址指针(下一个描述符地址)/ 发送帧时间戳高位 Transmit buffer 2 address pointer (Next descriptor address) Transmit frame time stamp high */ TxBufIndex = 0; for (i = 0, next = 0; i < NUM_TX_BUF; i++) { if (++next == NUM_TX_BUF) next = 0; Tx_Desc[i].CtrlStat = DMA_TX_TCH | DMA_TX_LS | DMA_TX_FS; Tx_Desc[i].Addr = (U32)&tx_buf[i]; Tx_Desc[i].Next = (U32)&Tx_Desc[next]; } /* 发送描述符列表地址寄存器指向发送描述符列表的起始处 */ ETH->DMATDLAR = (U32)&Tx_Desc[0]; }
这里是将发送描述符做成了环形队列进行初始化,通过DMA发送描述符结构体成员Next指向下一个描述符的地址,从而组成一个环形队列。这样DMA方式数据发送的时候就可以做成FIFO的形式,提升DMA发送效率。DMA发送描述符定义和DMA缓冲定义:
#define NUM_RX_BUF 4 /* 接收缓冲个数 (4*1536=6K) */ #define NUM_TX_BUF 2 /* 发送缓冲个数 (2*1536=3K) */ #define ETH_BUF_SIZE 1536 /* 发送/接收缓冲大小定义 */ /* DMA 接收描述符定义 */ typedef struct { U32 volatile CtrlStat; U32 Size; U32 Addr; U32 Next; } TX_Desc; static TX_Desc Tx_Desc[NUM_TX_BUF]; /* DMA发送描述符 */ static U32 tx_buf[NUM_TX_BUF][ETH_BUF_SIZE>>2]; /* DMA发送描述符缓冲 */
12. 剩下的函数主要是使能MAC的DMA方式发送和接收功能,并使能以太网中断。
6.4.5 数据包发送函数send_frame
下面是数据包的发送函数,主要是通过初始化函数中建立的MAC DMA发送描述符实现FIFO方式的数据帧发送。
/* ********************************************************************************************************* * 函 数 名: send_frame * 功能说明: 传递数据帧给MAC DMA发送描述符,并使能发送。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void send_frame (OS_FRAME *frame) { U32 *sp,*dp; U32 i,j; j = TxBufIndex; /* 等待上一帧数据发送完成 */ while (Tx_Desc[j].CtrlStat & DMA_TX_OWN); sp = (U32 *)&frame->data[0]; dp = (U32 *)(Tx_Desc[j].Addr & ~3); /* 复制要发送的数据到DMA发送描述符中 */ for (i = (frame->length + 3) >> 2; i; i--) { *dp++ = *sp++; } /* 设置数据帧大小 */ Tx_Desc[j].Size = frame->length; /* 发送描述符由DMA控制发送 */ Tx_Desc[j].CtrlStat |= DMA_TX_OWN; if (++j == NUM_TX_BUF) j = 0; TxBufIndex = j; /* 开始帧传输 */ /* DMASR 以太网 DMA 状态寄存器 向ETH_DMASR寄存器[16:0]中的(未保留)位写入1会将其清零,写入 0 则不起作用。 位1 TPSS:发送过程停止状态 (Transmit process stopped status) 当发送停止时,此位置 1。 */ ETH->DMASR = DSR_TPSS; /* DMATPDR 以太网DMA发送轮询请求寄存器 应用程序使用此寄存器来指示DMA轮询发送描述符列表。 位 31:0 TPD:发送轮询请求(Transmit poll demand) 向这些位写入任何值时,DMA都会读取ETH_DMACHTDR寄存器指向的当前描述符。如果 该描述符不可用(由CPU所有),则发送会返回到挂起状态,并将ETH_DMASR寄存器位2 进行置位。如果该描述符可用,则发送会继续进行。 */ ETH->DMATPDR = 0; }
6.4.6 以太网中断函数ETH_IRQHandler
以太网中断函数主要用于实现数据包的接收,主要是通过初始化函数中建立的MAC DMA接收描述符实现FIFO方式的数据帧接收。
/* ********************************************************************************************************* * 函 数 名: ETH_IRQHandler * 功能说明: 以太网中断,主要处理从MAC DMA接收描述符接收到的数据帧以及错误标志的处理。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void ETH_IRQHandler (void) { OS_FRAME *frame; U32 i, RxLen; U32 *sp,*dp; i = RxBufIndex; /* 循环所有接受描述符列表,遇到未接收到数据的退出循环 */ do //--------------(1) { /* #define DMA_RX_ERROR_MASK (DMA_RX_ES | DMA_RX_LE | DMA_RX_RWT | \ DMA_RX_RE | DMA_RX_CE) 有错误,放弃此帧数据,错误类型包含如下: 位15 DMA_RX_ES:错误汇总(Error summary),即CRC错误,接收错误,看门狗超时,延迟冲突等。 位12 DMA_RX_LE:长度错误(Length error) 该位置1时,指示接收帧的实际长度与长度/类型字段的值不符。该字段仅在帧类 型位(RDES0[5])复位后有效。 位4 DMA_RX_RWT:接收看门狗超时 (Receive watchdog timeout) 该位置1时,表示接收看门狗计时器在接收当前帧时超时,且当前帧在看门狗超 时后被截断了 位3 DMA_RX_RE: 接收错误 (Receive error) 该位置1时,表示在帧接收期间,当发出RX_DV信号时,会发出RX_ERR信号。 位1 DMA_RX_CE: CRC 错误(CRC error) 该位置1时,表示接收的帧发生循环冗余校验(CRC)错误。只有最后一个描述符 (RDES0[8])置1时,该字段才有效 */ if (Rx_Desc[i].Stat & DMA_RX_ERROR_MASK) { goto rel; } /* #define DMA_RX_SEG_MASK (DMA_RX_FS | DMA_RX_LS) 位9 FS:第一个描述符 (First descriptor) 该位置1时,指示此描述符包含帧的第一个缓冲区。如果第一个缓冲区的大小为0,则第二 个缓冲区将包含帧的帧头。如果第二个缓冲区的大小为0,则下一个描述符将包含帧的帧头。 位8 LS:最后一个描述符 (Last descriptor) 该位置1时,指示此描述符指向的缓冲区为帧的最后一个缓冲区。 下面的函数用于判断此帧数据是否只有一个缓冲,初始化接收描述符列表的时候,每个描述符仅设置了 一个缓冲。 */ if ((Rx_Desc[i].Stat & DMA_RX_SEG_MASK) != DMA_RX_SEG_MASK) //--------------(2) { goto rel; } RxLen = ((Rx_Desc[i].Stat >> 16) & 0x3FFF) - 4; if (RxLen > ETH_MTU) { /* 数据包太大,直接放弃 */ goto rel; } /* 申请动态内存,RxLen或上0x80000000表示动态内存不足了不会调用函数sys_error() */ frame = alloc_mem (RxLen | 0x80000000); /* 如果动态内存申请失败了,放弃此帧数据;成功了,通过函数put_in_queue存入队列中 */ if (frame != NULL) { sp = (U32 *)(Rx_Desc[i].Addr & ~3); dp = (U32 *)&frame->data[0]; for (RxLen = (RxLen + 3) >> 2; RxLen; RxLen--) { *dp++ = *sp++; } put_in_queue (frame); //--------------(3) } /* 设置此接收描述符继续接收新的数据 */ rel: Rx_Desc[i].Stat = DMA_RX_OWN; if (++i == NUM_RX_BUF) i = 0; } while (!(Rx_Desc[i].Stat & DMA_RX_OWN)); RxBufIndex = i; /* DMASR DMA的状态寄存器(DMA status register) 位7 RBUS:接收缓冲区不可用状态 (Receive buffer unavailable status) 此位指示接收列表中的下一个描述符由CPU所拥有,DMA无法获取。接收过程进入挂起状态。 要恢复处理接收描述符,CPU应更改描述符的拥有关系,然后发出接收轮询请求命令。如果 未发出接收轮询请求命令,则当接收到下一个识别的传入帧时,接收过程会恢复。仅当上一 接收描述符由DMA所拥有时,才能将ETH_DMASR[7]置1。 DMAIER的接收缓冲区不可用中断RBUIE是bit7,对于的接收缓冲区不可用状态在DMA状态寄存器中也是bit7。 */ if (ETH->DMASR & INT_RBUIE) { /* 接收缓冲区不可用,重新恢复DMA传输 */ ETH->DMASR = ETH_DMASR_RBUS; ETH->DMARPDR = 0; } /* DMASR DMA的状态寄存器(DMA status register) 这里实现清除中断挂起标志 位16 ETH_DMASR_NIS:所有正常中断 (Normal interrupt summary) 位15 ETH_DMASR_AIS:所有异常中断 (Abnormal interrupt summary) 位6 ETH_DMASR_RS :接收状态 (Receive status) 此位指示帧接收已完成,具体的帧状态信息已经包含在描述符中,接收仍保持运行状态。 */ ETH->DMASR = ETH_DMASR_NIS | ETH_DMASR_AIS | ETH_DMASR_RS; }
- 由于初始化的时候创建了一个MAC DMA描述符的FIFO,这里是通过do while语句读取FIFO中所有已经接收到的数据包。
- 要理解这个函数的作用,首先需要明白初始化DMA接收描述符的时候已经设置每个描述符仅有一个缓冲,这里就是判断此描述符是否只有这一个缓冲地址。
- 通过函数put_in_queue就将接收到的数据帧存储到RL-TCPnet协议栈中了,供上层API使用。
6.4.7 中断开关函数int_enable_eth和int_disable_eth
这两个函数比较简单,实现了以太网中断的开关设置。
/* ********************************************************************************************************* * 函 数 名: int_enable_eth * 功能说明: 使能以太网中断 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void int_enable_eth (void) { NVIC->ISER[1] = 1 << 29; } /* ********************************************************************************************************* * 函 数 名: int_disable_eth * 功能说明: 使能以太网中断 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void int_disable_eth (void) { NVIC->ICER[1] = 1 << 29; }
6.4.8 网线插拔检测中断EXTI9_5_IRQHandler
(特别注意,如果开发板上电前,网线已经插到板子上面了,这种情况是不会触发中断的,其余情况都会触发中断)
中断函数EXTI9_5_IRQHandler的作用只有一个,就是实时检测网线的插拔状态。具体实现代码如下:
/* ********************************************************************************************************* * 函 数 名: EXTI9_5_IRQHandler * 功能说明: PH6引脚的中断处理 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ #define ETH_CONSTATUS //--------------(1) #define ETH_CONNECT "ETH_LINK Connect\r\n" #define ETH_DISCONNECT "ETH_LINK Disconnect\r\n" void EXTI9_5_IRQHandler(void) { U32 regv, tout; if (EXTI_GetITStatus(EXTI_Line6) != RESET) { /* 可以考虑在此处加入延迟,有时连接状态变了,但是寄存器没有及时更新*/ regv = read_PHY(PHY_REG_INTERRUPT); //--------------(2) if(regv & (1 << 2)) { /* 重新插入后要多读几次,保证寄存器BMSR被更新 */ for(tout = 0; tout < 10; tout++) //--------------(3) { regv = read_PHY (PHY_REG_BMSR); if (regv & (1 << 2)) { break; } } /* 连接上网线 */ if(regv & (1 << 2)) //--------------(4) { #ifdef ETH_CONSTATUS //--------------(4) const char *pError = ETH_CONNECT; uint8_t i; #endif g_ucEthLinkStatus = 1; #ifdef ETH_CONSTATUS for (i = 0; i < sizeof(ETH_CONNECT); i++) { USART1->DR = pError[i]; /* 等待发送结束 */ while ((USART1->SR & USART_FLAG_TC) == (uint16_t)RESET); } #endif } /* 网线断开 */ else //--------------(5) { #ifdef ETH_CONSTATUS //--------------(6) const char *pError = ETH_DISCONNECT; uint8_t i; #endif g_ucEthLinkStatus = 0; #ifdef ETH_CONSTATUS for (i = 0; i < sizeof(ETH_DISCONNECT); i++) { USART1->DR = pError[i]; /* 等待发送结束 */ while ((USART1->SR & USART_FLAG_TC) == (uint16_t)RESET); } #endif } } /* 清中断挂起位 */ EXTI_ClearITPendingBit(EXTI_Line6); } }
- 如果用户需要网线插拔时,串口可以打印相应的信息出来,使能这个宏定义即可。默认情况下,此宏定义是注销掉的。另外,特别注意一点,如果首次下载程序到板子里面,此功能不好用的话,将板子重新上电就好了,以后开关电源也都没有影响,出现这种情况的原因估计是PHY芯片没有正常复位并初始化。
- 读取PHY芯片的中断寄存器,通过此寄存器的bit2可以检测网线的插拔状态变化。如果发生了变化,此位会被置1,读取完毕此寄存器后,此位会被自动清零。
- 通过PHY芯片的中断寄存器仅仅能够判断网线的插拔状态发生了变化,但是不知道网线是插上了还是拔下来了,这个时候就需要通过BMSR寄存器进行判断。这里需要多读几次,防止BMSR寄存器还没有更新。
- 如果寄存器BMSR的bit2是1,表示网线插入。
- 如果用户使能了宏定义#define ETH_CONSTATUS,插拔网线时会打印插拔状态信息。
- 如果寄存器BMSR的bit2是0,表示网线拔出。
- 如果用户使能了宏定义#define ETH_CONSTATUS,插拔网线时会打印插拔状态信息。
6.5 总结
本章节就为大家讲解这么多,主要是为学习下个章节RL-TCPnet的移植做准备。学完本章后,务必将STM32参考手册中MAC章节读一遍。