denghuagong
级别: 探索解密
精华主题: 0
发帖数量: 27 个
工控威望: 174 点
下载积分: 1950 分
在线时间: 74(小时)
注册时间: 2011-10-15
最后登录: 2026-04-01
查看denghuagong的 主题 / 回贴
楼主  发表于: 11天前
SET  
      SAVE  
      =     L     68.1
      A     #COM_RST
      JCN   A7d0
      L     #I_ITLVAL
      T     #sIanteilAlt
      L     0.000000e+00
      T     #LMN
      CLR  
      =     #QLMN_HLM
      =     #QLMN_LLM
      T     #LMN_P
      T     #LMN_I
      T     #LMN_D
      L     W#16#0
      T     #LMN_PER
      TAK  
      T     #PV
      T     #ER
      T     #sInvAlt
      T     #sRestInt
      T     #sRestDif
      T     #sRueck
      T     #sLmn
      =     #sbArwHLmOn
      =     #sbArwLLmOn
      JU    A7d1
A7d0: L     #CYCLE
      DTR  
      L     1.000000e+03
      /R    
      T     #rCycle
      L     #PV_PER
      ITD  
      DTR  
      L     3.616898e-03
      *R    
      T     #Istwert
      L     #PV_FAC
      *R    
      L     #PV_OFF
      +R    
      T     #Istwert
      CLR  
      A     #PVPER_ON
      NOT  
      JCN   A7d2
      L     #PV_IN
      T     #Istwert
A7d2: L     #Istwert
      T     #PV
      L     #SP_INT
      TAK  
      -R    
      T     #ErKp
      L     #DEADB_W
      NEGR  
      <R    
      JCN   A7d3
      L     #ErKp
      L     #DEADB_W
      +R    
      T     #ER
      JU    A7d4
A7d3: L     #ErKp
      L     #DEADB_W
      >R    
      JCN   A7d5
      L     #ErKp
      TAK  
      -R    
      T     #ER
      JU    A7d4
A7d5: L     0.000000e+00
      T     #ER
A7d4: L     #ER
      L     #GAIN
      *R    
      T     #ErKp
      L     #TI
      DTR  
      L     1.000000e+03
      /R    
      T     #rTi
      L     #TD
      DTR  
      L     1.000000e+03
      /R    
      T     #rTd
      L     #TM_LAG
      DTR  
      L     1.000000e+03
      /R    
      T     #rTmLag
      L     #rCycle
      L     5.000000e-01
      *R    
      L     #rTi
      TAK  
      <R    
      JCN   A7d7
      L     #rCycle
      L     5.000000e-01
      *R    
      T     #rTi
A7d7: L     #rTd
      L     #rCycle
      <R    
      JCN   A7d8
      T     #rTd
A7d8: L     #rCycle
      L     5.000000e-01
      *R    
      L     #rTmLag
      TAK  
      <R    
      JCN   A7d9
      L     #rCycle
      L     5.000000e-01
      *R    
      T     #rTmLag
A7d9: CLR  
      A     #P_SEL
      JCN   A7da
      L     #ErKp
      T     #Panteil
      JU    A7db
A7da: L     0.000000e+00
      T     #Panteil
A7db: CLR  
      A     #I_SEL
      JCN   A7dc
      A     #I_ITL_ON
      JCN   A7dd
      L     #I_ITLVAL
      T     #Ianteil
      L     0.000000e+00
      T     #sRestInt
      JU    A7e2
A7dd: CLR  
      A     #MAN_ON
      JCN   A7df
      L     #sLmn
      L     #Panteil
      -R    
      L     #DISV
      -R    
      T     #Ianteil
      L     0.000000e+00
      T     #sRestInt
      JU    A7e0
A7df: L     #rCycle
      L     #rTi
      /R    
      L     #ErKp
      TAK  
      T     LD    70
      TAK  
      L     #sInvAlt
      +R    
      L     LD    70
      *R    
      L     5.000000e-01
      *R    
      L     #sRestInt
      +R    
      T     #Diff
      L     0.000000e+00
      >R    
      A     #sbArwHLmOn
      O     #INT_HOLD
      L     #Diff
      L     0.000000e+00
      =     L     68.2
      <R    
      A     #sbArwLLmOn
      O     L     68.2
      JCN   A7e1
      T     #Diff
A7e1: L     #sIanteilAlt
      L     #Diff
      +R    
      T     #Ianteil
      L     #sIanteilAlt
      TAK  
      -R    
      L     #Diff
      +R    
      T     #sRestInt
A7e0: JU    A7e2
A7dc: L     0.000000e+00
      T     #Ianteil
      T     #sRestInt
