北航云盘 (buaa.edu.cn)
(夹带一点私货:指的自己在电脑备份文件时简单地复制文件夹,跳过了所有重名的文件而不选择覆盖,这样导致有的同名文件没有更新)
定义
sym, syms 符号变量
sym a; syms a b; f = str2sym('a+ b^2');
方程组定义
提示:现控的实验一般涉及到状态方程的建立,而实验中往往会有一些具体的物理模型,比如倒立摆小车,需要通过一些受力分析等建立物理公式的方程,并规定一些物理量为状态变量来抽象解出A,b,c,从而建模;这里使用方程组的意义就在于建立具体的方程并解方程
形式
equ1 = a+b ==1; %举例单个方程的定义,注意一定要用等于判断符 equ = [equ1, equ2, equ3... ];
举例,下图为倒立摆小车模型
%以倒立摆小车为模型举例 syms F M m b l t dt ddt x dx ddx P N J g equ = [F == (M+m)*ddx+b*dx+m*l*ddt*cos(t)-m*l*(dt^2)*sin(t), -P*l*sin(t)-N*l*cos(t)==J*ddt, N ==m*ddx+m*l*ddt*cos(t)-m*l*(dt^2)*sin(t), P-m*g == -m*l*ddt*sin(t)-m*l*dt^2*cos(t)];
解方程组以及结果获取
这一步也与得到状态方程密切相关,因为往往需要获得的状态变量的个数少于欲求解的方程的组数(也就是会有一些无关的中间变量需要被消除),例如上面倒立摆小车的受到的N和P,在求解中不需要,所以也需要将其作为变量求解,以防止其在状态变量的导数表达式中出现
先上模板
res = solve(equ1); %一般的单个方程求解,在符号变量单一的情况下这个就行 res = solve(equ,[x1,x2,x3,x4]); %这个用于方程组,第一个参数传前面定义好的方程组(本质为一个数组) %第二个参数传求解的变量,即把其他的变量视为参数(常量)
举例,接上面的倒立摆小车
res = solve(equ,[ddx,ddt,N,P])
(这样在ddx和ddt的表达式中不会出现N和P,只会有状态变量和输入的力F)
符号表达式的化简
最后就是让计算结果能化简得好看一些,但是首先介绍变量替换,这个用途很多
先上模板
res = subs(res,{variables_to_be_replaced},{given_values}) %替换结果是不会作用于存表达式的变量本身,必须给其重新赋值才会更新表达 %第一个参数是表达式 %第二个参数是表达式中要被替换的变量 %第三个参数是给变量赋的值
物理模型中通常会有三角函数,而往往导致无法根据表达式来得到矩阵,所以需要线性化(一般都是用无穷小量来化简),这时候替换就很有用处,仍以倒立摆小车为例,使用如下近似处理,在matlab中实现也用的是替换
res_ddt = subs(res.ddt,{cos(t),sin(t),dt^2},{1,t,0});
用来分别求出A,b,c的每一项,比如在求A时,只用关心状态变量导数与状态变量的关系,那么u(输入)就可以直接替换为0,其他依照此思路
- 接着是简单的化简和合并同类项
模板
%化简,直接传表达式即可 simplify(res) %合并同类项,一般需要指定以那些变量来合并,不是很必需 collect(res,{variables})
例子,接上例,这里提一嘴,建议使用matlab的实时脚本显示结果,这样有利于得到美观的表达(在matlab命令行的输出做不到这一点)
以上,基本上可以靠这些较好地建模了
求导
dfa = diff(f函数,a对哪个变量求(偏)导);积昏
intb = int(f函数,b对哪个变量求积分,b1,b2区间);拉氏变换
%t还是得事先定义好,s求拉氏变换时会自带 ft = exp(2*t); L1 = laplace(ft);拉氏反变换
ftt = ilaplace(L1);
状态方程直接建模(这个见simulink环节,如果是.m脚本编写,其实只需要规定矩阵即可)
A = [0 1 0; 0 12 1; 0 1 6]; b = [0;0;1]; c = [1 0 0]; d = zeros(3,1);
状态方程转传递函数ss --> tf
注意返回值格式,至于转zpk等,就利用tf或者num,den来,见前经典控制理论总结http://t.csdn.cn/AZ6ud,这里不赘述
[num,den] = ss2tf(A,b,c,d); %举例,利用1.的模型 [bb,aa] = ss2tf(A,b,c,0); gs9 = tf(bb,aa);
传递函数转状态方程 tf --> ss
[A,B,C,D]=tf2ss(num,den);
稳定性判据:李雅普诺夫判据,理论请查阅相关资料(其实是本菜狗也没掌握),关键公式见下
(csdn的公式有点拉,这里就只贴一个方程了)
所以一般规定N为单位阵,带入求M,看M是否正定
判断正定可以直接看矩阵的形式或者使用chol对其平方根分解,但是如果不正定,会报错,所以使用一下matlab的异常语法
A3 = [0 1 0; 0 -2 1; -1 0 -1]; b3 = [0 0 1].'; p3 = lyap(A3',eye(3)) p3_1_1 = p3(1,1) p3_2_2 = det(p3(1:2,1:2)) p3_det = det(p3) % 使用定义判断p3各阶的行列式是否为正 try p3_frac = chol(p3) catch Error disp(Error); disp("该系统不稳定"); end % 能平方根分解,则正定->渐进稳定
可控性
获取可控性矩阵的秩,判断是否为n即可
sys_controlable = rank(ctrb(A,b)); %这个返回秩,为n即可控
可观性
同上,获取可观性矩阵的秩,判断是否为n
sys_visual = rank(obsv(A,c));%这个返回秩,为n即可控
状态反馈的使用
首先是根据已有的目标系统特征根p_target来设计反馈矩阵,对于特征根中没有重复的使用place即可,有重复的需要使用acker
A1 = [0 1; -103.57 -3.945]; b1 = [0;1]; c1 = [100 0]; %p都是列向量 p_target = [-7.35+7.5i -7.35-7.5i].'; k1 = place(A1,b1,p_target)
重要提醒:上面针对的是系统极点配置,求k,而如果是观测器的极点配置,则应该使用如下形式(不相信可以自己找个模型算一下h,比对哪种结果正确)
%接上例,观测器极点配置到-57和-57 p_vis_target = [-57 -57].'; h1 = acker(A1',c1.',p_vis_target) %注意这里是A的转置和c的转置
重点:期望极点的设计
这里由于比较复杂,所以直接copy自己实验报告的内容了,其中感觉也有瞎哔哔的地方,具体使用什么方式最佳以实际仿真效果为准(放假了有点小摆hhh)
在计算以及仿真时,从调参的难易程度以及效果上,个人认为调参效果:LQR最佳
调参难度:LQR<主导极点
,LQR所需要的参数直观反应着被调状态的权重,权重几乎直接决定了其收敛、振荡、速度等性能,根据LQR求出的k也能看出设置不同权重时k的变化如何,从而快速掌握调节方向;主导极点主要是设置距离虚轴很近的一对极点来使得系统的状态整体表现为所需的性能,而一般使用共轭极点,利用二阶系统的欠阻尼性能分析,也可以得到较好效果,只是实轴上极点的选择需要考虑系统状态变量间相互作用的影响而谨慎设计;PID应用也比较广泛,工程中有很多三个值设计的口诀;根轨迹法主要是由于状态反馈不改变系统零点,而根据闭环零极点对系统性能的影响综合考虑根轨迹增益,对于状态量较多时并不直观,也不容易观察。 若未来需要进行系统控制时,会优先考虑更多使用LQR进行设计
LQR控制器设计
属于是由偏往正讲了,这个LQR设计器的思路请见控制巨佬的视频讲解,原理以及实现全都有了
【【Advanced控制理论】8_LQR 控制器_状态空间系统Matlab/Simulink建模分析】 【Advanced控制理论】8_LQR 控制器_状态空间系统Matlab/Simulink建模分析_哔哩哔哩_bilibili
总之就是Q是一个正定矩阵(一般取对角阵),Q中对角线的大小代表对应位置的状态量的贡献“权重”,所以当某个状态量权重越大,状态反馈后输出的振荡性越弱(k值会更大,意味着负反馈作用更强)、快速性提升;R类似,一般控制时会取得小一些,调节QR时要注意观察k中各项的变化,这样才好控制调节
主导极点法
老实说,这个方法比LQR还要直接,因为核心思想就是,利用主导极点基本上决定了系统的动态性能,一般整2个,用二阶系统(多为欠阻尼)来设计性能(比如超调量、调节时间),而且还有对应公式便于求得阻尼比和角频率,其余的极点设计得距离虚轴比较远即可(5倍实部以上,也不应该选的太大,具体看仿真效果),最后出来的效果一般都很接近设计的性能
举例(系统性能根据题目要求的来设计就好,比如要求位移x5秒内稳定,那对应的状态变量的那一组(每一个状态变量都会有一组Abcd,对应一个传递函数,但是由于A一样)的调节时间就要小于5s)
将系统的主导极点设计为一对共轭的极点,并用二阶欠阻尼的形式分析该系统,这里假定系统调节时间为Ts = 4s,初定ζ= 0.7,由下面公式:
计算得wn = 1.607,再利用二阶系统的标准表达式求主导极点
wn = 1.607; kesai = 0.7; roots([1 2*wn*kesai wn^2])
得到结果,并自己设计两个非主导极点,总体设计得到的p_target为[-15 -15 -1.1249 + 1.1476i -1.1249 - 1.1476i]
附加注意的点:与经典控制理论不同的是,有n个输出(看c的size),由Abcd转为的tf表达式就有n组,也就是n个传递函数,但是他们共享同一组特征根(因为Gs=c·(sI-A)^-1·b,但是进行了状态反馈后,仍旧有类似于经典控制理论高阶系统降阶的那种效果,会改变系统的闭环增益,影响最终稳态值(所以要针对性地给系统补充一个闭环增益,自己做的实验中一般都是一个状态变量对应输出稳态值不为0,其他都为0,所以直接加闭环增益都可以),那应该怎么加这个增益呢,请看下面这个例子
在研究阶跃响应的状态下,系统传递函数由[1]->[2],而令s=0带入(如果是别的函数,比如三角函数,那么应该先将G(s)转为G(wj),再求输入的三角函数对应的w0下的|G(w0 j)|的值,想一想频率响应部分的知识),得到的G’(s)/G(s)就应该是我们为了让G(s)的效果与添加状态反馈之前一致所需要手动增加的闭环增益
建模时效果就应当如下
pid调节
道理同上那句话,有多少输出,就有多少个传递函数,需要调节哪一个变量,那就针对那一个传递函数进行,回到经典控制的pid设计方式就行,用好matlab自带的pid函数,举个例子就是了
pid =pid(p,i,d,tf); gs_pid = feedback(pid*gs,1); %gs是传递函数
以及用好matlab实时脚本的控件--滑块(下图中就是针对x的这个状态变量进行调节)
根轨迹法
个人感觉没有掌握好这个方法,而且用起来还是有些不方便,具体可以参考这个视频
【自动化考研,根轨迹法超前校正,上海交通大学王显正教材例题解读!】 自动化考研,根轨迹法超前校正,上海交通大学王显正教材例题解读!_哔哩哔哩_bilibili
额外补充一下在根轨迹图上画点和标注文字的函数,以下仅展示局部
figure; subplot(2,2,1); %这里是求得第一个传递函数的零点,分别把实部和虚部得到 zs = roots(nums(1,:)); re = real(zs); im = imag(zs); %这里就是画点了 plot(re,im,'+r'); hold on %添加一下文字注释 text(re,im,num2str(zs)); rlocus(gs_x); hold off title('x'); axis normal
效果如下图(画点的参考文章找不到了,还请自行csdn或者being)
ss建模
使用元器件:state-space模块(十分方便,比一点点用积分器和gain搭建快多了)
(注:不用它而直接搭建时就是下面的鬼样子)
如何使用它呢:首先双击开这个大可爱,发现其实它里面就让填Abcd,但是老实填入是不够的,因为未来大概率要用到状态反馈,如果输出直接是y了,那又怎么获得x来反馈呢,所以根据y = cx+du,那么c设置为单位阵,d为零阵(保持行数与状态变量个数一致,列数与输入的个数一致),输出的y就是每个状态变量(下图为真实系统的状态模型部分)
接着后面利用gain来对输出的状态变量再作用c,这样就得到y了,而状态变量是一个复合信号,需要分解才便于单独观察,在元器件库中搜索demux,可以分解为多个信号(图里标错了),以上内容为橙色框外的解释
接下来是观测器(橙色框内)部分,为什么这里要把b单独提出来呢,仔细观察,因为观测器的反馈向量H是一个列向量,H(y-cx_hat)的结果是一个(n*1)的向量,这一部分想直接和u(在图例中为一个单输入,所以是1*1的格式)一起加为输入是不对的,所以选择了直接在外部,将u左乘b,变为(n*1的样子),而在观测器的ss模型中,也需要对参数格式微调,这里也仍然只输出x_hat的观测状态变量,y_hat单独计算
这里重点注意B的格式,它直接定义为单位阵了,所以满足公式
额外补充
好像基本上建模按照上面来就差不多了,再补充一些杂碎的东西
simulink实现
如何观察
由于相轨迹是状态变量和其导数的关系,所以需要单独得到这两个量
一般对于没有指定状态变量是误差量的,都默认观察输出和输出的导数,从线性环节的分母中拿1/s出来,做积分器(添加方法上面说了),如果分母没有,那就分子分母同时乘s,将引出的y和y_dot分别作为x和y轴变量的信号加入元器件XY Graph,运行仿真,会自动弹出绘图小窗,但是这个小窗的显示范围需要自己调整,双击可设置
接下来是建模
线性环节在经典控制理论就已经掌握,故不赘述,这里主要说非线性环节
这里用到了matlab_function这个元器件,其定义是用.m脚本实现,添加该元器件后双击可以进入这个函数的.m定义,定义方法与自己写的自定义函数一样,下面细说
如上图所示,图中已经给好了两种常用的matlab函数自定义方法,一种是用if-else的条件语句来分别定义输出;
function output = fcn_name(param1, param2,...) expression; end
一种是用向量点乘的方法,格式为
output = (expression).*(condition) + ...
个人推荐第二种表达,因为第二种能适用于x是向量的情况(第一种使用向量就会出问题)
参数设置好后还可以在simulink里面传参,使用constant元器件即可(常数)
几个典型环节代码分享
死区
function y = dead_zone(x,d,k) %传入x,d,k y = k*(x-d).*(x>d) +k*(x+d).*(x<-d)+0.*(x>=-d & x<=d); end
摩擦
function y = fric(x,fs,fd,k) % 摩擦特性,传入x/static静摩擦/dynamic动摩擦因数/k斜率 dx = [diff(x) 0]; y = (k*x+fd).*(x>0)+ (k*x-fd).*(x<0)+fs.*(x == 0 & dx >=0) +-fs.*(x==0 & dx<0); end
饱和
function y = saturate(x,k,s) % 饱和特性,传入x/k/s y = k*s.*(x>s)+ -k*s.*(x<-s)+ k*x.*(-s<= x & x <= s); end
继电
这里使用了一个骚操作,参考的文章为这个
张聚,杨马英,王万良.基于Simulink的非线性系统自激振荡的仿真研究[J].电气电子教学学报,2003(02):40-43.
以及这个
http://t.csdn.cn/YwZ0Y
核心思想是:对于正常的非滞环部分,用x的范围就可以限定,而对于滞环的部分的输出,是直接等于上一次的y的值不变(比如dx之前对应的y值是1,那么下一次采样进入滞环时,y值还是上次的1,而不会跳到-1去),在simulink中使用了memory元器件来实现保存上一次输出的作用(书上是通过求导数来实现,这样整出来涉及首尾的dx不好处理,最后结果不正确,所以改用这种思路)
function y = relay(x,last,h,m) % 继电特性,传入x/last/h/m y = m.*(x>h)+(-m).*(x<-h)+(last).*(-h <= x && x <= h); end
写脚本建模
这里利用死区环节举例说明前面的第二种自定义函数表达多么方便,注意看注释,其中涉及的画图方法不赘述,这里演示的是将信号作用于非线性环节后的绘图
(这里使用global是因为后面用代码绘制相轨迹需要)
k = 1; d = 0.5; global t_ed; t_ed = 20; t = 0:0.005:t_ed; %这里直接创建了向量t len = length(t); x = sin(t); %直接作用得到了x figure; plot(t,x,'Color','#008080'); hold on y1 = dead_zone(x,d,k); %将x传入自定义函数,由于使用第二种方法,所以可以直接对x向量处理得到y向量,函数代码与前面一致 plot(t,y1,'Color','#D2691E'); legend('x','y'); title('dead_zone'); hold off
补充,针对代码版本的,继电环节则需要改为下面比较麻烦的版本(dx是试出来的,倒没有问题)
function y = relay(x,h,m) % 继电特性,传入x/k/m dx = [diff(x) 0]; y = m.*(x>h | (x>-h & dx<0) )+-m.*(x<-h | (x< h & dx >0)); end
脚本&相轨迹
注意:做的自控实验题目涉及绘制二阶线性系统的相轨迹部分我才是用代码完成的,含有非线性环节还是用上面simulink做更好一些
从外向内分析
最外层,比如要绘制一组相同阻尼比,不同角频率的图像,先在外部分别定义好每次绘制所需的参数(ζ和wn),规定global是因为之后要在内层函数中继续使用(有的内层函数不方便传参)
使用方式类似于
%外层 global kesai global wn kesai1 = 0; wn1 = 10 fcn(); %fcn函数内部 function fcn() %先声明 global wn %再使用 disp(wn); end
而外层的函数就可以是
global t_ed t_ed = 10; figure; wn = 10; kesai4 = [-1 -1.5]; for i= 1:2 kesai = kesai4(i); subplot(1,2,i); draw_phase_locus(); title('kesai = ',num2str(kesai)); end
中间层函数
现在更新了全局变量的值,内部就需要处理二阶微分方程的解,这里使用了ode45函数([t,y] = ode45(odefun,tspan,y0)),传入odefun(具有指定的格式,内层讲),第二个参数是时间的范围[t_st,t_ed];第三个参数,每一组(m,n)表示二阶微分方程的一组初值,就可以画出一个相轨迹,所以这里用两层for取得多组解,并且配合hold on来绘制于同一个figure中,(下面的负阻尼的情况其实相轨迹的斜率很大,所以需要限制显示范围,用了axis函数)
function draw_phase_locus() global t_ed for m = -10:4:10 for n = [-10 10 -1 1] [t,y] = ode45(@odefun,[0,t_ed],[m,n]); %求微分方程随着不同时间的解 hold on plot(y(:,1),y(:,2)); axis([-5 5 -90 90]); %限制范围 end end hold off end
内层函数
这里的表达有点类似于状态方程,输出为状态量的导数,输入的y就是状态量,二阶微分方程变形一下就可以写出dy2的表达式了
function dy = odefun(t,y) global kesai global wn dy = zeros(2,1); dy(1) = y(2); dy(2) = -2*kesai*wn*y(2)-wn^2*y(1); end
最后展示一下效果
直接用脚本绘制,把负倒描述函数用句柄函数定义好,然后取得实部与虚部plot即可
句柄函数定义
这里有所讲究,因为前面提到过,使用向量运算的方式可以直接处理一列(行)x,而不用再for循环遍历,以继电特性为例,其描述函数如下
这里面含X的项的表达一定要在运算符号前加一个 . ,表示向量运算(不然会报错)
ry = @(x) -1./( 4*m*sqrt(1-(h./x).^2)./(pi*x)- sqrt(-1)*(4*m*h./(pi*(x.^2))) ).*(x>=h);
使用函数
这里要注意X的取值范围,根据滞环特性,如果X始终小于h,那么久没有意义,所以X最小得从h开始,然后给句柄函数传参就完事了
x = h:0.2:20; res = ry(x);
绘图
也就是把运算结果的实部和虚部分别提出来(matlab的real和imag函数支持对向量进行这个操作,真的方便),当然为了方便观察,也可以考虑限制一下绘图范围,用axis函数
plot(real(res),imag(res)); %画出非线性的图像 axis([-3 1 -1.5 1.5]); %限制绘图区域
总体代码
h = 0.5; m = 1; ry = @(x) -1./( 4*m*sqrt(1-(h./x).^2)./(pi*x)- sqrt(-1)*(4*m*h./(pi*(x.^2))) ).*(x>=h); x = h:0.2:20; res = ry(x); plot(real(res),imag(res),"Color",'r'); hold on nyquist(gs4); axis([-3 1 -1.5 1.5]); hold off;
自控2完结撒花✿✿ヽ(°▽°)ノ✿,此生无悔入自控