news 2026/9/21 7:09:36

GD32H759 RT-Thread以太网驱动移植实战:从RMII到Ping通

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
GD32H759 RT-Thread以太网驱动移植实战:从RMII到Ping通

前面一篇把GD32H759的工程搭建、时钟、串口这些基础项跑通之后,很多朋友问我下一步该搞什么。我的建议是:先碰以太网。原因很直白,工控设备一旦要联网,不管你是做远程运维、数据采集,还是和PLC对接Modbus TCP,以太网都是绕不开的底座。这一篇专门讲GD32H759 + RT-Thread下的enet驱动,目标是让网卡真正工作起来——板子能从路由器拿到IP、能被局域网ping通、能跑lwIP协议栈,为后续应用层开发打地基。

这篇内容适合正在评估GD32H759做产品、想在RT-Thread里把以太网跑通,或者调了好几天始终link不上、dhcp超时的朋友。我会把方案选型思路、硬件连接要点、驱动移植步骤、核心代码逻辑,以及最后实测和踩坑记录全部整理出来。内容不绕弯子,直接给可落地的做法。

1. 整体设计与方案选型思路

1.1 为什么选GD32H759做工控平台

先说说这块芯片。GD32H759属于兆易创新的高性能系列,Cortex-M7内核,主频能跑到500MHz以上,片内RAM和Flash的容量都非常宽裕,外设也堆得相当全。最关键的,是它内置了10/100M以太网MAC控制器,这意味着我们不需要外挂一颗带MAC的协议栈芯片,只需要在外部接一颗PHY收发器,再配合RT-Thread自带的lwIP协议栈,就能实现完整的网络通信能力。

在工控项目里,这种“MCU内置MAC + 外部PHY”的方案几乎是标准做法。对比一下:

  • 用SPI接口的W5500这类集成协议栈芯片,开发简单,但占用MCU资源,吞吐量上限低,而且多一层SPI瓶颈。
  • 用内置MAC的MCU,数据走DMA直接进内存,吞吐量高,CPU占用小,成本也更可控,但驱动开发复杂度上来了。
  • 用Linux + 应用处理器,功能强大但是成本、功耗、实时性都不可控,很多工控现场不需要那么重的系统。

GD32H759的定位很适合“中等复杂度、高实时性、要联网”的工控场景,比如伺服驱动器、边缘采集网关、协议转换器。这类设备既需要实时控制能力,又希望保留一定的通信带宽,Cortex-M7的算力和内置MAC正好都覆盖到了。

1.2 协议栈方案:为什么直接用RT-Thread

如果你只是想让板子能发个UDP报文,裸机+lwIP裸移植也能做到。但一旦进入产品开发阶段,你要考虑的不只是“能ping通”,而是“稳定跑几个月不挂、异常能恢复、后续能扩展其他业务”。这时候用RT-Thread这种RTOS的价值就体现出来了。

RT-Thread相比裸机的优势主要有几个:

第一,网络分层清晰。RT-Thread把网络栈抽象成SAL + lwIP + netdev三层,应用层用的是标准socket接口。你今天用lwIP,明天想换其他协议栈,上层socket代码不用动。

第二,配套组件现成。比如DHCP客户端、ping工具、netstat、tftp、web server,很多都能直接从软件包拉下来用。在msh命令行里敲几个命令就能完成网络调试,不需要自己写测试代码。

第三,线程模型天然适合网络应用。收包中断进来,驱动把数据丢给协议栈线程处理,不会阻塞你的控制逻辑。用裸机的话,你得自己协调中断和主循环的时序,一不留神就丢包。

所以本篇所有内容都基于RT-Thread来展开。如果你问“能不能用裸机?”——能,但那是另一个工作量级别的故事了。

1.3 RT-Thread网络架构到底是怎么分层的