A7e2: L     #ErKp
      T     #Diff
      CLR  
      A     #MAN_ON
      NOT  
      A     #D_SEL
      JCN   A7e3
      L     #rCycle
      L     5.000000e-01
      *R    
      L     #rTmLag
      +R    
      L     #rTd
      TAK  
      /R    
      T     #Verstaerk
      L     #Diff
      L     #sRueck
      -R    
      L     #Verstaerk
      *R    
      T     #Danteil
      L     #sRueck
      T     #RueckAlt
      L     #rCycle
      L     #rTd
      /R    
      L     #Danteil
      *R    
      L     #sRestDif
      +R    
      T     #RueckDiff
      L     #RueckAlt
      +R    
      T     #sRueck
      L     #RueckAlt
      TAK  
      -R    
      L     #RueckDiff
      +R    
      T     #sRestDif
      JU    A7e4
A7e3: L     0.000000e+00
      T     #Danteil
      T     #sRestDif
      L     #Diff
      T     #sRueck
A7e4: L     #Panteil
      L     #Ianteil
      +R    
      L     #Danteil
      +R    
      L     #DISV
      +R    
      T     #dLmn
      CLR  
      A     #MAN_ON
      JCN   A7e5
      L     #MAN
      T     #dLmn
      JU    A7e7
A7e5: CLR  
      A     #I_ITL_ON
      NOT  
      A     #I_SEL
      JCN   A7e7
      L     #LMN_HLM
      L     #DISV
      -R    
      L     #Ianteil
      TAK  
      >R    
      L     #dLmn
      L     #LMN_HLM
      =     L     68.2
      >R    
      A     L     68.2
      L     #dLmn
      L     #LMN_D
      -R    
      L     #LMN_HLM
      =     L     68.2
      >R    
      A     L     68.2
      JCN   A7e8
      L     #DISV
      -R    
      T     #rVal
      L     #dLmn
      L     #LMN_HLM
      -R    
      T     #gf
      L     #Ianteil
      L     #rVal
      -R    
      T     #rVal
      L     #gf
      >R    
      JCN   A7e9
      T     #rVal
A7e9: L     #Ianteil
      L     #rVal
      -R    
      T     #Ianteil
      JU    A7e7
A7e8: L     #LMN_LLM
      L     #DISV
      -R    
      L     #Ianteil
      TAK  
      <R    
      L     #dLmn
      L     #LMN_LLM
      =     L     68.2
      <R    
      A     L     68.2
      L     #dLmn
      L     #LMN_D
      -R    
      L     #LMN_LLM
      =     L     68.2
      <R    
      A     L     68.2
      JCN   A7e7
      L     #DISV
      -R    
      T     #rVal
      L     #dLmn
      L     #LMN_LLM
      -R    
      T     #gf
      L     #Ianteil
      L     #rVal
      -R    
      T     #rVal
      L     #gf
      <R    
      JCN   A7ec
      T     #rVal
A7ec: L     #Ianteil
      L     #rVal
      -R    
      T     #Ianteil
A7e7: L     #Panteil
      T     #LMN_P
      L     #Ianteil
      T     #LMN_I
      L     #Danteil
      T     #LMN_D
      L     #ErKp
      T     #sInvAlt
      L     #Ianteil
      T     #sIanteilAlt
      CLR  
      =     #sbArwHLmOn
      =     #sbArwLLmOn
      L     #dLmn
      L     #LMN_HLM
      >=R  
      JCN   A7ed
      SET  
      =     #QLMN_HLM
      CLR  
      =     #QLMN_LLM
      T     #dLmn
      SET  
      =     #sbArwHLmOn
      JU    A7ee
A7ed: CLR  
      =     #QLMN_HLM
      L     #dLmn
      L     #LMN_LLM
      <=R  
      JCN   A7ef
      SET  
      =     #QLMN_LLM
      T     #dLmn
      =     #sbArwLLmOn
      JU    A7ee
A7ef: CLR  
      =     #QLMN_LLM
A7ee: L     #dLmn
      T     #sLmn
      L     #LMN_FAC
      *R    
      L     #LMN_OFF
      +R    
      T     #dLmn
      T     #LMN
      L     2.764800e+02
      *R    
      T     #dLmn
      L     3.251100e+04
      >=R  
      JCN   A7f1
      T     #dLmn
      JU    A7f2
A7f1: L     #dLmn
      L     -3.251200e+04
      <=R  
      JCN   A7f2
      T     #dLmn
A7f2: L     #dLmn
      RND  
      T     #LMN_PER
A7d1: CLR  
      A     L     68.1
      SAVE  
      BE    
附件: STL.doc (40 K) 下载次数:15
zx1234
级别: 论坛先锋
精华主题: 0
发帖数量: 923 个
工控威望: 1285 点
下载积分: 8177 分
在线时间: 654(小时)
注册时间: 2012-10-02
最后登录: 2026-04-08
查看zx1234的 主题 / 回贴
1楼  发表于: 10天前
这段代码是西门子S7系列PLC的标准PID控制功能块(FB41 "CONT_C")的STL语句表源码,是工业控制中最经典的连续量PID算法实现,我帮你完整解析其结构、原理和每段逻辑作用:

一、整体功能定位

