SCL PID功能块源码拆解:抗饱和、无扰切换与前馈改造

发布时间:2026/11/3 12:43:16
SCL PID功能块源码拆解:抗饱和、无扰切换与前馈改造

简介:面向西门子博途自动化工程师与PLC学习者,这是一份用SCL编写自定义PID功能块的源代码文档,适合需要理解反馈控制实现、或在项目中复用PID逻辑的中级读者参考。文档共1个doc文件,压缩包约19KB,篇幅精简,核心为FUNCTION_BLOCK「PID」的变量声明与结构定义。源码按输入、输出、输入输出及内部变量分区,涵盖COM_RST、MAN_ON、P_SEL/I_SEL/D_SEL、CYCLE、SP_INT、PV_IN、GAIN、TI、TD、DEADB_W、LMN_HLM/LLM等参数,并给出LMN、LMN_PER、LMN_P/I/D及PV等输出,便于对照理解比例、积分、微分分量与限幅、初始化、手动切换的处理脉络。功能块启用S7_Optimized_Access,可帮助读者留意优化访问下的编程差异。已有4187人学习,适合作为博途PID自编功能块的结构模板与参数调试参考,读者可据此梳理控制参数整定思路,并迁移到温度、流量等闭环控制场景。

1. 一块温度回路改不动的那个下午

去年帮一个做热处理设备的朋友收拾电气柜,他们原来那路温度控制是用组态向导生成的 PID 块,参数只能在界面里点点鼠标、拖拖滑块,结果调试时想把输出上限锁死在 85%,发现块内部已经把逻辑焊死了,进不去也出不来。当时就在想,如果手上有一块纯 SCL 写的、变量和步序都摊在明面上的 PID 功能块,很多这种“拧螺丝”的活儿根本不会卡住。

这份资源给的就是这么一块 PID 功能块源代码,从 VAR_INPUT 到 VAR 静态区一共六十来个变量,把模式切换、设定值通道、整定参数、标定因子、抗积分饱和状态全部铺出来,BEGIN 之后留白交给实现者。它适合两类人:一类是刚接手 PLC 控制回路、想知道 PID 每个引脚到底影响哪一步的计算的新手;另一类是嫌自带块不够改、想自己接前馈或者加限幅策略的老手。下面我按“接口先看清楚 → 算法挨个走一遍 → 上机跑通 → 踩过的坑 → 改造入口在哪”的顺序拆。

2. 接口拆解:把 35 个引脚分成四组,才看得懂这块 PID 的设计边界

刚拿到源码,第一眼容易被 35 个对外引脚吓到,其实按“谁决定它工作、谁喂它数据、谁改它手感、谁校正它输出”四组一分,结构立刻就清楚了。这块的设计思路挺典型的:模式开关不跟整定参数混在一起,标定量单独成组,输出侧把原始调节值和物理量出口分开。下面按输入、输出、静态三块铺开。

2.1 输入侧四组变量:模式开关、设定值通道、整定通道、标定通道

先看模式开关这组:COM_RST是完全重启,MAN_ON切手动,PVPE_ON选外设过程变量通道,P_SEL / I_SEL / D_SEL三路分量使能,INT_HOLD保持积分,I_ITL_ON把积分量拉到一个初值。这八个 Bool 决定了下一拍走哪条计算分支,是整块的控制流开关。很多人第一次看到P_SEL / I_SEL / D_SEL会以为它们是“三选一”,其实这里是“三选 N”,关掉某一路只是把该分量清零、不进合成,不会影响其它两路的计算链路,这个认知差异后面会单独说。

设定值通道就三根线:SP_INT内部设定值、PV_IN过程变量输入、PV_PER外设过程变量。三者不是并列关系,PVPE_ON选了PV_PER之后PV_IN就不再进入计算。PV_IN一般接的是已经归一化的工程量,PV_PER接的是 0..27648 的原始字,两块通道的标定系数共用同一组PV_FAC / PV_OFF,但归一化处理必须在进块之前或者进块之后自己补上,这一块没有替你做。

整定通道是四个参数:GAIN比例增益、TI积分时间、TD微分时间、TM_LAG微分滞后。注意这里的单位是 Time 类型,调用时直接给T#5S这种格式,不要塞纯数字,否则在 SCL 里会报类型不匹配。GAIN是无量纲的倍数,不是百分比,不要拿 HMI 上 0..100 的百分数直接喂进去。