在动手写驱动之前,我强烈建议先把RT-Thread的网络分层结构看清楚,不然调试的时候会迷失方向。层次从上到下大致是:

  • 应用层:socket、BSD、或者RT-Thread的AT组件。你写的业务代码在这一层。
  • SAL(Socket抽象层):RT-Thread抽象出来的统一socket接口,屏蔽底层协议栈差异。
  • lwIP:真正的TCP/IP协议栈实现,负责IP、TCP、UDP、ICMP这些协议逻辑。
  • netdev:RT-Thread的网卡设备管理层,维护IP地址、网关、网卡状态这些信息。
  • 驱动层:我们的enet驱动,负责和GD32H759的MAC硬件打交道,以及通过MDIO管理外部PHY。

一个UDP报文从网线进来,流程大约是:PHY收到模拟信号,转成数字电平给MAC,MAC通过DMA写入内存,驱动把缓冲区交给lwIP,lwIP解析成socket数据,最终送到应用的recvfrom。反过来,应用调用sendto,数据从socket传入lwIP,lwIP封包,驱动把包写到DMA描述符,MAC发出去,PHY转成电信号上线路。

驱动层的责任就是打通“MAC硬件”和“lwIP/协议栈”之间的双向通路。搞清楚这个,后面调试的时候就能定位问题发生在哪一段。

2. 移植前的硬件确认与准备

2.1 RMII引脚分配与原理图核对

GD32H759的以太网MAC支持MII和RMII两种对外接口。工控小板上几乎都用RMII,原因是引脚少:数据线只要TXD0/TXD1、RXD0/RXD1,控制线加TX_EN、CRS_DV,再加上REF_CLK和MDIO/MDC,全部加起来十根出头。MII要用两倍以上的引脚,在寸土寸金的PCB上不划算。

引脚对应关系(以RMII为例):

信号方向说明
TXD0/TXD1输出MAC发送数据
TX_EN输出发送使能
RXD0/RXD1输入MAC接收数据
CRS_DV输入载波侦听/数据有效
REF_CLK输入或输出50MHz参考时钟
MDC输出管理接口时钟
MDIO双向管理接口数据

拿到一块新的GD32H759板子,第一件事不是写代码,而是翻原理图,确认PHY芯片的REF_CLK是由MCU给还是PHY自己产生。这个方向如果搞反了,网卡是无论如何也工作不起来的,后面我会展开讲。

另外要注意引脚复用功能。GD32H759的引脚复用很灵活,同一个引脚可能同时是串口、定时器、以太网。在代码里,除了使能外设时钟,还要调用对应的GPIO复用配置,把引脚切到ENET功能上。漏了这一步,信号根本到不了MAC。

2.2 PHY芯片选型与外围电路

GD32H759的内置MAC是10/100M的,对应常见的百兆PHY芯片都能直接接。我自己板子上用的是LAN8720A,这颗在工控小板上极其常见,RMII接口,3.3V供电,外围只有一颗25MHz晶振和几个电容电阻,非常省事。如果你用的板子是其他PHY,比如YT8512、DP83848,驱动里的PHY寄存器操作部分需要相应调整,但整体框架一样。

PHY外围电路有几个细节容易踩坑:

一是PHY的地址。LAN8720A默认PHY地址是0,靠外部电阻拉高拉低引脚决定。这个地址要和驱动里读PHY ID/寄存器时使用的地址一致,否则MDIO读写全部石沉大海。

二是复位电路。有些PHY要求上电后先拉低复位脚再释放,并且要等一段时间才能访问寄存器。如果驱动初始化太急,读PHY寄存器会读回全1或者全0,表现就是PHY ID不对,link状态永远起不来。

三是终端电阻/网络变压器。RMII数据线通常是3.3V的,但网线侧是差分信号,中间必须过网络变压器。工控环境里变压器还能起隔离作用,防止共模干扰打坏芯片。这个属于硬件范畴,但做驱动的人也应该看一眼,因为如果你的“网卡link不上”是硬件问题,你在软件上再折腾也是白费。

2.3 MDIO、中断与复位引脚的“隐藏细节”

MDIO是MAC管理PHY寄存器的通道。默认MDC时钟频率建议别太高,一般在2.5MHz以下比较稳。RT-Thread的GD32驱动里会有对应配置,如果PHY的寄存器读取偶尔正常偶尔乱码,先检查MDC分频是否合适。

