主题
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.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 控制器对象。参数按位置顺序传入,可从末尾省略,但不能跳过中间参数。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | kp | number | 否 | 1 | 比例系数 |
| 2 | ki | number | 否 | 0.5 | 积分系数 |
| 3 | kd | number | 否 | 0.1 | 微分系数。传 0 表示按 PI 控制器整定 |
| 4 | ms | integer | 否 | 500 | 采样周期,单位毫秒 |
| 5 | outmax | number | 否 | 100 | 输出上限 |
| 6 | outmin | number | 否 | 0 | 输出下限 |
返回值
| 类型 | 说明 |
|---|---|
| userdata | PID 控制器对象。必须用变量接住 |
示例
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)设置目标值(设定点)。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | setpoint | number | 是 | — | 期望达到的目标值 |
返回值
无返回值。
示例
lua
ctrl:set_set(50.0) -- 目标 50ctrl:set_in
lua
ctrl:set_in(input)只写入当前过程值,不触发运算,也不返回输出。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | input | number | 是 | — | 当前过程值 |
返回值
无返回值。
说明
- 一般不需要用它。要运算并拿输出请直接用
ctrl:loop(input)。
ctrl:loop
lua
ctrl:loop(input) --> number传入当前过程值,执行一次 PID 运算并返回输出。这是主要的驱动函数。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | input | number | 是 | — | 当前过程值 |
返回值
| 类型 | 说明 |
|---|---|
| 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),并把整定出的参数自动应用到控制器。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | callback | function | 是 | — | 整定完成回调,入参 (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。
注意事项:同一份脚本要跨平台时,先判平台再选闭环方案。