这是CONT_C FB41的核心STL实现,用于连续量PID调节,包含:

- 复位初始化
- 过程值PV处理与标定
- 死区非线性处理
- 比例P、积分I、微分D分离计算
- 抗积分饱和(Clamping Anti-Windup)
- 手动/自动切换
- 输出限幅与百分比转换
- 周期时间标定与离散化处理

二、代码分段详细解析

1. 复位初始化段(A7d0之前)

SET
SAVE
=     L     68.1
A     #COM_RST
JCN   A7d0
 

-  COM_RST=1  时执行冷启动复位
- 清零所有中间变量、PID分量、状态位
- 初始化历史值寄存器( sIanteilAlt 、 sRueck 等)
- 跳转到A7d1结束本次扫描

2. 周期时间与过程值PV处理(A7d0开始)

L     #CYCLE
DTR
L     1.000000e+03
/R
T     #rCycle
 

- 将调用周期 CYCLE (ms)转为秒 rCycle ,用于离散PID计算

L     #PV_PER
ITD
DTR
L     3.616898e-03
*R
T     #Istwert
L     #PV_FAC
*R
L     #PV_OFF
+R
T     #Istwert
 

- 模拟量输入 PV_PER (0~27648)标定转换为工程值 Istwert 
- 支持 PVPER_ON 选择直接使用 PV_IN 浮点数

3. 偏差计算与死区处理

L     #SP_INT
TAK
-R
T     #ErKp
L     #DEADB_W
NEGR
<R
JCN   A7d3
...
 

- 计算设定值SP与过程值PV的偏差 ErKp 
- 死区 DEADB_W 判断:死区内偏差ER=0,抑制小幅度振荡

4. PID参数时间常数转换

L     #TI
DTR
L     1.000000e+03
/R
T     #rTi

L     #TD
DTR
L     1.000000e+03
/R
T     #rTd
 

- 积分时间TI、微分时间TD(ms)→ 秒 rTi / rTd 
- 同时做最小时间钳位,避免分母为0或算法不稳定

5. 比例分量P计算

A     #P_SEL
JCN   A7da
L     #ErKp
T     #Panteil
 

-  P_SEL=1 启用比例, Panteil = GAIN * ER 
- 否则P分量清零

6. 积分分量I计算(核心抗积分饱和)

A     #I_ITL_ON
JCN   A7dd
L     #I_ITLVAL
T     #Ianteil
 

-  I_ITL_ON 积分初始化赋值
- 手动模式 MAN_ON 时积分跟踪输出,实现无扰切换
- 采用**梯形积分(Trapezoidal Integration)**提高精度
- 关键逻辑:输出限幅时抑制积分累积(Anti-Windup)
-  sbArwHLmOn / sbArwLLmOn 饱和标志
- 积分增量被钳位,防止积分饱和失控

7. 微分分量D计算

A     #D_SEL
JCN   A7e3
L     #rCycle
L     5.000000e-01
*R
L     #rTmLag
+R
L     #rTd
TAK
/R
T     #Verstaerk
 

- 带一阶滤波 TM_LAG 的微分算法,抑制噪声
- 微分针对偏差变化率计算,避免设定值跳变引起冲击

8. 输出合成与手动/自动切换

L     #Panteil
L

这是豆包解读的
楼的话
级别: 正式会员
精华主题: 0
发帖数量: 11 个
工控威望: 56 点
下载积分: 601 分
在线时间: 8(小时)
注册时间: 2024-01-09
最后登录: 2026-03-30
查看楼的话的 主题 / 回贴
2楼  发表于: 10天前
这段代码是用西门子S7系列PLC的语句表(STL)编写的。它实现了一个带有限幅和标度转换功能的PID控制器输出处理程序。
代码功能解析:
PID参数赋值 (A7ec - A7e7):
将内部变量 #Panteil, #Ianteil, #Danteil 的值分别传送给系统的PID功能块接口变量 #LMN_P, #LMN_I, #LMN_D。
将 #ErKp 和 #Ianteil 的值存储到 #sInvAlt 和 #sIanteilAlt,这通常用于计算积分饱和。
将 #rVal 的值传递给 #Ianteil。
高低限位比较与输出 (A7e7 - A7ef):
代码读取计算后的PID输出值 #dLmn。
首先判断 #dLmn 是否大于等于高限值 #LMN_HLM:
如果是,则将输出 #QLMN_HLM 置位,#QLMN_LLM 复位,并将 #dLmn 设为高限值。同时设置标志位 #sbArwHLmOn。
如果不是,则进入下一步。
接着判断 #dLmn 是否小于等于低限值 #LMN_LLM:
如果是,则将输出 #QLMN_LLM 置位,#QLMN_HLM 复位,并将 #dLmn 设为低限值。同时设置标志位 #sbArwLLmOn。
如果不是,则两个限位输出 #QLMN_HLM 和 #QLMN_LLM 都被复位。
标度变换与限幅 (A7ee - A7f2):
线性变换: 将经过限幅的 #dLmn 通过公式 #dLmn = #dLmn * #LMN_FAC + #LMN_OFF 进行线性标度变换,结果存入 #dLmn 并最终输出到 #LMN。
工程量程转换: 将 #dLmn 乘以系数 2.764800e+02 (即276.48),这个系数通常是将浮点数形式的百分比(0-100%)转换为STEP 7中标准模拟量输出的整型格式 (0 - 27648)。
最终钳位: 最后,将计算结果 #dLmn 与最大值 3.251100e+04 (32511) 和最小值 -3.251200e+04 (-32512) 进行比较,确保其在INT类型的有效范围内,超出则强制设为边界值。
四舍五入: 使用 RND 指令对最终结果进行四舍五入,并通过 T 指令传输给 #LMN_PER。
程序结束 (A7d1):
CLR 清除RLO(逻辑运算结果)。
A L 68.1 是一个无操作指令,常用于调试或作为跳转目标。

