Skip to content

pid — PID 闭环控制 ​

模块名:pid

选型提醒:本模块的适用平台

pid 模块在 Windows / Linux 上位机(软 PLC、模拟器) 与 4G 机型(LP8910 / LP8913 / LP8916 / LP8922, RDA8910 平台) 上都有;ESP32 机型(LP3201 / LP3202 / LP3203 / LP3222 / LP3232 / LP3288)没有这个库, 脚本里调用 pid.creat 会报 "attempt to index a nil value (global 'pid')"。

本章适用于上位机与 4G 机型。

ESP32 机型上要做闭环调节,请用以下替代方案:

  • 推荐:使用梯形图的 PID 指令(FNC 88)。设备固件已实现,格式 PID S1 S2 S3 D,并支持继电反馈法自整定,详见 PID 运算(FNC 88)
  • 或在 Lua 里自行实现增量式 PID(约数十行,用 timer.start 定周期计算,读写 plc.d / plc.df)

比例-积分-微分闭环控制,并内置基于继电反馈法的参数自整定。每个控制器是一个独立对象,可以同时创建多个。loop 按创建时设定的采样周期决定是否重新运算,两次调用间隔小于采样周期时直接返回上一次的输出。

除 pid.creat 外,其余函数在库表和对象元表里都注册了,ctrl:loop(v) 和 pid.loop(ctrl, v) 等价。

PID 闭环控制框图

函数总览 ​

库级函数 ​

函数作用
pid.creat([kp, ki, kd, ms, outmax, outmin])创建一个 PID 控制器对象

对象方法 ​

方法作用
ctrl:set_set设置目标值(设定点)
ctrl:set_in只更新当前输入值,不触发运算
ctrl:loop传入当前值执行一次运算并返回输出
ctrl:get_out读取上一次的输出值,不重新运算
ctrl:at_start启动参数自整定,完成时回调
ctrl:at_stop停止参数自整定

本模块不注册常量。


pid.creat ​

lua
pid.creat([kp, ki, kd, ms, outmax, outmin]) --> pid

创建一个 PID 控制器对象。参数按位置顺序传入,可从末尾省略,但不能跳过中间参数。

参数

#参数类型必填默认值说明
1kpnumber否1比例系数
2kinumber否0.5积分系数
3kdnumber否0.1微分系数。传 0 表示按 PI 控制器整定
4msinteger否500采样周期,单位毫秒
5outmaxnumber否100输出上限
6outminnumber否0输出下限

返回值

类型说明
userdataPID 控制器对象。必须用变量接住

示例

lua
-- kp=2.0, ki=0.3, kd=0.1, 采样 1000ms, 输出 0~100
ctrl = pid.creat(2.0, 0.3, 0.1, 1000, 100, 0)

说明

  • 负的 kp/ki/kd 会被忽略(保持原值)。

ctrl:set_set ​

lua
ctrl:set_set(setpoint)

设置目标值(设定点)。

参数

#参数类型必填默认值说明
1setpointnumber是—期望达到的目标值

返回值

无返回值。

示例

lua
ctrl:set_set(50.0)   -- 目标 50

ctrl:set_in ​

lua
ctrl:set_in(input)

只写入当前过程值,不触发运算,也不返回输出。

参数

#参数类型必填默认值说明
1inputnumber是—当前过程值

返回值

无返回值。

说明

  • 一般不需要用它。要运算并拿输出请直接用 ctrl:loop(input)。

ctrl:loop ​

lua
ctrl:loop(input) --> number

传入当前过程值,执行一次 PID 运算并返回输出。这是主要的驱动函数。

参数

#参数类型必填默认值说明
1inputnumber是—当前过程值

返回值

类型说明
number当前输出值,被限制在 [outmin, outmax] 之间

示例

lua
timer.start(1000, function()
    local temp = plc.df(100)          -- 读当前温度
    local out  = ctrl:loop(temp)      -- 运算
    plc.d(200, math.floor(out))       -- 输出给梯形图
end)

说明

  • 内部按创建时的 ms 判断是否重新计算;间隔小于 ms 时直接返回上一次的输出。定时器周期建议与 ms 保持一致。
  • 自整定进行期间,loop 负责推进整定过程,此时输出由整定逻辑控制(会在上下限之间大幅切换)。

ctrl:get_out ​

lua
ctrl:get_out() --> number

读取上一次的输出值,不重新运算。

参数

无参数。

返回值

类型说明
number最近一次 loop 计算出的输出值

ctrl:at_start ​

lua
ctrl:at_start(callback)

启动参数自整定。整定过程由后续的 loop 调用推进,完成时调用 callback(p, i, d),并把整定出的参数自动应用到控制器。

参数

#参数类型必填默认值说明
1callbackfunction是—整定完成回调,入参 (p, i, d)。类型不是 function 会直接报错

返回值

无返回值。

示例

lua
ctrl:at_start(function(p, i, d)
    print(string.format("自整定完成: P=%.3f I=%.3f D=%.3f", p, i, d))
    fdb.kv_set("pid_params", { p = p, i = i, d = d })   -- 存下来下次直接用
end)

-- 自整定期间照常调用 loop 推进过程
timer.start(1000, function()
    ctrl:loop(plc.df(100))
end)