中端口脚的作用是把PHY的link状态变化、协商完成这些事件通知给MCU。RT-Thread的驱动有两种处理PHY状态的方式:一种是用中断,PHY状态变了就进中断去读寄存器;另一种是轮询,驱动线程隔几百毫秒去读一次PHY寄存器,把link状态同步给lwIP。

两种方式各有利弊。中断响应快,但如果不加防抖处理,link抖动时会频繁触发中断,反而增加CPU负担。轮询实现简单、稳定,工控场景我反而更推荐,几百毫秒的延迟完全不影响使用。很多RT-Thread官方BSP里的enet驱动就是轮询模式,跑起来效果非常稳。

还有一个细节:PHY的硬件复位脚。如果MCU有IO接到PHY复位脚,最好在初始化的时候先拉低、保持几十毫秒、再释放。如果复位脚悬空或者电容复位时序不够,PHY可能处于半初始化的状态,后续所有寄存器操作都不正常。我在调试中就遇到过一次,板子上一颗PHY偶尔工作偶尔不工作,最后发现是复位电容容值太小,复位脉冲宽度不足。

3. RT-Thread enet驱动移植与配置实操

3.1 驱动代码从哪里来

RT-Thread官方有很多GD32系列BSP,比如GD32F4系列,工程里自带drv_enet.c。但GD32H759毕竟是比较新的芯片,如果你的RT-Thread版本里没有现成的H759 BSP,有两个办法:

第一,去Gitee/GitHub拉最新版本RT-Thread源码,看develop分支里是否已经合入了GD32H7系列BSP。有就直接用,省事。

第二,参考同系列BSP自己改。GD32系列的外设库风格非常统一,以太网驱动在F4、F7、H7之间的差异主要是寄存器基地址、时钟树配置、引脚复用号这些。拿GD32F4的drv_enet.c做底子,对照H759的参考手册把相关寄存器定义和时钟配置改过来,完全可行。

我当时就是用的第二个办法。最核心的修改点在于:时钟树里ENET相关时钟源、RMII参考时钟方向、GPIO复用功能号,以及中断号。这几个地方和芯片强相关,必须按H759的手册逐一核对。

3.2 关键配置项:lwIP、netdev、DHCP

驱动代码就位后,需要在RT-Thread的配置里把网络组件打开。如果你用RT-Thread Studio,可以在图形化配置界面里勾选,本质上是修改rtconfig.h里的宏定义。几个核心配置项如下:

  • RT_USING_LWIP:打开lwIP协议栈。
  • RT_LWIP_MEM_ALIGNMENT:内存对齐字节数,通常填4,配合DMA要求。
  • RT_LWIP_PBUF_NUM:PBUF数量,这个值太小会丢包,太大又浪费内存,工程上一般先给一个中等值跑通,再根据压力测试调优。
  • RT_USING_NETDEV:打开网卡设备管理,这是ifconfig、DNS、路由表这些功能的基础。
  • RT_LWIP_DHCP:开启DHCP客户端。调试阶段可以先开,方便拿到IP;但工控产品正式部署时我建议改成静态IP,后面会解释原因。
  • RT_LWIP_IPADDR、RT_LWIP_GWADDR、RT_LWIP_MSKADDR:静态IP地址、网关、掩码,如果不开DHCP,就在这里填。

编译前还要检查一个东西:堆内存大小。lwIP是出了名的内存大户,如果board初始化里的堆设置太小,协议栈起来之后分不出内存给PBUF和PCB,会导致莫名其妙的收发失败。H759的内存够大,但RT-Thread默认堆可能只给了1MB甚至更少,可以酌情加大。

3.3 驱动初始化流程与启动日志解读

配置完成后编译烧录,板子启动时msh控制台一般会打印类似下面这样的信息:

[I/eth] eth0 link up [I/eth] eth0 ip: 192.168.1.100

如果一切顺利,最后你会看到一个IP地址。但实际调试中大概率不是一次过的,所以我们来看驱动初始化到底做了什么。