SAVE 将当前RLO状态保存到BR(位堆栈寄存器),用于块调用后的返回状态。
BE 结束当前逻辑块的执行。

转换成SCL
// --- PID 参数赋值 ---
LMN_P := Panteil;
LMN_I := Ianteil;
LMN_D := Danteil;

sInvAlt := ErKp;
sIanteilAlt := Ianteil;

Ianteil := rVal;

// --- 高低限位比较与输出 ---
sbArwHLmOn := FALSE; // 初始状态复位
sbArwLLmOn := FALSE;

// 判断是否高于高限
IF dLmn >= LMN_HLM THEN
    QLMN_HLM := TRUE;
    QLMN_LLM := FALSE;
    dLmn := LMN_HLM; // 将输出钳位到高限值
    sbArwHLmOn := TRUE;
ELSE // 如果不高于高限,则检查是否低于低限
    QLMN_HLM := FALSE; // 先复位高限输出
    
    IF dLmn <= LMN_LLM THEN
        QLMN_LLM := TRUE;
        dLmn := LMN_LLM; // 将输出钳位到低限值
        sbArwLLmOn := TRUE;
    ELSE
        QLMN_LLM := FALSE; // 不在限值内,复位所有报警输出
    END_IF;
END_IF;

// --- 标度变换与限幅 ---
sLmn := dLmn; // 可能的内部状态反馈

// 应用线性变换因子和偏移量
dLmn := (dLmn * LMN_FAC) + LMN_OFF;
LMN := dLmn; // 输出中间结果

// 转换为S7标准模拟量输出格式 (INT)
dLmn := dLmn * 2.7648e+02;

// 对最终输出值进行钳位,防止溢出 INT 范围
IF dLmn >= 32511.0 THEN
    dLmn := 32511.0;
ELSIF dLmn <= -32512.0 THEN
    dLmn := -32512.0;
END_IF;

// 四舍五入并赋值给最终输出变量
LMN_PER := ROUND(dLmn);
秋雨231
级别: 略有小成
精华主题: 0
发帖数量: 149 个
工控威望: 283 点
下载积分: 1039 分
在线时间: 201(小时)
注册时间: 2013-03-06
最后登录: 2026-04-04
查看秋雨231的 主题 / 回贴
3楼  发表于: 5天前
   牛,上古大神
flyfeky
bilibili 工控小工匠孔
级别: 论坛先锋
精华主题: 0
发帖数量: 928 个
工控威望: 1093 点
下载积分: 2298 分
在线时间: 692(小时)
注册时间: 2012-05-09
最后登录: 2026-04-06
查看flyfeky的 主题 / 回贴
4楼  发表于: 5天前
      SET  
      SAVE  
      =     L     68.1              // 暂存当前RLO(逻辑运算结果)到本地临时变量L68.1,用于最后的ENO输出
      A     #COM_RST                // 检查是否触发"完全重启"
      JCN   A7d0                    // 如果没有重启,跳转到A7d0执行正常PID运算
      // === 下面是 COM_RST = 1 时的初始化复位操作 ===
      L     #I_ITLVAL
      T     #sIanteilAlt            // 积分初始值赋给历史积分量
      L     0.000000e+00
      T     #LMN                    // 输出清零
      CLR  
      =     #QLMN_HLM               // 清除达到上限标志
      =     #QLMN_LLM               // 清除达到下限标志
      T     #LMN_P                  // P分量清零
      T     #LMN_I                  // I分量清零
      T     #LMN_D                  // D分量清零
      L     W#16#0
      T     #LMN_PER                // 模拟量输出清零
      TAK                           // 交换累加器1和2 (这里实际是把0放在ACC1,刚才的W#16#0放ACC2,无实际意义,可能是编译器冗余)
      T     #PV                     // 过程变量清零
      T     #ER                     // 误差清零
      T     #sInvAlt                // 上一次误差清零
      T     #sRestInt               // 积分余数清零
      T     #sRestDif               // 微分余数清零
      T     #sRueck                 // 微分反馈清零
      T     #sLmn                   // 上次输出清零
      =     #sbArwHLmOn             // 清除抗积分饱和上限激活标志
      =     #sbArwLLmOn             // 清除抗积分饱和下限激活标志
      JU    A7d1                    // 跳转到程序结尾

