从零打造可落地的直流电机 PID 驱动系统 (十一):控制超调【完整工程源码 + 可直接复用】 一、“超调” 的含义结合 PID 电机控制场景在自动控制尤其是你图片中的PID 电机控制领域超调Overshoot是描述系统响应特性的核心指标指当系统响应目标设定值时输出量超过目标设定值的现象。二、结合电机控制的具体举例使用 “电机启动场景” 来举例 假设你设定电机的目标转速是1000 转 / 分钟RPM给电机发送启动指令后电机转速快速上升试图追上 1000 转的目标但由于 PID 参数设置不合理比如比例项 P 太大导致控制力度过猛转速没有平稳停在 1000 转而是冲到了 1200 转 / 分钟随后系统才通过 PID 的反馈调节把转速拉回 1000 转并稳定。在这个过程中转速从 1000 转冲到 1200 转的行为就是超调超出的 200 转就是超调的幅度对应的 “超调量” 为(1200-1000)/1000 × 100% 20%你图片里说的 “启动时超调严重”就是指这个超出的幅度很大比如超调量超过 30% 甚至更多会带来机械冲击大、系统不稳定等问题。验证来源超调的定义来自自动控制原理的标准理论参考经典教材《自动控制原理》胡寿松 主编第七版中 “线性系统的时域分析” 章节其中明确了阶跃响应的性能指标 超调量σ%的定义为其中cmax​ 是系统阶跃响应的最大值c(∞) 是系统的稳态值即目标设定值这个指标就是用来量化超调的严重程度。一、先搞懂PID 每个参数是怎么影响 “超调” 的以场景中的电机转速闭环控制为例目标是让电机转速快速、平稳地达到设定值比如 1000 转 / 分钟超调就是转速超过 1000 转的部分。PID 控制的位置式公式你之前提到的 u(k)Kp​⋅e(k)Ki​⋅∑i0k​e(i)Kd​⋅(e(k)−e(k−1)) 其中设定转速实际转速当前误差Kp​比例系数PKi​积分系数IKd​微分系数D每个参数对 “超调” 的影响是调整的核心依据参数对超调的影响直观理解调整逻辑Kp​比例Kp​越大控制力度越强电机转速冲得越快但超调会明显增大甚至出现持续振荡。因为接近目标转速时误差变小控制力度不会立刻降低容易 “冲过头”。先设小逐步增大以响应速度为参考同时避免超调失控。Ki​积分Ki​的作用是消除 “稳态误差”比如电机稳定后转速和设定值差几转但Ki​过大会导致积分饱和系统快到目标值时过去的误差还在累积控制输出仍然很大强行把转速推过目标值超调量显著增大甚至出现二次超调。必须在 P 和 D 调好后再加入以 “消除稳态误差但不增大超调” 为目标。Kd​微分Kd​是超调的 “天敌”它会检测误差的变化速度当转速快速接近目标值误差快速减小时微分项会产生一个反向的 “刹车力”提前降低控制输出防止转速冲过目标值显著减小超调量抑制振荡。但Kd​过大会放大噪声导致电机高频抖动。加入 D 的核心目的就是抑制超调是调整的关键步骤。二、一步一步调整 PID 参数减小超调工程凑试法电机控制通用这个方法是工业上最常用的 “凑试法”按顺序调整避免参数互相干扰目标是响应快 超调小 稳态误差为 0。步骤 1关闭 I 和 D只调 P打基础初始设置Ki0Kd0Kp设为一个很小的值比如 0.1具体数值和你的电机、驱动有关先从小的来。给电机一个阶跃指令比如从 0 转直接设为 1000 转观察转速变化此时是纯比例控制电机转速会慢慢上升稳定后会有稳态误差比如稳定在 900 转达不到 1000 转且超调可能很小但响应很慢。逐步增大Kp每次 0.1重复阶跃测试当Kp增大到某个值时转速会开始出现 “冲过目标值再回来” 的现象比如冲到 1100 转再降到 1000 转此时超调开始出现。继续增大Kp直到转速出现等幅振荡比如在 900-1100 转之间来回波动不发散也不收敛记录此时的Kp为Kp0振荡周期为T0这是临界比例度法的参考值后续调整会用到。此时Kp可以先降到Kp0的 60%-70%比如Kp01降到 0.6-0.7作为 P 的初始值此时系统响应快超调有但不算太夸张。步骤 2加入 D重点减小超调关键步骤保持刚才调好的Kp不变Ki还是 0开始增大Kd每次 0.01小步调整因为 D 对噪声敏感。重复阶跃测试观察转速响应曲线的变化随着Kd增大你会发现转速冲到目标值的速度变慢了一点但冲过目标值的幅度超调明显减小振荡次数也变少了。比如之前超调 30%冲到 1300 转现在降到 10% 以内冲到 1100 转以内这就是 D 的 “刹车” 效果。调整到什么程度超调量明显减小比如 10%同时电机没有出现高频抖动比如转速在 1000 转附近微小波动不是高频跳变。如果Kd太大会出现 “转速一直抖” 的情况此时要把Kd往回调一点。步骤 3加入 I消除稳态误差同时控制超调保持调好的Kp和Kd不变开始增大Ki每次 0.001小步调整因为 I 对超调影响很大。重复阶跃测试观察两个关键指标稳态误差比如之前稳定在 950 转现在会慢慢升到 1000 转说明积分在起作用。超调量如果Ki太大你会发现超调又变大了甚至出现 “冲过目标值回来后又冲一次” 的二次超调这就是积分饱和的表现。调整目标稳态误差消除转速稳定在 1000 转误差为 0。超调量没有明显增大比如保持在 5%调节时间从启动到稳定的时间也没有变长太多。如果超调变大就减小Ki或者稍微增大一点Kd来抑制。步骤 4微调三个参数平衡性能微调Kp如果响应太慢比如从 0 到 1000 转花了 2 秒可以稍微增大Kp但如果超调又变大了就再增大一点Kd。微调Ki如果稳态误差消除太慢比如稳定后过了 10 秒才到 1000 转可以稍微增大Ki但要注意超调有没有变大。最终的 “好参数” 标准响应速度快达到目标转速的时间短。超调量小比如 5%几乎看不到冲过头。稳态误差为 0稳定后转速和设定值一致。没有持续振荡或高频抖动。三、响应曲线示意图对比 “超调大” 和 “超调小” 的情况横轴是时间 t纵轴是电机转速 n (t)设定目标转速为N_set1000转/分钟用 ASCII 画两条典型的阶跃响应曲线曲线 1超调严重P 太大、D 太小、I 太大的坏参数特点转速冲到 1300 转超调 30%然后反复振荡很久才稳定超调严重。实现代码import numpy as np # 导入数值计算库numpy并重命名为npPython科学计算标准库 import matplotlib.pyplot as plt # 导入绘图库matplotlib的pyplot模块并重命名为pltPython绘图标准库 # -------------------------- 绘图中文显示配置 -------------------------- plt.rcParams[font.sans-serif] [SimHei]# 设置matplotlib默认字体为Windows黑体解决中文乱码问题 plt.rcParams[axes.unicode_minus] False # 解决坐标轴负号-显示为方框的问题 # -------------------------- 1. 二阶系统控制参数定义对应坏PID参数P太大、D太小、I太大 -------------------------- N_set 1000 # 系统目标值设定值对应原图的虚线基准值 n0 300 # 系统初始值曲线的起始数值 zeta 0.35 # 阻尼比控制振荡的衰减速度数值越小振荡越剧烈对应D太小 wn 0.25 # 自然频率控制系统响应的快慢数值越大响应越快对应P太大 # -------------------------- 2. 导入原始文本图的离散测量数据 -------------------------- # 时间轴数据从左到右的时间序列对应原图的水平位置 t_measured np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]) # 被控量数据每个时间点对应的测量值对应原图中*号的垂直位置 n_measured np.array([300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1200, 1100, 1000, 900, 800]) # -------------------------- 3. 定义二阶欠阻尼系统阶跃响应计算函数 -------------------------- # 函数功能根据控制理论公式计算连续的系统响应曲线 # 输入参数t-时间n0_val-初始值n_set-目标值zeta_val-阻尼比wn_val-自然频率 def second_order_response(t, n0_val, n_set, zeta_val, wn_val): delta n_set - n0_val # 计算阶跃输入的幅值目标值-初始值 omega_d wn_val * np.sqrt(1 - zeta_val**2) # 计算阻尼自然频率控制振荡周期 phi np.arctan(np.sqrt(1 - zeta_val**2) / zeta_val) # 计算响应的相位角 # 核心公式二阶欠阻尼系统阶跃响应标准公式计算每个时间点的系统输出值 response n0_val delta * (1 - (1 / np.sqrt(1 - zeta_val**2)) * np.exp(-zeta_val * wn_val * t) * np.sin(omega_d * t phi)) return response # 返回计算得到的系统响应值 # -------------------------- 4. 生成平滑的连续波形数据 -------------------------- t_continuous np.linspace(0, 30, 500) # 生成0~30的500个连续时间点让曲线平滑 # 调用函数计算连续时间点对应的系统响应值生成平滑波形 n_continuous second_order_response(t_continuous, n0, N_set, zeta, wn) # -------------------------- 5. 绘制波形图完全匹配图片描述 -------------------------- # 创建画布对象替代原写法彻底消除PyCharm报警 fig plt.figure() # 设置画布尺寸宽度12英寸高度7英寸 fig.set_size_inches(12, 7) # 绘制原始离散测量点蓝色星号标记原图的所有数据点 plt.scatter(t_measured, n_measured, colorblue, marker*, s120, label文本图中的测量点, zorder3) # 绘制连续平滑波形红色实线还原真实的系统振荡曲线 plt.plot(t_continuous, n_continuous, colorred, linewidth2, label实际二阶系统响应拟合, zorder2) # 绘制目标值虚线黑色虚线标记系统的基准设定值 plt.axhline(yN_set, colorblack, linestyle--, linewidth1.5, labelf目标值 N_set{N_set}) # 在图中添加文字标注对应系统响应的4个阶段调整后更贴合图片过程 plt.text(1, 200, 启动阶段, hacenter, fontsize12, colorgreen) # 标注启动阶段 plt.text(7, 1150, 冲过目标超调上升, hacenter, fontsize12, colororange) # 标注超调上升阶段 plt.text(10, 1320, 超调30%\n(目标值1000 → 峰值1300), hacenter, fontsize11, colordarkred, fontweightbold) # 标注超调峰值 plt.text(18, 1150, 反复振荡阶段, hacenter, fontsize12, colorred) # 标注振荡阶段 plt.text(26, 1050, 慢慢稳定衰减缓慢, hacenter, fontsize12, colorpurple) # 标注稳定阶段 # 设置坐标轴标签和字体大小 plt.xlabel(时间 t, fontsize13) # 设置横轴标签时间 plt.ylabel(被控量 n(t), fontsize13) # 设置纵轴标签被控量 # 设置图表标题匹配图片描述 plt.title(超调严重的系统阶跃响应P太大、D太小、I太大的坏参数, fontsize15, pad15) # 显示网格线虚线样式透明度0.6 plt.grid(visibleTrue, linestyle--, alpha0.6) # 显示图例解释图中线条/点的含义 plt.legend(fontsize11) # 自动调整图表布局防止文字被截断 plt.tight_layout(rect(0, 0.05, 1, 1)) # 给底部说明文字留出空间 # 标注系统响应特点与图片文字说明完全一致 plt.figtext(0.5, 0.02, 特点转速冲到1300转超调30%然后反复振荡很久才稳定超调严重。, hacenter, fontsize12, colorblack, wrapTrue) # 显示绘制好的波形图弹出独立窗口 plt.show() # PyCharm帮助文档链接无实际运行功能 # See PyCharm help at https://www.jetbrains.com/help/pycharm/曲线 2超调很小调好的 PID 参数特点转速只冲到 1010 转超调 10% 以内几乎没有振荡很快稳定在目标值这就是我们调整 PID 想要的效果。实现代码import numpy as np # 导入数值计算库numpy并重命名为npPython科学计算标准库 import matplotlib.pyplot as plt # 导入绘图库matplotlib的pyplot模块并重命名为pltPython绘图标准库 # -------------------------- 绘图中文显示配置 -------------------------- plt.rcParams[font.sans-serif] [SimHei]# 设置matplotlib默认字体为Windows黑体解决中文乱码问题 plt.rcParams[axes.unicode_minus] False # 解决坐标轴负号-显示为方框的问题 # -------------------------- 1. 二阶系统控制参数定义对应调好的PID参数超调小、无振荡 -------------------------- N_set 1000 # 系统目标值设定值对应原图的虚线基准值 n0 930 # 系统初始值曲线的起始数值匹配原图 zeta 0.8 # 阻尼比控制振荡的衰减速度ζ0.8对应超调≈1.5%几乎无振荡 wn 0.3 # 自然频率控制系统响应的快慢数值越大响应越快匹配原图快速稳定的趋势 # -------------------------- 2. 导入原始文本图的离散测量数据完全匹配原图*号 -------------------------- # 时间轴数据从左到右的时间序列对应原图的水平位置 t_measured np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]) # 被控量数据每个时间点对应的测量值对应原图中*号的垂直位置 n_measured np.array([930, 940, 950, 960, 970, 980, 990, 1000, 1010, 1020, 1010, 1000, 990, 980]) # -------------------------- 3. 定义二阶欠阻尼系统阶跃响应计算函数已改名微调消除重复报警 -------------------------- # 函数功能根据控制理论公式计算连续的系统响应曲线 # 输入参数t-时间n0_val-初始值n_set-目标值zeta_val-阻尼比wn_val-自然频率 def second_order_response_for_tuned_pid(t, n0_val, n_set, zeta_val, wn_val): # 阶跃输入的幅值目标值-初始值 delta n_set - n0_val # 阻尼自然频率控制振荡周期 omega_d wn_val * np.sqrt(1 - zeta_val**2) # 计算响应的相位角 phi np.arctan(np.sqrt(1 - zeta_val**2) / zeta_val) # 核心公式二阶欠阻尼系统阶跃响应标准公式计算每个时间点的系统输出值 response n0_val delta * (1 - (1 / np.sqrt(1 - zeta_val**2)) * np.exp(-zeta_val * wn_val * t) * np.sin(omega_d * t phi)) return response # 返回计算得到的系统响应值 # -------------------------- 4. 生成平滑的连续波形数据 -------------------------- t_continuous np.linspace(0, 30, 500) # 生成0~30的500个连续时间点让曲线平滑 # 调用函数计算连续时间点对应的系统响应值生成平滑波形已改为新函数名 n_continuous second_order_response_for_tuned_pid(t_continuous, n0, N_set, zeta, wn) # -------------------------- 5. 绘制波形图已修改初始化写法消除重复报警 -------------------------- # 合并创建画布和设置尺寸避免和之前的代码重复 plt.figure(figsize(12, 7)) # 绘制原始离散测量点蓝色星号标记原图的所有数据点 plt.scatter(t_measured, n_measured, colorblue, marker*, s120, label文本图中的测量点, zorder3) # 绘制连续平滑波形红色实线还原真实的系统响应曲线 plt.plot(t_continuous, n_continuous, colorred, linewidth2, label实际二阶系统响应拟合, zorder2) # 绘制目标值虚线黑色虚线标记系统的基准设定值 plt.axhline(yN_set, colorblack, linestyle--, linewidth1.5, labelf目标值 N_set{N_set}) # 在图中添加文字标注对应系统响应的关键阶段适配原图 plt.text(1, 925, 启动阶段, hacenter, fontsize12, colorgreen) # 标注启动阶段 plt.text(9, 1030, 轻微超调1020转超调2%, hacenter, fontsize11, colordarkred, fontweightbold) # 标注超调峰值 plt.text(12, 1005, 几乎无振荡, hacenter, fontsize12, colorred) # 标注振荡情况 plt.text(18, 1002, 快速稳定, hacenter, fontsize12, colorpurple) # 标注稳定阶段 # 设置坐标轴标签和字体大小 plt.xlabel(时间 t, fontsize13) # 设置横轴标签时间 plt.ylabel(被控量 n(t), fontsize13) # 设置纵轴标签被控量 # 设置图表标题完全匹配原图标题 plt.title(曲线2超调很小调好的PID参数, fontsize15, pad15) # 调整纵轴范围和原图一致突出数据细节 plt.ylim(900, 1050) # 显示网格线虚线样式透明度0.6 plt.grid(visibleTrue, linestyle--, alpha0.6) # 显示图例解释图中线条/点的含义 plt.legend(fontsize11) # 自动调整图表布局防止文字被截断给底部说明留出空间 plt.tight_layout(rect(0, 0.05, 1, 1)) # 标注系统响应特点与图片文字说明完全一致 plt.figtext(0.5, 0.02, 特点转速只冲到1010转超调10%以内几乎没有振荡很快稳定在目标值这就是我们调整PID想要的效果。, hacenter, fontsize12, colorblack, wrapTrue) # 显示绘制好的波形图弹出独立窗口 plt.show() # PyCharm帮助文档链接无实际运行功能 # See PyCharm help at https://www.jetbrains.com/help/pycharm/Matplotlibplt.figure用法Matplotlib 官方文档plt.figure支持直接传入figsize参数和fig.set_size_inches功能完全一致。参考https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.figure.html二阶系统响应公式参考《自动控制原理第 7 版》胡寿松科学出版社欠阻尼二阶系统阶跃响应公式验证正确。SimHei 字体配置Matplotlib 官方推荐的 Windows 中文配置方法参考https://matplotlib.org/stable/tutorials/text/text_props.html#non-latin-text四、验证来源验证来源PID 参数对超调的影响理论参考《自动控制原理》胡寿松 主编第七版第 3 章 “线性系统的时域分析”其中明确了 P、I、D 三个环节对系统阶跃响应超调量、调节时间、稳态误差的影响规律。工程凑试法调整步骤参考《过程控制工程》王骥程 主编中 “PID 控制器参数的工程整定方法”其中的 “凑试法” 和 “临界比例度法” 是工业控制中通用的参数调整流程适用于电机、温度、压力等多种闭环控制场景。电机 PID 控制实践参考 STM32 官方电机控制库ST Motor Control SDK的文档其中针对直流电机转速控制的 PID 调优指南明确了先调 P、再调 D、最后调 I 的顺序以及超调抑制的关键参数 D 的作用。