在hackster.io上看到有人问板上的phy在裸机模式下怎么驱动。
https://www.hackster.io/whitney-knitter/getting-started-with-avnet-s-zuboard-1cg-f1d793
嗨,我刚买了这块板来尝试一些在同一设备上混合软件和hdl的项目。我试图在A53-0上建立并运行一些UDP通信(在独立/裸机应用程序中),并尝试了UDP模板应用程序。不幸的是,Avnet在板上安装了一个不支持的以太网PHY芯片(Microchip
KSZ9131RNXC ),模板无法初始化它。 你知道有没有这种芯片的初始化代码的独立版本的例子吗? 提前感谢。
这个phy在linux下识别是没问题的,而官方demo都是在linux下跑的,因此不需要考虑这个问题。
但是裸机模式下默认的驱动确实不支持,所以需要我们手动修改。
首先在bsp包中添加lwip库,否则在软件模板中没法生成带lwip的例程。
选中platform.spr,选A53对应的Board Support Package,点Modify BSP Settings
选中lwip211,点ok重新编译bsp
注意在standalone界面,可以修改stdin和stdout的串口。一般现成的开发板串口都是固定uart0或者uart1,其实是可以自定义修改的。
bsp编译完成后,创建新程序,选lwIP Echo Server模板
创建完成后,打开main.c,根据你自己的网络环境,修改ip地址、掩码、网关,然后运行
运行起来一直卡在这里
单步跟踪发现进入到init_emacps后,问题出在函数XEmacPs_SetOperatingSpeed
源代码路径psu_cortexa53_0/standalone_psu_cortexa53_0/bsp/psu_cortexa53_0/libsrc/lwip211_v1_7/src/contrib/ports/xilinx/netif/xemacpsif_hw.c
因为speed只能为10,100,1000,而实际值识别到的speed为0,所以导致错误。
原因很明显了,驱动默认只支持Marvell、TI、Realtek这三个品牌的phy,其他品牌的phy需要我们自己根据手册来写驱动
可以看到
XEmacPs_PhyRead(xemacpsp, phy_addr, PHY_IDENTIFIER_1_REG,
&phy_reg);
if ((phy_reg != PHY_MARVELL_IDENTIFIER) &&
(phy_reg != PHY_TI_IDENTIFIER) &&
(phy_reg != PHY_REALTEK_IDENTIFIER)) {
xil_printf("WARNING: Not a Marvell or TI or Realtek Ethernet PHY. Please verify the initialization sequence\r\n");
}
关键是XEmacPs_PhyRead读PHY_IDENTIFIER_1_REG寄存器的值,根据这个值判断厂家型号
#define PHY_IDENTIFIER_1_REG 2
#define PHY_MARVELL_IDENTIFIER 0x0141
#define PHY_TI_IDENTIFIER 0x2000
#define PHY_REALTEK_IDENTIFIER 0x001c
所以要做的第一步就是查芯片手册,找到寄存器2对应的值,增加一个品牌分支
查下原理图,用的phy芯片是Microchip KSZ9131RNXC
https://www.microchip.com/en-us/product/KSZ9131#document-table
根据手册查找对应的寄存器描述,phy的id应该为0x0022
因此增加宏PHY_MICROCHIP_IDENTIFIER,值为0x0022
#define PHY_MICROCHIP_IDENTIFIER 0x0022
修改函数
void detect_phy(XEmacPs *xemacpsp)
void detect_phy(XEmacPs *xemacpsp)
{
u16_t phy_reg;
u32_t phy_addr;
u32_t emacnum;
if (xemacpsp->Config.BaseAddress == XPAR_XEMACPS_0_BASEADDR)
emacnum = 0;
else
emacnum = 1;
for (phy_addr = 31; phy_addr > 0; phy_addr--) {
XEmacPs_PhyRead(xemacpsp, phy_addr, PHY_DETECT_REG,
&phy_reg);
if ((phy_reg != 0xFFFF) &&
((phy_reg & PHY_DETECT_MASK) == PHY_DETECT_MASK)) {
/* Found a valid PHY address */
LWIP_DEBUGF(NETIF_DEBUG, ("XEmacPs detect_phy: PHY detected at address %d.\r\n",
phy_addr));
xil_printf(("XEmacPs detect_phy: PHY detected at address %d.\r\n", phy_addr));
if (emacnum == 0)
phymapemac0[phy_addr] = TRUE;
else
phymapemac1[phy_addr] = TRUE;
XEmacPs_PhyRead(xemacpsp, phy_addr, PHY_IDENTIFIER_1_REG,
&phy_reg);
if ((phy_reg != PHY_MARVELL_IDENTIFIER) &&
(phy_reg != PHY_TI_IDENTIFIER) &&
(phy_reg != PHY_REALTEK_IDENTIFIER)) {
xil_printf("WARNING: Not a Marvell or TI or Realtek Ethernet PHY. Please verify the initialization sequence\r\n");
if(PHY_MICROCHIP_IDENTIFIER == phy_reg){
xil_printf("Phy: MICROCHIP --Add by Leo 2023.12\r\n");
}
}
}
}
}
修改速度函数,增加对Microchip的判断分支
static u32_t get_IEEE_phy_speed(XEmacPs *xemacpsp, u32_t phy_addr)
{
u16_t phy_identity;
u32_t RetStatus;
XEmacPs_PhyRead(xemacpsp, phy_addr, PHY_IDENTIFIER_1_REG,
&phy_identity);
if (phy_identity == PHY_TI_IDENTIFIER) {
RetStatus = get_TI_phy_speed(xemacpsp, phy_addr);
} else if (phy_identity == PHY_REALTEK_IDENTIFIER) {
RetStatus = get_Realtek_phy_speed(xemacpsp, phy_addr);
} else if (phy_identity == PHY_MICROCHIP_IDENTIFIER) { //增加了对Microchip的判断分支
RetStatus = get_Microchip_phy_speed(xemacpsp, phy_addr);
} else {
RetStatus = get_Marvell_phy_speed(xemacpsp, phy_addr);
}
return RetStatus;
}
然后再增加get_Microchip_phy_speed的具体实现,
仔细对比了寄存器的差别,主要是速度寄存器的地址和解析与ieee不一样
注意要禁用RX delay和TX delay。开始我没有设置,在100M下工作正常,在1000M下怎么都ping不通,反复查了好久还以为是网络拓扑有问题,最后才发现是这个问题。
//Add by Leo Wang
#define MICROCHIP_CONTROL_REG 0x1F
#define MICROCHIP_SPEED_MASK 0x0070
#define MICROCHIP_SPEED_1000 0x0040
#define MICROCHIP_SPEED_100 0x0020
#define MICROCHIP_SPEED_10 0x0010
#define MMD_FUNCTION_DATA 0x4000
static u32_t read_mmd_register(XEmacPs *InstancePtr, u32 PhyAddress, u32 devadd,
u32 RegisterNum, u16 *PhyDataPtr)
{
u32_t phyregtemp;
u32_t RetStatus;
XEmacPs_PhyWrite(InstancePtr, PhyAddress, PHY_REGCR, devadd);
XEmacPs_PhyWrite(InstancePtr, PhyAddress, PHY_ADDAR, RegisterNum);
XEmacPs_PhyWrite(InstancePtr, PhyAddress, PHY_REGCR, devadd | MMD_FUNCTION_DATA);
RetStatus = XEmacPs_PhyRead(InstancePtr, PhyAddress, PHY_ADDAR, (u16_t *)&phyregtemp);
if (RetStatus != XST_SUCCESS) {
return XST_FAILURE;
}
*PhyDataPtr= phyregtemp;
return XST_SUCCESS;
}
static u32_t write_mmd_register(XEmacPs *InstancePtr, u32 PhyAddress, u32 devadd,
u32 RegisterNum, u16 PhyData)
{
XEmacPs_PhyWrite(InstancePtr, PhyAddress, PHY_REGCR, devadd);
XEmacPs_PhyWrite(InstancePtr, PhyAddress, PHY_ADDAR, RegisterNum);
XEmacPs_PhyWrite(InstancePtr, PhyAddress, PHY_REGCR, devadd | MMD_FUNCTION_DATA);
XEmacPs_PhyWrite(InstancePtr, PhyAddress, PHY_ADDAR, PhyData);
return XST_SUCCESS;
}
#define KSZ9131RN_MMD_COMMON_CTRL_REG 2
#define KSZ9131RN_RXC_DLL_CTRL 0x4c //2.76
#define KSZ9131RN_TXC_DLL_CTRL 0x4d //2.77
#define MICROCHIP_DLL_DELAY_NOT_USED 0x1000 //bit12 bypass rxdll/txdll
static u32_t get_Microchip_phy_speed(XEmacPs *xemacpsp, u32_t phy_addr)
{
u16_t control;
u16_t status;
u16_t status_speed;
u32_t timeout_counter = 0;
u32_t temp_speed;
u32_t RetStatus;
xil_printf("Start PHY autonegotiation \r\n");
XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_CONTROL_REG_OFFSET, &control);
XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_CONTROL_REG_OFFSET, control | IEEE_CTRL_RESET_MASK);
sleep(1);
RetStatus = XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_CONTROL_REG_OFFSET, &control);
if (RetStatus != XST_SUCCESS) {
xil_printf("Error during reset \n\r");
return XST_FAILURE;
}
//RGMII_ID SETUP
#if 0
// --> RX
RetStatus = read_mmd_register(xemacpsp, phy_addr, KSZ9131RN_MMD_COMMON_CTRL_REG, KSZ9131RN_RXC_DLL_CTRL, &control);
if (RetStatus != XST_SUCCESS) {
xil_printf("Error setting RX delay\n\r");
return XST_FAILURE;
}
control &= ~MICROCHIP_DLL_DELAY_NOT_USED;
write_mmd_register(xemacpsp, phy_addr, KSZ9131RN_MMD_COMMON_CTRL_REG, KSZ9131RN_RXC_DLL_CTRL, control);
#endif
#if 1
// --> TX
RetStatus = read_mmd_register(xemacpsp, phy_addr, KSZ9131RN_MMD_COMMON_CTRL_REG, KSZ9131RN_TXC_DLL_CTRL, &control);
if (RetStatus != XST_SUCCESS) {
xil_printf("Error setting TX delay\n\r");
return XST_FAILURE;
}
control &= ~MICROCHIP_DLL_DELAY_NOT_USED;
write_mmd_register(xemacpsp, phy_addr, KSZ9131RN_MMD_COMMON_CTRL_REG, KSZ9131RN_TXC_DLL_CTRL, control);
#endif
//REG4 10/100/1000M
XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_AUTONEGO_ADVERTISE_REG, &control);
control |= IEEE_ASYMMETRIC_PAUSE_MASK;
control |= IEEE_PAUSE_MASK;
control |= ADVERTISE_100;
control |= ADVERTISE_10;
XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_AUTONEGO_ADVERTISE_REG, control);
XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_1000_ADVERTISE_REG_OFFSET,
&control);
control |= ADVERTISE_1000;
XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_1000_ADVERTISE_REG_OFFSET,
control);
//REG0 1.自动协商使能 2.自动协商复位
XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_CONTROL_REG_OFFSET, &control);
control |= IEEE_CTRL_AUTONEGOTIATE_ENABLE;
control |= IEEE_STAT_AUTONEGOTIATE_RESTART;
XEmacPs_PhyWrite(xemacpsp, phy_addr, IEEE_CONTROL_REG_OFFSET, control);
//读REG1状态寄存器
XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_STATUS_REG_OFFSET, &status);
xil_printf("Waiting for PHY to complete autonegotiation.\r\n");
//根据bit5判断协商是否完成,没完成就一直等待,直到30秒超时
while ( !(status & IEEE_STAT_AUTONEGOTIATE_COMPLETE) ) {
sleep(1);
timeout_counter++;
if (timeout_counter == 30) {
xil_printf("Auto negotiation error \r\n");
return XST_FAILURE;
}
XEmacPs_PhyRead(xemacpsp, phy_addr, IEEE_STATUS_REG_OFFSET, &status);
}
xil_printf("autonegotiation complete \r\n");
//主要是修改这个速度寄存器的解析
XEmacPs_PhyRead(xemacpsp, phy_addr, MICROCHIP_CONTROL_REG,
&status_speed);
xil_printf("MICROCHIP_CONTROL_REG = 0x%.4x \r\n", status_speed);
if (0 == (status_speed & 0x0001)) {
temp_speed = status_speed & MICROCHIP_SPEED_MASK;
if (temp_speed == MICROCHIP_SPEED_1000)
return 1000;
else if(temp_speed == MICROCHIP_SPEED_100)
return 100;
else
return 10;
}
return XST_FAILURE;
}
寄存器0x1F说明如下:
运行结果:
正常识别到phy速度,我在电脑端用的是usb转百兆网卡,点对点方式连接的开发板,因此识别出来的速度是100M。
如果连到路由器这个值有可能是100或者1000。
现在10M网卡基本见不到了,如果显示是10大概率还是工作不正常,需要另行分析。
如果有多个网卡,windows下用-S(srcaddr的意思)来指定源网卡ip,linux下改成-I(interface)
192.168.1.100是电脑ip
192.168.1.10是开发板的ip
ping -S 192.168.1.100 192.168.1.10 -t
命令后的-t是一直ping,没有这个参数ping 4次就会停下来
在电脑端ping这个ip,正常ping通,说明phy确实已经正常工作。
这个phy的led有点特殊,我开始以为寄存器没配置对,仔细查了手册,发现正常工作就是这样。
我们正常的网口会有两个灯:
连接指示灯(绿色):连接指示灯亮就代表线路连接正常。
信号指示灯(黄色)
在连接指示灯亮的情况下,信号指示灯的含义如下:
a) 如果信号指示灯闪烁,代表信号正常,正在通信;
b) 如果信号指示灯灭,代表没有通信;
c) 如果信号指示灯长亮,代表网线短路。
而在这个板子
直接与电脑相连(100M):左边灯不亮,右边黄色闪烁。
连路由器(1000M):左边绿灯亮,隔几秒闪一下。右边灯不亮
先查RJ-45座的型号,ARJM11C7-502-KB-EW2,根据手册确定两个led各对应哪个io
https://abracon.com/Magnetics/ARJM11.pdf
根据手册和原理图,可以看出左边的灯对应的是Phy芯片的LED_LINK(LED2),右边的黄灯对应LED_Y(LED_ACT, LED1)
在phy手册里面有说明,在1000M情况下,LED2亮和闪烁。在100M的情况下,LED1常亮和闪烁。所以正常情况下,不会出现两个灯同时亮,只有1个灯会亮
再查Phy芯片手册