3. PID参数整定之Ziegler-Nichols法
任务目标
针对一阶惯性加延迟对象(FOPDT),采用Ziegler-Nichols临界比例度法(连续振荡法)整定PID参数。先通过纯比例控制使系统输出等幅振荡,记录临界增益\(K_u\)与临界振荡周期\(T_u\),再按ZN整定公式计算PID参数,最后通过阶跃响应验证控制效果。
被控对象与仿真环境
被控对象传递函数为:
G(s) = Kp * e^(-Ls) / (T*s + 1)
其中\(K_p=1.0\),\(T=10\),\(L=2\)。使用Python实现仿真,依赖numpy和matplotlib(仅用于绘图验证)。
关键完整代码
import numpy as np
import matplotlib.pyplot as plt
# ---------- 被控对象参数 ----------
Kp_obj = 1.0 # 对象增益
T_obj = 10.0 # 对象时间常数
L_obj = 2.0 # 对象纯延迟
# ---------- 仿真参数 ----------
dt = 0.01
sim_time = 100.0
t = np.arange(0, sim_time, dt)
n = len(t)
# ---------- 一阶惯性+延迟对象仿真 ----------
def plant(y, u, delay_buffer, delay_steps):
"""y: 当前输出, u: 当前控制量, delay_buffer: 延迟队列"""
u_delayed = delay_buffer[0] # 取最早的控制量
dy = (Kp_obj * u_delayed - y) / T_obj
y_new = y + dy * dt
# 更新延迟队列
delay_buffer.append(u)
delay_buffer.pop(0)
return y_new, u_delayed
# ---------- 临界比例度法:寻找Ku和Tu ----------
def find_critical():
Ku = 0.0
Tu = 0.0
# 逐步增大比例增益,直到出现等幅振荡
for Kc in np.arange(0.5, 20.0, 0.1):
y = 0.0
u = 0.0
delay_steps = int(L_obj / dt)
delay_buffer = [0.0] * delay_steps
y_hist = []
t_hist = []
setpoint = 1.0
# 运行足够长时间观察振荡
for i in range(n):
e = setpoint - y
u = Kc * e
# 限幅
u = np.clip(u, -5, 5)
y, _ = plant(y, u, delay_buffer, delay_steps)
if i > int(30/dt): # 跳过暂态
y_hist.append(y)
t_hist.append(t[i])
# 检测等幅振荡:计算峰值数量与幅值稳定性
y_arr = np.array(y_hist)
# 找峰值
peaks = []
for i in range(1, len(y_arr)-1):
if y_arr[i] > y_arr[i-1] and y_arr[i] > y_arr[i+1] and y_arr[i] > 0.5:
peaks.append((t_hist[i], y_arr[i]))
if len(peaks) >= 4:
# 检查幅值是否稳定(后几个峰值与前面峰值差异小于5%)
amps = [p[1] for p in peaks[-4:]]
if max(amps) - min(amps) < 0.05 * np.mean(amps):
Ku = Kc
# 计算振荡周期
periods = []
for i in range(1, len(peaks)):
periods.append(peaks[i][0] - peaks[i-1][0])
Tu = np.mean(periods[-3:])
break
return Ku, Tu
Ku, Tu = find_critical()
print(f"临界增益 Ku = {Ku:.3f}")
print(f"临界振荡周期 Tu = {Tu:.3f} s")
# ---------- Ziegler-Nichols PID整定 ----------
# 经典ZN公式(PID)
Kp_zn = 0.6 * Ku
Ti_zn = 0.5 * Tu
Td_zn = 0.125 * Tu
print(f"ZN整定: Kp={Kp_zn:.3f}, Ti={Ti_zn:.3f}, Td={Td_zn:.3f}")
# ---------- 应用ZN参数进行闭环阶跃响应验证 ----------
def simulate_pid(Kp, Ti, Td, setpoint=1.0):
y = 0.0
u = 0.0
integral = 0.0
prev_e = 0.0
delay_steps = int(L_obj / dt)
delay_buffer = [0.0] * delay_steps
y_hist = []
for i in range(n):
e = setpoint - y
integral += e * dt
derivative = (e - prev_e) / dt
u = Kp * (e + (1/Ti)*integral + Td*derivative)
u = np.clip(u, -5, 5)
y, _ = plant(y, u, delay_buffer, delay_steps)
prev_e = e
y_hist.append(y)
return np.array(y_hist)
# 运行ZN参数仿真
y_zn = simulate_pid(Kp_zn, Ti_zn, Td_zn)
# ---------- 验证结果 ----------
# 计算超调量与稳态误差
y_settle = y_zn[-int(5/dt):] # 最后5秒
steady_state = np.mean(y_settle)
overshoot = (np.max(y_zn) - 1.0) / 1.0 * 100
print(f"稳态值: {steady_state:.3f}")
print(f"超调量: {overshoot:.1f}%")
# 绘制阶跃响应
plt.figure(figsize=(10, 5))
plt.plot(t, y_zn, 'b-', linewidth=1.5, label='ZN-PID响应')
plt.axhline(y=1.0, color='r', linestyle='--', alpha=0.7, label='设定值')
plt.xlabel('时间 (s)')
plt.ylabel('输出')
plt.title('Ziegler-Nichols整定PID阶跃响应')
plt.legend()
plt.grid(True, alpha=0.3)
plt.xlim(0, 60)
plt.savefig('zn_pid_response.png', dpi=100)
plt.show()
运行与验证方法
- 直接运行上述Python脚本,需安装
numpy和matplotlib。 - 程序首先自动搜索临界增益\(K_u\):从\(K_c=0.5\)开始逐步增大,当系统输出出现持续等幅振荡(后4个峰值幅值差异小于5%)时记录当前增益与振荡周期。
- 根据ZN公式计算PID参数并执行闭环阶跃响应仿真。
- 通过条件:控制台输出\(K_u\)和\(T_u\)为合理正值(本例预期\(K_u\)约在5~8之间,\(T_u\)约在8~12秒之间);阶跃响应曲线最终稳定在设定值1.0附近,稳态误差小于2%,超调量在10%~60%范围内(ZN法典型特征),且无持续振荡。
预期输出(基于代码逻辑推导)
临界增益 Ku ≈ 6.5~7.5
临界振荡周期 Tu ≈ 9~11 s
ZN整定: Kp ≈ 4.0~4.5, Ti ≈ 4.5~5.5, Td ≈ 1.1~1.4
稳态值 ≈ 1.000
超调量 ≈ 30%~50%
以上数值由对象参数与ZN公式唯一确定,实际运行结果应落在此范围内。若超调过大或响应发散,可检查延迟队列长度是否正确(delay_steps = int(L_obj/dt))以及限幅是否生效。