A7d0: // === 正常PID运算开始 ===
      // 1. 周期时间处理 (毫秒转秒)
      L     #CYCLE
      DTR                           // 转换为浮点数
      L     1.000000e+03
      /R                            // 除以1000,得到秒为单位的采样周期 #rCycle
      T     #rCycle

      // 2. 过程变量 (PV) 输入处理与标准化
      L     #PV_PER                 // 加载外围设备输入(如模拟量输入PIWxxx)
      ITD                           // 整型转双整型
      DTR                           // 双整型转浮点数
      L     3.616898e-03            // 注意:1 / 27648 ≈ 0.0000361698,这是将0-27648转化为0-1.0的系数
      *R                            // 标准化为0.0 - 1.0
      T     #Istwert
      L     #PV_FAC                 // 乘以过程变量系数
      *R    
      L     #PV_OFF                 // 加上过程变量偏移量
      +R    
      T     #Istwert                // 得到实际的工程量过程变量
      CLR  
      A     #PVPER_ON               // 检查是否使用了外围设备输入
      NOT  
      JCN   A7d2                    // 如果 PVPER_ON = 1,跳过下面两句
      L     #PV_IN                  // 如果 PVPER_ON = 0,则直接使用 PV_IN 的值
      T     #Istwert
A7d2: L     #Istwert
      T     #PV                     // 最终的当前过程变量 PV

      // 3. 误差计算 与 死区处理
      L     #SP_INT                 // 设定值
      TAK                           // 堆栈操作:把SP_INT压入ACC2
      -R                            // ACC2 - ACC1 => SP_INT - PV => 误差
      T     #ErKp                   // 暂存误差 (未乘增益前的纯误差)
      L     #DEADB_W                // 加载死区宽度
      NEGR                          // 取反得到 -DEADB_W
      <R                            // 比较 ErKp < -DEADB_W ?
      JCN   A7d3                    // 如果不小于(即误差较大),跳转
      // 误差在负死区内 (-DEADB_W <= ErKp < 0)
      L     #ErKp
      L     #DEADB_W
      +R                            // ErKp + DEADB_W (把误差往0的方向拉)
      T     #ER                     // 保存死区处理后的误差
      JU    A7d4
A7d3: // 误差在正死区外
      L     #ErKp
      L     #DEADB_W
      >R                            // 比较 ErKp > DEADB_W ?
      JCN   A7d5                    // 如果不大于,说明在正负死区之间 (|ErKp| <= DEADB_W)
      // 误差在正死区外 (ErKp > DEADB_W)
      L     #ErKp
      TAK                           // 压栈
      -R                            // ErKp - ErKp = 0 (此处编译器逻辑冗余,本意应是减去DEADB_W,但实际结果等于ErKp)
      T     #ER                     // 保存误差
      JU    A7d4
A7d5: // 误差在死区范围内
      L     0.000000e+00
      T     #ER                     // 误差强制置为0
A7d4:
      // 4. 比例增益计算
      L     #ER
      L     #GAIN                   // 比例增益
      *R                            // 误差 * 增益 = P分量基础值
      T     #ErKp                   // ErKp 现在变成了 Kp * e

      // 5. 时间常数处理 (毫秒转秒,并进行下限限幅防止除零溢出)
      L     #TI
      DTR  
      L     1.000000e+03
      /R                            // 积分时间转秒
      T     #rTi
      L     #TD
      DTR  
      L     1.000000e+03
      /R                            // 微分时间转秒
      T     #rTd
      L     #TM_LAG                 // 微分滞后时间(一阶惯性环节)
      DTR  
      L     1.000000e+03
      /R                            // 转秒
      T     #rTmLag

      // 限制参数不能小于采样周期的一半,保证算法稳定性
      L     #rCycle
      L     5.000000e-01
      *R                            // Cycle / 2
      L     #rTi
      TAK                           // rTi 入栈
      <R                            // (Cycle/2) < rTi ?
      JCN   A7d7                    // 如果 rTi 够大,跳转
      L     #rCycle
      L     5.000000e-01
      *R    
      T     #rTi                    // 否则强制 rTi = Cycle / 2
A7d7: L     #rTd
      L     #rCycle
      <R                            // rTd < Cycle ?
      JCN   A7d8
      T     #rTd                    // 否则强制 rTd = Cycle
A7d8: L     #rCycle
      L     5.000000e-01
      *R    
      L     #rTmLag
      TAK  
      <R                            // (Cycle/2) < rTmLag ?
      JCN   A7d9
      L     #rCycle
      L     5.000000e-01
      *R    
      T     #rTmLag                 // 否则强制 rTmLag = Cycle / 2

      // 6. 比例 (P) 部分计算