标定通道负责把输入和输出从“块内单位”映射到“物理单位”:PV_FAC / PV_OFF是过程变量因子和偏移,LMN_FAC / LMN_OFF是调节值因子和偏移。一般做法是PV_IN = 原始值 * PV_FAC + PV_OFF,输出那边反过来做。LMN_HLM / LMN_LLM单独放,是因为它们不是标定量,是限幅值,在合成之后立刻生效。

组别关键变量影响的计算步骤
模式开关COM_RSTMAN_ONP_SEL/I_SEL/D_SELINT_HOLDI_ITL_ON选择本拍走哪条分支
设定值通道SP_INTPV_INPV_PER决定偏差来源
整定通道GAINTITDTM_LAG决定三路分量的幅度和相位
标定通道PV_FAC/PV_OFFLMN_FAC/LMN_OFFLMN_HLM/LMN_LLM把块内值和工程量互相换算

2.2 输出侧:双出口 LMN 与 LMN_PER 加三路分量单独出

输出有两路调节值,LMN是浮点工程量,LMN_PER是给外设的 Word 类型,两者是同一份数据的不同表达,不是互相独立的两个结果。常见做法是把LMN按LMN_FAC折算后再乘 27648 送给LMN_PER,这样上位机看浮点、执行机构看整型,两块口径才对得上。

QLMN_HLM和QLMN_LLM是两个 Bool 标志,在合成之后立刻根据限幅结果置位,它们是给程序逻辑用的,不是给操作员看的。比如做联锁的时候可以直接拿QLMN_HLM去触发降负荷,比在 HMI 上盯着数字判断要可靠。

LMN_P / LMN_I / LMN_D三个分量单独引出来,是这块最值得保留的设计。很多项目调 PID 调不动,就是因为看不到内部三路各自贡献了多少,只能凭输出波形猜。有了这三个引脚,把三条趋势曲线叠在一起看,谁在拉后腿一眼就知道。PV是经过标定后的当前过程值,方便在 HMI 上直接显示。

2.3 静态区三分法:上一拍状态、浮点补偿量、抗饱和状态各管一段

静态区有二十多个变量,看名字乱,按用途分也就三类。第一类是“上一拍状态”,比如sInvAlt上一拍偏差、sLanteilAlt上一拍积分量、sLmn上一拍输出,这三块是给微分和抗饱和逻辑做前后拍对比用的。

第二类是浮点补偿量,sRestint和sRestDif两个名字里带 Rest 的变量,专门用来吸收 S7 Real 精度不够带来的尾差。SCL 里的 Real 是 32 位浮点,累加多了会漂,所以常见做法是每拍只把精度溢出的一小段存回来,下一拍再补进去,这跟“Kahan 求和”是一个思路,只不过这里只用了最简单的一阶补偿。

第三类是本拍的中间量:Istwert是当前过程值、ErKP是偏差、gf是喂给三路计算的输入、Panteil / Ianteil / Danteil是三路分量、dLmn是合成量、rCycle / rTi / rTd / rTmLag是把 Time 类型转成浮点后的值。转换这一步很有必要,SCL 里 Time 不能直接参与四则运算,必须TIME_TO_REAL一下,否则在编译期报类型错。

3. 算法链路:一次扫描里,PV 如何走完“标定—偏差—三分量—限幅”的全程

这块源码的 BEGIN 之后是空的,但变量命名已经把每条分支标出来了。要让它跑起来,需要按 SCL 的顺序把步骤补全。下面按一拍扫描的执行顺序走,每一步都给代码和参数说明,这是把这块跑通的最短路径。

3.1 周期入口:完全重启、手自动切换与 PV 通道选择

第一段是进门逻辑,COM_RST为真时所有历史量归零,MAN_ON切手动,PVPE_ON选 PV 通道。这一段不涉及具体算法,但顺序不能乱,必须先清状态再判断手自动,否则手自动切换的瞬间会读到上电残留值。

