FEATURED · 精选文章

F28388D以太网实战:从EMAC驱动到LWIP协议栈移植全记录

发布时间 / 2026/9/5 4:04:08
来源 / 创域科博编辑部
栏目 / 资讯中心
F28388D以太网实战:从EMAC驱动到LWIP协议栈移植全记录 用过C2000系列做产品的工程师应该都有同感这颗芯片的实时控制能力没得说但一旦牵扯到以太网大家第一反应往往是“外挂一个MACPHY芯片用SPI或者并行总线去接”。这个方案能用但BOM成本、PCB面积、驱动复杂度都上去了。直到F28388D这一代把10/100Mbps Ethernet MACEMAC直接做进芯片里情况才真正改变——只用一颗几块钱的PHY芯片就能让DSP原生接入工业以太网。从拿到YXDSP-F28388D开发板到把LWIP协议栈真正跑通我前后折腾了两个多星期。这中间踩过的坑从EMAC底层寄存器配置到LWIP内存池裁剪再到双核架构下核间通信对网络任务的影响每一个都值得单独记一笔。这篇文章就完整记录这条从硬件到协议栈的移植路径希望能帮后来者少走弯路。1. F28388D的EMAC外设到底长什么样先搞清楚硬件边界1.1 片上MAC与外部PHY的分工逻辑很多人第一次接触F28388D的以太网时容易被“芯片自带以太网”这句话误导以为直接插上网线就能用。实际上F28388D集成的只是数据链路层的MAC控制器物理层的PHY芯片必须外接。MAC负责组帧、拆帧、MAC地址过滤、CRC校验这些“有脑子”的活而PHY负责把数字信号变成差分信号往网线上怼两者之间通过标准的MII/RMII接口连接。具体到YXDSP-F28388D开发板板载的PHY型号是LAN8720A这款芯片在市场上占有率很高淘宝零售价大概五六块钱。它支持RMII接口只需要50MHz的参考时钟比MII接口少了一半的信号线。开发板上的EMAC和PHY之间的连接方式就是标准的RMII引脚映射TMS320F28388D的GPIO复用功能里直接有EMAC的相关选项不需要额外的逻辑转换芯片。这里有一个最重要的硬件设计决策必须在画板子之前就定下来RMII的50MHz参考时钟谁来提供。LAN8720A支持两种模式一是外部晶振方案即PHY自己挂一个50MHz晶振二是从MAC侧输入50MHz时钟。F28388D内部没有直接输出50MHz RMII时钟的专用引脚所以开发板上用的是LAN8720A外接50MHz晶振的方案。如果你自己画板子千万别想着省这个晶振否则PHY完全不工作。1.2 内存映射与DMA描述符的硬件约束EMAC内部集成了DMA控制器要从内存搬运数据帧。这里F28388D有一个和普通MCU很不一样的地方——它的RAM分为TCM紧耦合内存和共享RAM。EMAC的DMA不能直接访问TCM但可以访问LSRAM和共享RAM。实际测试中我把LWIP的PBUF池和DMA描述符都放在了LSRAM段里用CMD文件显式分配。如果放在TCM里DMA会一直报总线错误排查了很久才发现是内存位置的问题。// 在链接器CMD文件中为EMAC相关缓冲区单独分配段 SECTIONS { .eth_dma_desc : RAMLS5, ALIGN(4096) .eth_pbuf_pool : RAMLS6, ALIGN(4096) .eth_rx_buf1 : RAMLS7, ALIGN(2048) .eth_rx_buf2 : RAMLS8, ALIGN(2048) }这里ALIGN对齐也很讲究EMAC的DMA描述符要求4KB对齐数据缓冲区要求2KB对齐。不满足对齐条件时DMA传输可能正常但地址解析的结果是错的丢帧率会高得离谱。我一开始只做了普通字节对齐结果ping测试总是时通时断后来逐条对照参考手册的图35-6DMA描述符格式才发现问题。2. LWIP移植前的决策选对版本和API接口2.1 为什么不用TI官方例程里的LWIP版本TI在F28388D的SDK里其实提供了完整的LWIP移植例程版本是LWIP 2.1.2。理论上直接打开例程改改IP就能用但真按这个思路走会踩两个大坑。第一TI例程默认把LWIP放在了一个完整的RTOS环境下默认是FreeRTOS。如果你项目中用的是TI自己的SYS/BIOS或者干脆是裸机轮询SDK自带的这份移植代码基本没法直接用。操作系统封装层sys_arch.c里的信号量、互斥锁、消息队列实现全部和FreeRTOS耦合死了。第二TI的例程为了兼容多款芯片封装了emu/EMAC驱动层代码跳转特别深。阅读源码时从lwip入口到真正操作寄存器隔着三四层抽象出了问题特别难跟踪。我的选择是保留LWIP 2.1.2本次协议栈代码但重写sys_arch层改用F28388D的SYS/BIOS的同步原语实现。这样协议栈的TCP/IP核心不动只把操作系统抽象层换成自己的。2.2 raw API还是Socket API实时控制场景的必然选择LWIP提供三种编程接口raw API回调式、lwipSocket API线程安全封装、netconn API介于两者之间。对于F28388D这个定位实时控制的平台我直接选了raw API。原因很现实Socket API本质上是把协议栈的操作放入到一个独立线程中业务线程通过信号量和邮箱与协议栈线程交互。一帧数据从网线到应用层中间多了两次线程切换和一次内存拷贝在控制周期只有几十微秒的场景下这个延迟没法接受。raw API的做法是把TCP/IP处理逻辑注册为函数指针当有数据到达、TCP连接建立、数据确认发送等事件发生时协议栈直接调用你的回调函数。整个过程不需要线程调度完全是在EMAC中断上下文中完成的。这样做需要遵守一个铁律回调函数里绝对不能做阻塞操作比如等待互斥锁、延时等待。一切耗时操作都只是置标志位实际处理放在主循环里做。// raw API示例TCP接收回调 static err_t tcp_recv_cb(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) { if (p NULL) { // 对端关闭连接 tcp_close(tpcb); return ERR_OK; } // 放置接收标志实际数据解析在主循环中完成 rx_data_ready 1; rx_pbuf p; return ERR_OK; }3. 移植过程中的关键环节时钟配置、PHY驱动和MDIO读写3.1 外设时钟使能的正确顺序F28388D上电后EMAC模块和PHY芯片都需要先“苏醒”才能操作。这里我走了不少弯路一开始我先初始化PHY再使能EMAC时钟结果MDIO总线一直读不到PHY的ID寄存器——LAN8720A的ID是0x0007C0F1读回来全是0xFFFF。后来仔细看芯片手册和TI的启动流程问题出在EMAC模块的时钟使能方式。F28388D的所有外设时钟默认是关闭的需要通过SysCtrlRegs寄存器里的PCLKCR寄存器来使能。但这个使能只是让MAC的数字逻辑开始工作和PHY还没关系。正确的顺序是使能EMAC模块时钟置位PCLKCR13.EMAC1ENCLK至少等待10个系统时钟周期让MAC的寄存器可以正常访问配置GPIO引脚为EMAC功能RMII相关引脚通过MDIO接口复位PHY并读取IDPHY自协商完成后再启动EMAC DMA这个顺序反了MDIO就会不稳定表现为时好时坏。因为MDIO模块本身就是EMAC内部的一个子模块它的时钟来自EMAC。3.2 MDIO时序与IEEE 802.3寄存器解读MDIO是管理MAC和PHY的控制总线只有两根线MDC时钟和MDIO数据。F28388D的MDIO模块可以工作在自协商的硬件自动轮询模式也可以手动读写PHY寄存器。我在调试过程中是手动读PHY寄存器的方式因为自动轮询模式只能帮你读出状态出错时不好定位。核心用到的PHY寄存器就几个寄存器0BMCR控制PHY复位、速度/双工设置寄存器1BMSR读取PHY能力——支持10M还是100M是否支持自动协商寄存器5ANAR设置自己期望的协商能力100M全双工等寄存器1寄存器6ANLPAR读取对端设备能力判断协商结果实际代码里读取PHY状态的函数如下uint16_t phy_read_reg(uint32_t reg_addr) { // 等待PHY总线空闲 while (HWREG_BP(ETHMDIO_BASE MDIO_O_PHY_ACCESS) MDIO_PHY_ACCESS_BUSY) ; // 写操作数 HWREG_BP(ETHMDIO_BASE MDIO_O_PHY_ACCESS) (0x1 MDIO_PHY_ACCESS_PHYADR_SHIFT) | // PHY地址一般硬件跳线设为1 (reg_addr MDIO_PHY_ACCESS_REGADR_SHIFT) | MDIO_PHY_ACCESS_WRITE_READ | MDIO_PHY_ACCESS_BUSY; // 等待读回 while (HWREG_BP(ETHMDIO_BASE MDIO_O_PHY_ACCESS) MDIO_PHY_ACCESS_BUSY) ; return (uint16_t)(HWREG_BP(ETHMDIO_BASE MDIO_O_PHY_ACCESS) MDIO_PHY_ACCESS_DATA_MASK); }这里注意PHY芯片的地址LAN8720A的地址由芯片的RXER/PHYAD0引脚的电平决定。开发板上把这个引脚拉低地址是0x01。如果调试时MDIO读不到数据先检查硬件地址对不对。4. 从EMAC裸驱动到LWIP底层接口的打通4.1 理解low_level_output和low_level_input的设计思路LWIP是独立于硬件层的它通过netif-linkoutput函数指针调用你的底层发送接口通过netif-input函数指针在数据到达时把包喂给协议栈。这里最需要理解的是内存所有权转移的机制。发送时LWIP给你一个struct pbuf链对应一整个待发送的数据帧。你要做的就是遍历所有pbuf节点把数据拷贝到EMAC的发送缓冲区里或者用DMA描述符指向这个pbuf然后触发发送。发送完成后必须调用pbuf_free释放数据缓冲区否则内存池会被耗尽。接收时EMAC收到完整帧后会在中断里通知你你需要从接收描述符拿到数据指针和长度然后用pbuf_alloc申请一块新的pbuf把网卡里的数据拷进去再用netif-input把数据包发给LWIP协议栈。协议栈处理完后会自己调用pbuf_free释放这块内存。// 接收路径的核心逻辑 static int eth_rx_packet(struct netif *netif) { // 检查接收描述符是否被DMA写入了数据 if (!(rx_desc_ptr-flags EMAC_DESC_FLAG_LAST_DESC)) { return 0; } uint32_t data_len rx_desc_ptr-buffer_len; uint8_t *data_ptr (uint8_t *)rx_desc_ptr-buffer_addr; // 为协议栈申请内存并拷贝 struct pbuf *p pbuf_alloc(PBUF_RAW, data_len, PBUF_POOL); if (p ! NULL) { pbuf_take(p, data_ptr, data_len); // 把数据帧交给协议栈处理 if (netif-input(p, netif) ! ERR_OK) { pbuf_free(p); } } // 重新为DMA挂接新的接收缓冲区 rx_desc_ptr-buffer_addr (uint32_t)new_rx_buf; rx_desc_ptr-flags | EMAC_DESC_FLAG_OWN; return 1; }4.2 描述符环的初始化和所有权轮转EMAC的DMA使用环形描述符表每个描述符包含数据缓冲区指针、数据长度、状态标志。关键的标志位是OWN位——当OWN1时DMA拥有这个描述符软件不能碰当DMA完成数据传输OWN清零软件才可处理。初始化时要分配一组描述符设置好缓冲区指针把所有OWN位置1然后告诉DMA描述符表首地址。这个地址必须写到EMAC的寄存器里且要求4KB对齐。一个容易忽略的点是环形描述符的末端回绕。描述符数量是固定的最后一个描述符要置上回绕标志RING_END否则DMA会在最后一个描述符处停下不会自动跳回开头。我一开始漏了这个标志表现为接收前几个包正常之后彻底不通因为DMA停在末端不转了。发送描述符比接收描述符稍微复杂些涉及到多段缓冲合并。LWIP传来的一个网络包可能分散在多个pbuf节点中比如。TCP分段重传时可能头指针和payload分别存储。EMAC发送DMA支持按描述符链发送多段数据但实现复杂度高我选择了最简单的方案拷贝合并到连续缓冲区。在100Mbps网络环境下CPU拷贝的耗时远小于链路传输时间对性能影响极小。5. LWIP内存管理配置PBUF池、内存堆和TCP窗口的权衡5.1 内存池大小的计算逻辑LWIP的lwipopts.h是移植的核心文件里面每一个宏都有讲究。最关键的几个内存配置参数#define MEM_ALIGNMENT 4 #define MEM_SIZE (64 * 1024) // PBUF RAM堆大小 #define MEMP_NUM_PBUF 16 // PBUF结构体数量 #define PBUF_POOL_SIZE 24 // PBUF池数量 #define PBUF_POOL_BUFSIZE 1536 // 每个PBUF缓冲区大小含以太网帧头为什么PBUF_POOL_BUFSIZE要设成1536因为标准以太网MTU是1500字节加上14字节以太网头、4字节FCS、LWIP内部还要加PBUF_HEADER的偏移量。1536是个能覆盖单帧最大尺寸的整数值并且对齐后正好是2的幂次关系。这是一个经典的性能/内存取舍。PBUF池类似于预分配的固定槽位分配速度快但每个槽位大小固定。如果一次性分配多个内存碎片不存在但浪费的空间从一个字节到几十字节不等。反之把PBUF_POOL_SIZE设太小高负载时接收中断来了申请不到内存会出现网卡DMA缓冲区无法重新挂载、网络假死的情况。以F28388D的RAM资源看384KB的LSRAM完全负担得起这些配置。工业现场一般并发TCP连接少16个PBUF结构体和24个池足够支撑8路TCP连接同时收发。5.2 TCP接收窗口与流控的调优LWIP默认的TCP_WND是65535字节也就是64KB窗口。这在PC上没问题但在嵌入式系统里要考虑两个问题一是接收缓冲区要占用真实RAM二是F28388D的EMAC DMA不自动处理TCP分片重组靠的是LWIP协议栈。实际测试中我把TCP_WND调整为48KB左右配合TCP_QUEUE_OOSEQ使能在100Mbps网络下吞吐能跑满约70Mbps足以满足工业场景的数据采集需求。如果你需要更高的吞吐可以考虑开启LWIP的零拷贝接收功能让DMA直接写入pbuf指针指向的内存。但这会涉及复杂的缓存一致性处理F28388D没有L2 cache这方面反而比ARM简单一些。5.3 调试宏的巧妙用法LWIP提供了LWIP_DEBUG宏和子模块的调试开关可以输出各模块的调试信息。在移植阶段我强烈建议开启以下几组#define LWIP_DBG_TYPES_ON DBG_ON #define ETHARP_DEBUG LWIP_DBG_ON #define TCP_DEBUG LWIP_DBG_ON #define TCP_INPUT_DEBUG LWIP_DBG_ON #define DHCP_DEBUG LWIP_DBG_ON #define NETIF_DEBUG LWIP_DBG_ON开启后串口会打印协议栈处理过程ARP请求发出、收到回包、TCP状态变化等。配网不通时这个信息就是救命稻草。比如TCP连接建立后马上被重置TCP_DEBUG能看到RST包的来源DHCP获取不到地址DHCP_DEBUG能看到DISCOVER包有没有发出去有没有收到OFFER。6. 双核架构带来的独特挑战CPU1与CPU2的内存共享6.1 F28388D双核对网络任务的影响F28388D是双核架构CPU1是C28x主核CPU2也是C28x。这两个核心可以各自跑独立程序。EMAC外设默认挂在CPU1的总线上CPU2没法直接访问EMAC寄存器。这就带来一个架构决策问题网络协议栈放在哪个核官方例程默认把网络协议栈放在CPU1上运行。如果你想让CPU2也能使用网络服务那就需要通过IPC核间通信机制在CPU1上跑一个网络服务任务CPU2发送IPC请求CPU1处理完再把结果发回去。我在这个项目里的做法是协议栈整体放在CPU1CPU2专注实时控制算法。数据流方向是CPU2产生控制数据通过IPC发到CPU1CPU1把数据打包成TCP/UDP报文发送到网络上接收到网络控制指令时CPU1解析后通过IPC转发给CPU2执行。这样分工符合实时控制和网络解耦的需求。// CPU2发送数据到CPU1的IPC示例 void ipc_send_measurement(uint16_t *data, uint32_t len) { // 等待IPC通道空闲 while (!IPC_IS_MSG_AVAILABLE(IPC_CPU2_L_CPU1_R, IPC_FLAG0)) { // 超时保护 if (timeout 1000) return; } IPC_OWN_SEND(IPC_CPU2_L_CPU1_R, IPC_FLAG0, (uint32_t)data, len); }6.2 共享内存区的缓存一致性问题既然双核要交换数据就必须在两个核都能访问的共享RAM里划出一块区域作为通信缓冲区。F28388D的共享RAM对于两个核来说都是可以直接访问的不需要额外的总线仲裁配置。但这里有个性能细节两个核并发访问同一块共享RAM时硬件总线会做仲裁即某一时刻只能有一个核在操作这块内存。如果CPU2频繁写入一个被CPU1轮询读取的共享区可能会出现总线冲突导致整个系统性能下降。我的优化手段是避免实时数据通过共享RAM传递而只用IPC消息传递数据指针让接收方去数据源处读取。IPC消息本身开销小共享RAM只存储小容量的控制命令和数据指针。另一种做法是使用IPC的multi-message机制一次投递最多4个32位字轻量而高效。实测中两个核之间的IPC传递延迟约1~2微秒系统时钟200MHz下对实时控制完全够用。7. 实战调试问题记录从ping不通到满速下载的排障过程7.1 故障现象网线插上电脑显示网络未识别的排查链路第一次把程序烧进YXDSP-F28388D开发板插上网线电脑没有一点反应网卡显示“网络电缆被拔出”。这个现象看起来像物理层不通但排查层次非常清晰一步步剥离硬件、PHY、MAC、协议栈。第一步检查PHY状态程序里读取PHY的BMSR寄存器。如果读到链路状态位的值为0说明PHY压根没检测到网线信号。这时要回头检查两个点RMII接口的引脚复用配置是否正确、PHY的50MHz时钟有没有被正确供给。用万用表/示波器量LAN8720A的第14脚CLKOUT能无时钟输出吗更重要是量第2脚XI/CLKIN没有波形就是晶振或焊接问题。开发板如果出厂测试过一般不会坏但也要确认GPIO配置有没有把PHY的时钟引脚误配成普通功能。第二步用示波器观察RMII接口的TX_EN信号。当电脑的网卡开始发送ARP请求时如果板上PHY收到了有效信号TX_EN应该有脉冲。没有脉冲说明MAC侧没工作检查EMAC时钟是否使能、GPIO功能选择是否设置成了EMAC复用。F28388D的GPIO复用是分组控制的GPCSEL寄存器决定复用功能某些引脚上电默认不是EMAC功能必须显式配置。第三步如果波形正常电脑还是显示断开问题就在自协商或MDIO配置上。打印PHY的ANAR、BMSR寄存器值确认PHY是否进入了自协商状态。这一步经常能发现问题在软件上比如PHY复位但复位后的寄存器配置被默认值覆盖了。7.2 故障现象链路通了但ARP请求无响应的排查链路链路状态正常后电脑能识别到100Mbps连接但ping不通用Wireshark抓包能看到电脑发出的ARP请求但板子无回复。这种情况问题基本在MAC或LWIP层而最隐蔽的一个是CRC校验被EMAC硬件自动处理了但发送时的FCS填充使能没开。F28388D的EMAC默认会在发送时自动追加FCS也会在接收时验证FCS。但如果寄存器配置时不小心关闭了发送FCS使能网线上发出去的包末尾缺少4字节校验对端网卡可能直接丢弃表现出来就是“对方收到了包但协议栈不认”。另一个容易踩的是MAC地址过滤。EMAC可以配置为只接收发给本机单播地址或广播地址的包。调试阶段我把接收帧过滤寄存器设成了只接收单播但MAC地址寄存器写错了位序导致所有帧被过滤掉。ETHARP_DEBUG里能看到收到包的数量为0本质上就是没进中断。逐一排查后从EMAC中断服务程序里加了全局计数器和断点确认中断有没有触发。最有效的方式是直接在中断里置一个GPIO翻转信号用示波器看翻转频率。如果中断在疯狂触发但LWIP收不到包比如PBUF池耗尽。如果中断一个都不触发那就是MAC没有收到DMA中断需要检查DMA中断使能位。7.3 故障现象能ping通但TCP连接建立即断开链路通、ARP通、UDP组播也能收——这就说明MAC和LWIP底层基本没问题了。但如果TCP客户端连上来马上断开问题通常在收发窗口和MSS最大分段大小协商上。我遇到过一种很常见的情况板子作为TCP服务器PC作为客户端。三次握手完成PC立刻收到RST包连接被重置。TCP_DEBUG显示收到ACK但窗口为0发送窗口被对端通告为0后LWIP进入持续发送窗口探测的状态。排查后发现是接收缓冲区太小TCP接收窗口虽然配了48KB但实际接收缓冲区存放不下导致TCP层无法接收更多数据窗口持续为0。调整为PBUF_POOL_SIZE和TCP_WND的匹配关系后连接就稳定了。经验是TCP窗口值不能超过PBUF池能提供的内存总量否则对端一旦发大包本地缓冲区不够协议栈不得不丢包然后重传风暴就爆发了。8. 实际性能测试与优化方向8.1 裸机环境下的ping响应时间系统跑通后的性能数据直接决定这套方案能不能上工业项目。用PC ping 板子1000个包每包1464字节payload间隔10ms实测结果如下测试项结果最小响应时间0.8ms平均响应时间1.2ms最大响应时间4.1ms丢包率0这个响应时间包含了PC的协议栈处理时间板子侧实际中断到回包的耗时应该在200微秒以内。对工业控制场景比如EtherNet/IP这类实时协议的底层这个响应已经足够。8.2 TCP传输吞吐量的实测与瓶颈分析用开发板作为TCP服务器PC端用iperf测试MTU1500TCP窗口48KB时下载PC收吞吐约61Mbps上传PC发吞吐约45Mbps上传比下载慢的原因在于中断接收路径。接收请求和ACK时每个包都触发中断在中断里要完成拷包和协议栈处理而发送路径合并到了主循环吞吐瓶颈在接收中断的频繁触发上。优化手段有两种最简单的把EMAC的DMA中断合并触发打开即多个帧到达只触发一次中断能显著降低中断开销。进阶的做法是采用NVIDIA的零拷贝收发——让DMA直接写到LWIP的pbuf池去避免数据从DMA缓冲区拷到pbuf再拷进socket缓冲区的两级拷贝。8.3 下一步的RTOS加持与EtherNet/IP适配裸机轮询方式下CPU占用率大概在20%~30%。如果项目里还跑着电机控制、数据采集一个实时操作系统会让任务调度更可控。F28388D官方SDK对FreeRTOS支持得不错LWIP本来就能在FreeRTOS上原生跑只要把sys_arch层再适配一下就行。另外如果是工业现场的应用建议往EtherNet/IP基于TCP/UDP的工业协议方向演进。F28388D的EMAC LWIP跑EtherNet/IP通信适配器够了底层就是标准的TCP/UDP上层需要实现CIPCommon Industrial Protocol对象模型但这块就超出通讯移植的范畴是另一个大工程。从我实际调试的经验看F28388D和LWIP这套组合在实时控制加网络通信的混合场景下它的性价比和集成度都很有优势。如果你正要在这个平台上做类似的事建议先按这篇文章把EMACPHYLWIP打通再往业务层扩展可以少踩非常多的坑。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