第13章

实战案例

这一章做什么

把前面的知识串起来,完整走几遍「需求 → 建模 → 设计 → 仿真 → 实现要点」。每个案例都标出了用到的章节,遇到不熟的地方可以跳回去看。案例里的数字都来自仿真(代码给出),换成你自己设备的参数照样能用。

  • 案例一:直流电机速度控制和位置控制(PI、串级、抗饱和、采样频率);
  • 案例二:加热器温度控制(阶跃测试、SIMC、只能加热、抗饱和、安全保护);
  • 案例三:小车倒立摆 / 平衡小车(状态空间、LQR、非线性仿真、执行器限幅);
  • 案例四:多旋翼姿态控制的思路(串级、滤波、实际坑);
  • 最后是一份从需求到上线的流程清单。

案例一:直流电机的速度与位置控制

用到:第2章 直流电机模型、第3章 一阶系统、第6章 PI、第8章 采样周期、第9章 抗饱和与串级。

1. 需求与模型

一台小型有刷直流电机,用 PWM 驱动(等效电压 ±12 V),编码器测转角和转速。要求:速度阶跃响应快且无明显超调,加负载时转速很快恢复;位置控制不能有超调。

参数值
电枢电阻 \(R\)、电感 \(L\)2 Ω、1 mH
转矩系数 \(K_t\)、反电动势系数 \(K_e\)0.05 N·m/A、0.05 V·s/rad
转动惯量 \(J\)、粘滞摩擦 \(b\)2×10⁻⁵ kg·m²、1×10⁻⁵ N·m·s/rad

按第2章的公式:\(K=\dfrac{K_t}{Rb+K_tK_e}=19.84\) (rad/s)/V,\(\tau=\dfrac{RJ}{Rb+K_tK_e}=15.9\) ms。电气时间常数 \(L/R=0.5\) ms,比 \(\tau\) 小 30 多倍,设计速度环时可以忽略。12 V 时空载转速约 238 rad/s(约 2270 rpm)。

$$\frac{\Omega(s)}{U(s)}\approx\frac{19.84}{0.0159s+1}$$

2. 速度环:PI,零极点对消

取 PI 的积分时间等于对象时间常数(\(T_i=\tau\)),PI 的零点正好抵消对象的极点,开环变成 \(\dfrac{K_pK}{\tau s}\),闭环是一阶的:

$$\Phi(s)=\frac{1}{\tau_{cl}s+1},\qquad \tau_{cl}=\frac{\tau}{K_pK}$$

希望闭环时间常数 \(\tau_{cl}=5\) ms(比开环快约 3 倍):\(K_p=\dfrac{\tau}{K\tau_{cl}}=0.16\) V/(rad/s),\(K_i=K_p/T_i=10.08\) V/rad。忽略延迟时相位裕度为 90°。

采样频率:截止频率约 \(1/\tau_{cl}=200\) rad/s。取 1 kHz,零阶保持损失 \(\omega_cT_s/2\approx5.7^\circ\),再加上计算延迟和被忽略的电气时间常数,相位裕度仍有 70° 以上(第8章 8.6 节)。

3. 仿真:电压限幅和抗饱和

速度设定从 0 阶跃到 100 rad/s。开头误差很大,\(K_p\times100=16\) V 超过了 12 V,电压饱和约 3 ms:

做法超调调节时间(2%)
不做抗饱和1.2%13 ms
条件积分(饱和时停止积分)037 ms
反算法抗饱和,\(T_t=T_i\)016 ms
设定值 12 ms 斜坡 + 条件积分023 ms(含斜坡时间),最大电压 9.6 V,不饱和

这个例子说明:饱和时间很短时,条件积分反而让收尾变慢(积分器在饱和期间完全停住,之后要慢慢补回来)。反算法或设定值斜坡效果更好。如果是长时间饱和(大幅加速、温度控制),条件积分就很合适。

负载扰动:转速稳定在 100 rad/s 时,突然加 0.01 N·m 的负载转矩,转速最低掉到 97.8 rad/s,然后靠积分作用恢复到 99.95 rad/s。如果只用 P 控制,会留下稳态误差(第3章 扰动误差)。

4. 位置环:串级

外环用 P 控制,输出作为速度环的设定值:\(\omega_{ref}=K_{pos}(\theta_{ref}-\theta)\),并把 \(\omega_{ref}\) 限制在 ±200 rad/s。内环(速度环)近似为 \(\frac{1}{\tau_{cl}s+1}\),外环对象就是 \(\dfrac{1}{s(\tau_{cl}s+1)}\)。

按「外环比内环慢 3~10 倍」,取 \(K_{pos}=40\) (rad/s)/rad,外环截止频率约 40 rad/s,是内环的 1/5。闭环特征方程 \(\tau_{cl}s^2+s+K_{pos}=0\),阻尼比 \(\xi=\dfrac{1}{2\sqrt{K_{pos}\tau_{cl}}}\approx1.1\),过阻尼,不会超调,正好满足定位要求。

