STM32H743+LAN8720A以太网实战:D-Cache缓存一致性与性能调优

发布时间:2026/9/28 18:16:01
STM32H743+LAN8720A以太网实战:D-Cache缓存一致性与性能调优 把STM32H743接上LAN8720A用CubeMX把LwIP和FreeRTOS一起配上这事听起来就是三个熟门熟路的模块拼在一起但真上手调的第二个晚上我就开始怀疑人生了。现象很怪F4上拷贝过来的代码换个芯片编译一把ping包开始随机丢跑几分钟后ETH DMA报错再往后直接HardFault。后来才反应过来H7这颗Cortex-M7带来的D-Cache把以太网这套DMA在内存和MAC之间搬数据的老玩法全打乱了。这篇文章我不打算讲太基础的怎么点CubeMX我默认你自己能找到ETH、LwIP、FreeRTOS的配置页。这篇真正想解决的是三个问题为什么H7上以太网必须认真处理缓存一致性CubeMX配完以后哪些选项会在后续给你埋雷以及当你想把这颗480MHz芯片的百兆口压榨出真实性能时该从哪些参数下手。适合正在做H743/H723类项目的朋友也适合从F4迁到H7、第一次直面D-Cache的团队参考。1. 拿到H743后第一个要搞清楚的Cache与DMA的关系1.1 从F4迁移过来时最容易踩的思维惯性在STM32F407上跑LwIP几乎所有人都是这么干的CubeMX开ETH、开LwIP中间件、生成代码、然后写业务逻辑。F4的Cortex-M4内核没有D-CacheCPU读内存、DMA写内存两边看到的是同一个SRAM里的同一个物理数据不存在旧数据这种概念。到了H743上M7核心多了一层D-Cache。这颗CPU的主频可以拉到480MHz但外部SRAM和Flash的访问速度远远跟不上D-Cache就是用来弥合这个速度差的——它会把CPU最近访问过的内存复制一份到核内高速缓存里后续CPU再读同一块地址直接命中缓存不用再跑一趟总线。听起来是好事但问题是DMA控制器访问内存时是绕开D-Cache的它直接读写物理内存。于是矛盾出现了CPU和DMA对同一块地址的数据可能各自持有不同的版本。以太网恰恰是DMA重度使用的场景。H743的MAC内部自带DMA引擎收发路径上所有数据包都靠DMA搬运。只要有DMA参与缓存一致性问题就绕不开。1.2 D-Cache的工作机制以及DMA为什么会被蒙在鼓里D-Cache的运作可以简单类比成工位旁边的临时货架。CPU要读某个产品内存地址先看临时货架Cache里有没有有就直接取没有才跑去仓库SRAM拿顺便在货架上留一份。这个机制对纯CPU访问是透明的完全没问题。问题出在还有另一个人也在操作仓库。以太网DMA就是这另一个人。它不看你工位上的临时货架直接去仓库搬货。于是两个典型错乱就产生了发送方向CPU把要发送的数据包写进内存Buffer这些数据可能还留在Cache里没真正落到SRAM。DMA此时去仓库搬货搬走的是旧数据或半新半旧的数据网上表现就是收发内容错乱、校验失败。接收方向DMA把网口收到的新数据写进了SRAM但CPU去读这块地址时Cache里还留着一份更早的旧副本CPU以为自己读到了数据实际读到的是过期内容表现出来就是收到的包内容不对或干脆认为没有新包。1.3 以太网场景下Cache障碍的两个方向如果只用一句话总结上一节DMA和CPU对内存数据的可见性不同步。具体到LwIP LAN8720A H743这套组合数据处理涉及三类内存对象内存对象谁在读/写Cache冲突风险DMA发送/接收描述符CPU初始化ETH DMA硬件读写高描述符的OWN位状态不同步会导致DMA认为缓冲区不可用或CPU误判完成状态数据包BufferCPU构造/解析DMA搬运高发送时Cache未回写接收时Cache未失效LwIP的PBUF链表结构CPU完全处理低不经过DMA但内存对齐问题会影响DMA的存取所以处理以太网Cache一致性本质上就是要对这三类内存做好同步工作。方式无非三种让CPU不缓存这块区域、手动刷Cache、或者干脆把D-Cache关掉。这三条路各有代价后面第3章我会把每种方案的坑和适用场景一次讲清楚。我在实际调试中建议的步骤是先用最粗暴的方式把链路跑通再用MPU方案稳定最后再谈性能优化。千万不要一上来就想着零拷贝和手动刷Cache那样出了问题你很难判断是协议栈问题还是缓存问题。2. CubeMX配置时钟、PHY、FreeRTOS与LwIP的一个都不能错CubeMX把STM32H743的图形化配置做得已经足够傻瓜但有几个关键选项是在图形界面里埋着雷的。这一章把配置链路拆成四块每块对应一个我实际踩过或见过别人踩的坑。2.1 RMII的50MHz参考时钟三种来源怎么选LAN8720A是RMII接口的百兆PHYRMII模式要求MAC和PHY必须共享同一个50MHz参考时钟。H743的MCO1、MCO2都可以输出时钟外部也可以用有源晶振直接馈送。三种方式在工程上都可行但稳定性差很多。方式一H743的MCO输出50MHz给LAN8720A。CubeMX里可以把MCO1配置为HSE经过PLL分频后的50MHz让PA8引脚输出接到LAN8720A的XTAL1/CLKIN。这个方案的优点是省一颗有源晶振BOM成本低。缺点是MCO输出的信号质量受GPIO驱动能力和输出负载影响尤其在PCB走线较长时波形畸变可能导致PHY偶尔检测不到时钟表现就是Link状态时好时坏或者协商成10M速率。我用这个方案试过近距离飞线能跑但稍微拉长一点线就开始不稳定。方式二外部有源50MHz晶振同时给LAN8720A的CLKIN和STM32H743的ETH_RMII_REF_CLK。这是工业级产品最常见的方案。因为RMII要求PHY和MAC时钟同源一颗晶振同时喂两端相位和频率天然一致信号质量也最好。代价是多一颗有源晶振占一点PCB面积。H743的ETH_RMII_REF_CLK引脚是PA1直接把有源晶振输出分两路接过去就行。方式三LAN8720A自己输出50MHz给STM32。LAN8720A的REFCK0引脚可以配置为时钟输出。这个方案依赖PHY内部的PLL对LAN8720A来说确实可行但我在H743上没实际量产过这种方式不做推荐主要是不想让MAC的时钟源受PHY初始化时序影响调试时多一层不确定性。提示不管用哪种方式务必在CubeMX里确认ETH配置页选择的是RMII而不是MII。选错接口模式引脚分配完全不对编译能过但PHY完全不通。2.2 LAN8720A的PHY地址、复位与寄存器验证LAN8720A的PHY地址由PHYAD0引脚决定绝大多数模块默认把PHYAD0接地地址是0x00。少部分模块会做跳线或上拉到地址0x01。CubeMX的ETH配置里有一个PHY Address选项必须和你的硬件实际地址一致否则MDIO读不到PHY的任何寄存器。PHY复位也要注意。LAN8720A的NRST引脚接RC复位是可以的但我更建议用H743的一个GPIO单独控制PHY复位。好处是可以在软件里做先复位PHY等它稳定后再初始化ETH的时序控制避免上电时PHY和MAC竞态。CubeMX里把GPIO配置成输出初始化顺序里GPIO先拉低20ms以上再拉高再跑MX_LWIP_Init()。验证PHY是否正常工作最直接的办法是读PHY ID寄存器。LAN8720A的寄存器0x02读出来应该是0x0007寄存器0x03读出来应该是0xC0F1。如果你能通过调试器读出这两个值说明MDIO通路、PHY地址、复位时序都没问题。读不出来优先查PHY地址和复位引脚。2.3 FreeRTOS集成细节Timebase、中断优先级与CMSIS版本CubeMX生成以太网FreeRTOSLwIP项目时有几个配置是连环的。第一SYS的Timebase Source必须从SysTick改成TIMx。因为FreeRTOS默认使用SysTick作为系统节拍。如果不改两个模块抢同一个定时器项目大概率在启动阶段就卡死或随机死机。CubeMX里把Timebase Source改成TIM6或TIM7这类基础定时器即可。第二FreeRTOS的CMSIS版本选V1还是V2。新版CubeMX默认CMSIS_V2生成的API风格和V1差别不小。如果你参考的老教程是V1信号量、队列API名称都对不上。我更推荐直接用CMSIS_V2跟新版本HAL兼容性更好后续用cmsis_os2的接口写业务也更接近标准。只是注意网上大量老代码是V1抄的时候要转换。第三ETH中断优先级。这是我认为CubeMX配置中最关键的一项。FreeRTOS有一个configMAX_SYSCALL_INTERRUPT_PRIORITY它规定哪些中断可以直接调用FreeRTOS的API。ETH中断优先级如果设置得比这个值更紧急数值更小那么你在ETH中断回调里调用xSemaphoreGiveFromISR这类接口轻则断言失败重则直接HardFault。我的做法是ETH中断优先级设置为高于普通外设中断、但低于SysTick和PendSV在CubeMX的NVIC里给ETH分配一个5~7范围内的优先级数字越大越不紧急确保它能安全调用FreeRTOS的FromISR接口。2.4 CubeMX里LwIP的关键选项速览LwIP中间件在CubeMX里有几个页面其中General页的LWIP_TIMER_ONDEMAND、LWIP_NETIF_STATUS_CALLBACK、LWIP_NETIF_LINK_CALLBACK建议打开Link状态反馈对调试以太网非常重要。LWIP_STATS建议开发期开着可以统计丢包、内存不足量产时再关。在Key Options里需要重点确认的是DISABLE_CHECKSUM相关选项。如果你在CubeMX里开启了硬件校验和卸载功能LwIP会跳过软件校验和计算由STM32的MAC硬件完成。H743的MAC支持收发校验和卸载开启后性能有明显提升。但要注意这块功能依赖CubeMX生成的stm32h7xx_hal_eth.c是否正确配置了MAC的校验和寄存器位我遇到过CubeMX版本升级后此选项默认值改变导致接收校验和在硬件层面被跳过、而LwIP软件层也跳过的双重问题最终是抓包才发现CRC校验全没过。所以DISABLE_CHECKSUM相关的宏务必确认打开或关闭的链路是一致的。3. 缓存一致性处理三种主流方案与我的取舍这一章是全文的核心。前面的配置只是让系统能跑而缓存一致性的处理直接决定系统跑多久不崩和跑多快。3.1 方案A全局关闭D-Cache不推荐但必须先会CubeMX里Cortex-M7的配置页有D-Cache的开关把它关掉以太网收发立刻恢复正常之前那些随机丢包、HardFault统统消失。原因是CPU对内存的所有访问都直接走总线和DMA看到的内容完全一致自然没有一致性问题。这个方案的代价是整体性能大幅下降。H743主频480MHz本身很快但CPU访问SRAM的延迟会被完全暴露代码里凡是有频繁内存操作的逻辑都会变慢。以太网场景下TCP吞吐会掉一截实测大概能掉20%~30%。如果项目只是能通就行对性能没要求这个方案确实最快。但既然选了H743这颗芯片关D-Cache几乎等于浪费了一半性能。提示关D-Cache后如果网络仍然不稳定那问题就和Cache无关要回到PHY配置、时钟、PCB布线上去查。3.2 方案B用MPU把以太网内存区域标记为非缓存这是我认为最推荐的方案也是H7系列跑LwIP的标准做法。思路是D-Cache不关但通过MPU设置一段内存区域为Normal, Non-cacheable把DMA描述符和收发Buffer都放在这段区域里。CPU访问这块区域不走CacheDMA也访问这块区域两边天然一致性。H743有1MB左右的总SRAM分布在不同域。以太网DMA描述符和Buffer建议放在AXI SRAM基地址0x24000000大小512KB里的一个独立段然后用MPU配置这段地址为不缓存。注意不要放DTCM0x20000001附近因为DMA无法访问DTCM放进去DMA会直接拿不到数据。MPU配置代码大概长这样void MPU_Config_ETH_Buffers(void) { MPU_Region_InitTypeDef MPU_InitStruct {0}; HAL_MPU_Disable(); MPU_InitStruct.Enable MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress 0x24000000; MPU_InitStruct.Size MPU_REGION_SIZE_512KB; MPU_InitStruct.AccessPermission MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable MPU_REGION_NOT_BUFFERABLE; MPU_InitStruct.IsCacheable MPU_REGION_NOT_CACHEABLE; MPU_InitStruct.IsShareable MPU_REGION_NOT_SHAREABLE; MPU_InitStruct.TypeExtField MPU_TEX_LEVEL1; MPU_InitStruct.SubRegionDisable 0x00; MPU_InitStruct.DisableExec MPU_INSTRUCTION_ACCESS_DISABLE; HAL_MPU_ConfigRegion(MPU_InitStruct); HAL_MPU_Enable(MPU_CONTROL_PRIVILEGED_DEFAULT); }代码里MPU_TEX_LEVEL1配合NOT_CACHEABLE、NOT_BUFFERABLE得到的就是Normal内存的非缓存属性。这样配置后CubeMX生成的LwIP驱动里DMA描述符和接收缓冲区只要落在这段区域内就不需要手动刷CacheCPU和DMA看到的数据永远一致。一个容易忽略的点非缓存区域不能只用一部分MPU Region是按2的幂次对齐的。如果你的以太网只用4KB内存但MPU Region最小也是4KB对齐的一个段而整个0x24000000被标记为非缓存后如果同一段里其他地方还要放高频访问的变量性能就会受损。所以更精细的做法是把描述符和Buffer放在一个专用段里并把该段大小设置为MPU能覆盖的最小次幂比如16KB或32KB这样对整体性能影响最小。3.3 方案C保留Cache手动Clean与Invalidate方案C是既要缓存性能又不想让MPU区域太大的折中。做法是不动MPU以太网Buffer区域仍然保持Cacheable但在每次DMA发送前调用SCB_CleanDCache_by_Addr把数据刷回物理内存在每次DMA接收后调用SCB_InvalidateDCache_by_Addr让CPU取数据时重新从内存加载。/* 发送前 */ SCB_CleanDCache_by_Addr((uint32_t *)buffer, length); /* 接收后 */ SCB_InvalidateDCache_by_Addr((uint32_t *)buffer, length);这套逻辑看起来干净但细节非常多。发送描述符的OWN位、数据包Buffer、接收描述符的起始地址三者的对齐和长度都必须满足Cache Line的要求H7的Cache Line是32字节。你刷Cache的起始地址如果不是32字节对齐刷的范围可能覆盖到相邻内存带来隐藏的并发问题。LwIP的PBUF结构本身地址和对齐并不保证刚好落在32字节边界所以手动刷Cache很容易刷多或者漏刷。我在项目里只会在极少数高频且固定长度的数据通道上用手动刷Cache比如某种固定长度的UDP上报报文。对于承载TCP或者多种包类型的通用以太网协议栈我不用方案C原因就是它太容易在边界上出问题排查成本高。3.4 项目里我最终怎么选我的最终选择是方案BMPU划一块非缓存区域DMA描述符和收发Buffer全部放在里面LwIP的PBUF池也从这块区域里分配。这样既能保留全局D-Cache给其他内存区域带来的性能收益又不碰手动刷Cache这种容易出错的逻辑。还有一个替换思路H7系列其实没有硬件层面的non-cacheable专用内存但CubeMX生成的链接脚本里通常会把以太网描述符放到一个独立section配合MPU确实够用。如果不想自己写链接脚本更简单的做法是把0x24000000整段512KB都配置成非缓存。代价是AXI SRAM中存储的变量全部失去缓存加速但对大多数只把AXI SRAM当大缓冲用的项目来说可以接受。我的项目里数据量不算大就这么干了省心。4. 性能调优实测从40Mbps到接近百兆的完整路径缓存一致性解决后系统已经稳定接下来就是性能问题。H743的百兆以太网理论吞吐上限大约是94Mbps11.75MB/s但默认配置下跑LwIPFreeRTOS实际远达不到这个数。我自己的实测数据可以作为参照。4.1 先跑一轮基准默认参数下的表现我用的是LwIP 2.1.2 FreeRTOS 10.x CubeMX生成代码PC端用iperf3打流。默认参数下PBUF_POOL_SIZE 10、TCP_WND 20480、TCP_SND_BUF 20480、CPU主频480MHz、MPU非缓存方案TCP下行吞吐大约40Mbps左右上行稍高一点到45Mbpsping延迟在0.5~2ms之间波动偶尔到5ms。这个成绩对很多能跑就行的项目够用但离百兆口应该有的水平差了一半多。接下来发现瓶颈不在PHY也不在MAC硬件而在LwIP的缓冲策略和任务调度方式上。4.2 调LwIP内存池与TCP窗口先打开lwipopts.h逐个看关键宏。PBUF_POOL_SIZE决定接收侧可用的PBUF池数量。默认10个对于TCP这种需要缓冲窗口内多个报文段的协议来说太小。当接收速率瞬时超过协议栈消费速度时PBUF池被占满新到的包只能丢弃TCP会通过重传机制补偿但吞吐被严重拖累。我把PBUF_POOL_SIZE调到30PBUF_POOL_BUFSIZE保持1524一个1518以太网帧加一点余量接收拥塞明显缓解。TCP_WND是TCP接收窗口大小默认20480字节。这意味着一端最多只能通告20KB的接收能力在百兆网络中这个窗口是吞吐的硬上限。我用64KB和TCP_MSS1460配合H743能通告更大的滑动窗口吞吐立刻上升。TCP_SND_BUF同理这是发送缓冲默认也是20480我调到64KB。同时在FreeRTOS层面给LwIP的tcpip_thread分配了更大的栈避免高流量下栈溢出。调整后的效果很直接下行吞吐到了60Mbps出头ping延迟稳定在0.3ms左右。到这个阶段协议栈配置层面的瓶颈基本榨干了。4.3 中断与接收线程的配合方式CubeMX默认生成的ethernetif.c里接收完成中断回调通常会直接参与数据搬运。但在FreeRTOS环境下中断里做太多事情是大忌。我的做法是把接收路径改成中断置信号量任务中处理收包。static void ethernetif_rx_task(void *argument) { ETH_BufferTypeDef RxBuffer; struct pbuf *p; for (;;) { xSemaphoreTake(rxSemaphore, portMAX_DELAY); while (HAL_ETH_ReadData(heth, RxBuffer) HAL_OK) { p pbuf_alloc(PBUF_RAW, RxBuffer.len, PBUF_POOL); if (p ! NULL) { pbuf_take(p, (uint8_t *)RxBuffer.buffer, RxBuffer.len); if (netif-input(p, netif) ! ERR_OK) { pbuf_free(p); } } } } }ETH中断里只做一件事调用HAL_ETH_IRQHandler在回调里用xSemaphoreGiveFromISR给接收任务发信号。这样做的好处是中断处理时间极短不会影响FreeRTOS的实时性也避免在中断上下文调用LwIP函数带来的重入风险。这里要提醒的是如果按上面的代码做了pbuf_take的拷贝会产生一次内存拷贝开销。纯性能角度更优的做法是让DMA直接写入PBUF池的内存接收后直接挂到netif-input零拷贝。但零拷贝会增加Buffer管理复杂度尤其要保证PBUF内存地址和长度都满足DMA约束。我的项目里先用了带拷贝的版本稳定后再切零拷贝。4.4 零拷贝与描述符方向的细节收益零拷贝优化是这次调优的最后一步。思路是在LwIP初始化阶段把PBUF池的内存统一从以太网专用非缓存段里动态分配然后在DMA描述符初始化时直接把描述符的Buffer地址指向PBUF的内存地址。这样接收路径上DMA直接把网口数据写入PBUF内存中断后任务直接把这个PBUF交给协议栈不需要pbuf_take的拷贝。low_level_init里描述符和PBUF内存要放在同一个非缓存区域。我额外做了一个自定义分配函数专门从MPU标记的非缓存SRAM中取PBUF池static uint8_t eth_pbuf_pool_buffer[PBUF_POOL_SIZE * PBUF_POOL_BUFSIZE] \ __attribute__((section(.eth_non_cacheable), aligned(32)));配合链接脚本里的.eth_non_cacheable段把这段内存放到0x24000000的固定区域确保MPU配置覆盖它。切到零拷贝后TCP吞吐从60Mbps提升到大约85~90Mbps。考虑协议栈本身、FreeRTOS任务切换和TCP协议开销这个数字已经接近H743LAN8720A这套组合的实用上限。如果还想往上顶就要压缩任务切换频率、考虑调整DMA描述符数量但边际收益已经很小。5. 真实踩坑记录三个经典故障的完整排查链路最后分享三个我在调试过程中真正遇到、且排查链路有代表性的问题。每个问题我尽量还原排查过程而不是直接给结论。5.1 现象一ping不通但PHY的Link已经是up这个现象最容易让人困惑PHY的Link状态寄存器显示已连接测线仪也判断网线没问题但ping就是不通。我的排查链路先读PHY的BSR寄存器确认bit2Link Status为1证明物理层没问题。确认RMII时钟示波器量PA1引脚ETH_RMII_REF_CLK有50MHz方波。这个信号如果不对PHY根本不会上报Link up所以不是这个问题。抓MAC侧的收发统计寄存器发现ETH DMA的接收描述符一直停在初始状态没有数据写入。说明数据没有从RMII进入MAC的DMA。检查PHY地址发现模块实际地址是0x01而CubeMX里配的是0x00。改为0x01后立刻能ping通。前面Link up显示正常是因为PHY的Link状态可以通过内部自环上报但MDIO读不到PHY IDMAC和PHY之间的管理通道是断的。这个坑的教训是Link up只能证明物理层通了不代表MAC管理通道通了。配置PHY地址前务必确认硬件上PHYAD0引脚的实际接法。5.2 现象二收包正常发送却卡死或丢包严重接收方向一切正常能通过网口收到数据、ARP也能响应但长时间发包或大流量发包时发送会突然卡死。看调试器的寄存器TX描述符的OWN位一直为1说明DMA认为描述符还在CPU手里CPU却认为早就交给DMA了。这个问题的根源极大概率是Cache一致性问题而且是在MPU没配置好或描述符内存不在非缓存区的场景下出现的。CPU把描述符的OWN位置1后OWN的状态没刷进物理内存DMA看到的还是旧值。排查时先确认描述符数组的内存地址是否在MPU配置的非缓存区域内如果没有MPU是否在发送路径上做了SCB_CleanDCache_by_Addr描述符本身是否满足4字节对齐H7的ETH DMA对描述符对齐要求非常严格。我的解决方法是放弃手动刷Cache改用MPU方案把描述符和Buffer一起放到非缓存区。这个方案改完后再也没出现发送卡死。5.3 现象三跑几分钟后整个网络模块死掉前面一切正常长时间运行后网络模块突然没反应。不是单片机死机其他任务还活着而是LwIP状态卡住。排查时的关键发现是ETH中断在长时间高负载后没有继续触发。我最终定位到两个叠加的因素一是PBUF池耗尽。默认PBUF_POOL_SIZE太小长时间高负载下接收路径如果偶尔处理不及时PBUF被占满新包丢弃TCP重传风暴进一步加剧最终协议栈的某个等待永远等不到可用缓冲。把PBUF_POOL_SIZE调大后缓解了很多。二是ETH中断优先级和FreeRTOS的冲突。当时ETH中断优先级设置得很高高于configMAX_SYSCALL_INTERRUPT_PRIORITY在中断里调用xSemaphoreGiveFromISR时虽然没崩溃但系统在高负载下出现了不可预测的调度异常最终表现就是网络任务饿死。把ETH中断优先级调整到FreeRTOS允许的安全范围内后问题彻底消失。5.4 调试工具与手段清单以太网调试不能只靠printf尤其是FreeRTOS下打印太多会改变时序反而掩盖问题。我常用的工具组合工具/手段用途WiresharkPC端抓包确认ARP响应、TCP握手、重传情况ST-LINK 调试器Live Watch实时看LwIP统计变量、PBUF剩余量、DMA描述符状态ETH DMA状态寄存器DMACSR查接收/发送中断标志位确认DMA是否还在工作PHY寄存器读写函数单独验证MDIO通路读PHY ID和Link状态RTT或串口时间戳日志打印关键事件比普通printf快对时序影响小我在调试的时候会把LwIP的LWIP_STATS宏打开它提供的lwip_stats结构体可以告诉你丢包发生在哪个环节是内存不足、校验失败还是接口层丢弃。这是定位性能问题最高效的入口。调优完成后再把统计关掉减少开销。H7系列的以太网调试和F4最大的不同就是必须时刻提醒自己Cache在哪里。每次遇到诡异现象先在脑子里过一遍这条路径上的数据是否涉及DMA如果涉及就先检查Cache一致性再检查逻辑。按照这个思路来H743LwIPFreeRTOSLAN8720A这套组合能少走很多弯路。