// 完全重启:清历史量、标志位和错误信号 IF COM_RST THEN sInvAlt := 0.0; sLanteilAlt := 0.0; sRestint := 0.0; sRestDif := 0.0; sLmn := 0.0; sbArwHLmOn := FALSE; sbArwLLmOn := FALSE; ER := 0.0; END_IF; // 过程变量通道选择与外设字归一化 IF PVPE_ON THEN // 27648 对应满量程 100%,按项目实际量程替换 100.0 Istwert := WORD_TO_INT(PV_PER) * 0.01; ELSE Istwert := PV_IN; END_IF; // 标定:因子乘、偏移加 Istwert := Istwert * PV_FAC + PV_OFF; PV := Istwert;

这一段里COM_RST的复位是持续复位的,不是边沿触发,只要它保持 TRUE,历史量每拍都会被重新清一次。所以 HMI 上“复位”按钮一般做成点动,按一下马上弹回来,不要做成开关。PVPE_ON分支里乘以 0.01 是把 0..27648 折算成 0..100 的百分数,是否乘这个系数取决于PV_FAC有没有把量程折算进来,两者只选其一,不要重复乘。PV的输出是给 HMI 看的中间结果,不影响下一步计算用的Istwert,这两个量在代码里是同一个值,只是名字不同。

3.2 偏差与死区:为什么先减 SP_INT 再做 DEADB_W

偏差计算是整块算法里最容易出错的一步,因为符号方向直接决定控制器是正作用还是反作用。这份源码的默认约定是ErKP = SP_INT - Istwert,也就是“测量值低于设定值时偏差为正”。如果要反过来,把SP_INT - Istwert改成Istwert - SP_INT就行,但改了之后 P 和 I 分量的符号也会跟着翻,所以通常的做法是保持偏差方向不动,只把GAIN给一个负值。

// 偏差计算 ErKP := SP_INT - Istwert; // 死区处理:|偏差| <= DEADB_W 时按零处理,避免在设定点附近来回抖 IF ABS(ErKP) <= DEADB_W THEN gf := 0.0; ELSE // 死区外保留原偏差,让控制器保持原来的响应 gf := ErKP; END_IF;

死区这一段的常见实现有两种:一种是像上面这样只做“削零”,一种是“削零后再减去 DEADB_W”。前者更保守,控制器在死区内输出完全静止;后者会让控制器在死区边界上带一点残余偏差,实际调试里更容易稳住。DEADB_W单位跟PV_IN一致,接温度就是摄氏度,别填成百分比。

3.3 比例与积分:抗饱和的两道闸门与积分保持

比例分量最简单,但GAIN的符号和P_SEL的联动方式会带来一点麻烦。P_SEL关掉时Panteil必须清零,不能保留旧值,否则切回来的瞬间会吃到一个陈旧的比例分量。积分分量要复杂得多,它涉及积分时间、周期、保持、初值四件事,抗饱和还要单独处理。

// 比例分量 IF P_SEL THEN Panteil := GAIN * gf; ELSE Panteil := 0.0; END_IF; LMN_P := Panteil; // 积分分量:先算保持和初值,再算增量 IF I_SEL THEN IF INT_HOLD THEN // 保持:沿用上一拍积分量,不累加 Ianteil := sLanteilAlt; ELSIF I_ITL_ON THEN // 初始化:直接拉到给定初值 Ianteil := I_ITL_VAL; ELSE // 正常累加:增量 = GAIN * (周期/积分时间) * 偏差 IF rTi > 0.0 THEN Iant := GAIN * (rCycle / rTi) * gf; Ianteil := sLanteilAlt + Iant + sRestint; // 把尾差存下来,下一拍补回去 sRestint := (sLanteilAlt + Iant) - Ianteil; END_IF; END_IF; // 第一道闸门:条件积分,超限就停累加 IF (Ianteil > LMN_HLM AND gf > 0.0) OR (Ianteil < LMN_LLM AND gf < 0.0) THEN Ianteil := sLanteilAlt; END_IF; ELSE Ianteil := 0.0; END_IF; LMN_I := Ianteil;

rTi > 0.0这个判断是必须的,TI填 0 相当于取消积分作用,如果不判就会除零。rCycle是采样周期,从CYCLE用TIME_TO_REAL转过来,单位要统一成秒。抗饱和这里用的是“条件积分法”,输出已经到上限、偏差还在继续往同方向推的时候,积分项停在上一次的值不动,这叫“退饱和”。比硬性截断积分输出要柔和,比反向计算法要简单。注意INT_HOLD和I_ITL_ON是互斥的,同时为真时按INT_HOLD优先,代码里要放在前面判断。