A7d9: CLR  
      A     #P_SEL                  // P选择开关
      JCN   A7da
      L     #ErKp
      T     #Panteil                // P分量 = ErKp (已经是 Kp * e)
      JU    A7db
A7da: L     0.000000e+00
      T     #Panteil                // P关闭,P分量为0
A7db:
      // 7. 积分 (I) 部分计算 (梯形积分法)
      CLR  
      A     #I_SEL                  // I选择开关
      JCN   A7dc                    // I关闭则跳转清零
      A     #I_ITL_ON               // 积分初始化开关
      JCN   A7dd
      L     #I_ITLVAL
      T     #Ianteil                // 如果开启初始化,直接赋初值
      L     0.000000e+00
      T     #sRestInt
      JU    A7e2
A7dd: CLR  
      A     #MAN_ON                 // 检查是否在手动模式
      JCN   A7df
      // --- 手动模式下的积分跟踪 (实现无扰切换) ---
      L     #sLmn                   // 上次的实际输出
      L     #Panteil                // 减去当前的P分量
      -R    
      L     #DISV                   // 减去干扰变量/前馈
      -R    
      T     #Ianteil                // 手动时,强制 I分量 = 输出 - P - 前馈,保证切自动时输出不跳变
      L     0.000000e+00
      T     #sRestInt
      JU    A7e0
A7df:
      // --- 自动模式下的实际积分运算 (梯形积分,提高精度) ---
      L     #rCycle
      L     #rTi
      /R                            // Cycle / Ti
      L     #ErKp                   // 当前误差
      TAK                           // ErKp 入栈
      T     LD    70                // 暂存当前误差到本地变量LD70
      TAK                           // 恢复 (Cycle/Ti) 到ACC1
      L     #sInvAlt                // 上一次的误差
      +R                            // (Cycle/Ti) + 上一次误差
      L     LD    70                // 取出当前误差
      *R                            // ((Cycle/Ti) + Er_alt) * Er_cur
      L     5.000000e-01
      *R                            // 乘以0.5 => 梯形面积: / 2.0
      L     #sRestInt               // 加上一次积分截断产生的余数(保证浮点累加不丢失精度)
      +R    
      T     #Diff                   // 本次积分增量 Diff
      
      // 抗积分饱和限幅判断
      L     0.000000e+00
      >R                            // Diff > 0 ?
      A     #sbArwHLmOn             // 并且上限抗饱和已激活?
      O     #INT_HOLD               // 或者积分被外部保持?
      L     #Diff
      L     0.000000e+00
      =     L     68.2              // 临时标志位:Diff < 0
      <R                            // Diff < 0 ?
      A     #sbArwLLmOn             // 并且下限抗饱和已激活?
      O     L     68.2              // 或者刚才的临时标志
      JCN   A7e1                    // 如果都不满足,正常积分
      T     #Diff                   // 否则强制积分增量为0 (停止积分)
A7e1: L     #sIanteilAlt            // 历史积分值
      L     #Diff                   // 加上计算出的增量
      +R    
      T     #Ianteil                // 得到最新的积分分量
      L     #sIanteilAlt
      TAK                           // 堆栈操作
      -R                            // Ianteil - sIanteilAlt (实际增加的量)
      L     #Diff
      +R                            // 实际增加的量 + 原来的Diff
      T     #sRestInt               // 计算出截断误差余数,留给下一个周期使用
A7e0: JU    A7e2
A7dc: // I_SEL = 0 时的处理
      L     0.000000e+00
      T     #Ianteil
      T     #sRestInt

      // 8. 微分 (D) 部分计算 (不完全微分,带一阶滞后滤波)
A7e2: L     #ErKp
      T     #Diff                   // Diff暂存误差
      CLR  
      A     #MAN_ON                 // 手动模式下微分不计算
      NOT  
      A     #D_SEL                  // D选择开关
      JCN   A7e3
      // 计算不完全微分滤波系数
      L     #rCycle
      L     5.000000e-01
      *R                            // Cycle / 2
      L     #rTmLag                 // 滞后时间
      +R                            // + TM_LAG
      L     #rTd                    // 微分时间
      TAK                           // 入栈
      /R                            // (Cycle/2 + TmLag) / Td => 滤波系数 alpha
      T     #Verstaerk
      // 计算微分分量
      L     #Diff                   // 当前误差
      L     #sRueck                 // 微分内部反馈量
      -R                            // 误差 - 反馈
      L     #Verstaerk
      *R                            // * 滤波系数
      T     #Danteil                // 得到微分分量输出
      // 更新微分内部反馈 (一阶惯性环节的差分方程)
      L     #sRueck
      T     #RueckAlt               // 备份旧的反馈
      L     #rCycle
      L     #rTd
      /R                            // Cycle / Td
      L     #Danteil
      *R                            // * 当前D分量
      L     #sRestDif               // 加上微分余数
      +R    
      T     #RueckDiff              // 反馈增量
      L     #RueckAlt
      +R    
      T     #sRueck                 // 新的反馈 = 旧反馈 + 增量
      L     #RueckAlt
      TAK                           // 堆栈操作
      -R                            // sRueck - RueckAlt (实际增加的反馈)
      L     #RueckDiff
      +R                            // + 原来的增量
      T     #sRestDif               // 计算微分截断余数
      JU    A7e4
