基于F28388D的以太网开发:EMAC驱动与LWIP移植实战
2026/9/20 18:37:20 网站建设 项目流程

去年年底我做了一批基于TI C2000系列的高实时性控制板,其中一块核心板用的是YXDSP-F28388D。硬件回来之后,调试工作基本顺利,唯独网络通信这一块卡了我将近两周。不是PHY不通,就是LWIP协议栈移植后ping不通,要么就干脆进不了中断。现在回头看,很多问题其实都是对芯片的EMAC模块理解不够深,以及移植LWIP时接口细节处理不到位导致的。

这篇文章我准备把整个调试过程完整记录下来,从F28388D的EMAC硬件特性、管脚配置、MDIO通信、PHY芯片驱动,到LWIP协议栈的底层接口封装、内存管理、中断处理,再到最终通过ping和TCP/UDP实测。内容会比较长,但基本都是可以直接复现的实操经验,哪怕是第一次接触C2000以太网开发的工程师,按这个思路走下来也能少踩很多坑。

先说清楚,这篇文章适合谁看:手里有YXDSP-F28388D开发板或者类似C2000系列芯片、要把网络通信跑起来的硬件工程师和嵌入式软件工程师。如果你只是做应用层开发,不碰底层协议栈,那直接看最后的常见问题部分就够了。

1. 为什么选F28388D跑以太网?先搞清楚硬件底子

1.1 双核架构里那颗容易被忽略的CM4内核

F28388D这颗芯片在C2000家族里属于比较特殊的存在,它不光是有一颗主频200MHz的C28x DSP内核,还集成了一颗运行频率最高200MHz的Arm Cortex-M4内核。很多人拿到这颗芯片只盯着C28x用,忽略了CM4的存在,但实际上这颗CM4就是为通信类任务准备的。它帮你把以太网、USB、CAN这类需要协议栈支撑的外设都挂在了CM4这一侧,让C28x腾出精力专心做实时控制。

从系统架构上看,F28388D的以太网MAC是一个符合IEEE 802.3标准的三速(10/100/1000Mbps)EMAC模块,内部自带DMA控制器,支持MII、RMII、RGMII三种接口模式。实际项目里,由于RMII只需要2根数据线加一根50MHz参考时钟,能省掉不少IO资源,所以我最终选的是RMII+100Mbps的方案。开发板载的PHY芯片是TI自家的DP83822,工作在RMII从模式,MAC侧提供50MHz的REF_CLK给它。

这里有个特别需要注意的点:F28388D的EMAC虽然挂在CM4总线上,但C28x通过IPC机制也能间接访问到MAC寄存器和DMA描述符。不过正常开发中不建议双核同时操作EMAC,否则DMA描述符的同步问题会让你崩溃。我实测下来最稳妥的做法是:把LWIP和EMAC驱动全部放在CM4上跑,C28x通过IPC消息和共享内存和CM4做数据交互。这样职责划分清楚,控制周期也不会被网络任务干扰。

1.2 从EMAC到PHY芯片的信号链路

搞清楚F28388D的EMAC和外部PHY芯片之间的连接方式,是移植前最重要的一步。整个链路分两部分:一边是MAC通过DMA访问内部RAM拿数据,另一边是MAC通过外部接口把数据发给PHY芯片,再由PHY芯片完成物理层的编码和收发。

RMII接口下,引脚信号总共有这么几个:TX_EN(发送使能)、TX0/TX1(2位发送数据)、RX0/RX1(2位接收数据)、REF_CLK(50MHz参考时钟)、CRS_DV(载波侦听/数据有效,接收方向)、MDIO(管理数据输入输出)、MDC(管理时钟)。

开发板上DP83822的地址我确认过是0x00(由RX_D0、RX_D1、RX_D2三个引脚上的上下拉电阻决定的),如果你的板子PHY地址不是0,后面所有PHY寄存器读写都会失败,这一点后面排查问题时还会提到。

除了数据通道,PHY芯片还需要单独的复位引脚和控制引脚。DP83822的复位一般是低电平有效,至少需要保持10ms以上才能稳定释放。另外DP83822有个RX_DV/RX_ER复用引脚,在RMII模式下要特别注意寄存器配置,否则接收方向会莫名其妙丢包。

1.3 开发工具链的版本匹配问题

把这一节单独拿出来写,是因为我在配置工程的时候吃过版本不匹配的亏。TI官方给的C2000Ware里带有Ethernet的例程,但例程默认是用CCS(Code Composer Studio)建的工程。如果你习惯用IAR或Keil,直接移植例程代码时,头文件路径、启动文件、链接脚本都要自己重新配一遍,工作量不小。

