控制系统——基础、导论;以直流电机控制系统为例详解微分方程模型、拉普拉斯变换、传递函数模型的理论、推导及仿真

目录

一、声明

二、前言

三、控制系统------孤立系统的数学建模

[3.1 动态系统和静态系统](#3.1 动态系统和静态系统)

[3.2 直流电机模型-微分方程(differential equations)](#3.2 直流电机模型-微分方程(differential equations))

[3.3 直流电机模型-传递函数(transfer function)](#3.3 直流电机模型-传递函数(transfer function))

[3.3.1 拉普拉斯变换](#3.3.1 拉普拉斯变换)

[3.3.2 拉普拉斯变换matlab演示及结论](#3.3.2 拉普拉斯变换matlab演示及结论)

[3.3.3 控制系统中拉普拉斯极点、零点的物理意义和物理直觉](#3.3.3 控制系统中拉普拉斯极点、零点的物理意义和物理直觉)

[3.3.4 拉普拉斯变换表](#3.3.4 拉普拉斯变换表)

[3.3.5 电机的传递函数](#3.3.5 电机的传递函数)

[3.4 直流电机传递函数模型simulink模拟](#3.4 直流电机传递函数模型simulink模拟)

[3.5 直流电机传递函数模型 matlab实时UI仿真](#3.5 直流电机传递函数模型 matlab实时UI仿真)

四、控制系统------环境中的系统、黑箱思维

[4.1 干扰(Disturbence)](#4.1 干扰(Disturbence))

[4.2 加入干扰的传递模型Simulink模拟](#4.2 加入干扰的传递模型Simulink模拟)

[4.3 黑箱思维](#4.3 黑箱思维)

五、总结

六、人工智能声明及个人声明

七、参考


一、声明

本文基于悉尼大学 ELEC3304 Control 课程学习整理,仅代表个人理解,不包含课程官方材料

本文仅用于学习记录和分享,任何组织、个人不得用于教材等任何商业形式的利用,不得以任何付费形式分享

项目、代码、文件等相关内容绝不得以任何形式用于课程作业

代码和仿真环境为:MATLAB R2026a、Simulink

二、前言

控制系统,指的是我们对一个系统的控制过程。

这个过程包括三个内容:

1:我们要对这个系统产生什么样的输入

2:这个系统获得输入后,会产生什么样的变化

3:系统变化后,会产生什么样的结果

这个过程我们可以简单描述为:

这里的Plant来源于早期专业术语,指的是"固定的机械设备",现在我们用来表示在系统中的实体。

这个实体通常包括执行器与传感器(执行和反馈)。

例如:当我们踩油门的时候,就是给出一个输入;接着车的发动机等执行器产生了动力,并最终导致了车的移动。这就是一个简单的完整系统。

我们在学习系统学时,必须有一个"错误的"信念:

在系统学的世界里,整个世界都是可以被描述为各种系统,我们可以不那么精确的理解、建模、预测并控制万物。

三、控制系统------孤立系统的数学建模

3.1 动态系统和静态系统

对于一个理想的静态系统,我们产生一个输入,系统就会瞬间产生输出。并且这个输出只与我们当前的输入有关。

例如:当我们拔掉灯的电源,灯立刻关闭。这可以近似为静态系统。

为什么要说近似呢?

因为实际上灯不是立刻灭掉的,哪怕这个灯只有一个电阻丝,也会存在感抗和容抗,它实际上是"逐渐"灭掉的。

这个世界上基本不存在理想的静态系统。

当我们踩下刹车,车并不是瞬间停下,这个停下的过程不仅取决于你踩刹车的力度,还取决于汽车当前的速度、汽车的重量、汽车制动的能力、地面的摩擦力、当前的风速等等。

或者换句话说:动态系统状态的改变不仅取决于对系统的控制输入,也取决于系统当前的状态和外界的干扰

3.2 直流电机模型-微分方程(differential equations)

如果我们要对电机(转速电机)接下来的转速做判断,那我们至少需要知道四个关键数值:

输入(input): v(t) 指输入电压

干扰(disturbance): τd​(t) 指外部负载转矩

状态(state): i(t),ω(t) 指当前电流和转速

前人对此建立微分方程模型为:

(3.2.a)

(3.2.b)

(3.2.c)

(3.2.d)

其中,

L是电感;R是电阻;K是电机常数;J是转动惯量;b是阻尼系数;e(t) 为反动电势;T(t) 为电机扭矩。

所以,

式 3.2.a 实际上是电气方程,在描述:施加的电压 = 电感消耗的电压 + 电阻需要的电压 + 反动电势消耗的电压

式 3.2.b 实际上是机械动力方程,在描述:转速的变化速度 = 电机驱动力 - 阻力

式 3.2.c 是反动电势,指的是电机内部产生的负作用感应电压

式 3.2.d 是电机扭矩,指的是系统产生的正作用电机扭矩

(注意,这个微分方程中的L为电感,不是拉普拉斯变换。)

通过这系列微分方程,实际上我们已经对一个独立的动态系统做了完整的过程建模。

从方程中,我们可以很轻松的描述电机的控制过程:

控制器输入电压→电流增加(电感阻止电流快速变化)→电机驱动力(电机扭矩)增加→

→转速增加(转动惯量阻止转速快速变化)→阻尼增加、反动电势增加→转速抑制、电流抑制

所以,整个过程是一个完整闭环,并且最终会趋于一个动态平衡。

这组微分方程真是优雅而美丽,完美诠释了数学的美,看的人多巴胺飙升。

但一个工程师进来看一眼就会说:算起来太麻烦了我不算了。

那有没有适合小学生,让小学生也能计算控制系统的方法呢?

有的兄弟有的,拉普拉斯变换。

3.3 直流电机模型-传递函数(transfer function)

微分方程中的导数计算起来太麻烦了,所以我们干脆把导数给换了。

3.3.1 拉普拉斯变换

想一想还是简单讲一下拉普拉斯变换,实际上这个知识应该是个先修课程。但考虑到这个知识太过于重要,而且很多人其实根本没学懂或者忘了(其实我也忘了),这里就用最简单的方法帮助理解。当然,最基础的知识默认你了解。

我们对一个最简单的指数函数求导:

函数本身不变,只是多乘了一个系数a

所以我们可以理解为,指数函数的求导是一个线性算数。

那我们完全可以用一个线性的方式去表达指数函数的求导。

巧合的是,任何信号都可以拆为若干个指数函数的信号组合。

也就是说,我们可以用指数函数来表达任何信号。

综上所述,信号的复杂导数,都可以视作指数函数乘以一个特征向量。

这里的特征向量可以类比为线性代数的特征向量。

所以,首先:

指数函数是导数算子的特征函数,它只改变大小,不改变任何其他特征

现在我们将特征值定义为s,那么这个理论公式为:

就是特征向量。

因此,控制系统,几乎都可以视作线性时不变系统(LTI system)

我们通过上述理论知道线性的含义,而时不变指的是参数不随时间改变。

当我们将特征向量 输入线性时不变系统,最后的输出一定只是 本身乘以一个倍数。

我们将这个倍数,称之为响应强度

而这个我们可以理解为探寻系统对于不同特征向量输入,作何响应的探针

根据以上推论,拉普拉斯提出了著名的拉普拉斯公式:

先别急着晕,这个公式根本不是让你计算的,理解起来其实也很简单

我们首先看等式右边函数的外层是一个积分,且上下限为零-正无穷。

至于为什么是零-正无穷,这是因为控制系统中不存在负时间。

而里面的x(t),是真实信号;是探针。

对两个函数先乘积再积分,能看出这两个信号的相关性(相似性)。

例如,两个sin(t)的乘积,在时域中永远为正,所以0-∞积分为正无穷。

这代表了两个信号完全一致。

但sinx和cosx的乘积,在时域中为正一段负一段,0-∞积分为-1和1震荡。

这代表了两个信号几乎不一致。

所以该积分事实上是在看0-+∞的时间域里,真实信号和探针信号的相关程度如何(信号像不像)。

这时候有人就说了,躯老师躯老师你水平太差了讲得一坨史,我都不知道你在干嘛,一点都听不懂。

没事,前面你一点都不懂也不影响做题。接下来才是重点。

3.3.2 拉普拉斯变换matlab演示及结论

别发呆了,回过神来。

假设我们的目标信号为:

拉普拉斯变换为:

当探针信号与目标信号为倒数的时候,也就是s=-2,即探针信号为时,乘积为1。

这个时候积分发散,变成无穷大。

我们用matlab验证一下:

Matlab 复制代码
%% 清理工作区
clc; clear; close all;

%% 1. 定义被积函数
f = @(t) exp(-2*t) .* exp(2*t);  % 实际计算结果恒等于 1

%% 2. 设置绘图范围(取 0 到 10 示意,足以看清趋势)
T_max = 10;
t_vals = linspace(0, T_max, 1000);

%% 3. 计算累积积分 F(T) = ∫₀ᵀ f(t) dt
% 方法一:数值积分(适用于任意复杂函数,这里演示用法)
F_vals = arrayfun(@(T) integral(f, 0, T), t_vals);

% 方法二:解析解(因为 f=1,所以 F=T,可以直接用)
F_exact = t_vals;  % 解析解

%% 4. 绘制图形
figure('Position', [100, 100, 800, 600]);

% 子图1:被积函数 f(t)
subplot(2,1,1);
plot(t_vals, f(t_vals), 'b-', 'LineWidth', 2);
xlabel('t');
ylabel('f(t) = e^{-2t} e^{2t}');
title('被积函数(恒等于 1)');
grid on;
ylim([0, 1.5]);  % 稍微留白
xlim([0, T_max]);

% 子图2:积分函数 F(T)
subplot(2,1,2);
plot(t_vals, F_vals, 'r-', 'LineWidth', 2); hold on;
plot(t_vals, F_exact, 'b--', 'LineWidth', 1.5);  % 解析解对照
xlabel('上限 T');
ylabel('F(T) = \int_0^T e^{-2t} e^{2t} dt');
title('积分值随上限 T 的变化(线性增长,趋于 +∞)');
legend('数值积分结果', '解析解 F(T) = T', 'Location', 'northwest');
grid on;
xlim([0, T_max]);

%% 5. 命令行输出结果提示
disp('因为 e^{-2t} * e^{2t} = 1,所以积分结果 F(T) = T');
disp(['当 T = ', num2str(T_max), ' 时,积分值 = ', num2str(F_vals(end))]);
disp('当 T -> +∞ 时,F(T) -> +∞,因此该反常积分发散。');

现在我们更改s的值。

Matlab 复制代码
function interactive_exp_gui()
    %% 交互式演示:改变 s 观察 e^{-2t} * e^{-st} 的变化
    clc; clear; close all;

    % 创建图形窗口
    fig = figure('Name', 'e^{-2t} · e^{-st} 动态演示', ...
                 'NumberTitle', 'off', ...
                 'Position', [200, 200, 800, 500]);

    % 坐标轴(用于绘制曲线)
    ax = axes('Parent', fig, 'Units', 'normalized', ...
              'Position', [0.10, 0.15, 0.70, 0.75]);
    xlabel('t');
    ylabel('f(t) = e^{-(s+2)t}');
    title('被积函数随 s 的变化');
    grid on; hold on;
    xlim([0, 10]);
    ylim([0, 5]);  % 初值

    % 初始化 s 值
    s = 0;
    % 计算并绘制初始曲线
    t_vals = linspace(0, 10, 1000);
    f_vals = exp(-(s+2)*t_vals);
    h_plot = plot(ax, t_vals, f_vals, 'b-', 'LineWidth', 2);

    % 显示 s 和积分值的文本框(注意:这里无法直接使用 sprintf 的换行符,改用 cell)
    txt_info = uicontrol('Style', 'text', ...
                         'String', sprintf('s = %.2f\n积分值 = %.4f', s, 1/(s+2)), ...
                         'Units', 'normalized', ...
                         'Position', [0.85, 0.60, 0.12, 0.15], ...
                         'FontSize', 12, 'BackgroundColor', 'w', ...
                         'HorizontalAlignment', 'left');

    % 创建滑块(控制 s)
    slider = uicontrol('Style', 'slider', ...
                       'Min', -2.5, 'Max', 5, ...
                       'Value', s, ...
                       'SliderStep', [0.01, 0.1], ...
                       'Units', 'normalized', ...
                       'Position', [0.10, 0.05, 0.70, 0.04], ...
                       'Callback', @updatePlot);  % 回调函数句柄

    % 滑块标签(显示范围)
    uicontrol('Style', 'text', 'String', 's = -2.5', ...
              'Units', 'normalized', 'Position', [0.02, 0.02, 0.08, 0.04]);
    uicontrol('Style', 'text', 'String', 's = 5', ...
              'Units', 'normalized', 'Position', [0.82, 0.02, 0.08, 0.04]);

    % ========== 嵌套回调函数 ==========
    function updatePlot(~, ~)
        % 获取当前 s 值
        s_new = get(slider, 'Value');
        
        % 重新计算函数值(注意:t_vals 在父函数中已定义)
        f_vals = exp(-(s_new+2)*t_vals);
        
        % 更新曲线数据
        set(h_plot, 'YData', f_vals);
        
        % 自动调整纵轴范围(避免曲线跑出)
        y_max = max(f_vals) * 1.2;
        if y_max < 0.1
            y_max = 0.5;
        end
        ylim(ax, [0, y_max]);
        
        % 计算积分值(解析解)
        denom = s_new + 2;
        if denom > 0
            integral_val = 1 / denom;
            status = sprintf('积分 = %.4f', integral_val);
        elseif denom == 0
            status = '积分 → ∞ (临界)';
        else
            status = '积分发散 (指数增长)';
        end
        
        % 更新文本信息
        set(txt_info, 'String', sprintf('s = %.2f\n%s', s_new, status));
    end

end

使用方法:在matlab中将以上代码保存为 interactive_exp_gui.m 文件,然后换一个文件运行interactive_exp_gui 函数并运行。

接下来你会看到这个窗口,如图所示,当s=-2时,积分发散(这个点没改描述,并非指数增长)。

拖动下面的滑块可以更改s的值。

当s<-2时,积分发散,并随着s变小,指数增长越来越快。

当s>-2时,积分收敛,并随着s的增加,收敛越来越快。

所以,在这个观察中,我们很容易观察到:

  • 当 s>−2(即 s+2>0):指数衰减,积分收敛,得到有限值 ​。

  • 当 s=−2(即 s+2=0):被积函数恒为常数,积分开始发散到无穷,为临界状态。

  • 当 s<−2(即 s+2<0):指数增长,积分发散。

因此:

1.变换结果 F(s)必须搭配其"收敛域"(ROC)才有意义

对于上述信号,其 ROC 为 Re(s)>−2(在这里实数 s 就是 Re(s))。缺少 ROC 的拉普拉斯变换表达式是不完整的,无法唯一对应回时域信号。

2.极点(Pole)决定时域响应的本质形态。

这个极点 恰好位于收敛域的边界上。

在控制论和信号处理中,极点的位置直接决定了时域信号 f(t)f(t) 的长期行为

  • 极点位于左半平面(实部 < 0,即 s<0s<0 区域)→ 时域信号衰减(稳定)。
  • 极点位于右半平面(实部 > 0)→ 时域信号增长(不稳定)。
  • 极点位于虚轴(实部 = 0)→ 时域信号等幅振荡或恒定(临界稳定)。

这里极点 −2 在左半平面,所以 随时间衰减到 0,系统是稳定的。

3. "频域"变量 s 的物理意义------衰减率

在拉普拉斯变换中,s=σ+jω(σ 是衰减因子,ω 是振荡频率)

我们这里只用了实部,及ω=0。

从交互图中可以看到:s 越大(越靠右),乘积 衰减得越快,积分面积(1/(s+2))就越小。

这直观地说明:拉普拉斯变换本质上是在"扫描"不同衰减速率下的指数核,看原信号与哪个衰减核的内积(面积)是多少。s 值调节了指数核的"探针"衰减速度。

4.微分方程的代数化------线性时不变系统的"特征值"

如果把这个变换应用到线性常系数微分方程上,结论是:时域的微分运算在频域变成了乘以 s 的代数运算

哦忘了说了,上面这些结论你也完全不用记。

你只需要知道,对于我们的信号:

对其做拉普拉斯变换:

这个积分根本不用算,是我们刚刚推出来的。

对于其他类型的拉普拉斯变换也是一样,直接套公式就行了。

-2+2=0,所以极点为-2,ROC:RE(s)>-2(收敛域)。

简不简单?我说小学生都会做真不是吹牛逼。

我们稍微复杂一点。

假设信号为:

我们注意到,等式右边有两个指数,那么根据上述的单边拉普拉斯变换定义式的积分过程......

算了不做了,直接拉普拉斯,右边等于

结果为:

所以极点为-2和-5,ROC:RE(s)>-2。

零点为-3.5。

欸这里有人问了,零点又是啥啊?

回忆我们刚刚对这个过程的解释:当我们输入一个信号,系统会做何反应。

零点意味着系统的输出为零。

也就是说,这个输入的信号进入系统后,被系统完全抵消掉了。

我们现在来用matlab观察一下原信号在复数平面上的三维曲面图。

Matlab 复制代码
%% 清理并准备
clc; clear; close all;

%% 1. 定义复频域范围(重点关注零点 -3.5 和极点 -2, -5)
sigma = linspace(-7, 1, 400);   % 实部 (σ)
omega = linspace(-4, 4, 400);   % 虚部 (ω)
[Sig, Omg] = meshgrid(sigma, omega);
s = Sig + 1i*Omg;

%% 2. 计算 X(s) = 1/(s+2) + 1/(s+5) 的幅度
% 合并通分:X(s) = (2s+7) / ((s+2)(s+5)),这样算更精确,避免大数相消
X = (2*s + 7) ./ ((s + 2) .* (s + 5));
Mag = abs(X);

% 限制幅度上限(否则极点处尖峰过高,导致零点看不清)
Mag(Mag > 20) = 20;

%% 3. 创建图形窗口
figure('Position', [100, 100, 1200, 500]);

%% ========== 子图1:复平面俯视图(零极点图) ==========
subplot(1, 2, 1);
% 绘制等高线俯视图,颜色深浅代表幅度
contourf(Sig, Omg, Mag, 50, 'LineColor', 'none');
colorbar; hold on;
xlabel('实部 \sigma');
ylabel('虚部 \omega (j\omega)');
title('复平面俯视图:幅度等高线');
axis equal; xlim([-7, 1]); ylim([-4, 4]);

% 标记极点 (红叉)
plot(-2, 0, 'rx', 'MarkerSize', 12, 'LineWidth', 2);
plot(-5, 0, 'rx', 'MarkerSize', 12, 'LineWidth', 2);
text(-2, 0.3, '极点 -2', 'Color', 'r', 'FontSize', 11);
text(-5, 0.3, '极点 -5', 'Color', 'r', 'FontSize', 11);

% 标记零点 (红圈) ------ 这是你问的 -3.5
plot(-3.5, 0, 'ro', 'MarkerSize', 12, 'LineWidth', 2);
text(-3.5, 0.3, '零点 -3.5', 'Color', 'r', 'FontSize', 11, 'FontWeight', 'bold');

% 标记收敛域边界 (s > -2 的区域)
line([-2, -2], [-4, 4], 'Color', 'k', 'LineStyle', '--', 'LineWidth', 1.5);
text(-1.8, 3.5, 'ROC: \sigma > -2', 'Color', 'k', 'FontSize', 10);

grid on;

%% ========== 子图2:3D 曲面图 ==========
subplot(1, 2, 2);
surf(Sig, Omg, Mag, 'EdgeColor', 'none', 'FaceAlpha', 0.9);
xlabel('实部 \sigma');
ylabel('虚部 \omega');
zlabel('|X(s)|');
title('3D 曲面图:幅度随 s 的变化');
xlim([-7, 1]); ylim([-4, 4]); zlim([0, 20]);
view(45, 30);  % 调整视角,方便观察凹陷
hold on;
grid on;

% 在3D图上显式标记零点(红色圆点,位于地面 z=0)
plot3(-3.5, 0, 0, 'ro', 'MarkerSize', 12, 'MarkerFaceColor', 'r', 'LineWidth', 2);
text(-3.5, 0, 0.5, '零点 -3.5', 'Color', 'r', 'FontSize', 12, 'FontWeight', 'bold');

% 在3D图上标记极点(尖峰位置,因为幅度截断在20,所以画在z=20处)
plot3(-2, 0, 20, 'rx', 'MarkerSize', 12, 'LineWidth', 2);
plot3(-5, 0, 20, 'rx', 'MarkerSize', 12, 'LineWidth', 2);
text(-2, 0, 21, '极点', 'Color', 'r', 'FontSize', 10);
text(-5, 0, 21, '极点', 'Color', 'r', 'FontSize', 10);

% 绘制一条沿实轴的黑色轨迹线,强调零点在实轴上
sigma_axis = linspace(-7, 1, 100);
omega_axis = zeros(size(sigma_axis));
s_axis = sigma_axis + 1i*omega_axis;
X_axis = (2*s_axis + 7) ./ ((s_axis + 2) .* (s_axis + 5));
Mag_axis = abs(X_axis);
Mag_axis(Mag_axis > 20) = 20;
plot3(sigma_axis, omega_axis, Mag_axis, 'k-', 'LineWidth', 2);

我们可以很直观的看出极点和零点的位置(虚线右侧为收敛域。)。

现在我们观察一下积分的曲线和数值变化。

Matlab 复制代码
%% 清理工作区
clc; clear; close all;

%% 1. 定义被积函数
x = @(t) exp(-2*t) + exp(-5*t);

%% 2. 设置时间范围(取 0 到 5 就足够看清收敛趋势了)
T_max = 5;
t_vals = linspace(0, T_max, 1000);

%% 3. 计算累积积分 F(T) = ∫₀ᵀ x(t) dt
% 方法一:解析解(精确,推荐)
F_vals = 7/10 - 1/2 * exp(-2*t_vals) - 1/5 * exp(-5*t_vals);

% 方法二:数值积分(通用,适用于任意复杂函数,作为校验)
% F_vals_num = arrayfun(@(T) integral(x, 0, T), t_vals);

%% 4. 绘制图形
figure('Position', [100, 100, 800, 600]);

% 子图1:被积函数 x(t)
subplot(2,1,1);
plot(t_vals, x(t_vals), 'b-', 'LineWidth', 2);
xlabel('时间 t');
ylabel('x(t) = e^{-2t} + e^{-5t}');
title('被积函数(指数衰减)');
grid on;
xlim([0, T_max]);

% 子图2:累积积分 F(T)
subplot(2,1,2);
plot(t_vals, F_vals, 'r-', 'LineWidth', 2); hold on;
% 绘制收敛目标线(y = 0.7)
yline(0.7, 'k--', 'LineWidth', 1.5);
xlabel('上限 T');
ylabel('F(T) = \int_0^T x(t) dt');
title('累积积分随上限 T 的变化(收敛于 0.7)');
legend('F(T)', '收敛极限 0.7', 'Location', 'southeast');
grid on;
xlim([0, T_max]);
ylim([0, 0.8]);  % 稍微留白

%% 5. 在命令行输出最终结果
disp('解析解:F(T) = 0.7 - 0.5*e^{-2T} - 0.2*e^{-5T}');
disp(['当 T -> ∞ 时,积分收敛于:', num2str(0.7)]);

可以看到,信号确实是在衰减,并且积分趋近于0.7。

这里有一个很有意思的点,如果 中s为零,即输入信号为零,则结果为0.5+0.2=0.7。

正好等于积分。

这里做一个补充。

虽然我们一直都在探讨拉普拉斯实部有数值,虚部为0的情况。但实际上虚部可能不为零。

另外,

综上来看,我们能从上图轻松的看出极点的实部正负和数值分别表示了衰减/发散和衰减/发散的快慢。但是零点的实部数值就很难在上图里直观看出来含义了。

我们先来对比一组零点为负和零点为正的积分图(极点都为负),就能很轻松的看出区别了。

Matlab 复制代码
%% 清理并准备
clc; clear; close all;

%% 1. 定义时域范围
t = linspace(0, 5, 1000);

%% 2. 定义两种情况的传递函数(极点相同,零点不同)
% 情况A:零点为负(左半平面)。对应 X(s) = (2s+7)/((s+2)(s+5))
% 部分分式展开 = 1/(s+2) + 1/(s+5),所以 x_A(t) = e^{-2t} + e^{-5t}(全为正)
x_A = exp(-2*t) + exp(-5*t);
F_A = 0.7 - 0.5*exp(-2*t) - 0.2*exp(-5*t);  % 累积积分,收敛于 0.7

% 情况B:零点为正(右半平面,即你要求的)。对应 X(s) = (s-1)/((s+2)(s+5))
% 部分分式展开 = -1/(s+2) + 2/(s+5),所以 x_B(t) = -e^{-2t} + 2e^{-5t}(会变号!)
x_B = -exp(-2*t) + 2*exp(-5*t);
% 累积积分:F_B(T) = ∫(-e^{-2t} + 2e^{-5t}) dt = 0.5*e^{-2t} - 0.4*e^{-5t} | 0->T
% 计算结果:F_B(T) = (0.5 - 0.4) - (0.5*e^{-2T} - 0.4*e^{-5T}) = 0.1 - 0.5*e^{-2T} + 0.4*e^{-5T}
F_B = 0.1 - 0.5*exp(-2*t) + 0.4*exp(-5*t);

%% 3. 绘制对比图
figure('Position', [100, 100, 1200, 600]);

% ===== 子图1:波形对比(看看谁过零) =====
subplot(2, 2, 1);
plot(t, x_A, 'b-', 'LineWidth', 2); hold on;
yline(0, 'k--'); % 零轴
xlabel('时间 t'); ylabel('x(t)');
title('负零点 (-3.5):波形全为正');
ylim([-0.5, 2.5]); grid on;

subplot(2, 2, 2);
plot(t, x_B, 'r-', 'LineWidth', 2); hold on;
yline(0, 'k--');
xlabel('时间 t'); ylabel('x(t)');
title('正零点 (+1):波形穿越零轴(过零)');
% 标记过零点(函数值为0的点)
t_zero = log(2)/3; % 解方程 -e^{-2t} + 2e^{-5t}=0 => e^{3t}=2
plot(t_zero, 0, 'ko', 'MarkerSize', 8, 'LineWidth', 2);
text(t_zero, 0.1, sprintf(' 过零点 t=%.3f', t_zero), 'Color', 'k', 'FontSize', 10);
ylim([-0.5, 1.5]); grid on;

% ===== 子图2:累积积分对比(面积是否反转) =====
subplot(2, 2, 3);
plot(t, F_A, 'b-', 'LineWidth', 2); hold on;
yline(0.7, 'b--', '收敛极限 0.7');
xlabel('上限 T'); ylabel('F(T) = \int_0^T x(t) dt');
title('负零点:面积单调递增,收敛正数');
ylim([0, 0.9]); grid on; legend('F_A(T)', 'Location', 'southeast');

subplot(2, 2, 4);
plot(t, F_B, 'r-', 'LineWidth', 2); hold on;
yline(0, 'k--');
% 找出F_B的最高点(导数=0,即x_B=0的点,正是刚才的过零点 t_zero)
F_max = 0.1 - 0.5*exp(-2*t_zero) + 0.4*exp(-5*t_zero);
plot(t_zero, F_max, 'ko', 'MarkerSize', 8, 'LineWidth', 2);
text(t_zero, F_max+0.02, sprintf(' 峰值 (面积最大)', 'Color', 'k'));
xlabel('上限 T'); ylabel('F(T) = \int_0^T x(t) dt');
title('正零点:面积先增后减,最终收敛负数');
ylim([-0.2, 0.5]); grid on;

%% 4. 输出关键结论到命令行
disp('========== 对比结论 ==========');
disp('1. 负零点(-3.5): 时域信号恒为正,面积单调增,收敛到 0.7');
disp('2. 正零点(+1): 时域信号在 t=ln(2)/3 ≈ 0.231 处过零变号');
fprintf('   → 导致面积先增加到 %.4f,随后下降,最终收敛到 %.1f\n', F_max, 0.1);
disp('这就是正零点(非最小相位)在时域上的真正嘴脸:起步先冲,然后反向滑落!');

很明显吧,可以去更改函数来观察变化。

零点实部的数值,这个数值小(离虚轴近)代表了对低频/衰减慢的信号抑制能力强,而数值大(离虚轴远)则代表对高频/衰减快的信号的抑制能力。

这个数值为负(左半面)代表了系统会超调,即系统会做出"过激"反应,但最终会稳定。

这个数值为正(右半面)代表了系统会反冲,绝对值(离原点越远),反冲越尖锐,但恢复也更快。

这个反冲在工程上极其危险。例如机长想把飞机拉起,结果飞机先往下栽一下,那不完了吗。

3.3.3 控制系统中拉普拉斯极点、零点的物理意义和物理直觉

讲了这么多理论,其实在实际任务中,用到更多的还是直觉。

在控制系统中的系统几乎都是衰减的。

当一辆车在路上滑行,如果不加油门,它总会停下来。

我转瞬间都想不到有什么生活中的系统本身是发散的。

我们前文说过,零点和极点的意义是:系统对于这个输入有什么反应。

但在这一节,我们不讲这么抽象的解释。

我们用一个例子来解释。

假设老刘现在拿着一杯水,但是老刘手有点抖,现在老刘拼命去控制,慢慢的几乎不抖了,这个系统极点就是负的(收敛)。

假设老刘这时候另一只手也握住杯子了,这只手有旧伤也在晃,但老刘又拼命控制住了。

这个系统就有两个通道,所以就有两个极点,并且这两个极点都是负的(双状态)。

如果仔细观察会发现,在某一个瞬间,老刘两只手虽然都在抖,但居然相互抵消了抖动,水没在晃!这个就是零点(结果抵消)。

这时候老刘其中一只手得了超级帕金森,老刘崩溃了,懒得控制了,手越来越抖,把水都抖飞出去出去了,这时候这个系统极点就是正的,系统开始发散了。

老刘调整了一会心情,重新尝试拿起水杯,但这一次他精神太紧绷,直接把水杯反着晃,水又飞出去一半,但最终老刘还是控制住了。这就是系统反冲,零点为正。

接下来,老刘猛地一下握住水杯,这次又洒了一些水,但至少又拉了回来。这就是系统过激,零点为负。

老刘思考了一会,突然顿悟了。

他慢慢伸出手臂,轻轻拿起杯子,仔细控制力道。终于水没有再洒出来。

事后老刘感慨,还好我学了拉普拉斯变换(泪目)。

3.3.4 拉普拉斯变换表

众所周知,拉普拉斯是不用算的,看到直接换就行了。连连看嘛。

以下不要死记硬背,重要的是理解和建立直觉。

核心变换表(这些基本上一直用,肯定能记住的)

时域原函数 f(t) (t≥0) 拉普拉斯像函数 F(s) 控制工程物理直觉
单位冲激 δ(t) 1 理想的"瞬间锤击",包含所有频率成分。用来测系统的"裸奔"响应(脉冲响应)。
单位阶跃 1(t) 最常用输入。突然加上电压/指令。s=0 处有一个极点,代表"记忆/积分"效应。
单位斜坡 t 匀速变化的指令(如恒速跟踪)。原点处有二重极点。
指数衰减 极点就在负实轴上(纯惯性,无振荡)。a>0 稳定,a<0 发散。
正弦波 sin⁡(ωt) 极点在虚轴上(±jω),临界稳定,永恒振荡(无阻尼)。
余弦波 cos⁡(ωt) 同上,但初始相位不同(分子有 s,代表初始速度不为零)。

工程领域变换表(我们的常见变换)

时域原函数 f(t) (t≥0) 拉普拉斯像函数 F(s) 核心特征
衰减正弦 极点位于 −a±jω。实部 −a 决定衰减快慢,虚部 ω 决定振荡频率。
衰减余弦 同上,但初始时刻幅值最大且无跳变。
过阻尼(双指数) 极点都是负实数(0,−a),没有振荡,慢慢爬升。
欠阻尼标准二阶 控制工程圣经公式 。分母两个极点决定一切:ζ(阻尼比)和 ​(无阻尼自然频率)。

光有原函数也不行,还得会运算性质。

性质名称 时域操作 频域操作 F(s)F(s) 应用场景
线性叠加 最简单的拆分。
时域平移(延迟) 纯延迟/运输延时 (如传送带、网络延迟)。 是右半平面零点的物理来源之一。
复频域平移 把虚轴上的振荡"拖拽"到左半平面(加入阻尼)。
微分(初始不为零) 物理上代表"惯性/电感"的阻碍,引入零点。
积分(累加) 物理上代表"位置/电容电压"的累积,增加一个 s=0s=0 的极点(提高系统型别)。
初值定理 不拆解函数,直接算出初始跳变值(算过冲大小极快)。
终值定理 **超实用。**直接算出稳态误差,前提是极点都在左半平面(除了原点)。

发现没,全部变成加减乘除了。

综上,虽然有很多细节和知识点没讲完,但这些知识基础和直觉能支撑后面所有理论的学习,不仅是控制系统,甚至包括所有信号处理。

3.3.5 电机的传递函数

小小拉普拉斯,拿捏拿捏。

如果深刻理解了前面的内容,那这一节开始的知识就弱的像个萝莉。

(3.3.a)

(3.3.b)

我们回看这个微分模型,现在对其做拉普拉斯变换。

直接对其做一个加减乘除的超高难度大动作,得出小学生级别的大结果。

我们观察第一个式子,有三个变量:V、I、Ω

其中V是控制器输入,Ω是输出,而I是系统执行器的中间变量。

我们现在需要消掉中间变量I,而第二个式子恰好可以做到。

哎呀中间过程太简单了不写了。

总之得到的结果是:

这就是电机的传递函数公式。

我们可以写为:

G(s) =

观察这个式子,根据我们上述对极点和零点的理解。

可以简单得出:

这个电机模型有两个极点,而不存在有限零点,即零点在无穷远处。

这是根据数学公式得出的,但其实,我们可以以物理直觉得出:

这个电机模型有两个储能系统-电感和转动惯量,所以必定存在两个状态-电流和转速,则必定存在两个极点,且函数必定为二阶系统。

而电机模型为单通道传导,不存在多个通道互相可以抵消,所以不存在零点(零点在无穷远处)。

而传递函数本身也一定程度上表达了物理意义。

例如:电感L和转动惯量J导致了系统延迟;电阻R和阻尼b影响了系统特性。

这里很多人会不理解传递函数的意义。

如果与微分方程对比,你会发现,微分方程描述了从输入到电流到转速的整个过程。

但我们工程师输入之后只需要知道输出就行了,我管你系统里有什么过程。

我觉得应该给传递函数起个别称叫老板函数。

但这种思维方式在今后的其他领域也十分重要:黑箱模式

即------如果内部的系统模式已经超出了理解和计算的复杂度阈值,那么更简单的方式是:不管内部过程,只要输入和输出的关系,把整个系统视作一个整体的黑箱。

再深入地说, (输出/输入)在表达的是,电机对于各种指数模式的放大能力(其实就是各种信号的放大能力)。

人话就是,输入电压变化,输出转速会怎么变化。

3.4 直流电机传递函数模型simulink模拟

将传递函数在simulink中建立模型。

输入为一个阶跃信号,用示波器观察输入和输出。

我们在0.1秒输入一个阶跃电压,电机转速并不会瞬间增长到阈值,甚至会出现短时间的波动。

这与我们前文所推导的过程一致。

这个从初始状态到稳定下来的过程,就叫暂态响应(Transient Response),我们从示波器中观察的时间大概为0.175-0.1=0.075秒。

而最终电机保持了一定的转速(当然我们在此无法判断具体转速),就是稳态行为(Steady-State Behaviour)。这个转速就叫稳态值(Steady-State Value)。

而参数的变化导致的影响,就是参数影响(Parameter Effects)。

根据我们之前的理论,电感和转动惯量的增大会导致延迟增加,现在我们来验证一下。

延迟增大到了0.4秒左右。但稳态值没有变化。

符合预期。

而根据推导,电阻和阻尼会更改系统特性,现在我们先只更改阻尼,验证一下。

可以看到,不仅稳态值降低了,波动也没那么剧烈了。

这和直觉相同:当阻尼增大,电机更慢、也更容易稳定。

接下来继续增大电阻R。

可以看到:系统更稳定了,但稳态值更低了。

但K就有点不符合直觉了。

从公式中就可以看出,K不断增大甚至会导致转速降低。

这是由于虽然扭矩增加了,但反动电势也更大,就会导致扭矩虽然增大了,但转速反而变低的情况。

一般来说,电磁耦合强度K导致了:扭矩越小,转速越快;扭矩越大,转速越小-这个近似逻辑。

但我觉得这个模拟还是不够,我们继续探究参数的影响。

3.5 直流电机传递函数模型 matlab实时UI仿真

请注意,如果要运行以下代码,必须在matlab中下载Control System Toolbox工具箱。

Matlab 复制代码
function motor_analysis_gui()
    % 交互式电机传递函数分析 GUI
    % G(s) = K / ((L*s + R)*(J*s + b) + K^2)
    
    % 创建主窗口
    fig = figure('Name', '电机传递函数阶跃响应分析', ...
                 'NumberTitle', 'off', ...
                 'Position', [100, 100, 1000, 600], ...
                 'Resize', 'off');
    
    % 左侧面板宽度
    leftWidth = 220;
    
    % 创建坐标轴(右侧)
    ax = axes('Parent', fig, ...
              'Units', 'pixels', ...
              'Position', [leftWidth+30, 60, 700, 480], ...
              'Box', 'on', ...
              'XLim', [0 15], 'YLim', [0 1.5], ...
              'FontSize', 11);
    xlabel(ax, '时间 (秒)');
    ylabel(ax, '幅值');
    title(ax, '单位阶跃响应');
    grid(ax, 'on');
    hold(ax, 'on');
    
    % 参数定义 [最小值, 最大值, 初始值]
    paramDef = struct('L', [0.01, 2.0, 0.5], ...
                      'R', [0.1,  5.0, 1.0], ...
                      'J', [0.01, 1.0, 0.1], ...
                      'b', [0.01, 1.0, 0.1], ...
                      'K', [0.1,  5.0, 1.0]);
    paramNames = fieldnames(paramDef);
    nParams = length(paramNames);
    
    % 布局参数
    spacing = 45;
    startY = 520;   % 从顶部开始
    sliderWidth = 70;
    labelWidth = 50;
    valWidth = 60;
    
    % 存储控件句柄
    handles = struct();
    handles.ax = ax;
    handles.sliders = cell(1, nParams);   % 存储所有滑块句柄
    handles.sliderParams = cell(1, nParams); % 存储每个滑块对应的参数名
    
    for i = 1:nParams
        name = paramNames{i};
        range = paramDef.(name);
        
        % 标签
        uicontrol('Parent', fig, 'Style', 'text', ...
                  'String', [upper(name) ' = '], ...
                  'Units', 'pixels', ...
                  'Position', [10, startY - (i-1)*spacing, labelWidth, 20], ...
                  'HorizontalAlignment', 'left', ...
                  'FontWeight', 'bold');
        
        % 数值显示
        valText = uicontrol('Parent', fig, 'Style', 'text', ...
                            'String', num2str(range(3), '%.3f'), ...
                            'Units', 'pixels', ...
                            'Position', [10+labelWidth, startY - (i-1)*spacing, valWidth, 20], ...
                            'HorizontalAlignment', 'left');
        
        % 滑块(普通 uicontrol,不使用 ContinuousValueUpdate)
        sl = uicontrol('Parent', fig, 'Style', 'slider', ...
                       'Min', range(1), 'Max', range(2), ...
                       'Value', range(3), ...
                       'Units', 'pixels', ...
                       'Position', [10+labelWidth+valWidth, startY - (i-1)*spacing, sliderWidth, 20], ...
                       'Callback', @(src,~) updatePlot());  % 点击或松开时更新
        
        % 保存滑块句柄和对应参数名
        handles.sliders{i} = sl;
        handles.sliderParams{i} = name;
        handles.(name).slider = sl;
        handles.(name).text = valText;
        handles.(name).range = range;
    end
    
    % 设置窗口鼠标移动回调(用于实时更新)
    set(fig, 'WindowButtonMotionFcn', @onMouseMove);
    
    % 初始绘制
    updatePlot();
    
    % ========== 鼠标移动回调 ==========
    function onMouseMove(~, ~)
        % 获取鼠标在 figure 中的像素坐标
        mousePos = get(fig, 'CurrentPoint');  % [x, y] (像素)
        if isempty(mousePos)
            return;
        end
        x = mousePos(1);
        y = mousePos(2);
        
        % 遍历所有滑块,检测鼠标是否落在滑块区域内
        for i = 1:length(handles.sliders)
            sl = handles.sliders{i};
            pos = get(sl, 'Position');  % [left, bottom, width, height]
            % 检查鼠标是否在滑块区域内(考虑滑块高度为 20,点击区域应包含整个矩形)
            if (x >= pos(1) && x <= pos(1)+pos(3)) && ...
               (y >= pos(2) && y <= pos(2)+pos(4))
                % 鼠标在某个滑块上,读取该滑块的值并更新曲线
                % 但为了避免重复更新,只更新一次即可(因为所有滑块值都需更新)
                % 但为了响应速度,我们直接更新所有参数并重绘
                updatePlot();
                break;  % 只触发一次更新
            end
        end
    end
    
    % ========== 更新函数 ==========
    function updatePlot()
        % 读取所有滑块当前值
        L = get(handles.L.slider, 'Value');
        R = get(handles.R.slider, 'Value');
        J = get(handles.J.slider, 'Value');
        b = get(handles.b.slider, 'Value');
        K = get(handles.K.slider, 'Value');
        
        % 更新数值显示
        set(handles.L.text, 'String', num2str(L, '%.3f'));
        set(handles.R.text, 'String', num2str(R, '%.3f'));
        set(handles.J.text, 'String', num2str(J, '%.3f'));
        set(handles.b.text, 'String', num2str(b, '%.3f'));
        set(handles.K.text, 'String', num2str(K, '%.3f'));
        
        % 构建传递函数
        den = [L*J, L*b + R*J, R*b + K^2];
        num = [K];
        sys = tf(num, den);
        
        % 计算阶跃响应
        t = linspace(0, 15, 500);
        [y, t_out] = step(sys, t);
        steady = dcgain(sys);   % 稳态值
        
        % 绘图
        ax = handles.ax;
        cla(ax);
        plot(ax, t_out, y, 'b-', 'LineWidth', 2);
        hold(ax, 'on');
        plot(ax, [0, 15], [steady, steady], 'r--', 'LineWidth', 1.5);
        hold(ax, 'off');
        
        xlim(ax, [0 15]);
        ylim(ax, [0 1.5]);
        legend(ax, {'阶跃响应', ['稳态值 = ' num2str(steady, '%.3f')]}, ...
               'Location', 'southeast');
        grid(ax, 'on');
        title(ax, '单位阶跃响应');
        drawnow;   % 强制刷新
    end
end

使用方法:将上述代码保存为motor_analysis_gui.m

然后新建文件输入motor_analysis_gui 运行

仿真UI界面如图所示。

通过改变参数,可以实时观察曲线变化。

如果你的电脑出现卡顿,请点击来改变参数而不是拖动滑块。

强烈建议运行此代码并改变参数观察结果,非常直观非常好玩。

四、控制系统------环境中的系统、黑箱思维

我们刚刚完成了对一个孤立系统的完整建模,如果他处在一个理想中的环境,那么这个建模堪称精确、优雅、美观。

但很可惜,这个世界不存在理想情况。

上述的传递函数优雅的描述了系统内的过程。但是,这个模型缺乏了一个非常重要的变量------

4.1 干扰(Disturbence)

我们开始进入控制系统的精髓部分了。

前面我们一直在研究,当我们产生一个输入,系统做出反应,产生一个输出。

这个过程准确的前提是,我们知道了所有的变量、掌控了所有的参数、知道所有的信息,并通过一个超级计算机模拟了所有状态。

这可能吗?

假如你与超级算力智能在打赌。

现在存在这样一个房间,这个房间的所有信息都被完全掌握,比当今世界上最顶尖的超级计算机还要顶尖一百倍的超算负责根据初始状态计算所有参数的变化,温度、湿度、气流、摩擦系数..

它计算着几百甚至上万种你听过或没听过的参数,产生的数据流可以轻松把当今世界上任何一个数据库撑爆。

而它的目的是计算出一天后这个房间会不会烧光。

超算智能花了十个小时计算出了结果:这个房子被烧光的概率近似于0。

它甚至挑衅式的列出了所有参数一天后的结果。

听起来这个超算似乎赢定了。

可恶,如何击败他呢?

进去点把火就行了。

因为你是无法预测的外部变量。

这个外部变量。就叫干扰(扰动)。

回到我们的电机,对于电机来说,扰动可以是额外的负载,可以是电源的波动,可以是一只Bug。

我们永远无法建立一个可以预测所有可能性的系统。

解决的方法也很简单,我不预测了。

所有来自外部的,无法预测的东西,全部归为扰动 d(t)。

现在电机微分方程的电机模型变为:

这里可以清楚的观察到,假如扰动本来为零,当系统处于稳态,则转速变化率为零,即左式为零。

当突然加上扰动,右式变为负数,且由于电感的存在,电流不能瞬时变化。

左式变为负数,且转速逐渐下降。

最后震荡后再稳态。

而转移模型变为:

等式右两项分别为控制输入产生的输出和扰动产生的输出

这是我们之前的结论

而扰动产生的输出极点一定和控制产生的输出一致(极点为系统本身存在的特性)。

即:分母一定相同。

而扰动是副作用,所以要在前加上负号。

当然,分子必须重新推导,过程类似于控制输入的推导,只是把控制输入设为零。

具体推导过程略。

结果为:

4.2 加入干扰的传递模型Simulink模拟

加入扰动,并且扰动为0.5s时的阶跃信号。

可以看到,扰动几乎可以视作一次"反向作用的信号"。

4.3 黑箱思维

还记得在本文的开头,我说我们必须先建立一个错误的信念------在系统学的世界里,整个世界都是可以被描述为各种系统,我们可以不那么精确的理解、建模、预测并控制万物。

一个合格的工程师必须拥有傲慢的美德,但也必须脚踏实地。

本文整个过程,是在简单复刻现代控制理论的产生过程,也是在不断推翻我们所理解的"控制"------即控制为绝对的、精确的、可解释的控制------并非。

我们做不到,至少暂时做不到。

控制系统学的精髓就在于此,我们可以把一个复杂的、难以解释的系统集,视作一个整体黑箱。

我们不需要知道黑箱内具体发生了什么,我们只需要知道它会产出什么。

如果你对大语言模型有了解,就会知道当前(截止于2026年8月)的主流模型,就是黑箱系统。

相对于我们本文所讲述的极其简单的电机模型,很多系统的复杂度已经远超想象,对他们的完美控制是不可实现的。

当然,一个理想的、完美的系统,是可以被绝对掌控的。那是后话了,是你们、我们、或是后人要做的事情。

五、总结

受限于精力和能力,我无法将这个美丽的学科介绍的更好。但我想,对于我自己来说暂时够用了。

或许不久以后,当我回看这篇学习笔记,会觉得青涩、幼稚且错误百出吧。

但就像系统一样,我们对其的理解就是从简单到复杂再到简单-我的意思是,先把书读厚,再把书读薄,这是一个过程。

六、人工智能声明及个人声明

我声明我使用了人工智能Deep seek、M365 Copilot帮助理解、搜索信息。

本躯水平有限,恳请斧正。

七、参考

老师:

Prof Daniel Quevedo,USYD

部分参考:

University of Sydney - course ELEC3304 ELEC9304 Control

相关推荐
lingchen19061 天前
用MATLAB r2010b和r2016b求解微分方程组
开发语言·算法·matlab
可编程芯片开发1 天前
基于李雅普诺夫算法的四旋翼无人机姿态跟踪控制系统simulink建模与仿真
matlab·无人机·制造·四旋翼无人机·李雅普诺夫·姿态跟踪控制
matlab代码3 天前
基于matlab的玉米种子计数设计(GUI界面)【源码58期】
开发语言·matlab
吃好睡好便好3 天前
转换函数的使用
学习·matlab·生活·转换函数
吃好睡好便好4 天前
取整函数的使用
学习·matlab·生活·取整函数
Funing75 天前
STM32 电机动态 PI 控制:从编码器反馈、增量式 PI 到 Matlab 参数拟合与 PWM 输出
stm32·嵌入式硬件·matlab·pi控制算法
zq_63895 天前
利用 MATLAB 调用 STK Object Model COM 显示雷达受干扰前后探测范围的变化
开发语言·matlab
zq_63895 天前
ObjectModel_diagram.pdf 文档详细总结
开发语言·matlab
88号技师5 天前
2026年SCI-分数牛顿衍生优化算法Fractional Newton-derived optimizer-附Matlab免费代码
开发语言·算法·数学建模·matlab·优化算法