A7e3: // D关闭或手动模式
      L     0.000000e+00
      T     #Danteil
      T     #sRestDif
      L     #Diff
      T     #sRueck                 // 重置反馈为当前误差,防止切自动时微分跳变

      // 9. 各分量求和
A7e4: L     #Panteil
      L     #Ianteil
      +R    
      L     #Danteil
      +R    
      L     #DISV                   // 加上干扰变量/前馈
      +R    
      T     #dLmn                   // 总输出 = P + I + D + DISV
      
      // 10. 手动模式覆盖
      CLR  
      A     #MAN_ON
      JCN   A7e5
      L     #MAN
      T     #dLmn                   // 手动模式下,输出直接等于手动设定值
      JU    A7e7

      // 11. 抗积分饱和 操作
A7e5: CLR  
      A     #I_ITL_ON
      NOT  
      A     #I_SEL                  // 确认积分是开启状态
      JCN   A7e7
      // 高限抗饱和判断逻辑
      L     #LMN_HLM
      L     #DISV
      -R                            // 上限 - 前馈
      L     #Ianteil
      TAK                           // 入栈
      >R                            // (上限-前馈) > I分量 ? (意味着P和D可以把总输出拉回到限幅内)
      L     #dLmn
      L     #LMN_HLM
      =     L     68.2              // 临时位赋值
      >R                            // 总输出 > 上限 ?
      A     L     68.2              // 与临时位相与
      L     #dLmn
      L     #LMN_D                 // 减去死区偏置?
      -R    
      L     #LMN_HLM
      =     L     68.2
      >R                            // (输出-死区) > 上限 ?
      A     L     68.2
      JCN   A7e8                    // 条件不满足,跳去查低限
      // 满足高限抗饱和条件,开始削弱积分
      L     #DISV
      -R    
      T     #rVal                   // rVal = 上限 - DISV
      L     #dLmn
      L     #LMN_HLM
      -R    
      T     #gf                     // gf = 超过上限的量
      L     #Ianteil
      L     #rVal
      -R    
      T     #rVal                   // rVal = I分量 - (上限-DISV)
      L     #gf
      >R                            // 超过量 > (I分量 - 上限允许的I空间) ?
      JCN   A7e9
      T     #rVal                   // 限制削减量不能超过gf
A7e9: L     #Ianteil
      L     #rVal
      -R    
      T     #Ianteil                // I分量减去削减量,实现抗积分饱和
      JU    A7e7
      // 低限抗饱和判断逻辑 (与高限镜像)
A7e8: L     #LMN_LLM
      L     #DISV
      -R    
      L     #Ianteil
      TAK  
      <R                            // (下限-前馈) < I分量 ?
      L     #dLmn
      L     #LMN_LLM
      =     L     68.2
      <R                            // 总输出 < 下限 ?
      A     L     68.2
      L     #dLmn
      L     #LMN_D
      -R    
      L     #LMN_LLM
      =     L     68.2
      <R                            // (输出-死区) < 下限 ?
      A     L     68.2
      JCN   A7e7                    // 不满足则不操作
      // 满足低限抗饱和条件,增加积分(向0方向拉)
      L     #DISV
      -R    
      T     #rVal
      L     #dLmn
      L     #LMN_LLM
      -R    
      T     #gf
      L     #Ianteil
      L     #rVal
      -R    
      T     #rVal
      L     #gf
      <R                            // 超出量 < (I分量 - 下限允许的I空间) ?
      JCN   A7ec
      T     #rVal
A7ec: L     #Ianteil
      L     #rVal
      -R    
      T     #Ianteil                // I分量减去负的削减量(即加上削减量),防止向下积分过度

      // 12. 变量保存与输出限幅
A7e7: L     #Panteil
      T     #LMN_P                  // 输出P分量监控
      L     #Ianteil
      T     #LMN_I                  // 输出I分量监控
      L     #Danteil
      T     #LMN_D                  // 输出D分量监控
      L     #ErKp
      T     #sInvAlt                // 保存当前误差供下个周期使用
      L     #Ianteil
      T     #sIanteilAlt            // 保存当前积分供下个周期使用
      CLR  
      =     #sbArwHLmOn             // 复位抗饱和标志
      =     #sbArwLLmOn
      // 判断输出是否越限并限幅
      L     #dLmn
      L     #LMN_HLM
      >=R  
      JCN   A7ed
      SET  
      =     #QLMN_HLM               // 达到上限标志置位
      CLR  
      =     #QLMN_LLM
      T     #dLmn                   // 强制输出等于上限值
      SET  
      =     #sbArwHLmOn             // 激活上限抗饱和标志,告知下一个周期停止积分
      JU    A7ee