驱动初始化的关键路径大概是:

  1. 使能GPIO时钟和ENET外设时钟。
  2. 配置RMII引脚复用。
  3. 配置ETH的DMA、MAC寄存器(工作模式、帧过滤、流控等)。
  4. 初始化DMA描述符链表和收发缓冲区。
  5. 通过MDIO复位PHY、读取PHY ID,确认PHY通信正常。
  6. 配置PHY的工作模式(百兆全双工等),等待link up。
  7. 向RT-Thread注册网卡设备,添加netdev。
  8. 启动DHCP或应用静态IP配置。

如果启动日志里连“phy id”或者“link up”都看不到,那就要回到硬件连接和PHY通信上去排查。如果看到了link up但拿不到IP,再检查DHCP配置和网络环境。

4. 核心代码与收发路径拆解

4.1 网卡是怎么注册进RT-Thread的

RT-Thread的驱动框架里,网卡本质上是一个设备对象加一个netdev描述。驱动初始化时要做两件事:调用rt_device_register注册设备,以及调用netdev_add把这个接口挂到协议栈的管理链表上。

在enet驱动源码里,你会看到一个结构体,里面填了初始化函数、打开/关闭函数、发送函数、接收回调等回调接口。当lwIP准备发送报文的时候,会调用到驱动提供的发送接口;当网卡硬件接收到数据并产生中断时,中断服务函数会调用接收逻辑,把报文推给lwIP的上层处理。

理解这个回调机制很重要。很多人调驱动只知道改硬件寄存器,忽略了RT-Thread设备框架原有的回调链路,结果网卡MAC收发了,但数据没走到lwIP,表现为“网卡好像活着,但ping不通”。

4.2 DMA描述符与数据收发路径

GD32H759的MAC带DMA控制器,收发数据靠DMA描述符链表。每个描述符对应一块缓冲区,描述符里记录了缓冲区地址、数据长度、状态标志。初始化时,驱动要准备一个发送描述符数组和一个接收描述符数组,把它们组成链表,然后把链表的基地址分别告诉DMA控制器的相关寄存器。

发送流程简单说:

  1. 应用层调用socket send。
  2. lwIP把数据封装成多个网络缓冲区(PBUF)。
  3. 驱动从PBUF里取出数据,复制到DMA发送缓冲区(或者零拷贝,取决于实现)。
  4. 驱动设置对应描述符的状态字,标记为“最后一个描述符”、“缓冲区有效”。
  5. DMA控制器看到描述符有效,自动把缓冲区的数据搬到MAC FIFO并发送出去。
  6. 发送完成后产生发送完成中断,驱动释放PBUF。

接收流程则是反过来:

  1. PHY收到数据,MAC的DMA把数据写入接收描述符指向的缓冲区。
  2. DMA置位描述符,产生接收中断。
  3. 中断服务函数里,驱动遍历接收描述符,把数据和长度传给netif,通知lwIP处理。
  4. lwIP处理完后,驱动重新把描述符置为可用状态。

这里有个非常经典的坑:DMA缓冲区地址必须对齐。GD32H759的DMA要求缓冲区地址按4字节甚至更高字节对齐,如果编译器分配出来的缓冲区地址不对齐,数据的第一个字节会错位,接收到的报文解析出来就是错乱的。很多驱动代码里会用RT_ALIGN宏来给缓冲区做对齐,这是有原因的。

4.3 PHY状态管理:link轮询与协商

MAC本身不感知网线是否插好,这个状态需要PHY告诉它。PHY的寄存器0(BCR)和寄存器1(BSR)是最常用的,寄存器1的bit2就是link状态位。驱动可以主动去读,读回来0说明网线没插好或者对端没开,读回来1说明物理链路已经通了。

我用的轮询方式是在一个驱动线程里定时读PHY寄存器,把状态同步给netdev,更新网卡UP/DOWN状态。轮询间隔取500ms到1s都可以。中断方式也可以,但工控环境里我更倾向轮询,省得处理电气噪声带来的误中断。