3.4 微分与输出合成:TM_LAG 真正作用是给微分加一阶滞后

微分分量在工程里是最容易搞出事的,纯微分对噪声的放大几乎是线性的,采样里稍微带一点毛刺,LMN_D就能抖出好几倍的幅度。TM_LAG就是用来压这个毛刺的,它给微分项加一个一阶低通,时间常数越大,微分越平滑但相位滞后越明显。

// 微分分量 IF D_SEL THEN IF rCycle > 0.0 THEN // 原始微分 = GAIN * (微分时间/采样周期) * 偏差变化量 Danteil := GAIN * (rTd / rCycle) * (gf - sInvAlt); ELSE Danteil := 0.0; END_IF; // 一阶滞后滤波 IF rTmLag > 0.0 THEN Danteil := Danteil * (rTmLag / (rTmLag + rCycle)) + sRestDif; sRestDif := Danteil * (rCycle / (rTmLag + rCycle)); END_IF; ELSE Danteil := 0.0; END_IF; LMN_D := Danteil; // 输出合成:手动优先,叠加干扰变量 IF MAN_ON THEN sLmn := MAN; ELSE sLmn := Panteil + Ianteil + Danteil + DISV; END_IF; // 限幅 IF sLmn > LMN_HLM THEN sLmn := LMN_HLM; QLMN_HLM := TRUE; ELSIF sLmn < LMN_LLM THEN sLmn := LMN_LLM; QLMN_LLM := TRUE; ELSE QLMN_HLM := FALSE; QLMN_LLM := FALSE; END_IF; // 输出标定 LMN := sLmn * LMN_FAC + LMN_OFF; // 外设出口,按 0..27648 折算 IF LMN_FAC <> 0.0 THEN LMN_PER := REAL_TO_WORD(LMN / LMN_FAC / 100.0 * 27648.0); END_IF; // 更新历史量,放在最后一步 sLanteilAlt := Ianteil; sInvAlt := gf;

DISV是干扰变量输入,用来接前馈补偿,比如已知某一路热源会额外加热、把它的贡献提前叠到输出上,不用等偏差出来再反应。MAN_ON为真时sLmn := MAN直接接管,P、I、D 三路全不参与合成,但历史量sLanteilAlt等仍要在末尾更新,否则手切自的瞬间积分会重新从一个过时的值开始累加。LMN_PER的折算里除以LMN_FAC是为了先把工程量还原成块内单位,再乘满量程系数,如果项目里LMN_FAC恒等于 1,这一段可以简化掉。

4. 上机跑通:在 SCL 环境里编译、调用与整定的三步

拿到源码之后别急着往设备上贴,先在空项目里把块跑通,让它对着一个模拟的 PV 输出稳定结果,再去现场。下面按“建块 → 实例化调用 → 整定”三步走,每一步都给到可以直接抄的段落。

4.1 建块与编译:从空 BEGIN 到可调用的 FB

新建一个 Function Block,语言选 SCL,把源码头部的变量声明原样贴进去,然后把 BEGIN 之后补成第 3 章那几段实现。这一步有两处最常见的编译报错要提前处理:Time类型变量必须先TIME_TO_REAL再参加运算,WORD和INT之间的转换必须显式调用WORD_TO_INT或INT_TO_WORD,不能靠隐式转换。

// 在 BEGIN 之后、主逻辑之前,先把时间量统一转成秒 rCycle := TIME_TO_REAL(CYCLE); rTi := TIME_TO_REAL(TI); rTd := TIME_TO_REAL(TD); rTmLag := TIME_TO_REAL(TM_LAG); // TI 时间为 0 时,让积分支路自动跳过,避免除零 IF rTi <= 0.0 THEN rTi := 0.0; END_IF;

编译通过之后不要马上写调用,先在块属性里把S7_Optimized_Access打开。这块源码头部的{ S7_Optimized_Access := 'TRUE' }就是告诉编译器用优化访问,实例 DB 的变量排列不再按字节偏移,读写速度会快一点,也就意味着不能再通过绝对地址去访问它的成员,写上位机通信的时候要换成符号访问。

4.2 实例化与调用:背景 DB、采样时钟与 PV_PER 通道

