理论 链接到标题
针对情形 链接到标题
- 需要对被控对象的多个物理量进行控制(比如同时控制位置和速度),将多个PID串级连接。(如果是并行的话可能会发生输出冲突)
- 对于单个变量的控制,在功能和性能上多环PID都会比单环控制器好。
注释
感性的理解就是我们,如果我们将速度环PID与电机看成一个整体,那么这个整体相当于一个自带定速控制的电机,其效果肯定单纯的开环电机要好。
实际控制流程 链接到标题
下面对实际的控制流程做一个分析。
- 初始时刻 目标位置、实际位置、目标速度、实际速度都为零
- 现在(用户)设置一个目标位置(非零的值,不妨设大于零),
- 位置环PID检测到差异,从而使得输出速度开始增大
- 目标速度=输出速度
- ∴速度环PID也检测到差异,输出PWM开始增大
- 并且由于速度环PID与电机闭环,因此可以使得电机输出一个趋向于目标速度的速度。
- 电机的转动使得实际位置开始与目标位置靠近。
- 因此输出速度也在逐渐的减小;
- 进一步的导致输出速度也开始减小;
- 理想状态时在目标速度趋向于实际位置的时候,输出速度(即目标速度)也趋向于零,形成的效果就是电机丝滑泊入目标位置,并不存在超调现象。(妙啊!)
提示
这也就是为什么我们刚刚说双环具有更高的响应速度和性能
代码实现:双环定速控制 链接到标题
初步移植 链接到标题
- 以02的定速控制代码为基础进行修改,因此也是准备将其变成内环的,因此对所有设计的变量都加前缀Inner。
- copy一份代码作为外环
- 首先改分频始终信号,将count改为count1(内环)和count2(外环)
- 并且也对外环的改成前缀Outer(这里视频中说的是用Keil的功能,不过笔者更倾向于用vscode的功能,)
修改外环代码 链接到标题
Act的获取 链接到标题
首先,由于Encoder_Get()
获取的事每次调用函数的位置增量,所以如果其在一个工程中被调用了两次,就不符合我们想要的差分效果了,因此,我们需要用两个中间变量先储存测量量。在一开始调用一次Encoder_Get()
,存储好每一周期测得的速度和位置:
/*(这段代码应该在count1计数的代码中)*/
Speed = Encoder_GetSpeed();
Location+=Speed;
/*然后在下面将其分别赋值给InneActr和OuterAct*/
外环的输出作为内环的输入 链接到标题
将外环的最后一句的输出改一下
InnerTarget = OuterOut;
于是限幅的范围也要做一点调整了。将外环的输出限制在内环的输入范围,之前这个也控制过,是150。于是限幅……
if (OuterOut > 100) {OuterOut = 100;}
if (OuterOut < -100) {OuterOut = -100;}
双环PID调参 链接到标题
警告
同时调节双环的六个参数是不可行的,内外环的参数会互相影响,很那看出结果找到最优解
注意到内外环虽然会相互影响,但是是相对独立的,并且内环脱离外环能独立工作
因此我们先调内环!
调内环 链接到标题
将外环的代码注释掉,(或者最重要的注释掉InnerTarget = OuterOut;
),烧录工程。
- 先调$K_p$,调整好的标准是开始出现一点点抖动
- 再调$K_i$,这部分可能需要结合着来调整$K_p$,反正最后反应迅速且抖动在能容忍范围内就是好调。
- 最后调$K_d$,可要可不要,如果前面调得好可以置0.
我的调节结果如下(我认为已经比较好切不出现明显抖动)
参数为
- $InnerK_p=0.42$
- $InnerK_i=0.41$
- $InnerK_d=0.00$
于是我们将我们的结果写出程序。
调外环 链接到标题
程序改动部分 链接到标题
- 将刚刚的外环代码取消注释
- 将量纲设置改一下,外环控的是位置,因此改成下面的程序
/*外环的量纲设置以及显示*/
OuterKp = RP_GetValue(1) / 4095.0 * 2;
OuterKi = RP_GetValue(2) / 4095.0 * 2;
OuterKd = RP_GetValue(3) / 4095.0 * 2;
OuterTarget = RP_GetValue(4) / 4095.0 * 816-408;//这是主要要修改的部分,将速度的量纲变成位置的量纲(或者说是数量级)
OLED_Printf(0, 16, OLED_8X16, "Kp:%4.2f", OuterKp);
OLED_Printf(0, 32, OLED_8X16, "Ki:%4.2f", OuterKi);
OLED_Printf(0, 48, OLED_8X16, "Kd:%4.2f", OuterKd);
OLED_Printf(64, 16, OLED_8X16, "Tar:%+04.0f", OuterTarget);
OLED_Printf(64, 32, OLED_8X16, "Act:%+04.0f", OuterActual);
OLED_Printf(64, 48, OLED_8X16, "Out:%+04.0f", OuterOut);
OLED_Update();
Serial_Printf("%f,%f,%f\r\n", OuterTarget, OuterActual, OuterOut);
- 烧入程序
实测调节 链接到标题
- 可以看到,在$K_p$、$K_i$和$K_d$都调节到0.00的情况下(也就是没有PID的情况下),电机能做到惊人的定速(图中的抖动是我用手稍稍施力扭动的结果),这是内环的功劳,此时即使有偏差,但是速度是稳住零的,不会抖动。
提示
观察图可以发现在前段有向目标值靠近的趋势,这是因为电位器不能完全保证$K_p$、$K_i$和$K_d$都为0.00的,因此不能做到严格的开环控制,外环PID还是会一点点的将实际位置往目标位置拽。
- 外环一般使用PD控制
- 先调$K_p$,调到电机能在目标位置附近抖动为止
注释不用慌,因为后面$K_d$会在一定程度上消除超调
- $K_i$最好不要给,可以发现一点点的$K_i$就能造成很大的超调和震荡,因为在这里本身也没有稳态误差
错误为什么没有稳态误差的证明还没有找到
- $K_d$可以较少超调,一直调,直至出现不可忍受的延迟。
提示
还是那句话,参数越大,响应越快,但是不可避免的抖动也就越快。
参数为
- $OuterK_p=0.44$
- $OuterK_i=0.00$
- $OuterK_d=0.40$
不过无论是追求快速型还是丝滑型,在转盘停下来后
- 误差都能被消除(或者说相对来说消除的比较干净),
- 且不会出现抖动。
这还是内环的功劳
代码实现:双控控制定速与定位 链接到标题
实际上这个表示并不严谨,应该是限速定位控制,方法是改刚刚代码中的限幅部分,
if (OuterOut > 20) {OuterOut = 20;}
if (OuterOut < -20) {OuterOut = -20;}
这里将100改成了20,
- 由于在调节的时候除了在结束时速度PID控制器基本都是满速控制的,因此可以认为我们实现了“定速”20。
- 另一方面,这个速度仍然是闭环控制的,因此如果手动加阻力,其out会增大使得速度仍然在20出。
图像如下,
- 图中红框部分是我用手施加阻力的情况,如果是单环定位控制这部分的抖动一定是剧烈的,但是在双环控制下可以看到仍然是很稳定的(当然,肯定是与无阻力情况有所不同的)。
- 另一方面,限速相当于主动降低延迟,因此过冲的情况几乎没有,如果追求丝滑的可以考虑(嘿嘿)……
PID代码的封装 链接到标题
实际上,这是在处理多级问题必须要考虑的,通过函数或者结构体的封装可以使得功能模块化,有助于提高代码的可读性,方便日后的维护和调用。
用结构体封装变量 链接到标题
提示
.h文件的写法
在之前学C语言时候并没有系统学习.h文件以及调用的写法,下面补充一些知识, 依据ai,对于PID.h文件的基础写法是这样
#ifndef __PID_H // 如果没有定义这个宏
#define __PID_H // 那么定义这个宏
// 这里放你的头文件内容(函数声明、类定义等)
#endif // __PID_H // 结束宏定义
代码解释也已经在里面了,