import numpy as np

R, Lm, Kt, Ke, J, b = 2.0, 1e-3, 0.05, 0.05, 2e-5, 1e-5
K = Kt / (R * b + Kt * Ke)                  # 19.84 (rad/s)/V
tau = R * J / (R * b + Kt * Ke)             # 15.9 ms
Kp = tau / (K * 0.005); Ki = Kp / tau       # 速度环 PI,闭环时间常数 5 ms


class PI:
    def __init__(s, kp, ki, ts, umin, umax):
        s.kp, s.ki, s.ts, s.umin, s.umax, s.i = kp, ki, ts, umin, umax, 0.0

    def update(s, r, y):
        e = r - y
        u_raw = s.kp * e + s.ki * s.i
        u = min(max(u_raw, s.umin), s.umax)
        if u == u_raw or (u_raw > s.umax and e < 0) or (u_raw < s.umin and e > 0):
            s.i += e * s.ts                    # 条件积分抗饱和
        return u


def run(T, theta_ref, Kpos=40.0, wmax=200.0):
    dt, Ts = 1e-5, 1e-3                       # 电机模型 10 µs 积分,控制器 1 kHz
    i = w = th = u = 0.0
    speed_pi = PI(Kp, Ki, Ts, -12, 12)
    log = []
    for k in range(int(T / dt)):
        t = k * dt
        if k % int(Ts / dt) == 0:             # 每 1 ms:位置环 → 速度环 → 电压
            w_ref = float(np.clip(Kpos * (theta_ref(t) - th), -wmax, wmax))
            u = speed_pi.update(w_ref, w)
            log.append((t, th, w, u))
        di = (u - R * i - Ke * w) / Lm        # 电路
        dw = (Kt * i - b * w) / J             # 机械
        i += dt * di; w += dt * dw; th += dt * w
    return np.array(log)


log = run(0.4, lambda t: 2 * np.pi if t >= 0.01 else 0.0)
print(log[:, 1].max())                        # 6.283:没有超调

5. 实现要点

案例二:加热器温度控制

用到:第10章 阶跃测试与两点法、第9章 SIMC 整定与抗饱和、第8章 采样周期、第12章 开关输出与回差。适用于 3D 打印机喷头和热床、小烤箱、恒温箱、热水器等。

1. 阶跃测试

在室温(25 °C)下把加热功率从 0 一下子调到 20%,记录温度直到稳定。假设得到:温度最终升高 60 °C,升高 17 °C(28.3%)用了 28 s,升高 37.9 °C(63.2%)用了 68 s。按两点法:

$$K=\frac{60}{20}=3\ \text{°C/\%},\qquad T=1.5\times(68-28)=60\ \text{s},\qquad L=68-60=8\ \text{s}$$

2. 整定

SIMC(\(\tau_c=L=8\) s):

$$K_p=\frac{T}{K(\tau_c+L)}=\frac{60}{3\times16}=1.25\ \text{\%/°C},\qquad T_i=\min(60,\ 64)=60\ \text{s},\qquad K_i=\frac{K_p}{T_i}\approx0.0208$$

采样周期 1 s 足够:截止频率约 \(1/(\tau_c+L)\approx0.06\) rad/s,\(\omega_cT_s\approx0.06\)。温度对象一般不需要 D。

3. 只能加热:抗饱和必不可少

加热器只能输出 0~100%,不能制冷。从 25 °C 升到 200 °C 时,开头误差 175 °C,\(K_p\times175\approx219\%\),远超 100%,功率会饱和很长时间:

在下面的 里对比(温度用相对室温的增量表示,设定值 175 对应 200 °C;扰动按钮模拟风扇打开、带走 10% 的加热功率):

4. 输出与安全

案例三:小车倒立摆(平衡小车)

用到:第2章 线性化、第11章 状态空间与 LQR、第8章 采样、第11章 互补滤波。两轮平衡车、平衡机器人的控制思路相同,只是模型里的参数和输入(车轮转矩)不同。

1. 模型

小车质量 \(M=0.5\) kg,摆杆末端质量 \(m=0.2\) kg,摆长 \(l=0.3\) m,水平推力 \(F\) 为输入。\(\theta\) 从竖直向上量起,向 \(+x\) 方向倾斜为正。由拉格朗日方程得到非线性模型,在 \(\theta=0\)(竖直向上)附近线性化(第2章):

$$\ddot x=\frac{F-mg\theta}{M},\qquad \ddot\theta=\frac{(M+m)g\theta-F}{Ml}$$

取状态 \(z=[x,\ \dot x,\ \theta,\ \dot\theta]^T\):

$$A=\begin{bmatrix}0&1&0&0\\0&0&-\frac{mg}{M}&0\\0&0&0&1\\0&0&\frac{(M+m)g}{Ml}&0\end{bmatrix},\qquad B=\begin{bmatrix}0\\\frac1M\\0\\-\frac{1}{Ml}\end{bmatrix}$$

