第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章節讀一遍。