|
楼主 |
发表于 2017-10-16 14:39:06
|
显示全部楼层
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)void init_ethernet ()
初始化以太网控制器。
(2)void send_frame (OS_FRAME *frame)
发送数据包给以太网控制器。
(3)void poll_ethernet (void)
从以太网控制器缓冲中读取数据包。
中断方式需要提供以下几个函数的实现:
(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文件里面提供了三个宏定义,分别如下:
u 用于驱动调试的宏定义
- /*
- *********************************************************************************************************
- * 用于本文件的调试
- *********************************************************************************************************
- */
- #if 1
- #define printf_eth printf
- #else
- #define printf_eth(...)
- #endif
复制代码 在底层驱动比较关键的地方都加上了函数printf_eth,用于驱动代码的调试,如果不想使用这个功能,将条件编译#if 1改成#if 0就可以了。如果PHY芯片正确驱动了,串口打印出来的效果就是如下这个样子的:
u 用于选择10Mbps,100Mbps或者Auto-Negotiation功能
- /*
- 默认情况下,我们选择是自动识别,即使用PHY芯片支持的Auto-Negotiation实现自适应10Mbps网络或者100Mbps网络
- 但是这种时间稍长,如果用户确定了使用的网络是10Mbps还是100Mbps,直接通过下面的宏定义选择即可,如果使用的
- 自适应,两个都不需要选择。
- */
- //#define _10MBIT_
- //#define _100MBIT_
复制代码 u 用于网线插拔消息实时打印
为了检查网线插拔是否正确识别,这里专门做了一个宏定义,方便串口打印,具体使用和说明看本章节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方式配置。具体实现的代码如下:
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方式的数据帧接收。
1. 由于初始化的时候创建了一个MAC DMA描述符的FIFO,这里是通过do while语句读取FIFO中所有已经接收到的数据包。
2. 要理解这个函数的作用,首先需要明白初始化DMA接收描述符的时候已经设置每个描述符仅有一个缓冲,这里就是判断此描述符是否只有这一个缓冲地址。
3. 通过函数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);
- }
- }
复制代码 1. 如果用户需要网线插拔时,串口可以打印相应的信息出来,使能这个宏定义即可。默认情况下,此宏定义是注销掉的。另外,特别注意一点,如果首次下载程序到板子里面,此功能不好用的话,将板子重新上电就好了,以后开关电源也都没有影响,出现这种情况的原因估计是PHY芯片没有正常复位并初始化。
2. 读取PHY芯片的中断寄存器,通过此寄存器的bit2可以检测网线的插拔状态变化。如果发生了变化,此位会被置1,读取完毕此寄存器后,此位会被自动清零。
3. 通过PHY芯片的中断寄存器仅仅能够判断网线的插拔状态发生了变化,但是不知道网线是插上了还是拔下来了,这个时候就需要通过BMSR寄存器进行判断。这里需要多读几次,防止BMSR寄存器还没有更新。
4. 如果寄存器BMSR的bit2是1,表示网线插入。
5. 如果用户使能了宏定义#define ETH_CONSTATUS,插拔网线时会打印插拔状态信息。
6. 如果寄存器BMSR的bit2是0,表示网线拔出。
7. 如果用户使能了宏定义#define ETH_CONSTATUS,插拔网线时会打印插拔状态信息。
6.5 总结
本章节就为大家讲解这么多,主要是为学习下个章节RL-TCPnet的移植做准备。学完本章后,务必将STM32参考手册中MAC章节读一遍。 |
|