我的建议是:如果条件允许,还是用CCS配合TI官方例程来做。因为C2000Ware里的例程是直接适配芯片的,包括管脚初始化、MAC复位时序、PHY配置代码都是验证过的,你拿到后改改IP地址就能先把链路跑通,然后再逐步替换成自己的业务代码。我用的版本是CCS 12.x配合C2000Ware 5.01,这套组合测试下来比较稳定。

2. EMAC底层驱动的搭建:从寄存器到DP83822

2.1 时钟树与管脚复用:第一道关卡

F28388D片内有两个PLL,分别给C28x和CM4提供时钟。以太网这块比较特殊,EMAC需要独立的时钟,在RMII模式下MAC侧需要提供50MHz的REF_CLK给PHY。这个50MHz不是随便从某个GPIO输出的,必须通过芯片内部的时钟分配器,把CM4的PLL输出分频后送到指定引脚。

管脚复用上,RMII模式涉及的几个关键IO分别是:PB7(RMII_CRS_DV)、PB8(RMII_RX0)、PB9(RMII_RX1)、PD0(RMII_TX_EN)、PD1(RMII_TX0)、PD2(RMII_TX1)、PD4(RMII_MDC)、PD5(RMII_MDIO)、PD6(EMAC_REF_CLK)。开发板上这些引脚默认可能有其他复用功能,比如接了LED或者按键,配置的时候一定要把GPIO的复用功能切换到EMAC外设上,否则信号出不去也进不来。

实际调试时我最常犯的错误是:GPIO配置好了,但忘了使能EMAC外设的时钟门控。F28388D的CM4侧每个外设都有独立的时钟使能位,藏在系统控制模块里。EMAC的时钟使能位不在常规的PCLKCR寄存器组里,而是在一个专属于CM4域的寄存器中,这一点和TI的TMS320F28xxx系列传统外设完全不同。

我在初始化代码里写了一组延时,用来保证管脚复用配置、时钟使能、外设复位释放之间的时序正确。实测发现GPIO配置寄存器写完后,如果不加几个时钟周期的延时马上操作EMAC,总线上会有概率出现冲突,轻则配置失败,重则直接触发硬件错误中断。

2.2 无需外部SDRAM:DMABUF描述符与缓冲区的内存布局

F28388D的EMAC自带一个DMA引擎,通过描述符链表的方式管理收发缓冲区。每个描述符包含数据缓冲区的地址、数据长度、控制标志和状态标志。DMA引擎会按照描述符链表依次搬运数据,收发各自独立维护一套描述符链表。

这块芯片的以太网控制器比较实用的一点是,描述符和缓冲区都可以放在普通的片上RAM里。F28388D的CM4域有256KB的RAM,分成多个bank,其中有一部分是紧耦合内存(TCM),访问速度最快。我在设计内存布局时,把LWIP的PBUF池和描述符都放在了非TCM区域,因为LWIP在运行时会频繁申请和释放内存,如果用TCM反而容易因为访问冲突引入不必要的等待周期。

收发缓冲区的大小我设置了1520字节,比标准的以太网MTU(1500字节)多出20字节,用于容纳VLAN标签或者将来加时间戳的扩展字段。实际使用中,如果只跑标准IP包,1520字节足够用了。每个方向的描述符数量我配置了16个,因为F28388D的EMAC DMA支持描述符预取机制,描述符太少容易在突发流量下出现DMA空闲等待,太多则浪费内存,16个是我测下来比较均衡的值。

2.3 PHY芯片DP83822的初始化过程

PHY芯片的寄存器读写是通过MDIO总线完成的。DP83822在上电后默认处于CMII模式,需要根据实际板子用的是MII还是RMII来修改寄存器。F28388D的EMAC模块的MAC控制寄存器里有个接口模式选择位,而PHY芯片这边也有对应的模式配置寄存器,两边必须匹配。

DP83822的初始化流程我总结为四步:复位、模式配置、ANEG(自动协商)配置、链路状态检查。

复位最简单,拉低复位引脚保持一段时间再释放。模式配置要读写PHY的寄存器0x1F(PHYCR),其中bit14和bit13控制RMII模式。ANEG配置是把寄存器0x00的bit12、bit13(速度/双工选择)都置为1,让PHY自动协商到100M全双工。

链路状态的检查是最容易踩坑的:DP83822的寄存器0x01(BMSR)bit2指示链路是否建立,但这个位在链路未建立时不是稳定状态。我把轮询函数写成一个带超时的循环,每10ms读一次,最多等2秒。如果2秒后还是没有建立链路,就打印错误并检查网线连接。