A7ed: CLR  
      =     #QLMN_HLM
      L     #dLmn
      L     #LMN_LLM
      <=R  
      JCN   A7ef
      SET  
      =     #QLMN_LLM               // 达到下限标志置位
      T     #dLmn                   // 强制输出等于下限值
      =     #sbArwLLmOn             // 激活下限抗饱和标志
      JU    A7ee
A7ef: CLR  
      =     #QLMN_LLM               // 在正常范围内,清除限幅标志
A7ee: L     #dLmn
      T     #sLmn                   // 保存限幅后的输出值,用于手动切换无扰跟踪

      // 13. 输出格式化与刻度转换
      L     #LMN_FAC                // 输出系数
      *R    
      L     #LMN_OFF                // 输出偏移
      +R    
      T     #dLmn
      T     #LMN                    // 输出实数工程量 (如 0 - 100.0)
      L     2.764800e+02            // 276.48 (注意:100.0 * 276.48 = 27648)
      *R    
      T     #dLmn                   // 将 0-100 映射到 0-27648
      // 整数上下限硬保护 (32511 和 -32512 是S7模拟量输出的极限值)
      L     3.251100e+04
      >=R  
      JCN   A7f1
      T     #dLmn                   // 限制不超过 32511
      JU    A7f2
A7f1: L     #dLmn
      L     -3.251200e+04
      <=R  
      JCN   A7f2
      T     #dLmn                   // 限制不小于 -32512
A7f2: L     #dLmn
      RND                           // 四舍五入转换为双整型
      T     #LMN_PER                // 最终输出到模拟量输出通道 (如 PQWxxx)

      // 14. 程序结束
A7d1: CLR  
      A     L     68.1              // 取出最初保存的RLO状态
      SAVE                          // 恢复到ENO,保证级联逻辑正确
      BE                            // 块结束


核心算法亮点分析
高精度的梯形积分
在 A7df 段中,代码没有使用简单的矩形积分 (Kp * e) * (T/Ti),而是使用了 ((Kp * e_cur) + (Kp * e_old)) / 2 * (T/Ti) 的梯形法则。更牛的是,它使用了 #sRestInt(余数寄存器)。因为浮点数运算存在截断误差,长时间运行会导致积分漂移,利用余数寄存器可以把每个周期丢失的小数位累加到下一个周期,这在工业浮点PID中是非常经典且高级的做法。
真正的不完全微分
在 A7e2 段中,纯微分在阶跃响应时会产生无穷大的尖峰,且容易引入高频噪声。这里实现了一个一阶惯性滤波器包裹的微分算法。通过计算 Verstaerk(增益系数)和 sRueck(反馈量),将微分的剧烈变化平滑掉,使得D作用在前期迅速起效,后期慢慢衰减。这里同样使用了 #sRestDif 余数机制。
主动抗积分饱和
在普通的PID中,如果输出已经达到100%,但误差依然为正,积分器会继续累加(积分饱和)。当误差反向时,需要花很长时间把积分“退”回来,导致严重的超调。
这段代码在 A7e5 段实现了条件积分:当检测到输出撞到限幅墙壁(QLMN_HLM=1)时,不仅停止积分(通过 A7e1 处的 sbArwHLmOn 标志),甚至主动把积分项往回“拉”(削减I分量),保证积分项永远不会超出物理输出能承受的极限,实现无超调。
完美的手动/自动无扰切换
在手动模式(MAN_ON=1)下:
输出直接跟随 MAN 值(A7e4段)。
积分项被强行计算为:I = 实际输出 - P分量 - 前馈 (A7dd段)。
这保证了当操作员从手动切回自动的那一瞬间,因为 P+I+D = P + (Out-Man-P) + D = Out,输出值绝对不会发生跳变(无扰动切换)。
硬件定标耦合
输入端:3.616898e-03 (即 1/27648) 将 PLC 模拟量输入(0-27648)隐形转化为 0.0~1.0 的标准化值,再乘以 PV_FAC 和 PV_OFF 转化为实际工程量(如温度0-400℃)。
输出端:先计算出 0.0~100.0 的百分比,然后乘以 276.48(2.764800e+02)还原为 0-27648 的整数量输出到模拟量通道,并硬性限幅在西门子S7模拟量允许的极限值 32511 / -32512 内。
西门子倍福WPF+C#数据库,槽式设备专家
flyfeky
bilibili 工控小工匠孔
级别: 论坛先锋
精华主题: 0
发帖数量: 928 个
工控威望: 1093 点
下载积分: 2298 分
在线时间: 692(小时)
注册时间: 2012-05-09
最后登录: 2026-04-06
查看flyfeky的 主题 / 回贴
5楼  发表于: 5天前
我直接让AI给你翻译的。
西门子倍福WPF+C#数据库,槽式设备专家