GD32VF103 CAN外设实战:从初始化到波形调试的完整验证链路

发布时间:2026/9/10 0:18:07
GD32VF103 CAN外设实战:从初始化到波形调试的完整验证链路
简介面向RISC-V架构GD32VF103微控制器开发者的CAN通信实践工程以NucleiStudio工程形式演示CAN发送功能适合嵌入式入门开发者及需要熟悉工业总线通信的工程师。压缩包共100个文件以C源文件和头文件为核心涵盖芯片外设驱动库与CAN模块初始化、发送、中断处理等逻辑另含Makefile、链接脚本与工程配置方便移植和二次开发包体仅248KB体积精简。目前已有1900人学习下载是学习GD32VF103 CAN模块的高频参考范例。工程提供完整可编译的驱动代码读者可对照CAN2.0A/B标准理解标准帧与扩展帧的发送流程并结合串口或LED调试方法实际掌握波特率分频、消息缓冲区使用、错误状态检测等关键技术进而打通从RISC-V平台外设配置到CAN总线通信验证的完整链路对提升嵌入式实战能力很有价值。 第一次拿到GD32VF103这块板子我最想做的不是点灯而是把CAN跑通。原因很实在这是一颗RISC-V内核的MCU外设虽然号称与STM32F103兼容但寄存器细节、中断响应路径、固件库接口都不一样不亲手跑一发CAN收发心里始终没底。于是就有了这个gd32vf_can_test0工程——把CAN0配置好能自发自收也能和另一块板子正常对发的最小测试工程最后打包成zip留档。这篇文章就把这个工程从规划到落地的过程完整拆开为什么要这么测、CAN外设怎么配、波特率怎么算、波形怎么判断、问题怎么排。如果你是刚拿到GD32VF103开发板或者想快速验证一颗RISC-V MCU上的CAN外设这篇应该能帮你省下不少折腾时间。整个验证链路不复杂但每一环都有值得记录的细节。1. test0不是随便写的这个CAN测试工程到底验证了什么1.1 压缩包名拆解gd32vf、can、test0各代表什么看到gd32vf_can_test0.zip这个文件名基本就能猜出这是个什么工程gd32vf对应GD32VF103系列MCUcan是对应的CAN外设测试test0是第0版冒烟测试zip是打包分发格式。第0版的意思很清楚就是不追求功能完整先把最基本的数据收发打通。结合很多人都在搜的gd32vf can这个关键词说明这块板子的CAN外设是大家关注的重点。实际上GD32VF103是RISC-V内核的MCU它的外设与GD32F103基本对齐CAN控制器也是bxCAN这套设计但和STM32F103还是有不少差异。后面我会专门说差异在哪因为这是移植代码时最容易翻车的地方。1.2 冒烟测试的边界test0管什么不管什么test0要验证的是CAN0的外设时钟门控有没有打开寄存器能不能正常访问GPIO引脚复用是否正确PB8进、PB9出CAN控制器能不能初始化成功位时序参数是否被正确写入发送邮箱能否写入数据、能否触发发送接收FIFO能否收到数据、中断能否触发过滤器配置是否正确期望的ID能不能放行test0不验证的是长期跑通信的稳定性比如24小时丢帧率错误帧计数与恢复策略多节点组网下的仲裁行为电磁干扰环境下的信号完整性一致性测试也就是位时间、采样点、终端电阻这些产线级验证项目把这些边界划清楚很重要。我在实际工作里见过不少同事拿一个冒烟测试的结果去推断产线性能最后被现实打脸。test0就是test0它的使命是告诉你这条路能走通而不是这条路能跑多快、多稳。1.3 为什么必须先分层验证我的测试计划分三层每一层有明确的通过标准第一层内部回环模式Loopback验证控制器内部的发送到接收路径不依赖外部硬件第二层两块板子正常模式互联验证物理层收发器和总线连接第三层示波器和CAN分析仪观察波形验证信号质量和参数匹配分层的逻辑很朴素如果第一层都过不了说明是软件配置问题不要急着怀疑线缆如果第一层过了第二层过不了大概率是硬件链路如果前两层都过了但波形难看那就是参数和布局问题。用这个顺序排查效率高很多不会把时间浪费在互相甩锅上面。2. GD32VF103的CAN外设与最小硬件接线引脚、收发器、位时序一次理清2.1 bxCAN控制器和STM32F103的差异在哪里GD32VF103的CAN控制器在功能层面和STM32F103的bxCAN非常接近都支持标准帧、扩展帧、远程帧有3个发送邮箱和2个接收FIFO。但注意这只是外设IP逻辑兼容不代表代码可以直接迁移。首先是固件库差异。你从STM32工程里复制过来的CAN_InitTypeDef、CAN_Transmit这些代码在GD32固件库里根本不存在。GD32的外设库用的是can_parameter_struct、can_init、can_message_transmit这套命名而且部分结构体字段的枚举值比如CAN_BT_BS1_5TQ和STM32的BXCAN_BS1_5TQ也不一样。其次是中断路径差异。STM32用NVIC管理中断GD32VF103用ECLIC也就是芯来的增强型中断控制器。中断使能函数、向量表组织方式都不一样。如果照搬STM32的中断初始化代码编译可能没问题但中断大概率不触发。这个坑我后面专门复盘。2.2 最小接线PB8/PB9、收发器与120Ω终端电阻GD32VF103的CAN0默认引脚是PB8RX和PB9TX不需要重映射就能用。如果你想用PD0/PD1才需要配置重映射但test0没必要给自己找这个麻烦。我的测试板电路大概是这样的PB8/PB9连接到一颗CAN收发器比如TJA1050收发器转出差分信号到CANH和CANL两条线总线两端各并联一个120Ω终端电阻。这里的终端电阻不是可选项是必须项它用来匹配传输线阻抗抑制信号反射。接线时最容易出错的三件事CANH和CANL接反。差分信号反了节点收到的一定是错误帧甚至直接进Bus Off。忘了共地。两板之间电源不共地CANH/CANL的参考电位不一致隐性电平都对不上接收端拿到的全是乱码。终端电阻只放了一端。总线没匹配高速率下信号反射大波形会有明显振铃。用万用表量总线两端的等效电阻是最快的硬件检查手段在断电、断开所有有源节点的情况下CANH到CANL之间应该量到约60Ω。这个值是两个120Ω终端电阻并联的结果如果量到120Ω说明有一端没接电阻如果量到几欧姆说明总线上多并了东西。顺便说一下网上经常有人问的CAN和RS485能不能共用一条差分线或者能不能做复用接口电路。我的建议是物理上可以设计切换电路但不要指望一套前端直接兼容两种协议。CAN是2.5V/3.5V对1.5V的电平体系RS485是A/B差分电平两者共模范围、收发器特性都不同。非要复用的话至少需要独立的收发器和切换控制而不是简单地把两根线并在一起。这个思路对硬件设计者来说比直接抄一个复用电路更靠谱。2.3 位时序与波特率推导顺带说清SJW的作用CAN总线的波特率不是随便写个数字就行的。它由外设时钟、预分频、时间段共同决定每个时间段又能细分成若干个时间量子Tq。位时间由四段组成同步段固定1Tq传播时间段包含在BS1里然后是相位缓冲段1BS1和相位缓冲段2BS2。采样点位置的计算公式是采样点百分比 (1 BS1) / (1 BS1 BS2) × 100%这个值很关键工程上一般推荐75%~87.5%。举个例子GD32VF103的APB1时钟通常配置为36MHz目标是500kbps预分频 prescaler 9BS1 5TqBS2 2Tq总位时间 1 5 2 8Tq波特率 36MHz / (9 × 8) 500kbps采样点 (1 5) / 8 75%这个参数组合是我在test0里用的。有些人图省事直接套STM32例程里的BS13、BS24采样点只到50%虽然也能通但总线抗干扰和抗时钟偏移能力明显差一截。位时序里还有一个SJWSynchronization Jump Width也就是热词里大家都在搜的同步跳跃宽度。SJW不直接参与波特率计算它决定的是重同步时相位补偿的最大范围。总线上的节点各有各的时钟源振荡器频率总有偏差加上温漂节点的位时间会慢慢漂移。接收方靠每个下降沿做同步当发现边沿比预期来得早或晚就会在相位缓冲段里吃掉或吐出一些时间这个最大调整量就是SJW。SJW设太小容错能力就很弱两个节点时钟偏差稍大就不断出错设太大采样点位置会受干扰位影响跳来跳去。test0这种简单场景取1Tq完全够用多节点组网可以放宽到2~4Tq。BAUD、SJW、BS1、BS2这四组参数建议先把采样点和位时间算清楚再谈SJW余量顺序别搞反。2.4 过滤器与ID匹配ACCCode/ACCMask的配置逻辑热词里有一条can通信acccode与accmask这俩概念对应的就是GD32固件库的filter_list和filter_mask字段。过滤器的作用是让接收方只收自己关心的ID减轻CPU负担。ACCCodefilter_list是期望接收的ID模式ACCMaskfilter_mask是掩码掩码位为1表示对应ID位必须与匹配值一致掩码位为0表示这一位不关心。举个例子如果我要只收标准帧ID等于0x123的报文在32位过滤模式下can_filter_parameter_struct filter; filter.filter_number 0; filter.filter_mode CAN_FILTERMODE_MASK; filter.filter_bits CAN_FILTERBITS_32BIT; filter.filter_fifo CAN_FIFO0; filter.filter_list_high 0x123 5; filter.filter_list_low 0x0000; filter.filter_mask_high 0x7FF 5; filter.filter_mask_low 0x0000; can_filter_init(filter);这段代码有个特别容易踩的坑标准帧ID在32位寄存器里的位置不在最低位需要左移5位。很多初学者直接写filter_list_high 0x123结果滤了半天什么都收不到还以为是硬件坏了。调试阶段我建议先把掩码全部设0也就是不过滤任何ID先让通信跑起来再一步步收敛掩码。这样做的好处是能明确区分问题到底是出在物理链路还是出在过滤器逻辑。掩码排查比盲猜快得多。3. CAN初始化代码的每个字段从波特率推导到过滤器掩码3.1 开发环境与工程骨架用GD32固件库不要直接搬HAL写这个test0工程我用的开发环境是Nuclei StudioEclipse系配合GD32VF103的标准外设库。工程骨架很简单一个main.c放主循环一个CAN业务文件包含GPIO、CAN初始化、发送、接收回调一个串口打印模块用来把调试信息吐出来一个延时函数用于测试节奏控制有人可能会问能不能用STM32的HAL库直接改答案是别费这个劲。GD32VF103虽然是RISC-V内核但它的固件库是从GD32F103那边移过来的寄存器位定义、外设基址、库函数命名都是自成一派。HAL库庞大的抽象层在RISC-V上多数不能直接编译跨内核交的学费远大于自己写几个函数。3.2 GPIO与时钟为什么PB8用上拉输入、PB9用复用推挽先开时钟再配引脚这个顺序别反过来否则寄存器写入无效。void can_gpio_config(void) { rcu_periph_clock_enable(RCU_GPIOB); rcu_periph_clock_enable(RCU_CAN0); gpio_init(GPIOB, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_8); gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9); }PB8是CAN0的RX配置为输入上拉模式。CAN控制器内部对RX引脚没有上拉需求但GPIO复位后默认可能是浮空输入浮空输入在总线上没有节点发送时会读到不确定电平。测试阶段为了避免干扰还是加上拉更稳。PB9是CAN0的TX配置为复用推挽输出引脚信号由CAN控制器接管这里不能配置成普通GPIO输出否则CAN控制器没法把TQ时序送到引脚上。3.3 CAN初始化参数每一个字段都拆开讲CAN初始化是整个工程的核心。GD32固件库把初始化参数统一放在一个结构体里can_parameter_struct can_parameter; can_parameter.working_mode CAN_NORMAL_MODE; can_parameter.resync_jump_width CAN_BT_SJW_1TQ; can_parameter.time_segment_1 CAN_BT_BS1_5TQ; can_parameter.time_segment_2 CAN_BT_BS2_2TQ; can_parameter.prescaler 9; can_parameter.time_triggered_mode DISABLE; can_parameter.auto_bus_off_recovery ENABLE; can_parameter.auto_wakeup ENABLE; can_parameter.auto_retrans_transmit ENABLE; can_parameter.rec_fifo_overwrite DISABLE; can_parameter.trans_fifo_order DISABLE; can_init(CAN0, can_parameter);逐个说一下字段working_mode工作模式。回环自测时用CAN_LOOPBACK_MODE正常通信用CAN_NORMAL_MODE。test0是从回环开始测的后面切回正常模式。resync_jump_width、time_segment_1、time_segment_2、prescaler这四个参数决定波特率和采样点前面已经算过这里直接用推导结果。time_triggered_mode时间触发模式普通CAN用不到关闭。auto_bus_off_recovery总线关闭后自动恢复。测试阶段必须开否则一次Bus Off就把控制器锁死了还得手动复位很耽误事。auto_retrans_transmit发送出错后自动重发。开这个省心发送函数只管往邮箱里写控制器自己保证总线仲裁失败后的重发逻辑。rec_fifo_overwrite接收FIFO溢出时是否覆盖旧数据。我建议关掉宁可丢新帧也不能把旧帧覆盖掉方便排查问题。trans_fifo_order发送邮箱的发送顺序按ID优先级还是按写入顺序。test0不关心默认关闭。初始化之后还要使能接收中断。注意这里有两个使能动作一个是内核级中断使能一个是CAN外设级中断事件使能两个都要做nvic_irq_enable(CAN0_RX0_IRQn, 0, 0); can_interrupt_enable(CAN0, CAN_INT_RX_FIFO0_NOT_EMPTY);第一行是使能ECLIC中断。不同固件版本函数名可能不同有些叫eclic_irq_enable有些在nvic_irq_enable里做了封装以你的库头文件为准。3.4 发送与接收中断RISC-V中断向量表与Cortex-M的差异发送部分我用一个简单封装方便在主循环里反复调用uint8_t can_test_send_standard_frame(void) { can_trasnmit_message_struct transmit_message; transmit_message.tx_sfid 0x123; transmit_message.tx_efid 0; transmit_message.tx_ff CAN_FRAME_STANDARD; transmit_message.tx_ft CAN_FT_DATA; transmit_message.tx_dlen 8; transmit_message.tx_data[0] 0x01; transmit_message.tx_data[1] 0x02; /* 其他数据位同理 */ return can_message_transmit(CAN0, transmit_message); }值得一提的是GD32固件库里这个结构体名拼写是can_trasnmit_message_structtransmit少了一个字母。这是官方固件库的历史遗留不是我的拼写错误看到这个命名不要慌照着固件库头文件写就行。接收中断写法void CAN0_RX0_IRQHandler(void) { can_receive_message_struct receive_message; can_message_receive(CAN0, CAN_FIFO0, receive_message); if (receive_message.rx_sfid 0x123) { /* 通过串口打印收到的8字节数据 */ } }这里要特别提醒RISC-V的ECLIC中断是向量模式中断服务函数名必须和启动文件里的向量表完全一致。很多从Cortex-M转过来的同事把ST的HAL_CAN_RxFifo0MsgPendingCallback搬过来当然进不了中断。在GD32VF103工程里你就得老老实实写CAN0_RX0_IRQHandler而且确认启动文件里对这个向量有定义。这个细节决定了整个中断方案是否成立。主循环的逻辑很朴素每秒钟发一帧标准帧while (1) { can_test_send_standard_frame(); delay_1ms(1000); printf(CAN test0 send ok\r\n); }接收端每收到一帧就在中断里把数据打印出来同时维护一个自增计数。测试通过的标准很简单连续收发100次计数完全一致且无错误。4. 三层测试台内部回环、双板互联、示波器波形判断4.1 第一层内部回环模式验证控制器自身测试第一步把working_mode改成CAN_LOOPBACK_MODE重新编译下载。回环模式下CAN控制器内部把发送输出直接接到接收输入不经过外部收发器也不依赖CAN_TX/RX引脚的实际电平。我的目的是先把软件链路跑通寄存器初始化、发送邮箱、发送调度、接收FIFO、中断回调、串口打印。这个阶段如果出问题百分之百是配置问题不用去动螺丝刀。实际测试中我在串口看到的现象是发送计数和接收计数严格同步增长每一秒一进一出。到这里第一层通过。虽然这看起来很简单但这是整个测试台的地基如果这层都过不去后面所有环节都无从谈起。4.2 第二层双板正常互联验证物理层回环通过后把working_mode改回CAN_NORMAL_MODE接上两块板子。A板作为发送方B板作为接收方然后反向再测一遍。双板互联最值得花时间检查的是硬件CANH接CANH、CANL接CANL别交叉两块板子的GND连在一起总线两端各有一个120Ω终端电阻如果这三点漏了任何一个都会出现代码没问题但就是不通的鬼现象。我见过有人排除了一整天软件问题最后发现是CANH和CANL接反了这种低级错误在疲劳状态下特别容易犯所以第一步用万用表量线远比看代码有效。4.3 第三层示波器看波形从显隐性电平到位宽抖动前两层验证的是通不通第三层验证的是好不好。这也是热词里不少人问的如何通过CAN总线波形判断通信的好坏。先说怎么接线。双通道示波器一个探头接CANH对GND另一个探头接CANL对GND用数学运算通道做CANH减CANL得到差分波形。或者直接把通道二接到CANL通道一接CANH用示波器的A-B模式。千万别用普通探头直接跨接在CANH和CANL之间测差分除非你的示波器和探头都是隔离的否则共地问题会干扰测量甚至损坏设备。以TJA1050这类5V供电收发器为例隐性电平CANH和CANL都约2.5V差分电压约0V显性电平CANH升到约3.5VCANL降到约1.5V差分电压约2V判断波形好坏我主要看四个点差分幅值是否达标。正常情况下显性差分幅值应在1.5V到3V之间。如果明显偏低可能总线上电阻太多、线缆过长、或者收发器驱动能力不足。位宽是否准确。500kbps的参数下1个位时间是2us。用示波器光标量连续几个显性位再平均误差应该在纳秒级。如果位宽明显偏大或偏小拿反向公式就算出实际波特率和代码参数对照。边沿是否干净。波形上升沿和下降沿应该陡峭、单调没有明显振铃。振铃严重说明终端电阻缺失或位置不对再不就是线缆分叉太多。位时间内波形平台是否稳定。如果显性位中段电压往下塌说明总线负载过重或线径太细。有一个细节很多人忽略CAN总线的仲裁过程也反映在波形上。两个节点同时发送时ID小的节点输出显性位会把ID大的节点输出的隐形位覆盖掉。所以当你看到报文的起始段有一个异常的、比正常完整位更窄的显性脉冲然后紧跟着一个完整报文这不一定是故障很可能是两个节点发生了一次仲裁。搞清楚这个再看波形就不会把正常的仲裁误判成干扰。4.4 波形质量判断表我把常见现象整理成一张表方便对照排查现象可能原因检查方法没有任何波形GPIO未配置/收发器未供电/没有节点在发查引脚配置、收发器电源、发送函数调用差分幅值偏小总线终端电阻过多/线缆过长/收发器驱动弱万用表量60Ω并联电阻换短粗线1位时间偏大或偏小预分频或BS1/BS2设置不对反算波特率对齐两端参数边沿振铃严重缺少终端电阻/走线分叉/接头松动总线两端各加120Ω检查连接显性位平台塌陷负载过重/线径过细/供电不足减轻总线负载换优质双绞线到这里三层测试全部通过的结论是这个test0工程在GD32VF103上验证了CAN控制器的功能、物理层的连接、波形质量都没有问题。接下来才敢放心在这个基础上写应用层。5. 踩过的坑和排查链路从接收不到数据到Bus Off再到RISC-V中断不触发这个工程虽然叫test0但我在调的时候几乎把常见的CAN坑都踩了一遍。下面四个是典型的、可复现的排查链路每个我都按现象-排查-根因-解决的顺序写。5.1 排查链路一发送成功但接收端没有任何数据现象发送函数正常返回说明报文已经进入发送邮箱但接收端串口一直没有打印。第一步先确认接收端的总线上有没有波形。拿示波器挂在接收端的CANH和CANL上如果能看到波形说明物理层没问题问题在接收端软件。第二步查过滤器和中断。我当初就是把掩码写错了标准帧ID左移的位段搞错导致过滤器把0x123这个ID给滤掉了。排查方法很简单先把掩码全设0也就是不过滤任何ID。如果能收到数据那就证明问题只在掩码配置回头对着每个位段重新检查。第三步查中断有没有触发。如果查询方式能在main循环里收到数据但中断方式收不到那就进入5.4的RISC-V中断问题。5.2 排查链路二回环全通双板一接上就哑火现象loopback回环测试完美通过切到normal模式接双板两边都收不到数据。这个现象百分之八十出在物理层。我的排查顺序是用万用表量两块板子的CANH之间、CANL之间的导通性确保线缆正确且没有虚接。断电量CANH到CANL之间的电阻。正常应该在60Ω左右。如果量到120Ω就是只接了一端终端电阻如果量到几欧姆就是总线上多并了电阻如果是开路两端都没接终端。确认两板共地。拿万用表量两块板子的GND之间电压应该在毫伏级。如果量出1V以上的压差赶紧补一根地线。我那次就是终端电阻只放了一块板补上120Ω之后立竿见影波形瞬间干净了。这个案例告诉我回环模式因为不经过物理层所以对硬件状态完全无感一旦切到正常模式物理层的每个小毛病都会被放大。5.3 排查链路三错误帧与Bus Off波形上其实有征兆现象双板好不容易通了跑一段时间后发送端报Bus Off通信中断。这里先说结论总线上一旦出现连续6个显性位就是错误帧的标志示波器上看得清清楚楚。CAN控制器的发送错误计数累积到256就会进入Bus Off状态这是bxCAN的自我保护机制防止故障节点持续污染总线。排查链路读错误状态寄存器确认是发送错误计数超限还是接收错误计数超限。用示波器对比两边的实际位宽。两块板子的晶振精度不同如果代码里的分频、位时序不一致实际波特率就可能差出百分之零点几。短时间看不出来跑久了重同步跟不上就会开始报错。检查SJW设置。如果总线上的时钟偏差确实略大把SJW从1Tq放宽到2Tq或4Tq重同步的容错范围更大能吸收更多时钟抖动。我在排查时发现两块板子的代码虽然都是500kbps但一块用的是BS13、BS24另一块用的是BS15、BS22采样点差很多在长距离通信下就露馅了。统一参数后问题消失。这个案例很典型两边都以为自己配置的是500kbps但实际位时序差异很大。5.4 排查链路四RISC-V环境里中断不进ISR现象使用轮询方式能正常接收数据但一旦改成中断方式接收FIFO明明有数据ISR就是不执行。这类问题在从Cortex-M转RISC-V的工程师身上出现频率很高。我在排查时按这个顺序确认中断服务函数名。进到启动文件或者中断向量表定义里搜CAN0_RX0看实际向量表里注册的函数名是什么。我见过有工程里把函数命名为CAN0_RX_IRQHandler少一个0结果向量表的对应项是弱符号中断自然没入口。确认中断使能函数真的执行了。有时候启动代码里自己又关了一次全局中断或者ECLIC的优先级配置不对导致中断被一直挂起。确认CAN外设中断事件是否使能。有些版本要同时使能外设级中断事件CAN_INT_RX_FIFO0_NOT_EMPTY和内核级中断两层都打开才算完。RISC-V的ECLIC和ARM的NVIC最大的区别在于NVIC的中断入口和启动文件自动关联习惯成自然ECLIC的向量表有时需要手动确认函数符号是否有效。养成在工程里搜一遍向量表再写ISR的习惯能省下一下午。5.5 给test0留个升级路径下一步可以加什么test0跑通只是起点后面的扩展路径我很明确数据链路层测试加入扩展帧、远程帧验证不同帧类型下的收发多节点仲裁测试至少3个节点不同的ID观察仲裁过程中ID优先级是否生效错误处理测试故意制造波特率不匹配验证Bus Off恢复逻辑采样点优化对不同线缆长度、不同波特率做采样点扫描找到最稳定参数一致性测试如果是要走产线的产品还得用专业CAN一致性测试设备覆盖物理层、位时序、协议层比test0要严格得多热词里那个can一致性测试其实就是产线级验证的一部分。test0能告诉你这个芯片能不能用CAN一致性测试能告诉你这条生产线出来的每一块板CAN都合格两者侧重不同但都需要从这样一个最小测试工程起步。最后说一个我个人很坚持的小习惯像gd32vf_can_test0这种工程我会在工程目录里单独留一个notes.md把示波器截图、波特率参数、板子跳线状态、当时用的收发器型号全部记下来。过几个月有人问这个参数当时怎么定的我能直接翻笔记复现而不是盯着代码猜。这个习惯帮我省过很多次返工也推荐给你试试。本文还有配套的精品资源点击获取