3. LWIP协议栈的移植:核心接口逐个击破

3.1 决定的瞬间:用RAW API还是NETCONN API?

LWIP支持三种编程接口:RAW API、NETCONN API(基于操作系统模拟层)和Socket API。F28388D上跑LWIP,选择哪种接口关系到整个软件架构的复杂度。

我在这颗芯片上最终选择了NETCONN API。原因有两个:第一,CM4内核上我跑了一个轻量级的FreeRTOS系统,LWIP可以通过sys_arch适配层挂到FreeRTOS上,用信号量和邮箱机制来同步中断和协议栈线程;第二,后续应用层要同时处理TCP服务、UDP广播、MQTT客户端多个连接,用NETCONN的多线程模型比RAW API的单线程轮询模型开发效率高得多。

如果你是不跑RTOS、只想要一个极简的TCP/IP栈,那RAW API在无操作系统环境下确实能省掉不少RAM开销。但F28388D这块芯片根本不缺RAM,所以没必要为了省那几十KB内存去把代码复杂度抬高。

3.2 sys_arch适配层:LWIP和FreeRTOS之间的桥梁

移植LWIP到带RTOS的环境,最核心的工作就是写sys_arch.c文件。这个文件实现了LWIP定义的操作系统抽象接口,包括信号量、互斥锁、邮箱(消息队列)、系统时钟等。

LWIP中的信号量分为二进制信号量和计数信号量,我在实现时直接封装了FreeRTOS的xSemaphoreCreateBinary和xSemaphoreCreateCounting。这里有个细节:FreeRTOS的二进制信号量在调用xSemaphoreGive时会有“优先级反转”的问题,LWIP已经考虑到了这一点,它在协议栈内部会尽量使用邮箱而不是信号量来做数据传递。所以我在实现邮箱时,用的是FreeRTOS的队列接口。

时钟接口比较简单,LWIP要求提供一个以毫秒为单位的系统时间函数。我用的是CM4内核的SysTick,配置成1ms中断一次,全局变量递增加上volatile修饰符,防止编译器优化后读不到最新值。

移植完成后,我在lwipopts.h里定义了NO_SYS为0,开启了OS支持。同时把内存池大小调大,因为默认配置的PBUF池在TCP通信时明显偏小,会导致收发的数据被频繁拷贝,降低吞吐量。

3.3 网卡驱动层:ethernetif.c的修改重点

LWIP的网卡驱动层核心函数就是low_level_init、low_level_output和low_level_input三个。

low_level_init要做的事非常多:初始化EMAC DMA描述符、建立收发描述符链表、使能MAC和DMA、设置接收过滤规则,最后还要把网卡的MAC地址写入硬件寄存器。F28388D的MAC地址寄存器是分高低两组存的,分别是MAC_SA0、MAC_SA1、MAC_SA2。我为了方便后续产品出厂时烧录唯一MAC,把MAC地址定义成了三个连续的16位变量,通过配置接口可以在初始化前修改。

low_level_output做的事情是把LWIP的PBUF数据通过DMA发送出去。这里有个重要的内存问题:LWIP传给网卡驱动的PBUF不一定是一段连续的内存,可能是多个分段组成的链表。但EMAC的DMA发送要求数据缓冲区是物理上连续的地址。解决方法是:在驱动内部预分配一块发送缓冲区,在low_level_output里把PBUF链表里的数据全部拷贝到这这块连续内存,然后再交给DMA发送。

实测下来,这种方式虽然多了一次memcpy,但对100Mbps以太网来说开销完全可以接受。我在测试中跑到了60Mbps以上的TCP吞吐,CPU占用率不到30%,说明拷贝不是瓶颈。

low_level_output的返回值要注意:必须返回ERR_OK或者ERR_IF,不能返回ERR_MEM这种错误码。因为LWIP在发送失败时会根据返回值决定是否重传,如果返回值不准确会导致协议栈状态机混乱。

low_level_input是在收到以太网帧时被中断服务函数调用的。在这个函数里把DMA描述符中收到的数据包装成一个PBUF结构,再调用netif->input函数交给协议栈。DMA描述符只有16个,如果入包速度大于协议栈处理速度,描述符会耗尽。这种情况下,我在中断里做了一次简单的丢包统计,如果描述符用完就直接丢弃新包。这样保证老任务不阻塞,新任务也不会把系统拖死。

3.4 中断服务函数的编写:把收包和协议栈剥离开