开环极点 0、0、±6.77:有一个正实极点,不加控制一定会倒。能控性矩阵满秩,所以可以用状态反馈镇定。

2. LQR 设计

最在乎角度(权重 100),其次是位置(10),速度和角速度各 1,控制力权重 1:

import numpy as np
import control as ct

M, m, l, g = 0.5, 0.2, 0.3, 9.81
A = np.array([[0, 1, 0, 0],
              [0, 0, -m * g / M, 0],
              [0, 0, 0, 1],
              [0, 0, (M + m) * g / (M * l), 0]])
B = np.array([[0], [1 / M], [0], [-1 / (M * l)]])
print(np.linalg.eigvals(A))                  # 0, 0, ±6.766:不稳定
print(np.linalg.matrix_rank(ct.ctrb(A, B)))  # 4:能控

Q = np.diag([10.0, 1.0, 100.0, 1.0])          # 位置、速度、角度、角速度的权重
R = np.array([[1.0]])                         # 控制力的权重
K, P, E = ct.lqr(A, B, Q, R)
print(K)     # [[ -3.162  -3.726 -26.848  -4.094]]
print(E)     # 闭环极点 -8.70±2.49j, -1.22±1.02j

控制律 \(F=-Kz=3.16x+3.73\dot x+26.8\theta+4.09\dot\theta\)。符号的含义:摆向 \(+x\) 倾斜(\(\theta>0\))时,小车向 \(+x\) 推,「追到摆的下面」把它扶正,这和人用手托住竖直木棍的动作一样。

3. 在非线性模型上验证

线性化模型只在小角度附近准确,所以要在完整的非线性模型上仿真:

from scipy.integrate import solve_ivp

def pendulum(t, z, Fmax):
    x, v, th, w = z
    F = float(np.clip(-K[0] @ z, -Fmax, Fmax))   # 状态反馈 + 推力限幅
    s, c = np.sin(th), np.cos(th)
    acc = (F + m * s * (l * w**2 - g * c)) / (M + m * s**2)
    return [v, acc, w, (g * s - acc * c) / l]

sol = solve_ivp(pendulum, [0, 6], [0, 0, 0.3, 0], args=(np.inf,), max_step=0.002)
print(sol.y[2, -1])      # 6 s 后角度 ≈ 0:稳住了
初始角度结果最大推力小车最远移动
0.1 rad(5.7°)稳住2.7 N0.09 m
0.3 rad(17°)稳住8.1 N0.29 m
0.6 rad(34°)稳住16.1 N0.68 m

执行器限幅:推力不够大时,能救回来的初始角度会变小。仿真结果:推力限 ±2 N 时约 0.26 rad(15°),±5 N 时约 0.58 rad(33°),±10 N 时约 0.88 rad(50°);不限推力时约 1.14 rad(66°),再大就超出了线性化模型的适用范围,线性控制器也救不回来。设计时要按「可能遇到的最大扰动」检查推力够不够。

采样频率:把连续设计的 \(K\) 直接用在零阶保持的数字控制器上,采样周期 100 ms 时闭环仍然稳定,120 ms 时就不稳定了。但这还没算计算延迟、传感器滤波和电机的响应时间。按第8章的经验 \(\omega_cT_s\approx0.1\sim0.3\)(最快的闭环极点约 9 rad/s),采样周期至少要 10~30 ms;实际的平衡车通常用 200 Hz~1 kHz,给延迟和噪声留足余量。

4. 实现要点

案例四:多旋翼姿态控制的思路

用到:第9章 串级与微分滤波、第8章 采样频率、第11章 传感器融合。

从需求到上线:流程清单

  1. 明确需求:被控量、精度、响应速度、允许的超调、扰动类型、执行器能力、安全要求;
  2. 建模:能写物理模型就写(第2章);写不出就做实验辨识(第10章);至少要知道增益、主要时间常数、延迟;
  3. 选结构:PI 还是 PID;要不要串级、前馈;多变量、不稳定对象考虑状态反馈(第9章、第11章);
  4. 设计参数:SIMC、零极点对消、频域校正、极点配置、LQR;检查相位裕度 45°~60°、幅值裕度 6 dB 以上(第5章);
  5. 考虑数字实现:采样频率、计算延迟、抗混叠、量化(第8章);
  6. 仿真:加上限幅、噪声、延迟、扰动、参数误差(±20%)再跑一遍(第7章);
  7. 实现:按第9章的清单写代码;加日志和保护;
  8. 上机调试:先开环确认方向和单位;小增益起步;逐步加大;记录曲线;
  9. 验收与维护:在不同工况下测试;记录最终参数和测试曲线;设备磨损或环境变化后定期检查。

下一步学什么

学完这十三章,已经能独立完成大多数单回路、串级、简单状态反馈的控制任务。继续深入可以看这些方向(以后也会补充到这份笔记里):