调用这一块有两种写法:一种是在 OB1 这种循环任务里直接调,采样时间靠CYCLE给;另一种是在循环中断 OB 里调,CYCLE跟 OB 的周期对齐。后者在高速回路上更稳,前者在慢速温度回路上够用。

// 在循环中断 OB30(假设周期 100ms)里调用 "PID_DB"( COM_RST := "Reset_PB", MAN_ON := "Manual_Mode", PVPE_ON := FALSE, P_SEL := TRUE, I_SEL := TRUE, D_SEL := FALSE, // 温度回路通常不加 D INT_HOLD := FALSE, I_ITL_ON := FALSE, CYCLE := T#100ms, // 与 OB30 周期一致 SP_INT := "Temp_SP", PV_IN := "Temp_PV", PV_PER := 0, MAN := "Manual_Out", GAIN := 2.5, TI := T#30S, TD := T#0S, TM_LAG := T#0S, DEADB_W := 0.2, LMN_HLM := 100.0, LMN_LLM := 0.0, PV_FAC := 1.0, PV_OFF := 0.0, LMN_FAC := 1.0, LMN_OFF := 0.0, I_ITL_VAL:= 0.0, DISV := 0.0, ER => "PID_Error", LMN => "Heat_Out", LMN_PER => "Heat_Out_Per", QLMN_HLM => "Out_High_Limit", QLMN_LLM => "Out_Low_Limit", LMN_P => "Trend_P", LMN_I => "Trend_I", LMN_D => "Trend_D", PV => "Temp_PV_Display" );

CYCLE一定要跟实际调用周期一致,填错这一项,积分和微分算出来的值会整体偏大或偏小。PV_PER在不用外设通道时给 0 就可以,但PVPE_ON为 FALSE 时PV_IN必须接上实际值,两个通道至少有一个是有效的。ER是 IN_OUT 类型的错误信号,用=>接输出侧,别用:=。

4.3 整定与在线监视:用 LMN_P、LMN_I、LMN_D 反推参数

整定的顺序是先把 I 和 D 关掉、只开 P,从一个小GAIN开始往上试,直到输出开始等幅振荡,记下这时的增益叫临界增益 Ku,周期叫临界周期 Tu。然后按 Ziegler-Nichols 经验公式给一组初值:GAIN = 0.6 * Ku、TI = 0.5 * Tu、TD = 0.125 * Tu。这一组只是起点,实际还要在现场微调。

在线监视的时候把LMN_P / LMN_I / LMN_D三条趋势和一个LMN输出曲线叠在一起看,比单看输出有信息量得多。如果LMN_P已经明显在振荡、LMN_I还在往上爬,说明积分时间给短了,饱和在推着系统走;如果三路都平滑,只有LMN不平滑,那问题多半在输出限幅或者外设折算上,跟整定参数无关。整定过程中DEADB_W不要一开始就设,会让输出“看着稳定”但其实偏差一直没消掉。

5. 避坑与排查:五条从现场带回来的记录

这块 PID 源码不算复杂,但变量多、分支多,实操里踩过的坑比看代码时预想的多得多。下面五条都是带具体现象、原因和解决方法的记录,能对号入座的话可以省掉不少来回。

5.1 现象:手动切自动瞬间输出跳变成满量程

原因:手自动切换时sLanteilAlt没有在手动期间继续更新,切回自动时积分量从一个陈旧的初值继续累加,加上Panteil因为偏差暴涨而冲高,两路叠起来直接把输出顶到上限。

解决:手动期间也要在末尾更新sLanteilAlt和sInvAlt,常见做法是在IF MAN_ON分支里把sLanteilAlt := LMN - Panteil - Danteil反推一下,让积分量跟着手动的实际输出走,切回自动时无扰切换。

5.2 现象:输出到上限以后再也退不回来

原因:积分饱和。偏差一直在同方向、输出一直顶在LMN_HLM,Ianteil每拍继续累加,等偏差反向时需要几十拍才能把积分退回来,这期间输出一直是上限。

解决:确认第 3.3 节里那道条件积分的闸门有没有写上。如果写了还是不退,检查LMN_HLM和LMN_LLM是不是被别的程序在线改过,或者LMN_FAC折算之后实际限幅点跟预期不一致。

5.3 现象:手动模式下输出值卡着不动

原因:MAN输入接的是一个静态变量,HMI 写完以后没有回写,或者MAN_ON为 TRUE 时MAN被别的逻辑置 0,块里sLmn := MAN拿到的一直是同一个值。

