RT1180这个Switch可能是我用过最复杂的MCU外设了,其实这还不是我第一次用Switch,当年还做工程师的时候,项目需要4个网口,但是主控MPC8313只有两个eTSC,所以通过Local Bus又外接了一个KSZ8842,这个片子就很折腾人,前后发现了俩个致命问题:
为了解决这些问题,请来了Micrel的FAE,折腾了半天他也搞不定,后来改版的时候把它换成了两个独立的KSZ8851(MAC+PHY)。
讲远了,还是回到RT1180上来,我们RT1180这个Switch的学名叫NETC,RT1180应该用的是这个IP的3.0的版本。下面是它的框图,从图中可知,CPU其实有两个独立的MAC:
ENETC0(Eth4):独立的千兆MAC,并未经过交换机。
ENETC1(Port4):通过Pseudo MAC连接到交换机的Port4口上。

因为网口比较多,这里画出了它们在EVK上的对应关系,方便大家理解和使用(大家也可以看板子上的丝印,对应ETHx):

今天我们先来学习下\netc\switch这个demo,从名字上看就知道它可以驱动switch,展示netc的switch的功能,并且写的非常“巧妙”,整个测试甚至都不需要网线就能进行。

这个demo的流程是这样的,首先初始化硬件,然后是MDIO,接下来会去初始化PHY,当PHY初始化完成后,进入Switch的测试流程。
intmain(void){status_t result = kStatus_Success; BOARD_InitHardware(); result = APP_MDIO_Init();if (result != kStatus_Success) { PRINTF("\r\nMDIO Init failed!\r\n");return result; } result = APP_PHY_Init();if (result != kStatus_Success) { PRINTF("\r\nPHY Init failed!\r\n");return result; } APP_SWT_MacLearning_Forwarding();while (1) { }}这里PHY的初始化需要重点介绍下,RT1180-EVK为了兼容EtherCAT的需求,ETH0和ETH4通过RMII模式连接的RTL8201(通过电阻可可修改为MII),其余网口通过RGMII连接的RTL8211F,所以初始化函数中定义了两个PHY的结构体,这里一定要睁大眼睛看,.autoNeg这个字段设置的是false,也就是说默认关闭了自协商,所以当PHY初始化完毕后,如果插上网线,这时发现电脑端网口有红色的×千万不要以为是板子出问题了,
phy_config_t phy8211Config = { .autoNeg = false, .speed = kPHY_Speed1000M, .duplex = kPHY_FullDuplex, .enableEEE = false, .ops = &phyrtl8211f_ops, }; phy_config_t phy8201Config = { .autoNeg = false, .speed = kPHY_Speed100M, .duplex = kPHY_FullDuplex, .enableEEE = false, .ops = &phyrtl8201_ops, };
并且在board.h中定义了每个PHY的物理地址:
/*! @brief The Ethernet PHY addresses. */#define BOARD_EP0_PHY_ADDR (0x03U)#define BOARD_SWT_PORT0_PHY_ADDR (0x02U)#define BOARD_SWT_PORT1_PHY_ADDR (0x05U)#define BOARD_SWT_PORT2_PHY_ADDR (0x04U)#define BOARD_SWT_PORT3_PHY_ADDR (0x07U)还需要注意的一点是,每个PHY在初始化的时候都开启了回环模式,这样做就不需要准备4,5根网线去连接RJ45了,也方便后面MAC地址学习。

接下来就进入APP_SWT_MacLearning_Forwarding函数了,这里先初始化了EP_Init,EP应该就是endpoint,针对这个demo用的是ENETC1PSI0,大家可以理解为框图里左边那个Port4,部分代码如下:

(void)EP_GetDefaultConfig(&g_ep_config); g_ep_config.si = EXAMPLE_SWT_SI; g_ep_config.siConfig.txRingUse = 1; g_ep_config.siConfig.rxRingUse = 1; g_ep_config.reclaimCallback = APP_ReclaimCallback; g_ep_config.msixEntry = &msixEntry[0]; g_ep_config.entryNum = 3; result = EP_Init(&g_ep_handle, &g_macAddr[0], &g_ep_config, &bdrConfig);if (result != kStatus_Success) {return result; }接下来就该初始化Switch了,部分代码如下,因为之前PHY设置了环回模式,所以一直处于link up的状态,很快就可以通过Link状态的检测,这里有个非常重要的参数g_swt_config.bridgeCfg.dVFCfg.mfo = kNETC_FDBLookUpWithDiscard,这个表示如果一帧数据的FDB查找表里没有匹配的目的地址,该帧会被直接扔掉,不会去泛洪,即使用FF FF FF FF FF FF这样的广播地址也不会去泛洪各个PORT,这样设置也是有原因的,我们之前在每个PORT的PHY测都做了环回,如果泛洪就会产生风暴。但是如果我们要跑Lwip,这个字段应该设置为kNETC_FDBLookUpWithFlood使能泛洪功能
SWT_GetDefaultConfig(&g_swt_config); /* Wait PHY link up. */ PRINTF("Wait for PHY link up...\r\n"); for (i = 0; i < EXAMPLE_SWT_MAX_PORT_NUM; i++) { /* Only check the enabled port. */ if (((1U << i) & EXAMPLE_SWT_USED_PORT_BITMAP) == 0U) { continue; } do { result = APP_PHY_GetLinkStatus(EXAMPLE_SWT_PORT0 + i, &link); } while ((result != kStatus_Success) || (!link)); result = APP_PHY_GetLinkModeSpeedDuplex(EXAMPLE_SWT_PORT0 + i, &phyMode, &phySpeed, &phyDuplex); if (result != kStatus_Success) { return result; } g_swt_config.ports[i].ethMac.miiMode = phyMode; g_swt_config.ports[i].ethMac.miiSpeed = phySpeed; g_swt_config.ports[i].ethMac.miiDuplex = phyDuplex; g_swt_config.ports[i].bridgeCfg.isRxVlanAware = false; } g_swt_config.bridgeCfg.dVFCfg.portMembership = 0x1FU; g_swt_config.bridgeCfg.dVFCfg.enUseFilterID = true; g_swt_config.bridgeCfg.dVFCfg.filterID = EXAMPLE_FRAME_FID; g_swt_config.bridgeCfg.dVFCfg.mfo = kNETC_FDBLookUpWithDiscard; g_swt_config.bridgeCfg.dVFCfg.mlo = kNETC_HardwareMACLearn; g_swt_config.cmdRingUse = 1U; g_swt_config.cmdBdrCfg[0].bdBase = &g_cmdBuffDescrip[0]; g_swt_config.cmdBdrCfg[0].bdLength = 8U; result = SWT_Init(&g_swt_handle, &g_swt_config); if (result != kStatus_Success) { return result; }接下来就会开始MAC地址学习,因为做了环回,这里组了一帧数据,源MAC地址的最后一个字节设置为PORT号,目的MAC地址的最后一个字节是F0,然后通过SWT_SendFrame将该帧通过指定的PORT口发送出去,参考代码如下:
APP_BuildBroadCastFrame(0xF0, i);txArg.ring = 0;result = SWT_SendFrame(&g_swt_handle, txArg, (netc_hw_port_idx_t)(kNETC_SWITCH0Port0 + i), false, &txFrame, NULL, NULL);static void APP_BuildBroadCastFrame(uint32_t dstPort, uint32_t srcPort){ uint32_t length = EXAMPLE_EP_TEST_FRAME_SIZE - 14U; uint32_t count; memcpy(&g_txFrame[0], &g_macAddr[0], 5U); g_txFrame[5] = dstPort; memcpy(&g_txFrame[6], &g_macAddr[0], 5U); g_txFrame[11] = srcPort; g_txFrame[12] = (length >> 8U) & 0xFFU; g_txFrame[13] = length & 0xFFU; for (count = 0; count < length; count++) { g_txFrame[count + 14U] = count % 0xFFU; }}当NETC接收到该帧后就会学习到该PORT的MAC地址,接下来就会进入Frame forwarding的流程,这里会遇到一个小小的bug,测试前代码会取消回环模式,这个demo本意还是希望用户可以用wireshark抓到板子发送帧的,但这里在解除环回后,没有检测link up状态,直接就发送了,PHY的协商是需要时间的,所以在PC端根本看不到发送的帧报文,会感觉这个demo好像没RUN成功,如果在每次发送那里打个断点或者每帧发送前加个2s的延时,就可以在PC端抓到报文了。
/* Disable the PHY loopback on this port */result = APP_PHY_EnableLoopback(EXAMPLE_SWT_PORT0 + i, false);if (kStatus_Success != result){ return result;}#endifNETC_PortGetPhyMacTxStatistic(g_swt_handle.hw.ports[i].eth, kNETC_ExpressMAC, &swtTxStatis);txCount = swtTxStatis.total511To1023BPacket;细心的小伙伴会发现,在Frame forwarding时,发送函数用的时EP_SendFrame,这个函数和之前MAC地址学习时用到的SWT_SendFrame函数作用都是发送网络帧,区别在于EP_SendFrame相当于从PORT4口发送到Switch上,通过交换机从不同的PORT口发送,而SWT_SendFrame是直接从某个PORT口发送的:
APP_BuildBroadCastFrame(i, 0xF0);result = EP_SendFrame(&g_ep_handle, 0, &txFrame, NULL, NULL);好了,这个Demo基本上讲完了。下期我们讲NETC的SWITCH如何适配LWIP。