说明

  • 算法是继电反馈法(relay feedback / Åström–Hägglund):整定期间输出会在上下限之间来回切换,激励系统振荡,测出临界增益 Ku 和临界周期 Pu,再按 Ziegler-Nichols 公式给出参数。
  • 执行前必须确认被控对象能承受输出的剧烈切换,并且有独立于 Lua 的超温/超压硬件保护。
  • 控制类型由 pid.creat 的 kd 决定:kd != 0 按 PID 整定(Kp=0.6Ku、Ki=1.2Ku/Pu、Kd=0.075·Ku·Pu),kd == 0 按 PI 整定(Kp=0.4Ku、Ki=0.48Ku/Pu、Kd=0)。
  • 整定结果有上限钳制:kp ≤ 200、ki ≤ 3600、kd ≤ 900,且都不小于 0。
  • 已在自整定中时重复调用不会重新启动。整定过程中出现异常会恢复到调用前的参数。

ctrl:at_stop ​

lua
ctrl:at_stop()

停止参数自整定,恢复当前的 kp/ki/kd 并切回自动运行。

参数

无参数。

返回值

无返回值。

说明

  • 未在自整定状态时调用是空操作,不报错。

常见用法 ​

温度闭环:读取温度 → PID 运算 → 输出到模拟量

lua
ctrl = pid.creat(2.0, 0.3, 0.1, 1000, 100, 0)
ctrl:set_set(60.0)                       -- 目标 60℃

timer.start(1000, function()             -- 周期与 ms 一致
    local temp = plc.df(100)
    local out  = ctrl:loop(temp)
    plc.d(200, math.floor(out))          -- 0-100 给到梯形图做 PWM 或模拟量
end)

开机先自整定,成功后保存参数

lua
ctrl = pid.creat(1, 0.5, 0.1, 1000, 100, 0)
ctrl:set_set(60.0)

ctrl:at_start(function(p, i, d)
    print("整定完成", p, i, d)
    plc.df(300, p); plc.df(302, i); plc.df(304, d)   -- 存到寄存器方便上位机查看
end)

timer.start(1000, function()
    plc.d(200, math.floor(ctrl:loop(plc.df(100))))
end)

注意事项 ​

  • 驱动 loop 的定时器周期应与 pid.creat 的 ms 一致,偏差过大会使积分和微分的时间尺度失真。
  • 自整定期间输出会在全开与全关之间反复切换,应先确认工艺允许,并配置硬件级保护。
  • 输出上下限 outmax/outmin 在创建时确定,之后不能改。
  • 可以同时创建多个 PID 对象,各自独立运算,互不影响。

例程 ​

⚠ 本模块仅 Windows / Linux 上位机(软 PLC、模拟器)提供,ESP32 机型(LP3202/LP3203/LP8910 等)无 pid——ESP32 上要做闭环请用梯形图 PID 指令(FNC 88)或自行实现增量式 PID。统一模板「场景 / 完整代码 / 要点 / 验证方法 / 注意事项」。

例程 1:温度闭环——读温度→PID 运算→输出 ​

适用:上位机 / 软PLC模拟器 | 依赖模块:pid timer plc | 难度:★☆☆

场景:把 D100 温度闭环到 60℃,输出 0~100 给梯形图。

完整代码(可直接下载运行):

lua
ctrl = pid.creat(2.0, 0.3, 0.1, 1000, 100, 0)   -- kp,ki,kd,采样ms,上限,下限
ctrl:set_set(60.0)

timer.start(1000, function()             -- 周期与 creat 的 ms 一致
    local temp = plc.df(100)
    local out  = ctrl:loop(temp)
    plc.d(200, math.floor(out))
end)

要点:loop 由定时器周期调用;定时器周期与 creat 的 ms 保持一致。

验证方法:温度逐步逼近 60℃,输出在 0~100 之间收敛。

注意事项:ESP32 上 pid 为 nil;周期与 ms 不一致会使积分/微分时间尺度失真。


例程 2:开机先自整定,完成后保存参数 ​

适用:同上 | 依赖模块:pid fdb | 难度:★★☆

场景:先继电反馈法自整定,整定完成把参数存下,下次直接用。

完整代码(可直接下载运行):

lua
ctrl = pid.creat(1, 0.5, 0.1, 1000, 100, 0)
ctrl:set_set(60.0)
ctrl:at_start(function(p, i, d)
    print("整定完成", p, i, d)
    plc.df(300, p); plc.df(302, i); plc.df(304, d)
end)

timer.start(1000, function()
    plc.d(200, math.floor(ctrl:loop(plc.df(100))))
end)

要点:整定期间 loop 正常调用即可推进;输出会大幅切换,需硬件级保护。

验证方法:一段后打印整定参数,输出收敛。

注意事项:整定期间输出在上下限间反复切换;执行前确认被控对象能承受。


例程 3:注意事项——ESP32 没有 pid ​

适用:同上 | 依赖模块:pid | 难度:★☆☆

完整代码(可直接下载运行):

lua
-- ❌ 错误:ESP32 上 pid 为 nil
ctrl = pid.creat(2.0, 0.3, 0.1, 1000, 100, 0)   -- attempt to index a nil value

-- ✅ 正确(ESP32):用梯形图 PID 指令,或自行实现增量式 PID

要点:平台上架前确认 pid 是否存在;跨平台脚本用 if pid then ... else ... end 分支。

验证方法:上位机/模拟器能创建对象;ESP32 报 nil。

注意事项:同一份脚本要跨平台时,先判平台再选闭环方案。

LPPLC 工业物联网云平台 · 软硬件与云平台全栈自研