另外,PHY在上电后可能需要几百毫秒到一两秒的自协商时间,才能和对端交换机协商出百兆全双工模式。所以如果你在系统启动后立刻检查link状态,大概率是down的,不要着急,等一两秒再查。这也是为什么调驱动时,要在启动日志里加一点延迟再打link状态,不然会误判驱动有问题。

5. 从编译通过到Ping通的完整验证

5.1 拿到IP之前,先看启动日志

编译烧录后,串口助手里打开msh控制台,按复位键。我建议关注几个关键输出点:

  1. 驱动初始化有没有报错。比如“enet init failed”之类的提示,有的话直接查程序卡在哪个函数。
  2. 是否打印了PHY ID。能读出正确的PHY ID,说明MDIO链路是通的,硬件连接基本没问题。
  3. 是否出现link up。出现了说明物理层通了,接下来就是网络层的事。

我一个经验:调试阶段最好用路由器或者交换机连接,不要直连电脑。因为有些电脑网卡自协商和PHY芯片兼容性一般,直连反而出现协商失败的情况。直连不行的时候把速率手工固定成百兆试试,又是另一个故事了。

5.2 ifconfig、ping、TCP收发测试

如果启动日志里出现了IP,说明DHCP已经成功,接下来进入验证阶段。在msh里敲ifconfig,会看到网卡名称、IP、掩码、网关、物理地址这些信息。

然后从电脑ping板子,或者从板子ping电脑:

msh /> ping 192.168.1.1

能看到回包并且延迟在10ms以内,基本说明收发路径是通的。但ping通了不代表高负载传输没问题,因为ICMP报文小,不会触发大缓冲区和高吞吐路径。所以还要跑一下TCP/UDP数据收发。

我测试的时候习惯在电脑上开一个TCP Server工具,板子里跑一个socket client尽力地发送数据,观察有没有丢包、重传、粘包。也可以用RT-Thread自带的iperf软件包,直接测吞吐量。实测下来,GD32H759的百兆以太网,RT-Thread + lwIP默认配置下,TCP吞吐在45~65Mbps之间是正常的,UDP一般能到70~80Mbps。如果比这个数值低很多,检查是不是中断优先级不合理、PBUF数量太少、或者驱动里做了不必要的拷贝。

5.3 静态IP还是DHCP

调试阶段用DHCP省事,自动获取IP。但工控产品部署到现场,路由器不是你想换就能换的,现场网络环境五花八门。我的习惯是功能验证通过后,果断改成静态IP配置,并且把DHCP关掉。

原因有三条:第一,DHCP依赖服务器,一旦现场网络里没有DHCP服务,或者服务器响应慢,设备启动要等好几个超时周期才能跳到静态IP逻辑,用户体验极差。第二,工控设备通常需要固定IP被别人连接,如果DHCP分配的地址漂移了,上位机配置就全乱了。第三,静态IP在lwIP里配置是一次性的,不依赖网络环境,可靠性高。

所以我的产品默认都是静态IP,只有在调试模式或者特殊配置场景才临时开启DHCP。

6. 常见问题与排查技巧实录

6.1 问题速查表

调enet驱动那几天,我前前后后遇到不少问题,有些是硬件层面,有些是软件层面。整理成一张速查表,方便大家遇到类似现象直接对症下药:

现象可能原因排查方向
PHY ID读出来全FMDIO/MDC引脚复用没配置;PHY地址不对;PHY复位时序不够检查GPIO复用、PHY地址电阻、复位电路
PHY ID能读到但link一直down网线没插好;对端没通电;RMII参考时钟方向配置错;PHY自协商失败示波器看REF_CLK和TX_EN,检查时钟方向配置
link up了但ping不通MAC地址全零;lwIP内存不足;PBUF数量太少;DMA描述符初始化错误检查MAC地址配置、RT_ALIGN对齐、PBUF/描述符数量
DHCP一直获取不到IP网线物理层有问题;DHCP客户端没启动;协议栈内存不够先用静态IP,若能通说明DHCP配置或网络环境问题
TCP传大文件卡死或断流发送描述符数量不够;中断优先级。协议栈线程抢占导致丢包增加描述符个数,调整中断优先级和线程优先级
上电刚开始正常,跑一会网口挂死DMA描述符耗尽;PHY寄存器状态机停止;协议栈线程被饿死看DMA中断错误标志,检查驱动是否有死锁或描述符回收bug