F28388D的EMAC DMA中断源比较多:接收完成、发送完成、接收错误、发送错误、链路状态变化等。在CM4上,这些中断统一映射到EMAC的中断线,需要在中断服务函数里读取DMA中断状态寄存器来判断具体事件。

我最开始直接把协议栈处理函数放在中断里调用,发现一旦网络流量稍微大一点,其他中断就会严重滞后,C28x的实时控制任务也会被动受影响。后来把整个设计改成:中断里只做最轻量的事情——把收到的数据帧放入一个环形缓冲区,置一个标志位,然后发送一个信号量唤醒协议栈线程。协议栈线程是优先级比较低的,它被唤醒后从环形缓冲区取数据,再调用ethernetif_input完成PBUF的构造和上报。

实测下来这个优化非常有效。在64字节小包、全速发送的情况下,协议栈线程的CPU占用率稳定在可接受范围内,其他任务的实时性也没有被破坏。

4. 联调阶段:从ping通到TCP吞吐测试

4.1 第一个里程碑:ping通本地回环

当代码写完,第一次上电准备ping的那一刻,整个人的心跳跟这个时钟频率差不多。

我的测试方法是:先ping通PC上的虚拟网卡,再连接到开发板,第一次ping通了,说明MAC地址和PHY的链接都基本正常了。但这里有一个迷惑性很强的情况:开发板上电几秒后,PC网卡显示网络已连接,但是ping不通。这个现象通常不是因为收发通道坏了,而是因为ARP请求没有得到响应。

ARP响应的前提是LWIP协议栈要能正确读取到网卡的MAC地址。我排查下来,问题出在NETIF初始化时传入的MAC地址和我写入EMAC硬件寄存器的地址不一致。两者偏一位,ARP就无法回包,PC端的ARP缓存刷新后,ping才显示超时。还有一种可能性是接收中断没有正常工作,网卡收到了ARP请求,但CPU没感知到数据到了。排查方式是在LWIP网卡接收回调里打一个串口日志,看看收到包的时候是否会进入中断。

4.2 丢包率的那些坑:ARP缓存超时、DMA描述符耗尽

ping通了并不代表一切正常。我在测试1000个ping包时,发现偶尔会有丢包,但丢包率在0.1%左右,看起来问题不大。但那个时期总感觉哪里不对劲。

排查一圈后发现:问题出在ARP缓存上。由于我的PC和开发板长时间没有通信,ARP缓存超时后,PC发往开发板的数据会先发一个ARP请求。开发板的回复偶尔会晚于PC的ARP超时(默认3秒),导致PC认为ARP失败,后续的TCP/UDP数据包自然发不出去。

解决这个问题的思路有两个:一是缩短开发板ARP响应的时间,这个和LWIP协议栈内部处理ARP请求的优先级有关,不太好改;二是在应用层和PC上做处理,把PC的ARP缓存设置为不超时,或者在开发板加一个周期性的ARP广播包,我最后用了第二个方案,在空闲任务里每5秒主动发送一个免费ARP包,把链路状态保持在活跃状态。

另一个内网大规模抓包时发现的问题:如果DMA描述符数量太少,一旦出现突发流量,接收方向的数据包就会被丢掉。我测试时把接收描述符扩展到24个后,突发流量下的丢包率明显下降了。

4.3 测试TCP和UDP:吞吐量实测数据

为了验证整体性能,我在开发板上用LWIP的NETCONN接口分别写了一个TCP回环服务器和一个UDP回环服务器。PC端用网络调试助手和iperf3分别测试。

TCP测试时,TCP窗口大小设置为64KB,实测吞吐率在55-60Mbps之间。这个成绩对于100Mbps以太网已经不错了,瓶颈主要在协议栈的内存拷贝和中断处理上,进一步优化可以开启LWIP的零拷贝选项,但那样会增加PBUF的维护复杂度,也会增加DMA缓冲区的管理难度。

UDP的测试更直观,我让PC端每秒发送1000个UDP包(每个包1024字节负载),开发板原样回传,PC端统计返回率,结果几乎达到100%。这个结果说明,在中等流量下,驱动的收包能力和协议栈的处理能力都能跟得上。

4.4 与C28x的交互:让实时控制核心和网络通信协调工作

前面说了,F28388D的双核最终要协同工作。我在CM4上跑的LWIP和网卡驱动,而C28x跑的是电机控制算法。两个核心通过IPC寄存器组通信。

实际工程中,控制指令从PC下发到CM4,CM4通过IPC发给C28x;C28x实时调整PWM输出,同时把采样到的电流、速度回传给CM4,再由CM4通过TCP/UDP上报给上位机。整个过程中CM4的以太网中断不会干扰C28x的PWM中断,因为两者分别在各自的内核中断控制器下,不共享中断线。

