0. 简介

最近作者受到邀请,让我帮忙给刚入门的学弟讲讲滑模控制。可是作者也不知道怎么向未入门的学弟讲解这些基础知识,所以作者翻了翻近几年写的很好的文章以及视频。综合起来,来总结出一套比较基础,且适用于初学者的文章吧。这里我们先贴一下王崇卫同学的笔记。

对应的视频连接在下面:

[video(video-9orVpxnN-1667703763994)(type-bilibili)(url-https://player.bilibili.com/player.html?aid=22205745)(image-https://img-blog.csdnimg.cn/img_convert/5e04fceb4cdfffedd7c6539a3a95b6de.jpeg)(title-【Advanced控制理论】17)]

1. 滑模控制目的

对于滑模控制而言,我觉得我们先要明白其目的再来学习。一开始我们对滑动控制的定义是:滑动模式是先使用受控系统产生两个以上的子系统,然后再刻意加入一些切换条件产生滑动模式,以达成控制目标的一种技术。

滑模控制(sliding mode control, SMC)也叫变结构控制,其本质上是一类特殊的非线性控制,且非线性表现为控制的不连续性。这种控制策略与其他控制的不同之处在于系统的“结构”并不固定,而是可以在动态过程中,根据系统当前的状态(如偏差及其各阶导数等)有目的地不断变化,迫使系统按照预定“滑动模态”的状态轨迹运动。

例如滑动模式控制中存在滑动曲面$s=0$,一开始时,系统会在有限时间内到达滑动曲面,之后就会沿着滑动曲面移动。在滑动模式的理论叙述中,系统会约束在滑动曲面上,因此只需将系统视为在滑动曲面上滑动。不过实际系统的实现是用高频切换来让系统近似在滑动曲面上滑动,高频切换的控制信号让系统在很邻近滑动曲面的范围内切跳(chatter),而且其频率是不固定的。虽然整体系统是非线性的,不过下图中,当系统到达滑动曲面后,理想(没有切跳)系统会限制在$s=0$的滑动曲面上,滑动曲面是线性时不变系统,在原点处指数稳定。

2. 滑模控制优缺点

2.1 滑模控制的优点:

滑动模态可以设计且与对象参数和扰动无关,具有快速响应、对参数变化和扰动不灵敏( 鲁棒性)、无须系统在线辨识、物理实现简单

2.2 滑模控制的缺点:

当状态轨迹到达滑动模态面后,难以严格沿着滑动模态面向平衡点滑动,而是在其两侧来回穿越地趋近平衡点,从而产生抖振——滑模控制实际应用中的主要障碍。国内外主要通过改进滑模趋近律达到减弱抖振的目的。

3. 滑模控制需要条件

上文讲到滑模变结构控制器设计也包括两部分,一是能从状态空间的任何位置有限时间到达滑模面 $s = 0$,二是在滑模面上可以收敛到原点(平衡点)。这也就代表我们要存在有一个稳定的滑模面,且该滑模面是可达的。为此有以下四个条件:

  • 稳定性条件:在s=0的滑模面上,状态是收敛的,即滑动模态存在;
  • 可达性条件:在切换面s=0以外的运动点将于有限时间内到达切换面;
  • 保证滑模运动的稳定性;
  • 达到控制系统运动品质要求。

下面将按照四个条件来叙述如何设计滑模控制的控制器,这里的部分内容借鉴了文章滑动模型控制(Sliding Mode Control),并结合作者的理解进行写作。

3.1 被控系统的滑模面生成

首先第一步就是我们需要明白,我们需要找到一个滑模面来让被控系统在滑模面上维持稳定。
例如假设存在一个被控系统:
\begin{aligned} \dot{x}_1 &= x_2 \ \dot{x}_2 &= u \end{aligned}

这个时候我们就需要根据被控系统设计一个滑模面,滑模面一般可以设计为如下的形式
s(x) = \sum_{i=1}^{n-1} c_i x_i + x_n
因为在滑模控制中,要保证多项式$p^{n − 1} + c_n p^{n − 2} + \cdots + c_2 p + c_1$为Hurwitz (简单来说这条条件是为了满足状态在$s=0$的滑模面上可以收敛)。

什么是Hurwitz,即上述多项式的特征值的实数部分在左半平面,即为负。

我们可以看到上述的被控系统是存在有两个变量,所以需要取$n=2$,即 $s ( x ) = c_1 x_1 + x_2$,为了保证多项式 $p+c_1$为Hurwitz,需要多项式$p+c_1=0$的特征值实数部分为负,即$c_1>0$。

我们知道滑模控制需要使得状态$x_1$ 和$x_2$的导数均达到零,我们令 $s=0$,分析一下结果有
$\left{\begin{aligned} &cx_1 + x_2 = 0 \ &\dot{x}_1 = x_2 \end{aligned}\right. ~~ \Rightarrow ~~ c x_1 + \dot{x}_1 = 0 ~~ \Rightarrow ~~ \left{\begin{aligned} &x_1(t) = \text{e}^{-ct} x_1(0) \ &x_2(t) = \dot{x}_1(t) = -c x_1(0) \text{e}^{-ct} \end{aligned}\right.$
通过上式可以看到状态$x_1$ 和 $x_2$ 最终都是趋向于零的,而且速度是以指数速率趋紧的。指数速率意味着当$t=1/c$时,趋零过程完成$63.2\%$,当$t=3/c$时,趋零过程完成 $95.021\%$。那么我们通过调节参数$c$的大小即可实现对趋零速度的调节,$c$ 越大,速度越快。

因此如果满足了 $s=cx_1 + x_2=0$,那么系统的状态$x_1$ 和$x_2$也将沿着滑模面趋近于零 ($s=0$称之为滑模面)。

3.2 可达性控制器设计

在拿到滑模面后则证明被控系统的稳定性条件成立,下面一步就是可达性条件,即状态$x$ 从状态空间中任意一点出发,可以在有限时间到达 $s=0$ 的滑模面上,此时我们可以采用李雅普诺夫间接法来分析,从前面可知,切换函数 $s$ 是状态变量 $x$ 的函数,取以下的李雅普诺夫函数

V = \frac{1}{2} s^2

对时间求导可得
\begin{aligned} \dot{V} &= s \dot{s} \ &= s (-\text{sgn}(s) - s) \ &= -\text{sgn}(s) s - s^2 \ &= -(|s| + s^2) < 0 \end{aligned}

为了使系统稳定,我们需要使$\dot{V}<0$,即 $s \dot{s}<0$。此时系统对于 $s$而言是渐进稳定,不能保证其有限时间到$s=0$ 的滑模面上(渐进稳定是当 $t$趋于无穷时,状态变量 $x$ 趋于 $0$,即无限时间到达),因此需要 $s \dot{s}<-\sigma$,$\sigma$是一个极小的正数。$\color{red}{以上就是可达性条件成立的必要依据}$。

但是实际上每次设计总不能都用李雅普诺夫函数判断,于是人们就提出了趋近律这一概念,常用的趋近律有如下几种,其中$\text{sgn}(s)$ 是符号函数, $s>0,\text{sgn}(s)=1; s<0, \text{sgn}(s)=-1; s=0, \text{sgn}(s)=0$:

  1. 等速趋近律: $\dot{s} = -\epsilon ~\text{sgn}(s), ~~~~\epsilon > 0$

  2. 指数趋近律:$\dot{s} = -\epsilon ~\text{sgn}(s) - k s, ~~~~\epsilon > 0, k>0$

  3. 幂次趋近律: $\dot{s} = -k |s|^\alpha ~\text{sgn}(s) - k s, ~~~~k>0, 0<\alpha<1$

一般在使用时候我们需要完成这些参数的调整,一般我们使用的是指数趋近率,并将$\epsilon$和$k$的值均设为1,简化为:

\dot{s} = ~\text{sgn}(s) - s

然后我们可知$s ( x ) = c_1 x_1 + x_2$,则$\dot{s} = ~\text{sgn}(s) - s = c_1 \dot{x_1} + \dot{x_2} = c_1x_2+u$。则我们可以得到控制器$u$为:

u = ~\text{sgn}(s) - s - c_1x_2

这就得到了我们必要的两个条件即,存在滑模面$s$以及可达性控制器$u$.

4. 滑模控制Python代码

下面是最简单的python代码

# -*- coding: utf-8 -*-

import numpy as np
import control as ct
import matplotlib.pyplot as plt

k1 = 3
k2 = 4

xref = 0
vref = 0

# System state: x,v
# System input: ux,uv
# System output: x,v
# System parameters: none
def mass_update(t, x, u, params):
    # Return the derivative of the state
    return np.array([
        x[1],               # dx = v
        u[1] + np.sin(2*t)  # dv = u + d
    ])

def mass_output(t, x, u, params):
    return x # return x, v (full state)

unit_mass = ct.NonlinearIOSystem(
    mass_update, mass_output, states=2, name='unit_mass',
    inputs=('ux','uv'),
    outputs=('x', 'v'))

###############################################################################
# Control 
###############################################################################
# u = -k1*x -k2*v
def control_output(t, x, u, params):
    return  np.array([0,k1*u[0] + k2*u[1]])

# Define the controller as an input/output system
controller = ct.NonlinearIOSystem(
    None, control_output, name='controller',        # static system
    inputs=('x', 'v'),    # system inputs
    outputs=('ux','uv')                            # system outputs
)

###############################################################################
# Target
###############################################################################
def target_output(t, x, u, params):
    xref, vref = u
    return np.array([xref,vref])

# Define the trajectory generator as an input/output system
target = ct.NonlinearIOSystem(
    None, target_output, name='target',
    inputs=('xref', 'vref'),
    outputs=('x', 'v'))

###############################################################################
# System Connect
###############################################################################
fastest = ct.InterconnectedSystem(
    # List of subsystems
    (target,controller, unit_mass), name='fastest',

    # Interconnections between  subsystems
    connections=(
        ('controller.x','target.x','-unit_mass.x'),
        ('controller.v','target.v','-unit_mass.v'),
        ('unit_mass.ux', 'controller.ux'),
        ('unit_mass.uv', 'controller.uv'),
    ),

    # System inputs
    inplist=['target.xref', 'target.vref'],
    inputs=['xref','vref'],

    #  System outputs
    outlist=['unit_mass.x', 'unit_mass.v'],
    outputs=['x', 'v']
)

###############################################################################
# Input Output Response
###############################################################################
# time of response
T = np.linspace(0, 10, 100)
# the response
tout, yout = ct.input_output_response(fastest, T, [xref*np.ones(len(T)),vref*np.ones(len(T))],X0=[1,-2])

plt.close()
plt.figure()
plt.grid()
plt.xlabel("Time(s)")
plt.plot(tout,yout[0],label='distance(m)')
plt.plot(tout,yout[1],label='velocity(m/s)')
plt.legend()
plt.title('unit mass modle(without disturbance)')
plt.show()

我们发现滑模面$s$是被包含在$u$当中,通过不断地利用$u$区域去调节,从而完成所有输入量的调节,即$x^{k+1}_1$是由$x^{k}_1+\dot{x}_1$组成。利用$u$计算出当前时刻的导数,完成状态量的相加。我们可以从另一段代码中更直观的看到这部分的操作

import matplotlib.pyplot as plt

'''
指数趋近律、等速趋近律、准滑模控制的车辆跟随问题仿真, 运行结果以图片形式保存在同目录下。
'''

# q1, q2分别是切换函数ei1, ei2前面的系数
q1, q2 = 2, 1
# lan是指数趋近律前面的系数
lan = 0.5
# 设定期望车间距均为12
l1, l2 = 12, 12
# 设定汽车质量均为1000
m1, m2= 1000, 1000
# 设定动力学模型分子的速度平方项前的系数ci均为0.5(按照模型符号是负的)
c1, c2 = 0.5, 0.5
# 设定动力学模型分子的常数项系数Fi均为200(按照模型符号是负的)
f1, f2 = 200, 200
# 设定五辆车汽车的位移、速度、加速度
x0, x1, x2,  = [100.], [90.], [79.5]
v0, v1, v2, = [20.], [19.], [18.]
a1, a2, = [0.], [0.]


# 设定趋近律
def reaching_law(m:int , s:float, q2:int, mode='exponential'):
    '''
    mode: 指数趋近律exponential| 等速趋近律uniform| 准滑模控制quasi_sliding
    '''
    if mode == 'exponential':
        return -m * lan * s / q2
    if mode == 'uniform':
        epslion = 0.3
        if s > 0:
            return -m * epslion / q2
        if s == 0:
            return 0
        if s < 0:
            return m * epslion / q2
    if mode == 'quasi_sliding':
        delta, epslion = 0.8, 2.
        if s < -delta:
            return m * epslion / q2
        if s > delta:
            return -m * epslion / q2
        else:
            return -m * epslion * s / (delta * q2)


# 设定第一辆车的加速度(分段函数), 要注意t的长度和a0的长度相等
def get_a0(t:list):    
    a0 = []
    for i in t:
        if i < 4:
            a0.append(0)
            continue
        if i >= 4 and i < 7:
            a0.append(-0.25*(i-4))
            continue
        if i >= 7 and i < 10:
            a0.append(-0.75)
            continue
        if i >= 10 and i < 16:
            a0.append(0.25*(i-10)-0.75) 
            continue
        if i >= 16 and i < 19:
            a0.append(0.75)
            continue
        if i >= 19 and i < 22:
            a0.append(0.25*(19-i)+0.75)
            continue
        if i >= 22 and i <= 30:                 # 注意i=30, 所以是取两端, 故为301份
            a0.append(0)
    return a0


if __name__ == "__main__":

    t = [float(i/10) for i in range(301)]       # 将30秒划分成301份, [0, 0.1, 0.2, ..., 29.9, 30]
    a0 = get_a0(t)

    # 四辆车的车间距误差ei1列表
    e11 = [x1[0] - x0[0] + l1]
    e21 = [x2[0] - x1[0] + l2]

    # 四辆车的车间距误差导数ei2的列表
    e12 = [v1[0] - v0[0]]
    e22 = [v2[0] - v1[0]]

    # 四辆车切换函数的列表
    s1 = [q1 * e11[0] + q2 * e12[0]]
    s2 = [q1 * e21[0] + q2 * e22[0]]

    # 四辆车控制律的列表
    u1, u2, u3, u4 = [0], [0], [0], [0]

    for i in range(1, 301):
        # 最前车0的速度、加速度更新,可以看出更新时用了直线等效, 0.1指的是时间标度(列表t划分的, 也是之后绘图打印的x轴)
        v0.append(v0[i-1] + 0.1 * (a0[i] + a0[i - 1]) * 0.5)
        x0.append(x0[i-1] + 0.1 * (v0[i] + v0[i - 1]) * 0.5)

        # 车1的车间距误差及导数更新
        e11.append(x1[i-1] - x0[i-1]+l1)
        e12.append(v1[i-1] - v0[i-1])
        # 车1的切换函数更新
        s1.append(q1 * e11[i] + q2 * e12[i])
        # 等效控制
        u1equ = c1 * (e12[i] + v0[i]) * (e12[i] + v0[i]) - m1 * q1 * e12[i] / q2 + m1 * a0[i] + f1 
        # 反馈控制(指数趋近律)
        u1n = reaching_law(m1, s1[i], q2)                           # 默认采用指数趋近律, 下同
        # u1n = reaching_law(m1, s1[i], q2, mode='uniform')         # 采用等速趋近律
        # u1n = reaching_law(m1, s1[i], q2, mode='quasi_sliding')   # 采用准滑模控制
        # 更新控制律
        u1.append(u1equ + u1n)
        # 利用控制律更新车1的加速度、速度、位移, 加速度是利用动力学模型得到的
        a1.append((-c1 * v1[i-1] * v1[i-1] + u1[i] - f1) / m1)
        v1.append(v1[i-1] + 0.1 * (a1[i] + a1[i - 1]) * 0.5)
        x1.append(x1[i-1] + 0.1 * (v1[i] + v1[i - 1]) * 0.5)

        # 车2、3、4过程同车1  
        e21.append(x2[i-1] - x1[i-1]+l2)
        e22.append(v2[i-1] - v1[i-1])
        s2.append(q1 * e21[i] + q2 * e22[i])
        u2equ = c2 * (e22[i] + v1[i]) * (e22[i] + v1[i]) - m2 * q1 * e22[i] / q2 + m2 * a1[i] + f2
        u2n = reaching_law(m2, s2[i], q2)                           # 默认采用指数趋近律
        # u2n = reaching_law(m2, s2[i], q2, mode='uniform')         # 采用等速趋近律
        # u2n = reaching_law(m2, s2[i], q2, mode='quasi_sliding')   # 采用准滑模控制

        u2.append(u2equ + u2n)
        a2.append((-c2 * v2[i-1] * v2[i-1] + u2[i] -f2) / m2)
        v2.append(v2[i-1] + 0.1 * (a2[i] + a2[i - 1]) * 0.5)
        x2.append(x2[i-1] + 0.1 * (v2[i] + v2[i - 1]) * 0.5)



    # 开始绘图
    # 绘制加速度曲线
    plt.figure()                            # 设置画布
    plt.plot(t, a0, label='car 0')     # :是指绘制点划线
    plt.plot(t, a1, label='car 1')
    plt.plot(t, a2, label='car 2')
    plt.xlabel("Time(s)",fontsize=13)
    plt.ylabel("Acceleration(m/s^2)",fontsize=13)
    plt.xlim(0, 30)    
    plt.legend()
    plt.savefig('./acceleration.png')       # 保存图像

    # 绘制速度曲线
    plt.clf()                               # 清空画布,不然会前后图像会重叠
    plt.plot(t, v0, ':', label='car 0')    
    plt.plot(t, v1, ':', label='car 1')
    plt.plot(t, v2, ':', label='car 2')
    plt.xlabel("Time(s)",fontsize=13)
    plt.ylabel("velocity(m/s)",fontsize=13)
    plt.xlim(0, 30)    
    plt.legend()
    plt.savefig('./velocity.png')           # 保存图像

    # 绘制位置曲线
    plt.clf()
    plt.plot(t, x1, ':', label='car 1')
    plt.plot(t, x2, ':', label='car 2')
    plt.xlabel("Time(s)",fontsize=13)
    plt.ylabel("position(m)",fontsize=13)
    plt.xlim(0, 30)    
    plt.legend()
    plt.savefig('./position.png')

    # 绘制车间距误差ei1曲线
    plt.clf()
    plt.plot(t, e11, label='car 1')
    plt.plot(t, e21, label='car 2')
    plt.xlabel("Time(s)",fontsize=13)
    plt.ylabel("space error(m)",fontsize=13)
    plt.xlim(0, 30)
    plt.legend()
    plt.savefig('./space_error.png')

    # 绘制车间距误差导数ei2曲线
    plt.clf()
    plt.plot(t, e12, ':', label='car 1')
    plt.plot(t, e22, ':', label='car 2')
    plt.xlabel("Time(s)",fontsize=13)
    plt.ylabel("space_error_derivative(m)",fontsize=13)
    plt.xlim(0, 30)
    plt.legend()
    plt.savefig('./space_error_derivative.png')

    # 绘制切换函数曲线
    plt.clf()
    plt.plot(t, s1, label='car 1')
    plt.plot(t, s2, label='car 2')
    plt.xlabel("Time(s)",fontsize=13)
    plt.ylabel("Switching Function",fontsize=13)
    plt.xlim(0, 30)
    plt.legend()
    plt.savefig('./Switching_Function.png')

    # 绘制控制输入U曲线
    plt.clf()
    plt.plot(t, u1, label='car 1')
    plt.plot(t, u2, label='car 2')
    plt.xlabel("Time(s)",fontsize=13)
    plt.ylabel("Control Input",fontsize=13)
    plt.xlim(0, 30)
    plt.legend()
    plt.savefig('./Control_Input.png')






5. 参考链接

https://blog.csdn.net/weixin_36815313/article/details/120633444

https://zhuanlan.zhihu.com/p/536964794

https://zh.wikipedia.org/wiki/%E6%BB%91%E5%8B%95%E6%A8%A1%E5%BC%8F%E6%8E%A7%E5%88%B6

https://www.cnblogs.com/hitwherznchjy/p/16221206.html