行业资讯
📅 2026/9/8 15:02:34
YXDSP-F28388D以太网移植实战:EMAC驱动与LWIP协议栈接入详解
拿到YXDSP-F28388D开发板那天我原本的计划是先跑个电机控制样例毕竟C2000系列在工业控制圈里的名声太响。但这块板子跟常见的C2000开发板不太一样除了两个C28x DSP内核还塞了一个Cortex-M7内核外设列表里更躺着一个完整的Ethernet MAC模块。刚开始我也没太把这个网口当回事直到项目需求里明确写了“设备需要和产线上位机通过TCP/IP完成状态上报和固件升级”我才意识到这次必须认认真真把EMAC硬件和LWIP协议栈从底层啃一遍。这篇文章就把整个移植过程记录下来从F28388D的EMAC外设特征、RMII接口和PHY芯片的硬件设计到描述符驱动、MDIO读写、LWIP的lwipopts配置和netif接口对接再到最后ping通、长时间传输验证的完整经历。如果你刚好也在折腾YXDSP-F28388D或者用的是其他带以太网MAC的MCU这篇文章里的思路和踩坑点应该能帮你省下好几天的排查时间。1. 这块开发板的以太网外设为什么值得单独写一篇1.1 C28x与Cortex-M7双核架构下的网络外设位置F28388D在TI C2000家族里算是非常“跨界”的存在。它内部有两个C28x DSP核心负责实时控制、电机算法、采样处理这些重负载任务同时还有一个Cortex-M7核心主频和性能都不错。我的第一反应是这芯片是不是在“不务正业”一个单片机带以太网MAC还要配一个M7核怎么想都不像传统DSP该干的事。但真正动手查手册之后才发现这个设计特别聪明。C2000的核心强项是控制环路而以太网协议栈、TCP/IP解析、文件传输这些事情非常吃通用处理能力如果硬塞给C28x反而会拖累控制实时性。让Cortex-M7去跑LWIPC28x继续专注FOC或PFC算法两者通过共享内存或IPC通信这种分工在实际项目里非常高效。YXDSP这块开发板做的事情就是把这些外设资源全部引出来。板载的RJ45网口、PHY芯片、调试器接口还有针对C2000扩展的排针让我可以直接拿它做原型验证。对整个项目来说这块板子承担的是“通信网关”角色底层设备数据由C28x采集和运算M7核处理网络协议再把数据包送上以太网。1.2 以太网接入给工业控制场景补上了哪块短板以前做C2000项目通信方式基本就是RS485、CAN、SPI/UART这些。它们各有各的优势但放到产线远程监控这个场景里短板很明显CAN看着挺快实际传大量日志或固件升级文件时带宽不够RS485虽然抗干扰好但标准Modbus协议栈对大数据块传输和动态拓扑支持有限。Ethernet的优势不用多说带宽高、生态成熟、工具链丰富上位机随便一个TCP/UDP客户端就能对接。F28388D这颗芯片直接内置以太网MAC等于硬件上已经把口子留好了软件上只需要把LWIP跑起来。对很多工业设备来说有了以太网接口就意味着可以脱离调试串口直接用网络做远程配置、Web页面监控、OTA升级。这也是我为什么在这个项目里宁可花时间把EMAC底层一点一点调通也要把网口真正用起来的原因。2. 硬件链路线索从F28388D的EMAC引脚到PHY芯片RMII接口2.1 为什么选择RMII而不是MIIF28388D的EMAC模块同时支持MII和RMII两种外部接口。MII是经典的100M以太网接口信号线比较多TXD[3:0]、RXD[3:0]、TX_ER、RX_ER、CRS、COL再加上时钟总共十几根线RMII则把数据线砍成TXD[1:0]、RXD[1:0]控制信号合并为TX_EN和CRS_DV整体引脚数少了一半。我一开始差点选了MII原因很朴素MII是老前辈资料多TI手册里很多例子都用MII照着写不容易出错。但看了原理图才发现YXDSP这块板子引出的PHY接口就是RMII而且RMII对实际项目有非常实际的好处引脚占用少PCB布线压力小。F28388D本身外设多引脚复用紧张把EMAC占用的引脚控制在RMII范围剩下还能给电平转换、GPIO、PWM保持足够余量。RMII有个硬性要求参考时钟必须精确且稳定频率是50MHz。这要求不像串口那样有点偏差也能忍50MHz如果没有或者抖动过大数据就会偶发错包、丢包。设计上我强烈建议让PHY芯片自己输出50MHz时钟给MCU而不是让MCU去给PHY供时钟。为什么PHY靠近网口时钟走线短MCU更好控制时序而且很多PHY芯片内置时钟电路省掉一颗外部有源晶振。修改后的文字只显示本段内容如果不够长请告诉我在F28388D手册里EMAC外设的引脚复用寄存器需要把对应PINMUX配置成EMAC功能。RMII模式下至少用到的信号就是这些信号方向作用REF_CLK输入50MHz参考时钟由PHY或外部时钟源提供TX_EN输出发送使能TXD[1:0]输出发送数据CRS_DV输入载波侦听/数据有效RXD[1:0]输入接收数据MDC输出MDIO管理时钟MDIO双向MDIO管理数据如果你手头的开发板原理图走的是MII接口那引脚更多但只要软件里把EMAC模块配置成MII模式一样能跑。重点是你要对应好板子上实际引出的PHY连接方式硬件和软件必须保持一致。2.2 PHY选型对比LAN8720A和它的替代者PHY芯片负责把MAC层的数字信号转换成模拟差分信号送到网线上。F28388D本身不带PHY必须在板子上外接一颗。YXDSP这块开发板用的是LAN8720A这颗芯片在工业模块和评估板里非常常见我后来也对比过其他几颗PHY芯片接口RMII参考时钟特点LAN8720ARMII支持外部50MHz或25MHz晶振封装小、便宜、资料多DP83848MII/RMII25MHz晶振TI自家工业级温度范围好KSZ8081RMII支持外部50MHz或25MHz晶振功耗低和LAN8720A接近TJA1100车载百兆—针对车载不太适合通用开发LAN8720A有个很实用的特性它有两种时钟模式。常见做法是给PHY提供一颗25MHz晶振PHY内部锁相环倍频到50MHz然后通过CLKOUT引脚把50MHz时钟送出来给MCU做REF_CLK。这样做的好处是板上只需要一颗普通晶振成本低。另一种做法是外部直接输入50MHz差分或单端时钟给PHYMCU侧自己负责生成参考时钟。我这次设计采用的是第一种YXDSP开发板上PHY主控25MHz晶体CLKOUT接F28388D的EMAC REF_CLK引脚整体非常干净。选PHY还要注意一个细节PHY地址。LAN8720A的SMI地址由PHYAD0引脚电平决定常见配置为0或1。板上如果PHYAD0接地那么MDIO读取地址就是0如果接上拉就是1。调试的时候第一件事就是读PHY ID寄存器确认这个地址对不对不然后面全是白忙。2.3 设计原理图时容易翻车的地方时钟、复位和地平面硬件问题很多时候在原理图阶段就能避免。我这次真正动手之前仔细看了YXDSP开发板的原理图有三个地方容易翻车值得单独拿出来说。第一是复位引脚。PHY芯片的NRST不能一直悬空必须有明确的上电时序。很多MCU的EMAC模块都要求PHY复位释放后至少等待一段时间才能开始MDIO访问。LAN8720A的复位低电平有效一般需要RC电路或者由MCU GPIO控制复位。如果GPIO默认状态刚好是低电平就会把PHY一直按在复位里MAC怎么配置都白搭。第二是时钟走线。REF_CLK是50MHz虽然频率不算夸张但它是所有收发时序的基准走线尽量短远离高频开关节点和电感。我见过有人把REF_CLK走在DC-DC电感旁边结果连续丢包重新布板才好。开发板虽然不需要你布线但如果你之后自己出板子这条一定要记住。第三是地平面。网口RJ45座子外壳、变压器中心抽头、PHY芯片的地处理不好会出现共模干扰轻则ping值抖动重则网络不通。正规设计一般是RJ45的金属外壳通过一个高压电容接到机壳地信号地单点连接PHY的模拟电源和数字电源用磁珠隔开。F28388D开发板已经有成熟设计但如果你照着画板这一块不要图省事。3. 底层驱动动手EMAC寄存器、描述符环和PHY自协商3.1 最小初始化序列时钟、复位、MAC地址和收发控制软件层面的第一步是把F28388D的EMAC模块从零初始化起来。我在TI官方例程基础上做了精简去掉了调试打印保留核心步骤。代码里的寄存器名可能因为SDK版本不同有差异但逻辑大差不差。void emac_init(void) { // 1. 使能EMAC外设时钟并做外设复位 SYSCTL_peripheralReset(SYSCTL_PERIPH_EMAC); while (SYSCTL_peripheralResetStatus(SYSCTL_PERIPH_EMAC) ! SYSCTL_STATUS_COMPLETE); SYSCTL_peripheralEnable(SYSCTL_PERIPH_EMAC); // 2. 关闭MAC发送和接收清掉原有状态 EMACRegs.EMAC_CR.bit.TX_EN 0; EMACRegs.EMAC_CR.bit.RX_EN 0; // 3. 设置MAC地址不能全零否则会产生接收过滤问题 EMACRegs.EMAC_MAC_INDEX 0; EMACRegs.EMAC_ADDR_LOW 0x11223344; EMACRegs.EMAC_ADDR_HIGH 0x00005566; // 4. 配置RMII模式 EMACRegs.EMAC_CR.bit.MII 0; // 0表示RMII1表示MII // 5. 描述符基地址将在下一步配置 // 6. 使能发送和接收 EMACRegs.EMAC_CR.bit.TX_EN 1; EMACRegs.EMAC_CR.bit.RX_EN 1; }这段代码看起来简单但里面藏着一个项目里很常见的坑MAC地址不能全零。如果你的开发板没有预设MAC地址程序里又刚好是全零初始化接收方向会出现很诡异的现象——PHY明明link up但收不到任何数据包或者收到后被过滤掉。我当时排查了很久最后把MAC地址改成板卡贴纸上的地址才正常。还有一个容易被忽略的点EMAC模块在使能收发之前一定要先把DMA复位和描述符准备好。顺序错了寄存器配置可能被后续的软复位冲掉表现就是“我明明写了寄存器怎么读出来还是0”。3.2 发送/接收描述符环的设计MAC把网络数据交给DMA搬运而DMA本身不知道数据在哪它依赖一个叫“描述符环”的数据结构。每个描述符包含控制字段、数据缓冲区指针、数据长度和状态标志。发送和接收各有一个环我的做法是在内存里定义两个结构体数组。#define EMAC_RX_BUF_NUM 4 #define EMAC_TX_BUF_NUM 4 typedef struct { uint32_t hwNext; uint32_t hwBufferPointer; uint16_t bufferLength; uint16_t flags; } emac_desc_t;接收描述符在初始化时就要把缓冲区地址填进去然后把OWNER位置成DMA所有。这样DMA收到数据后会直接写到缓冲区再更新状态位。发送描述符则是软件先把数据填充到缓冲区然后把描述符交给DMA等DMA发送完成后检查状态位。描述符数量不是越多越好。接收缓冲区我用了4个正常100Mbps流量下足够发送缓冲区也用4个。如果你要做大量UDP包突发可以调大一些但每个缓冲区都会吃掉RAM嵌入式设备内存本来就紧张要合理规划。描述符本身必须满足对齐要求。F28388D的EMAC模块要求描述符按4字节或更高的边界对齐缓冲区最好像我这样用__attribute__((aligned(32)))强制对齐。如果不做对齐DMA访问描述符时会出错表现是发送一直停在链头不前进。3.3 SMI/MDIO读写与PHY自协商PHY芯片内部寄存器需要通过SMI接口访问。SMI有两根线MDC时钟和MDIO数据。F28388D的EMAC模块里提供了MDIO控制器你只需要配置分频系数使MDC频率满足PHY芯片要求。LAN8720A的MDIO允许最高2.5MHz如果你的系统时钟是100MHz分频系数就要大于40。uint16_t phy_read(uint8_t phy_addr, uint8_t reg) { // 配置MDC分频 EMACRegs.EMAC_MDIO_CONTROL.bit.CLK_DIV 50; // 100MHz / 50 2MHz // 发起读命令 EMACRegs.EMAC_MDIO_ACCESS.bit.PHYAD phy_addr; EMACRegs.EMAC_MDIO_ACCESS.bit.REGADR reg; EMACRegs.EMAC_MDIO_ACCESS.bit.CTL 0; // 0表示读操作 EMACRegs.EMAC_MDIO_ACCESS.bit.WRITE 1; // MRDY 置位启动事务 EMACRegs.EMAC_MDIO_ACCESS.bit.MRDY 1; // 等待忙状态结束 while (EMACRegs.EMAC_MDIO_ACCESS.bit.BUSY) ; return EMACRegs.EMAC_MDIO_DATA.bit.DATA; }PHY自协商是Link Up的关键。上电后PHY会自动和交换机或PC网卡协商速度和工作模式我们不需要太多干预但软件需要去读状态寄存器确认协商结果。LAN8720A的基本状态寄存器在Reg 1bit1表示链接状态如果为0说明物理层没连上。我在这个阶段遇到过一个问题MDIO读回来的数据全是0xFFFF。当时第一反应是PHY地址错了后来用万用表去量才发现是PHY的复位引脚还拉着低电平PHY根本没启动。所以调试MDIO之前先确认PHY芯片电源、时钟和复位三件事能省掉大量时间。4. LWIP协议栈移植从lwipopts到netif接口4.1 版本和内存策略的选择底层驱动跑通之后就要把LWIP协议栈接进来了。我用的是LWIP 2.1.3版本这个版本在嵌入式社区里比较成熟坑少。相比老版本2.1.x对内存池管理、TCP性能有了不少改进而且TI官方例程大多也基于这个版本跟F28388D的匹配度更高。LWIP可以配置成无操作系统模式也可以配置成带操作系统模式。F28388D的Cortex-M7内核很适合跑FreeRTOS所以我选择了NO_SYS0。这样LWIP自己会创建tcpip_thread通过邮箱和信号量跟硬件驱动交互。内存策略是移植中最容易出问题的环节。LWIP有两种内存分配方式内存堆mem_malloc和内存池memp。我的建议是接收路径尽量使用PBUF_POOL也就是预先分配好的固定大小缓冲区这样在中断上下文里分配pbuf也不会产生不可控的等待。下面是我使用的关键配置经过长时间验证没有出现内存耗尽问题#define MEM_SIZE (32 * 1024) // 内存堆大小 #define PBUF_POOL_SIZE 24 // pbuf池数量 #define PBUF_POOL_BUFSIZE 1520 // 单个缓冲区大小能容纳一个完整以太网帧 #define TCP_MSS 1460 #define TCP_WND (4 * TCP_MSS) // 收窗口 #define TCP_SND_BUF (8 * TCP_MSS) // 发送缓冲 #define LWIP_DHCP 0 // 固定IP暂时不跑DHCP #define LWIP_STATS 0 // 关闭统计减少CPU开销这里有件事必须提醒PBUF_POOL_BUFSIZE不能小于1518。以太网最大帧是1518字节加上VLAN标签会到1522。我看到的很多问题都是因为缓冲区设置成了1500结果收到较大帧时被截断TCP校验一直过不去。宁可多给一点不要刚好卡在临界值上。4.2 sys_arch适配信号量、互斥锁和时间基准带操作系统的LWIP需要实现一个sys_arch层。如果你用过FreeRTOS你会发现LWIP本来就有对应的port只是不同平台封装方式不太一样。F28388D上的实现思路是信号量用FreeRTOS的SemaphoreHandle_t互斥锁用MutexHandle_t线程创建用xTaskCreate。信号量是LWIP和硬件驱动之间最核心的联系。接收中断里拿到网络数据并不直接调用协议栈处理而是通过tcpip_input把pbuf发给tcpip_thread。如果tcpip_input内部阻塞等待信号量那中断上下文必须使用xSemaphoreGiveFromISR而不是加锁版本否则会触发FreeRTOS的断言。时间基准也别忘了。LWIP的TCP超时重传、ARP表老化都依赖系统时间。在F28388D上我直接用Cortex-M7的SysTick来提供毫秒级时间戳然后在sys_arch里实现u32_t sys_now(void) { return (u32_t)tick_ms; }这个函数看起来简单但在早期移植时我犯过一个错误没有开SysTick中断导致sys_now永远返回0TCP连接迟迟建立不起来。如果你发现TCP三次握手一直卡在SYN_SENT先确认sys_now是否在持续增长。4.3 netif驱动的三个核心函数init、output、inputLWIP通过struct netif来抽象网卡设备我们需要注册一个网络接口并提供初始化、发送和接收三个核心能力。low_level_init主要工作是初始化EMAC硬件、填充netif的MAC地址、设置MTU为1500。MTU如果设得过大超过底层缓冲区会导致大包无法发送设得过小浪费协议栈能力。1500是标准值配合PBUF_POOL_BUFSIZE1520非常合适。low_level_output负责把LWIP给出来的pbuf链表转成DMA能识别的连续数据。pbuf本质上可能是一个链表结构每个节点只有一段数据而DMA描述符往往只指向一个连续的地址。我采用的办法是如果pbuf的整个数据量能拼成一个连续缓冲区就直接填写描述符否则先复制到一个静态发送缓冲区再做DMA发送。static err_t low_level_output(struct netif *netif, struct pbuf *p) { uint8_t *buf tx_buffer[0]; int len 0; for (struct pbuf *q p; q ! NULL; q q-next) { memcpy(buf len, q-payload, q-len); len q-len; } // 刷新数据缓存保证DMA能读到最新数据 SCB_CleanDCache_by_Addr((uint32_t *)buf, len); // 绑定发送描述符触发DMA发送 return emac_tx_start(buf, len); }low_level_input则是在接收中断里调用从接收描述符里拿到数据用PBUF_RAM类型创建pbuf拷贝数据后交给netif-input。这里有个关键点在中断里尽量不要做耗时长的memcpy。幸好一个以太网帧最大1518字节拷一次也就几个微秒在可接受范围内。如果你想极致优化可以直接让DMA缓冲区作为pbuf的payload避免拷贝但实现复杂度会高很多我这次先选择了稳妥方案。5. 联调记录从ping不通到稳定传输5.1 第一次ping和Wireshark抓包所有代码写完、编译下载后我直接在PC上用ping 192.168.1.100测试。第一次ping的结果和大家猜的一样请求超时。我当时没有着急改代码而是接上Wireshark抓包仔细看的过程很关键。Wireshark抓包会显示PC发出的ARP请求。如果PC的ARP请求能看到但收不到开发板应答那说明问题出在开发板的MAC接收路径或者ARP协议栈处理上。如果连开发板的网口link都没有起来Wireshark上会看到PC网卡自己报“网络不可达”。那次我抓包后发现ARP请求确实是发出去了但迟迟没有回复说明LMAC收中断已经跑了但ARP包没被正确处理。排查一圈后发现问题在MAC地址我初始化的MAC地址跟板卡上实际配置的不一致导致ARP缓存里记录的是另一个地址。把MAC地址修正后ARP正常应答ping也通了。ping通那一刻整个串口调试终端跳出“Reply from 192.168.1.100: bytes32 time1ms TTL64”时确实很有成就感。但对一个真实项目来说ping通只能说明链路层通了不能说明TCP/IP可靠。5.2 CM7缓存一致性引发的隐蔽故障有个问题是ping通了之后才暴露出来的。刚开始ping包偶尔会丢一两个丢包率大概1%左右表面上是偶发但我觉得不对劲。TCP传大文件的时候速度明显偏低而且偶尔出现校验失败重传。最终锁定是Cortex-M7的D-Cache和DMA的一致性冲突。CM7内核自带D-CacheCPU访问内存时会先经过缓存而DMA是直接从物理内存读写数据两颗“大脑”对同一块内存的认知不一致就会产生数据错乱。接收方向EMAC DMA把网线上收到的数据写进缓冲区但CPU通过D-Cache去读时读到的却是缓存里旧的脏数据导致以太网帧内容错误发送方向CPU把报文写入发送缓冲区DMA去读时又可能读到缓存中还没写回内存的数据导致发出去的包是垃圾数据。解决办法是发送之前做Cache Clean接收之后做Cache Invalidate。我在代码里已经加了SCB_CleanDCache_by_Addr后来又补上了接收路径// 接收完成中断里 SCB_InvalidateDCache_by_Addr((uint32_t *)rx_buffer, len); pbuf_alloc(/* 分配PBUF_RAM */); memcpy(p-payload, rx_buffer, len); netif-input(p, netif);加了Invalidate之后丢包问题消失TCP传输速率也明显稳定。这里有个小经验如果F28388D你用的是Cortex-M7并且开了D-Cache最好把描述符数组和接收缓冲区都放进一个不被缓存覆盖的内存区域或者在初始化时直接把D-Cache关掉。关掉D-Cache最简单但牺牲性能保留Cache加上手动Clean/Invalidate性能更好代价是代码逻辑要仔细。5.3 长时间传输和TCP吞吐率观察ping稳定后我开始做长时间传输测试。测试方法是开发板作为TCP ServerPC作为客户端连接开发板不断发送1KB的递增计数包PC统计丢包和乱序。我让这个测试连续跑了两天48小时里没有出现一次重连计数包序号连续说明TCP可靠性没有问题。吞吐率方面用TCP传输一个大文件实际速率大概在3MB/s左右。这个数值对于100Mbps以太网来说不算高但对于一颗以实时控制为主要任务的MCU来说已经很够用。如果你想继续优化可以从几个方向入手增大TCP_SND_BUF、调整PBUF_POOL_SIZE、把接收中断改成轮询或NAPI模式但通常会牺牲一些CPU时间需要权衡。还有一个细节开发板作为TCP Server时我设置的是静态IP没有跑DHCP。工业环境里推荐固定IP因为你要让整个产线设备在同一个网段让上位机能够精确找到它。用DHCP虽然方便但地址一旦变化上位机软件就要做地址发现工程复杂度上升很多。6. 这次移植复盘几个值得拿出来反复说的坑6.1 PHY地址和SMI时钟是排查“link不起来”的第一站如果网络不通我现在的第一反应顺序是先看PHY有没有起来再看MDIO能不能正确读到PHY寄存器最后才去查MAC配置。这个顺序是我踩过坑以后总结出来的。PHY起没起来最简单的方法就是读PHY寄存器。LAN8720A的PHY ID寄存器应该能读到标准值如果读回来0xFFFF或者0x0000先怀疑硬件电源、时钟、复位。如果硬件没问题再查SMI分频。有些开发板的系统时钟可能不是你以为的频率导致MDIO超过2.5MHz上限SMI通信不稳定读出来的数据时对时错。YXDSP这块板子默认PHY地址在原理图上能看到但不同批次可能存在跳线差异。你代码里设置的phy_addr如果和硬件跳线不一致MDIO永远读不到正确数据。所以我在驱动里做了一个小工具初始化时从0到31扫描PHY地址把所有能读到合法PHY ID的地址打印出来这样就不用手动猜了。6.2 中断上下文里的内存分配要非常克制LWIP的接收路径绕不开中断凡是中断里涉及的操作都必须保持短小和行为可预测。我在移植时一开始直接在接收中断里调用了pbuf_alloc用的是PBUF_POOL类型因为该类型是从预分配池中取速度很快没问题。但如果用PBUF_RAM类型内部会走mem_malloc如果内存碎片化严重分配时间会变得不稳定可能直接影响中断响应。另一个原则是中断里不要做TCP层处理。tcpip_thread就是专门用来处理协议栈的你要做的只是把数据包交给它然后立刻退出中断。如果自作聪明在中断里调用TCP处理函数不仅容易崩还会把整个系统的实时性拉低。我用FreeRTOS的时候特别确认了tcpip_input可以从中断安全上下文调用因为它内部使用的是sys_mbox_trypost这类不阻塞的接口。6.3 如果重来一次我会把代码拆成怎样的结构这次移植从EMAC底层到LWIP接入代码量不小。如果未来我还要在另一个项目里复用这套逻辑我不会再把所有逻辑堆在一个大C文件里。我会分成这样几层emac_drv.c/h负责F28388D EMAC寄存器初始化、描述符环管理、MAC地址设置。phy_drv.c/h负责PHY芯片SMI读写、复位、自协商状态机隔离具体PHY型号。netif_emac.c/h作为LWIP和emac_drv之间的适配层实现low_level_init、low_level_output、low_level_input并且处理Cache Clean/Invalidate。lwipopts.h和sys_arch.c单独管理不跟业务代码混在一起这样升级LWIP版本时直接替换。这种分层的好处是PHY芯片换掉或者EMAC寄存器细节变化时不需要把LWIP层全部重写改一个文件就够了。我在这次项目后期就曾经因为从LAN8720A换到另一颗PHY只改了phy_drv.c里的寄存器操作其他部分几乎没有动验证了这种拆分的价值。另外还有一个小建议在工程里加入一个简易的MAC地址管理机制。开发板每块都有一个固定MAC地址但批量生产时最好从配置区读取而不是硬编码。否则两台设备同时接入同一个局域网MAC冲突会让网络时好时坏排查起来非常痛苦。这次我在代码里做了一个默认地址加一个可配置地址的结构真正量产时可以通过Bootloader改写算是提前做了一点准备。整个项目做完我再回头去看F28388D的以太网部分反而觉得它没那么神秘。EMAC硬件摆在手册里LWIP源码就在那里真正难的是把硬件中断、DMA缓存、协议栈线程、PHY状态机这些分散的模块串成一个整体让它们协同工作。这块开发板给我最大的帮助是让我能够在真实硬件上反复验证链路和协议栈的每一个阶段而不是停留在纸面配置。如果接下来你要在自己的板子上做网络功能我建议也按照这个顺序先把PHY调通再跑裸机收发最后接LWIP一步一步来反而最快。