在测试中,我把TCP回环和电机控制同时打开,观察C28x侧的执行时间、PWM波形是否受网络通信的影响。实测下来,TCP传输持续进行时,PWM周期抖动不超过0.5%,控制环的完整性保持得很好。这也验证了当初选择双核任务的正确性。

5. 常见问题速查与排错技巧

5.1 错误总结表:遇到这些情况优先查哪里

现象可能原因排查步骤
网络连接显示已连接,但ping不通EMAC MAC地址配置错误对比NETIF初始化传入的地址和硬件寄存器实际写入的地址,抓ARP包确认是否发出ARP响应
网络显示未连接PHY芯片MII/RMII模式不匹配读取PHY寄存器0x1F确认模式配置,检查MAC控制寄存器的接口模式位
MDIO读写超时PHY地址配置错误确认DP83822的引脚上下拉,读取PHY的ID寄存器验证地址是否正确
短包正常、长包丢包DMA缓冲区长度不足将缓冲区设置为1520字节以上,确认描述符链表绑定正确
偶尔ping超时,部分包丢失接收描述符数量不够或ARP超时增加接收描述符数量,加入免费ARP机制保持链路状态
系统进入硬件错误中断内存访问冲突或描述符内存被踩踏检查描述符内存区域是否与其他变量重叠,确认DMA缓冲区地址对齐到32字节
TCP吞吐量极低LWIP内存池太小或收发有额外拷贝调大lwipopts.h中的PBUF池和TCP窗口,查看mem_stats统计

5.2 不要迷信官方例程:两个容易被忽视的细节

C2000Ware里的官方以太网例程,“可以直接跑通”这件事是建立在特定开发板、特定编译器、特定版本的库函数匹配的基础上的。如果你换了一个PHY芯片,哪怕只是换了PHY的地址,都要重新检查MDIO时序。

DP83822在RMII从模式下,对REF_CLK的质量要求非常高。我用示波器量过几次,发现如果REF_CLK的上升沿不够陡峭,PHY芯片会周期性地误判数据。后来我把时钟源从GPIO的常规输出改成了芯片内部的专用时钟输出,波形明显变好,丢包率也降了下来。这一条在硬件设计阶段就要提前规划好,不能等软件调完了再改板。

5.3 调试过程中的工具推荐:示波器、逻辑分析仪、Wireshark

调试以太网,工具很重要。软件层面,Wireshark是必备的,它能看到PC端的ARP、ICMP、TCP/UDP报文交换情况,很多协议栈问题抓包看一眼就能定位。

硬件层面,百兆以太网虽然信号频率不算高,但调试PHY芯片时用示波器看RMII接口的TX/RX波形、REF_CLK质量还是很有效的。至少要把REF_CLK的频率精度、上升下降时间、TX_EN和TX0/TX1的对齐关系测一遍。

逻辑分析仪更像是跑协议时的辅助工具,它能同时抓多路信号,观察MDIO时序和PHY寄存器的读写内容。不过如果你在软件层已经能正常读取PHY寄存器了,逻辑分析仪的作用就不大了。但排查MDIO问题时它比示波器方便,因为能看到协议帧的完整序列。

6. 写在最后:再聊聊F28388D网络通信的扩展想法

做了几个月的以太网通信项目,我的感受是,F28388D这颗芯片强大的地方在于它把实时控制和网络通信放在同一个芯片里,设计得好的话,两者可以互不干扰地协同工作。相比传统的“MCU+ARM+网卡芯片”的多芯片方案,集成度高的优势非常明显,尤其在体积和功耗敏感的应用场景下。

如果你后续要把这个方案用在自己的产品上,有几个方向可以扩展:一是把EMAC直接接到MII接口的千兆PHY上,这样可以支持千兆以太网,但前提是CM4的频率和DMA描述符数量都要相应调整;二是在CM4上跑一个完整的嵌入式Linux,那样可以用标准Linux网络协议栈,连LWIP都省了,但启动时间和实时性会有所牺牲;三是增加EtherCAT或其他工业现场总线的支持,这个已经是C2000系列的强项,和以太网配合起来能够覆盖更多应用场景。

最后再分享一个小经验,做这种偏底层的以太网开发,遇到问题时不要急着搜“为什么ping不通”,而是先按照链路层→网络层→传输层的顺序逐层排查,确认PHY在工作、MAC能发能收、ARP能通、TCP能传,一层层排查下来,大部分问题都能快速定位。我踩过的这些坑,希望对你有帮助,也欢迎在调试中遇到新问题时回来交流。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询