ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

从STM32迁移到HC32F4A0:10路USART多串口项目实战与踩坑记录

从STM32迁移到HC32F4A0:10路USART多串口项目实战与踩坑记录 去年年底接了一个工业数据采集网关的项目最开始用的是STM32F407功能也基本能跑但量产成本压力一来MCU这颗料就成了必须动刀的地方。对比了一轮国产替代方案之后最后选了华大半导体HC32F4A0。原因不复杂同样是Cortex-M4F核心资源甚至比F407更阔气10个USART对多串口采集类项目来说太有吸引力了。但真从STM32切过去过程并没有想象中顺利特别是USART这部分前前后后踩了不少坑。如果你也打算从STM32迁移到HC32F4A0或者正在为多串口项目选型这篇内容应该能帮你省掉不少调试时间。我尽量按实际踩坑的顺序写把时钟、GPIO复用、中断、DMA、波特率计算这些关键点都过一遍尤其是10个USART怎么统一管理。文章里的代码是基于当前华大官方DDL库写的不同SDK版本的函数名可能略有差异但核心思路通用。1. 为什么从STM32转战HC32F4A01.1 项目背景与选型理由这个项目是一台边缘采集网关上面要接8路RS485传感器、1路RS232调试口、1路RS232工业仪表另外还要预留以太网和USB。最开始用STM32F407做原型验证功能确实能跑通但看串口资源的时候就有点难受F407的串口数量在当时的封装下不够用还得分出USART做调试和协议解析最后不得不用SPI外挂扩展串口芯片成本和PCB面积都上去了。后来拿到HC32F4A0的规格书第一眼被它的串口数量吸引。10个USART意味着我可以把一路调试口、一路RS232仪表口、八路RS485全部用硬件串口搞定不再需要扩展芯片。除了串口数量它还保留了M4F的FPU和DSP指令跑Modbus RTU、边缘协议解析、浮点温湿度计算都够用。再加上华大在工业控制领域的稳定供货和价格优势这个替换就顺理成章了。当然选型不能只看串口多。我对照过HC32F4A0和STM32F407的关键资源这里列一张表方便你快速判断资源项STM32F407我原来的型号HC32F4A0我实际用的型号内核Cortex-M4F主频168MHzCortex-M4F主频最高240MHzFlash最高1MB最高2MBSRAM128KB 64KB CCM384KB级别串口资源数量偏紧型号不同有差异10个USART可灵活配置为UART模式以太网部分型号带MAC部分型号带MAC配置要按具体封装查工业外设常规偏工业控制场景定时器、模拟外设丰富所以我的结论是如果你的项目里串口数量超过6路而且对成本比较敏感HC32F4A0确实值得试。但值得试不代表能无缝移植因为它的外设架构、驱动库风格和STM32差别很大尤其是GPIO复用和时钟树这两块。1.2 迁移前先想清楚的问题从STM32到HC32F4A0不是简单把芯片型号改一下、函数名字换一下就行的。我第一次拿到开发板时习惯性地按STM32的思路去初始化USART配置GPIO、开启外设时钟、调波特率、使能收发。结果完全没反应串口助手一个字节都收不到。后来才发现HC32F4A0的外设时钟默认很多都是关闭的而且GPIO的复用功能配置方式和STM32完全不一样。STM32的HAL库里有GPIO_InitTypeDef把引脚模式设置成GPIO_MODE_AF_PP再指定Alternate就完了HC32F4A0则是用GPIO_SetFunc这样的函数把某个引脚直接切换成特定外设功能。这个思路差异不大但细节很磨人因为你不查引脚复用表根本写不对。另外还有一个坑STM32的调试器默认能连SWD和JTAG而华大这颗料如果恰好把串口引脚和其他功能复用在调试脚上配置完之后调试器可能直接连不上。我在后面专门讲引脚复用时再展开。2. 迁移前必须知道的架构差异2.1 时钟树差异是第一个坑STM32的老玩家都知道SystemInit之后只要把RCC的PLL配好外设时钟基本不用管HAL库会自动把APB1、APB2的时分频处理好。HC32F4A0不一样它的外设时钟更像手动挡你要用哪个外设必须先在SYSCTRL里把对应外设的时钟开关打开然后再确认这个外设挂在哪个时钟总线上。华大官方DDL库一般会有类似这样的操作SYSCTRL_ClkEnable(SYSCTRL_CLK_USART0);这行代码放在USART初始化前面很多人会漏。如果漏掉后面所有USART_UART_Init、USART_WriteData调用都不生效而且不容易排查因为编译不报错寄存器也像是能写实际上模块根本没跑起来。还有一个容易忽略的点USART的波特率计算依赖外设时钟不同型号或不同封装下USART可能挂在PCLK0上也可能挂在PCLK1上两个时钟频率不一定一样。后面我会专门讲波特率坑这里先记住一句话先查芯片手册里的时钟树再写USART初始化代码。2.2 GPIO与复用功能配置方式不同在STM32上把一个引脚变成USART1_TX一般是这样GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_9; GPIO_InitStruct.Mode GPIO_MODE_AF_PP; GPIO_InitStruct.Pull GPIO_PULLUP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate GPIO_AF7_USART1; HAL_GPIO_Init(GPIOA, GPIO_InitStruct);到了HC32F4A0DDL的写法是GPIO_SetFunc(GPIO_PORT_A, GPIO_PIN_09, GPIO_FUNC_USART0_TX); GPIO_SetFunc(GPIO_PORT_A, GPIO_PIN_10, GPIO_FUNC_USART0_RX);看起来只是换了个函数名但关键是第三个参数。GPIO_FUNC_USART0_TX这类宏定义不是所有外设都有统一名字你得在SDK的头文件里查或者对照数据手册里的引脚功能复用表找。这块最烦的是同一个USART可能支持多组引脚比如USART0_TX既可能出现在PA09也可能出现在PB10不同封装支持的组还不一样必须仔细看你的芯片封装引脚排列。我建议在画原理图之前就把所有需要的串口引脚在Excel里排一遍先排除掉和SWD、以太网、晶振引脚冲突的情况。否则等到PCB打样回来再改引脚工程量会非常大。2.3 中断系统与NVIC逻辑要重写STM32用HAL库时中断处理函数是固定的比如USART1_IRQHandler。HC32F4A0也类似但中断使能、挂起清除的细节有区别。我踩过的一个典型坑是接收中断标志位的清除方式不一样如果在中断里顺手调了一个清标志函数反而可能会导致下一个字节进不来或者连续进中断造成死循环。所以迁移时不要照着STM32的中断代码硬套必须先看HC32F4A0的数据手册里关于USART中断标志位的说明尤其是读数据寄存器和清除标志位的先后关系。很多外设中断标志是读寄存器时自动清掉的你再去手动清一次容易出问题。后面第5章我会把这个坑的实战场面讲一遍。3. 十个USART的资源分布与初始化思路3.1 HC32F4A0的USART外设资源HC32F4A0的串口都叫USART但实际用法上经常跑成UART模式也就是异步收发不输出时钟信号。很多刚接触的人会被名字吓到其实不用担心USART本身是支持同步模式的但我们在RS232、RS485、Modbus这类场景里基本只用异步模式。这里把USART、UART、I2C、SPI这几个常见串行接口的区别顺手放一下方便新手理解协议方向需要几根线有没有时钟线典型用途USART/UART全双工异步为主2根TX/RX或4根带流控异步时不需要同步时有一根CLKRS232、RS485、调试口I2C半双工2根SDA/SCLSCL由主机产生传感器、EEPROMSPI全双工3根或4根SCK由主机产生Flash、LCD、SD卡回到HC32F4A0我实际用到的串口数量是10路。DDL里一般会提供stc_usart_uart_init_t这样的结构体初始化时把波特率、数据位、停止位、校验位填进去然后调USART_UART_Init。如果你只用UART异步模式就不需要关心同步模式的CLK引脚和时钟极性配置。3.2 用一张表一个结构体管理全部串口10个串口如果每个都单独写一套初始化代码代码量会爆炸而且维护麻烦。我最开始写USART0的时候确实老老实实复制了五遍后来要加第8路传感器时彻底受不了改成了一张配置表加循环初始化的结构。先定义串口配置结构体typedef struct { M4_USART_TypeDef *usart; // USART 外设寄存器基地址 IRQn_Type irq; // 中断号 uint32_t baud; // 波特率 uint8_t txPort; // TX引脚所在端口 uint16_t txPin; // TX引脚 uint8_t rxPort; // RX引脚所在端口 uint16_t rxPin; // RX引脚 uint8_t txFunc; // TX复用功能编号 uint8_t rxFunc; // RX复用功能编号 } HcUsartCfg;然后建一张全局配置表把10路串口的信息都放进去static const HcUsartCfg g_usart_config[] { { USART0, USART0_IRQn, 115200, GPIO_PORT_A, GPIO_PIN_09, GPIO_PORT_A, GPIO_PIN_10, GPIO_FUNC_USART0_TX, GPIO_FUNC_USART0_RX }, { USART1, USART1_IRQn, 9600, GPIO_PORT_B, GPIO_PIN_03, GPIO_PORT_B, GPIO_PIN_04, GPIO_FUNC_USART1_TX, GPIO_FUNC_USART1_RX }, // ... 其他串口按实际原理图填充 };初始化函数就可以统一循环处理void HcUsart_InitAll(void) { for (uint8_t i 0; i USART_NUM_MAX; i) { SYSCTRL_ClkEnable(g_usart_config[i].usart); GPIO_SetFunc(g_usart_config[i].txPort, g_usart_config[i].txPin, g_usart_config[i].txFunc); GPIO_SetFunc(g_usart_config[i].rxPort, g_usart_config[i].rxPin, g_usart_config[i].rxFunc); stc_usart_uart_init_t uartInit; memset(uartInit, 0, sizeof(uartInit)); uartInit.u32Baudrate g_usart_config[i].baud; uartInit.enDataBits USART_DATABITS_8; uartInit.enParity USART_PARITY_NONE; uartInit.enStopBits USART_STOPBIT_1; USART_UART_Init(g_usart_config[i].usart, uartInit); USART_FuncCmd(g_usart_config[i].usart, USART_FUNC_RX | USART_FUNC_TX, ENABLE); } }这样以后要改某一路的波特率或者换引脚只需要改这一张表不用在代码里翻来翻去。10路串口的项目配置表就是最好的活文档。3.3 引脚复用查表比看寄存器更快第一次配置HC32F4A0引脚时我犯了经验主义错误以为可以直接翻STM32CubeMX自动生成的代码找规律。结果HC32F4A0没有对应的图形化配置工具或者至少不能完全替代数据手册。正确的做法是打开对应型号的数据手册找到GPIO功能复用列表然后按功能查引脚。举个例子你要找USART2_TX就别靠记忆硬背先确认自己的封装型号然后在复用表里搜索USART2_TX看它能出现在哪几个引脚上再根据PCB布局选一组不冲突的。这里有个很关键的原则所有串口接收引脚最好优先选带外部中断能力的如果暂时用不到DMA至少中断接收要可靠。另外提醒一句HC32F4A0的SDK头文件里关于GPIO复用功能的宏非常多直接用GPIO_FUNC_USART0_TX这种宏反而比写数字可靠。如果你在代码里看到一个类似GPIO_SetFunc(GPIO_PORT_A, GPIO_PIN_09, 6)的数字这个6就是复用功能编号具体含义必须去手册里查别乱猜。4. USART实战配置从基础收发到中断DMA4.1 最基础的轮询收发跑通第一路串口不管后面用不用中断我建议第一路串口先用轮询方式跑通这样方便验证时钟、引脚、波特率这些基础配置是不是对的。以USART0为例完整初始化流程是三步开外设时钟、配引脚复用、配串口参数。void Usart0_PollingInit(void) { stc_usart_uart_init_t stcUartInit; SYSCTRL_ClkEnable(SYSCTRL_CLK_USART0); GPIO_SetFunc(GPIO_PORT_A, GPIO_PIN_09, GPIO_FUNC_USART0_TX); GPIO_SetFunc(GPIO_PORT_A, GPIO_PIN_10, GPIO_FUNC_USART0_RX); memset(stcUartInit, 0, sizeof(stcUartInit)); stcUartInit.u32Baudrate 115200u; stcUartInit.enDataBits USART_DATABITS_8; stcUartInit.enParity USART_PARITY_NONE; stcUartInit.enStopBits USART_STOPBIT_1; USART_UART_Init(USART0, stcUartInit); USART_FuncCmd(USART0, USART_FUNC_RX, ENABLE); USART_FuncCmd(USART0, USART_FUNC_TX, ENABLE); }然后写两个最简单的收发函数。如果只是想打个字符出来可以像下面这样void Usart0_PutChar(uint8_t ch) { while (USART_GetStatus(USART0, USART_FLAG_TX_EMPTY) RESET) { // 等待发送寄存器空 } USART_WriteData(USART0, ch); } uint8_t Usart0_GetChar(void) { while (USART_GetStatus(USART0, USART_FLAG_RX_NOT_EMPTY) RESET) { // 等待接收数据 } return (uint8_t)USART_ReadData(USART0); }不要觉得轮询收发简单就没价值这个阶段能帮你确认三件事外设时钟有没有开、引脚复用选没选对、波特率配置是否合理。只要这三项有任何一项不对轮询模式马上会暴露问题。4.2 中断收发与空闲检测不掉数据的方案轮询收发跑通后再上中断。中断接收的好处是MCU不用一直在那边死等收到数据后自动进中断把数据放进缓冲区主循环再去做协议解析。使能接收中断的代码大致是USART_IntCmd(USART0, USART_INT_RX_NOT_EMPTY, ENABLE); NVIC_EnableIRQ(USART0_IRQn);中断服务函数里我习惯只做一件事把数据读出来放进接收缓冲区。不要在中断里做协议解析、字符串拼接、printf这类耗时操作否则高速串口很容易丢数据。volatile uint8_t rx_buf[512]; volatile uint16_t rx_len 0; void USART0_IRQHandler(void) { if (USART_GetIntStatus(USART0, USART_INT_RX_NOT_EMPTY) ! RESET) { uint8_t data (uint8_t)USART_ReadData(USART0); if (rx_len sizeof(rx_buf)) { rx_buf[rx_len] data; } } }这段代码有一个问题如果某帧数据中间间隔较长主循环怎么知道一帧数据已经收完了在串口通信里常见做法是把帧头帧尾设计好比如Modbus RTU就是靠3.5个字符时间间隔来判断帧结束。如果在HC32F4A0上做类似设计可以额外使能空闲中断。空闲中断表示接收线上已经空闲了一段事件大概率就是一帧数据到达完毕主循环这时再去解析缓冲区逻辑会清晰很多。当然如果SDK版本里的空闲中断API名称不一样使用前一定要查头文件。实在没有空闲中断也可以用定时器做超时判断效果类似只是CPU占用会稍微高一点。4.3 DMA收发大数据量才安全10路串口如果都靠CPU中断去搬运数据其实压力也不小尤其是波特率拉到921600甚至更高的时候。我的做法是对数据量大的串口启用DMA接收数据量小但实时性要求高的串口保留普通中断接收。DMA在HC32F4A0上的基本用法核心是把外设数据寄存器地址配成源地址把内存缓冲区地址配成目的地址然后指定传输长度DMA_SetSrcAddr(DMA_CH0, (uint32_t)USART0-DR); DMA_SetDstAddr(DMA_CH0, (uint32_t)rx_dma_buf); DMA_SetTransferCnt(DMA_CH0, RX_BUF_SIZE); DMA_EnableChannelInt(DMA_CH0); DMA_Start(DMA_CH0);这里有三个要注意的点。第一DMA通道不是随意选的要看DMA请求映射表不是所有USART都能同时复用同一个DMA通道。第二缓冲区长度要和DMA传输长度一致否则传输完一轮后没有及时重装后续数据就丢了。第三DMA模式和中断接收的缓冲区管理不太一样DMA到达设定长度会产生中断你不一定要把一帧数据整个收完才去处理可以配合半满中断数据量到一半时先处理前半段速度会快很多。DMA首战容易踩的坑是把源地址和目的地址写反了结果串口收到的全是0xFF或者乱码。排查时先看DMA的状态寄存器再检查地址配置不要一上来就怀疑波特率。5. 踩坑实录与排查心得5.1 波特率不对先查外设时钟搬到HC32F4A0后第一路串口的波特率问题让我折腾了一个晚上。现象很典型115200波特率的传感器数据发到电脑上全是乱码但用示波器抓TX引脚波形又确实有数据在跳变只是高低电平宽度不对。我一开始怀疑是时钟树配置里PLL倍频没有生效反复看了好几遍PLL配置频率都对。后来才意识到问题出在USART外设时钟源上我配置的波特率换算公式里用到的外设时钟和我实际给USART提供的时钟根本不是同一个。用公式表达一下波特率换算通常是UBRR PCLK / (16 * BAUD)或者8倍过采样时用UBRR PCLK / (8 * BAUD)如果我以为是120MHz的PCLK但USART实际挂在8MHz的HRC内部时钟上那么UBRR算出来的值会偏小实际波特率会偏大偏差超过8%串口必然乱码。正确做法是查芯片手册里USART挂在哪个时钟树下再确保SYSCTRL_ClkEnable和时钟源切换都正确。这个坑在STM32上很少遇到因为在STM32的默认配置里外设时钟树已经被CubeMX拖好了。5.2 发送卡死在等待标志位第二个坑出现在发送函数里。用轮询方式发送时如果连续发大量数据偶尔会卡死在while (USART_GetStatus(USART0, USART_FLAG_TX_EMPTY) RESET)这个循环里。一开始我以为是软件死循环后来仔细看数据手册才发现问题出在我发送完一包数据后想立刻关闭TX功能来省电。当时我的代码逻辑是发送完最后一批数据立刻调用USART_FuncCmd(USART0, USART_FUNC_TX, DISABLE);。看起来没什么问题但发送完最后一个字节后数据虽然写进了数据寄存器移位寄存器可能还没有把数据完全移出。这时候关闭TX功能等于把最后一个字节的半截信号直接切断了导致下一次发送状态标志没被正确唤醒卡死在等待里。解决办法是发送完一包后不要立刻关TX。如果确实需要低功耗等发送完成标志置位后再关闭while (USART_GetStatus(USART0, USART_FLAG_TX_COMPLETE) RESET) { // 等待移位寄存器移完 } USART_FuncCmd(USART0, USART_FUNC_TX, DISABLE);这里有一个经验**能等发送完成标志就尽量等不要只等发送数据寄存器空。**数据寄存器空只代表数据从CPU这边拿走了不代表物理线上的电平已经发完了。5.3 中断标志位清除顺序不能乱这个坑是在调第3路串口的时候发现的。当时接收中断很不稳定经常收一个字节之后就再也不进中断下一包数据只有复位后才能继续收。我用调试器查看中断状态寄存器发现RX标志位一直是置1的好像中断一直处于触发状态但程序没有进入中断服务函数。翻了半天SDK源码最终确认问题出在手动清标志。HC32F4A0的USART接收寄存器在读数据时通常会自动清掉RX标志位但我在中断服务函数里习惯性地调了一个清除中断标志的API先把这个标志清了再去读数据寄存器。结果数据还没读出来标志已经被清掉硬件判断这个中断已经被处理后续字节就只能等下一个中断事件触发。看起来是我以为清了其实清了等于白清。正确做法是按数据手册推荐的顺序先读数据寄存器让硬件自动清状态标志如果需要再手动清除其他状态位。千万别把STM32里先清标志再读数据的习惯带过来不同芯片的中断标志清法真的不一样。5.4 10路串口的中断优先级分配串口一多中断优先级分配就变成性能瓶颈。我把10路串口全部使能接收中断之后最开始只是随便设了几个优先级结果发现低优先级的串口偶尔丢字节。原因是高优先级中断长时间占用CPU时低优先级串口的接收缓冲被覆盖硬件FIFO又在溢出边缘疯狂丢数据。后来我按数据重要程度重新分配优先级。调试口和数据量最大的串口给抢占优先级0普通传感器串口给1或2不重要的扩展串口给3。同时把NVIC分组配置成抢占优先级数量比较多的一种NVIC_SetPriorityGrouping(NVIC_PRIORITY_GROUP_4); NVIC_SetPriority(USART0_IRQn, 0); NVIC_SetPriority(USART1_IRQn, 1); NVIC_SetPriority(USART2_IRQn, 1); // ...这里有个细节如果10路串口的接收数据最终都要交给同一个协议解析模块那中断里最好只收数据进环形队列优先级只影响收包实时性不影响解析正确性。真正怕的是高优先级中断里的协议处理函数调用太长时间导致低优先级串口FIFO溢出。所以中断服务函数越短越好能放到主循环做的事情别放到中断里做。5.5 常见问题速查表调试10路串口的过程中我把遇到的问题整理成了一张速查表后面排查效率提高不少。现象可能原因处理方式串口完全没反应外设时钟没开调用SYSCTRL_ClkEnable确认时钟树配置波特率乱码外设时钟源和波特率公式不一致查PCLK来源确认UBRR计算用的时钟首字节丢失初始化后立即发送发送状态未稳定初始化后加短延时或先读一次状态寄存器接收中断进不去RX中断没使能或标志位被错误清除检查USART_IntCmd注意读数据时自动清标志发送卡死等待了不正确的发送标志或提前关闭TX等待TX_COMPLETE不要发完立刻关外设DMA收到全0xFFDMA源地址/目的地址写反检查DMA地址配置先读DMA状态寄存器引脚复用冲突两个外设配置了同一个引脚查引脚复用表换用其他可用引脚调试器连不上串口或其他外设复用了SWD引脚先断开其他外设或用Connect Under Reset这张表其实不只在HC32F4A0上适用只要是类UART外设很多排查思路都是通用的。6. 调试工具与工作流建议6.1 逻辑分析仪才是串口调参神器多串口项目调试我强烈建议准备一台8通道以上的逻辑分析仪。串口助手只能看到某一根串口线发过来的数据但当系统里同时有10路USART在工作需要确认某路波形是否正确、波特率有没有偏差、引脚复用有没有冲突时逻辑分析仪一抓一个准。我有一次排查某路传感器偶发乱码串口助手看到的是半小时出一次乱码完全没法定位。后来用逻辑分析仪同时挂上传感器TX和MCU的RX引脚才发现那个传感器上电瞬间会有几十毫秒的电平抖动MCU在初始化时误收了一个字节。这个问题靠串口助手很难发现但逻辑分析仪一眼就能看出来。HC32F4A0的USART基本都是TTL电平可以直接用逻辑分析仪测量不需要加RS232转TTL模块。6.2 裸机状态机还是RTOS10路串口一多很多人会想上RTOS用每个串口一个接收任务的方式。这个思路没毛病但裸机方案在串口数据量不太大时反而更香。我的项目里大部分传感器的上报周期是100ms一包每包几十字节裸机中断接收加主循环按帧解析完全够用还省去了RTOS的任务调度开销。如果坚持上RTOS记得每一个串口的接收中断里用xQueueSendFromISR或者信号量通知对应任务不要在中断里直接做延迟或阻塞操作。尤其当10路串口同时涌入数据时RTOS调度频繁如果中断服务函数里出现阻塞整个系统的时间确定性会崩串口丢帧就是家常便饭。实际上我最后采用的是裸机超级循环的架构中断只负责把数据塞进环形队列主循环按优先级轮询各路队列解析出一帧完整数据后再分发到业务逻辑。这样代码量虽然比RTOS任务版本多一点但出问题好查复现概率也低。6.3 最后分享一个小技巧如果你在调试HC32F4A0的时候发现SWD连不上先别急着怀疑芯片坏了。绝大部分原因是引脚复用了可能是USART的TX/RX恰好和SWDIO、SWCLK共用也可能是调试口占用了启动配置引脚。我的做法是把调试串口固定在另外一组独立IO上确保它不占用SWD引脚。万一真遇到连不上的情况按住复位键点击下载选择Connect Under Reset通常能救回来。还有一个更实用的小技巧量产程序里也保留一路调试串口用跳线决定是否输出日志。这样在客户现场出现通信问题时不需要重新烧录程序只要把跳线插上就能通过日志定位是传感器协议问题还是MCU侧解析问题。这个习惯帮我省了很多出差成本。从STM32转到HC32F4A0最核心的收获不是多认识了几个外设API而是真正理解了一件事换MCU平台不能只图引脚兼容或者代码能编译过时钟树、中断、外设寄存器这些东西都得重新建立认知模型。10个USART确实好用但前提是把它当成一个新平台认真对待。如果你正准备做类似迁移我希望这些踩坑记录能让你少走几步弯路尤其是那10路串口规划好配置表之后真的一路通、路路通。
返回列表