解决:在调用之前串一句"Manual_Out" := "Manual_In";明确手自动切换关系,HMI 写MAN用一个独立的中间变量,不要在块引脚上直接接 HMI 标签。手动值的量程要跟LMN一致,不要用百分比直接喂。

5.4 现象:PV_PER 读进来的数值明显不对

原因:PV_PER是Word类型,PLC 模拟量模块给出的原始值 0..27648 对应 0..100%,但现场变送器可能是 4..20mA 对应 -50..150℃,量程和偏移没有在PV_FAC / PV_OFF里体现,或者WORD_TO_INT转换后直接按字面值用了。

解决:先确认模块通道的原始值范围,再把归一化和工程量换算拆成两步:第一步RawToPercent := WORD_TO_INT(PV_PER) / 27648.0 * 100.0,第二步EngValue := RawToPercent * PV_FAC + PV_OFF。PVPE_ON为 TRUE 时PV_IN那一侧也要置成一个安全值,防止通道切换时读到脏数据。

5.5 现象:改了 DEADB_W 但输出抖动没有变化

原因:死区只对偏差做削零,但微分项是拿gf跟上一拍sInvAlt做差,而死区处理之后gf在死区内是跳变的(0 或原值),微分照样会在死区边界上打出毛刺。另外TM_LAG没设的时候微分是裸的。

解决:微分回路要么关掉D_SEL,要么在死区处理之后把gf送去做一阶平滑再进微分,TM_LAG至少给到 2 到 3 倍采样周期。纯温度回路一般不加 D,靠 P 和 I 调就够了,加 D 反而容易把噪声放大成输出抖动。

6. 二次开发:从这块 PID 到带前馈和分段限幅的自定义控制块

这块源码的 BEGIN 之后是空的,等于把“怎么算”这个决定权留给了使用者,这也是它比向导块更好用的地方。真要往生产环境推,我一般会做三处改造,而不是原样贴上去。

第一处是把DISV用起来做静态前馈。比如加热设备里有一路固定的伴热功率,它的开度跟主回路无关,可以直接按比例折算后送进DISV,这样主 PID 只需要补足剩余部分,整定压力立刻小一半。具体做法是在调用之前加一句"PID_DB".DISV := "Trace_Heat_Setpoint" * 0.3;,0.3 是伴热功率占主回路满量程的比例,按现场实测填。

第二处是把LMN_HLM / LMN_LLM改成随工况变化的动态限幅。这块源码的限幅是静态变量,但在加热和保温两个阶段,输出上限是不一样的。可以在调用前用一个 MOVE 指令在线改写:"PID_DB".LMN_HLM := 85.0;,接着再调块,注意顺序不能反,先写限幅再调用。改完之后,QLMN_HLM的输出就是按新限幅判断的,联锁逻辑跟着一起变。

第三处是把积分初值I_ITL_VAL接到一个切换条件上。设备启动时如果想从上次停机的输出位置无缝接上,可以在COM_RST之后紧接着给一拍I_ITL_ON := TRUE; I_ITL_VAL := "Last_Out";,然后立刻复位I_ITL_ON,这样积分就从上次的输出值起算,不用从 0 慢慢爬。

改动效果的验证,我会做两件事:一是在线看LMN_P / LMN_I / LMN_D三条曲线的量级是否仍然在合理区间(比例分量应跟偏差同量级,积分分量应是单调的,微分分量应接近 0);二是把设定值拉一个 5% 的阶跃,看输出在 10 到 15 个采样周期内能不能进入稳态,进不去就说明限幅或前馈里某一项给错了。改完不要只看一次,至少连续观察一个完整的设备启停周期。

我自己带项目时的习惯是,任何一次改过 PID 参数或逻辑之后,都强制把“手动给 0 → 手动给满 → 切自动 → 设定值阶跃 → 复位”这五步完整走一遍,哪怕只改了 0.1 的增益也走,因为很多翻车就出在“看着没动”的那点改动上。这块源码的变量命名和方法把大部分黑匣子都打开了,愿意动手拆的话,前馈、限幅、抗饱和这些想加就能加,剩下的就是按自己的回路把系数一个一个调出来。希望帮到你。

本文还有配套的精品资源,点击获取