6.2 一套实用的排查链路

遇到问题先别慌,按链路从物理层往上层查,比盲目改代码高效得多。

第一步,用示波器或逻辑分析仪看RMII引脚。确认REF_CLK这个50MHz时钟是否存在且稳定。这是我最先查的信号,因为它的方向很容易配置错。再用示波器看MDIO有没有波形,有波形说明MCU在尝试访问PHY,MDIO波形异常就能把问题缩小到引脚配置或时序。

第二步,确认PHY寄存器能读。随便读一个寄存器,比如PHY ID寄存器或状态寄存器,能读到非全F、非全0的值,说明物理链路和MDIO通路都OK。读不到数据就回头查PHY地址、复位、MDC时钟分频。

第三步,关掉DHCP,手动配一个局域网内空闲的IP,然后ping网关。能ping通说明MAC和协议栈工作正常,问题可能只是在DHCP环节。

第四步,看协议栈统计信息。RT-Thread的lwIP组件会统计收发包数量、丢弃包数量。我在驱动里会临时打印接收中断产生的次数,对比lwIP实际处理的报文数量,能快速判断问题是丢在中断到协议栈这一段,还是协议栈处理不过来。

6.3 几个值得警惕的可靠性设计点

驱动能跑通,只是第一步。工控产品讲究的是7x24小时稳定,这方面有几个设计细节我想额外啰嗦一句。

第一,MAC地址不能是写死的某个特殊值,也不能全零。很多板厂出厂时会在Flash的OTP区烧录MAC地址,驱动初始化时优先读这个,读不到再用默认值。如果产品批量出货,MAC地址重复会导致局域网里地址冲突,是很低级的故障。

第二,看门狗要能监控协议栈线程。工控系统里,如果某个网络异常导致协议栈线程卡死,系统不能“静静死掉”,要能自动复位恢复。我在产品里会给网络监控线程喂狗,这个线程周期性检查PHY link状态和协议栈内存水位,连续异常累计到阈值就主动触发系统复位。

第三,链路闪断处理。插拔网线、交换机重启,会导致PHY状态反复跳变。轮询驱动里如果一检测到link down就直接释放所有TCP连接,用户体验会非常差。最好加一个消抖逻辑,比如连续3次检测到down才正式切换状态。

第四,DMA描述符和内存池要监控。长时间运行后,如果内存泄漏或者描述符耗尽,网卡会“假死”。所以我在驱动里加了DMA错误中断处理,遇到溢出、总线错误这类异常时,先把描述符链表重置,而不是让系统卡死。

写在实际调试之后

这一路调下来,最大的体会是:enet驱动卡住的地方,大多不在代码逻辑本身,而在硬件细节的确认上——时钟方向、PHY地址、复位时序、DMA对齐,每一个小问题都可能让你排查一整天。好在思路理顺之后,这套东西是可复现的,遇到问题按物理层、MAC层、协议栈层逐层拆就行。

GD32H759 + RT-Thread的以太网驱动跑通,只是网络应用的开始。后面我会继续分享在它上面做Modbus TCP从站、远程固件升级、以及如何优化lwIP协议栈参数来适配不同工控场景。如果你也在调H759的网络功能,希望这篇能帮你少踩几个坑。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/21 6:59:02

固态变压器SST:从工频变压器到碳化硅模块的电力电子革命

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/21 5:30:43

2026研发效能管理平台选型指南:7款主流工具深度对比

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/21 5:30:40

睡眠耳机怎么选?蓝牙主动降噪与久戴不痛的核心参数解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/21 5:19:26

Ghidra逆向分析实战:从Java环境配置到完整反编译流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/21 5:13:20

DeerFlow 2.0 深度拆解:从 Deep Research 到 Super Agent Harness 的架构演进

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华