調試時使用的程序:https://blog.csdn.net/ZLK1214/article/details/105457370
【杜邦線(或普通電線)影響時鐘信號的完整性】
筆者調試STM32F107VC單片機驅動DP83848以太網芯片時,用了兩塊板子。
一塊是浩普電子的開發板(下面的左圖),另一塊是自己用麪包板搭出來的(下面的右圖)。
浩普電子的開發板是印製PCB板,上面採用的是RMII接口,能夠調通。
但是麪包板搭的那塊板子,背面全是自己接的線,只能調通MII接口(25MHz時鐘),無法調通RMII接口(50MHz時鐘)。程序運行後,既不能發送也不能接收。但網卡的燈是正常的,網線插拔也能正常檢測。
板子上的50MHz時鐘由單片機的PA8 MCO產生,該時鐘接到了PA1上用作RMII_REF_CLK,另外又接到了DP83848芯片的X1接口上給DP83848提供時鐘。於是用示波器看一下這幾個引腳的時鐘波形。
PA8的時鐘波形如下:
可以看到頻率爲50MHz,波形還算正常。
再看看DP83848 X1引腳的時鐘波形:
頻率仍然爲50MHz,波形正常,所以DP83848能正常工作。
但是PA1處的時鐘卻只有25MHz,波形已經變得不規則了。單片機得不到正確的REF_CLK,因此發送和接收都無法正常進行,這就是問題的所在。
筆者後面嘗試又在芯片上面又接了一根線,連接PA8和PA1,這下不但RMII不行,連MII都不能正常工作了,網口上的燈也不亮了,估計是加了一根線後,PA1和DP83848 X1的時鐘信號更加不規則了。用示波器看下PA1的波形,雖然頻率是49MHz,但是波形非常不規則。
筆者的RMII程序能夠在兩個PCB印製板上正常運行,唯獨只有麪包板上不能運行。
這說明,連線的質量和長短會影響高速數字信號的傳輸。調試DP83848網口程序時最好不要用麪包板和杜邦線,尤其是長度很長的那種杜邦線。
另外,在使能了AFIO_MAPR_ETH_REMAP的情況下,使用MII接口時,PD8(ETH_RX_DV)應該接到DP83848芯片的39引腳(RX_DV)上,PA0(ETH_CRS)應該接到DP83848芯片的40引腳(CRS)上。但在使用RMII接口時,PD8(ETH_CRS_DV)應該接到DP83848芯片的40引腳(CRS_DV)上。也就是說兩種模式下PD8連接的引腳是不一樣的,這一點要注意。
【自動協商】
ETH->MACCR寄存器中的DM位決定了網絡通信採用半雙工還是全雙工,FES位決定了網絡通信的速度是10Mbps還是100Mbps。這兩位設置的如果和實際網絡環境不符,則無法通信。應該使用DP83848的自動協商(Auto Negotiation)功能獲取網絡環境配置,然後正確設置這兩位。標準庫的ETH_Init函數和HAL庫的HAL_ETH_Init函數裏面就包含了自動協商的代碼。
寄存器操作參考代碼:
// DP83848中的一些寄存器位
#define DP83848_BMCR 0x00 // Basic Mode Control Register
#define DP83848_BMCR_ANE _BV(12) // Auto-Negotiation Enable
#define DP83848_BMSR 0x01 // Basic Mode Status Register
#define DP83848_BMSR_ANC _BV(5) // Auto-Negotiation Complete
#define DP83848_BMSR_LS _BV(2) // Link Status
#define DP83848_PHYSTS 0x10 // PHY Status Register
#define DP83848_PHYSTS_DS _BV(2) // Duplex
#define DP83848_PHYSTS_SS _BV(1) // Speed10
void eth_write_reg(uint8_t addr, uint16_t value)
{
ETH->MACMIIDR = value;
ETH->MACMIIAR = (1 << ETH_MACMIIAR_PA_Pos) | (addr << ETH_MACMIIAR_MR_Pos) | ETH_MACMIIAR_CR_Div42 | ETH_MACMIIAR_MW | ETH_MACMIIAR_MB;
while (ETH->MACMIIAR & ETH_MACMIIAR_MB);
}
uint16_t eth_read_reg(uint8_t addr)
{
ETH->MACMIIAR = (1 << ETH_MACMIIAR_PA_Pos) | (addr << ETH_MACMIIAR_MR_Pos) | ETH_MACMIIAR_CR_Div42 | ETH_MACMIIAR_MB;
while (ETH->MACMIIAR & ETH_MACMIIAR_MB);
return ETH->MACMIIDR;
}
// PHY自動協商(非常重要!!!!)
// 根據自動協商結果配置ETH_MACCR的DM(duplex mode)和FES(fast ethernet speed)位
void eth_auto_negotiation(void)
{
uint16_t value;
while ((eth_read_reg(DP83848_BMSR) & DP83848_BMSR_LS) == 0); // 等待網線插好
value = eth_read_reg(DP83848_BMCR);
if ((value & DP83848_BMCR_ANE) == 0) // 若DP83848外部接線沒有接成自動協商模式
eth_write_reg(DP83848_BMCR, value | DP83848_BMCR_ANE); // 則手動執行自動協商
while ((eth_read_reg(DP83848_BMSR) & DP83848_BMSR_ANC) == 0); // 等待自動協商完畢
// 根據自動協商結果配置MACCR寄存器
value = eth_read_reg(DP83848_PHYSTS);
if (value & DP83848_PHYSTS_DS)
ETH->MACCR |= ETH_MACCR_DM;
if ((value & DP83848_PHYSTS_SS) == 0)
ETH->MACCR |= ETH_MACCR_FES;
}
【接口選擇】
AFIO->MAPR的AFIO_MAPR_MII_RMII_SEL和AFIO_MAPR_ETH_REMAP這兩位配置不正確,或者配置的時機不正確,都將導致網口無法收發數據包。
AFIO_MAPR_MII_RMII_SEL決定了是選擇MII接口還是RMII接口。AFIO_MAPR_ETH_REMAP決定了ETH的引腳是否重映射,主要體現在接收引腳的不同:到底是用PB和PC口來接收數據,還是用PD口來接收數據。
進行AFIO配置前一定要確保AFIO的時鐘是打開了的:RCC->APB2ENR |= RCC_APB2ENR_AFIOEN
如果要使用RMII接口,則必須在ETH的時鐘打開之前配置好,否則配置是無法生效的,進而導致網口無法收發數據包!!
__HAL_RCC_AFIO_CLK_ENABLE();
__HAL_AFIO_ETH_RMII();
上面這兩句話必須在下面三句話之前執行,否則不能生效。
__HAL_RCC_ETHMAC_CLK_ENABLE();
__HAL_RCC_ETHMACRX_CLK_ENABLE();
__HAL_RCC_ETHMACTX_CLK_ENABLE();
所以說,heth.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII這句話根本不起作用,執行HAL_ETH_Init函數的時候,ETH的時鐘已經打開了,所以根本沒有辦法將MII變成RMII,後面網卡也就無法收發數據包了!
另外,ETH的三個時鐘必須全部開啓。必須在RCC中同時打開ETH的三個RCC時鐘,哪怕只想發送數據,也必須打開MACRX的時鐘。
【MCO引腳產生錯誤頻率的時鐘】
使用MII接口時,MCO應該給DP83848提供25MHz的時鐘。使用RMII接口時應該提供50MHz的時鐘。因此兩種模式下,PLL3應該使用不同的分頻係數。
MII接口:HAL_RCC_MCOConfig(RCC_MCO, RCC_MCO1SOURCE_PLL3CLK_DIV2, RCC_MCODIV_1);
RMII接口:HAL_RCC_MCOConfig(RCC_MCO, RCC_MCO1SOURCE_PLL3CLK, RCC_MCODIV_1);
【用庫接收數據包】
在HAL庫中,通過判斷ETH_DMA_FLAG_R標誌位判斷是否有新數據包到來,用HAL_ETH_GetReceivedFrame函數接收數據包。
if (__HAL_ETH_DMA_GET_FLAG(&heth, ETH_DMA_FLAG_R) != RESET)
{
__HAL_ETH_DMA_CLEAR_FLAG(&heth, ETH_DMA_FLAG_R);
while (HAL_ETH_GetReceivedFrame(&heth) == HAL_OK)
ethernetif_input(&netif_dp83848);
}
接收完每個數據包後一定要自己將該數據包涉及到的所有接收描述符還給DMA(將OWN位置位),然後把SegCount成員清零。最後檢查一下RBU標誌位,檢查是否出現了接收緩衝區不夠用的問題,如果因接收緩衝區不夠用而導致接收停止,應將DMARPDR寄存器寫0恢復接收(這個操作,HAL庫裏面沒有封裝成宏或函數,所以只能直接寫寄存器完成)。下面這段示例代碼是在ethernetif_input裏面執行的。
(lwip 2.0.3版本中的ethernetif.c文件位於lwip-2.0.3.zip壓縮包的src/netif文件夾下。而lwip 2.1.0~2.1.2版本中的ethernetif.c文件則被移動到了contrib-2.1.0.zip壓縮包的examples/ethernetif文件夾裏面了, 裏面有一些細微的修改)
// 確認收到數據
desc = heth.RxFrameInfos.FSRxDesc;
for (i = 0; i < heth.RxFrameInfos.SegCount; i++)
{
desc->Status |= ETH_DMARXDESC_OWN;
desc = (ETH_DMADescTypeDef *)(desc->Buffer2NextDescAddr);
}
heth.RxFrameInfos.SegCount = 0;
if (__HAL_ETH_DMA_GET_FLAG(&heth, ETH_DMA_FLAG_RBU) != RESET)
{
printf("Receive buffer unavailable!\n");
__HAL_ETH_DMA_CLEAR_FLAG(&heth, ETH_DMA_FLAG_RBU);
WRITE_REG(heth.Instance->DMARPDR, 0);
}
將SegCount清零的操作非常重要。因爲HAL庫的HAL_ETH_GetReceivedFrame函數有一個bug,如果某個接收描述符的FS(First Segment)和LS(Last Segment)位不同時爲1,程序能正常處理。但是如果同時爲1,那麼SegCount就會一直增加而不能回到0。這裏將SegCount清零,就是爲了避免這個bug帶來的影響。實際上,標準外設庫(SPL)裏面也有類似的問題。
【ETH緩衝區只支持SRAM不支持Flash】
以太網外設的DMA是專用DMA,與其他外設所用的DMA1和DMA2沒有關係。在lwip協議棧中如果遇到q->payload指向Flash區域(地址爲0x08000000~0x08ffffff)的情況,必須把數據複製到SRAM中(地址爲0x20000000~0x2fffffff),否則幀肯定會發送失敗。
【關於網口的燈】
最後再說明一下,網口的黃燈應該接的是DP83848的LED_ACT(26腳),綠燈應該接LED_LINK(28腳)。正常情況下是插入網線後綠燈亮,拔掉網線後綠燈滅,網線有數據傳輸時黃燈閃爍,不要把燈接錯了。微雪的DP83848模塊就是把燈接錯了。