【经验分享】调试STM32F107VC单片机驱动DP83848以太网PHY芯片时遇到的问题

调试时使用的程序: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模块就是把灯接错了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章