在刚开始学习PID的时候,都需要借助PID的曲线来理解比例,积分,微分这三个参数的具体作用。但是这些曲线生成一般都需要借助上位机软件或者在网页上才能实现。如果是在单片机上调试程序的话,想要看曲线,一般就是通过串口将数据发送出去,然后使用串口波形显示软化将曲线打印出来。
如果使用的是CCS软件来开发的话,那么就可以直接使用CCS软件自带的图形显示工具来打印曲线。
比如要测试的代码如下:
typedef struct
{
float P;
float I;
float D;
float limit;
}PID;
typedef struct
{
float Current_Error; //当前误差
float Last_Error; //上一次误差
float Previous_Error; //上上次误差
}ERROR;
// 位置式PID
float PID_Realize(ERROR *err, PID *pid, float feedback, float reference)
{
float iError, Realize; // 当前误差 实际输出
iError = reference - feedback; // 计算当前误差 期望值-当前值
err->Current_Error += pid->I * iError; // 误差积分
err->Current_Error = err->Current_Error > pid->limit ? pid->limit : err->Current_Error; //积分限幅
err->Current_Error = err->Current_Error < -pid->limit ? -pid->limit : err->Current_Error;
Realize = pid->P * iError //比例P
+ err->Current_Error //积分I
+ pid->D * (iError - err->Last_Error); //微分D
err->Last_Error = iError; // 更新上次误差
return Realize; // 返回实际值
}
// 增量式PID
float PID_Increase(ERROR *err, PID *pid, float feedback, float reference)
{
float iError, Increase; // 当前误差 最后得出的实际增量
iError = reference - feedback; // 计算当前误差
Increase = pid->P * (iError - err->Last_Error) //比例P
+ pid->I * iError //积分I
+ pid->D * (iError - 2 * err->Last_Error + err->Previous_Error); //微分D
err->Previous_Error = err->Last_Error; // 更新前次误差
err->Last_Error = iError; // 更新上次误差
return Increase; // 返回增量
}
这是两种很典型的PID算法,一个位置式,一个增量式。下面就在CCS软件中测试这两个函数,这里使用的是dsp28335芯片。首先定义PID和ERROR结构体变量。
PID pid = { 0.1, 0.01, 0.001, 100.0 };
ERROR err = { 0.0, 0.0, 0.0 };
接下来就可以直接在main函数中调用上面两种pid的算法了。
float ref = 100; //参考值
float sample1=0,sample2=0; //采样值
void main()
{
InitSysCtrl();
InitPieCtrl();
IER = 0x0000;
IFR = 0x0000;
InitPieVectTable();
LED_Init();
while(1)
{
sample1 += PID_Realize(&err,&pid,sample1,ref);
sample2 += PID_Increase(&err,&pid,sample2,ref);
}
}
将需要设定的参考值ref
设置为100,然后两个采样值sample1
、sample2
都设置为0,将PID计算出来的结果作为第二次的输入,然后在调用PID计算,这样一直循环。
代码编写完成之后,直接debug开始调试。
进入调试界面之后,在变量sample1
上单击鼠标右键,选择添加到观察窗口
弹出的窗口上点OK
此时窗口右边就会出现一个变量显示区域
同样在变量sample2
上也单击右键,添加到观察窗口。
添加完成之后,将黄色箭头的图标选中,这个是实时刷新功能,这样在代码全速运行的时候,就可实时看到变量值在变化。接下来全速运行代码。
此时可以看到这两个变量值都停在在100作用不变化了,没有看到变换的过程。下面将这两个值添加到图形显示中。在观测窗口变量值上单击鼠标右键,选择Graph。
此时就会新增一个图形显示窗口
在另一个变量上右键,选择Graph。
此时又增加了一个显示窗口,但是这两个窗口重叠在了一起,看起来不方便,在窗口标题处按住鼠标左键,拖动窗口,将两个图形显示调节到合适的位置。
然后再这两个图形显示窗口上,将实时刷新的按钮选中。
接下里全速运行程序。
这时候PID调节的曲线就显示出来了,但是这个曲线和PID调节的曲线看起来不太一样,难道是程序有问题吗?当然不是程序的问题,是这个图形显示的刷新率太低了,而我们的程序执行的太快了。下面在代码中添加延时。
while(1)
{
sample1 += PID_Realize(&err,&pid,sample1,ref);
sample2 += PID_Increase(&err,&pid,sample2,ref);
DELAY_US(1000*200);
}
重新编译运行代码
这时候就可看到右侧的变量和曲线都在缓慢的变化,可以看到PID的调节过程了。
为了方便修改PID的参数,将结构体变量pid
也添加到观察窗口中。在代码中选中pid
然后右键添加到观察窗口。
此时右边的观察窗口中就多了一个pid
的结构体变量,将PID前面的加号点开,这时候就可以看到结构体中每个成员的值了。
这时候在变量窗口中就可以直接修改这些参数的值了。鼠标双击要修改的值,选中值之后,直接输入要改的值,然后按回车键。
这里将P的值修改为0.2
修改完成之后,底下的曲线没有发生变化。要想重新看pid的调节过程,这里将两个采样值都改为0 。
这时候PID函数就会从0开始重新计算,底下的曲线也会重新绘制。
这样只需要在变量观察窗口直接修改PID的参数,就能实时看到PID曲线的效果了。不用在程序里面修改一次参数然后又重新仿真运行。
还有一种方法,不需要加延时也能实时查看波形。下面就介绍一下这个方法。
定义两个数组来存放采样值,然后增加一个变量来记录数组的下标。在主函数中将采样的两个值分别存放到数组中,当数组个数超过200个之后,将下标清零。此时代码中将延时函数去掉了。没有延时,相当于代码按照正常的计算速度运行。
int cnt=0;
float value1[200];
float value2[200];
while(1)
{
sample1 += PID_Realize(&err,&pid,sample1,ref);
sample2 += PID_Increase(&err,&pid,sample2,ref);
//DELAY_US(1000*200);
if(cnt<200)
{
value1[cnt] = sample1;
value2[cnt] = sample2;
cnt++;
}
else
cnt =0;
}
仿真运行代码,进入仿真界面后再工具栏选择 Tools—Grapg—Single Time
这时候会弹出一个对话框,在对话框中填入需要观测的数组信息。
将数组相关信息填写进去,然后点OK按钮,此时就会新增一个图形显示窗口
按照同样的方法,在工具栏选择 Tools—Grapg—Single Time,将value2数组也添加到图形显示中,然后选中实时刷新按钮。
然后在 cnt=0
这一行打个断点,用来观察一次计算完成之后的波形。全速运行代码。
程序在断点处停止后,左边两个图形显示的是正常的PID调节曲线,右边两个是阶跃信号。这是因为取消延时后,图形显示的刷新率太低,不能实时捕获到每个数据,所以直接显示变量的曲线就看不出来细节了,而数组显示比较完整,是因为代码中将过程中的所有数据都先存储到了数组中,然后根据数组中的数据来绘制曲线。
继续全速运行,这时候曲线就会变成一条直线,这是因为PID调节完成之后,数组中的值被更新了。
如果还想继续观察调节过程,在程序中断之后,将sample1
和sample2
的值改为0,修改完之后,这时变量的曲线值也会从100变为0.
全速运行代码
这时候左边两个数组的曲线就会被重新绘制出来。
如果要修改PI参数的话,每次程序在断点处停止之后,修改PI参数,然后将sample1
和sample2
的值改为0,接着全速运行,就可观测到新的PI参数生成的曲线了。
这样在学习PID的时候就不用借助上位机软件,使用CCS本身的图形显示工具,就能看到不同参数下PID调节的差别了。
完整代码如下
#include "DSP2833x_Device.h" // DSP2833x Headerfile Include File
#include "DSP2833x_Examples.h" // DSP2833x Examples Include File
#include "leds.h"
typedef struct
{
float P;
float I;
float D;
float limit;
} PID;
typedef struct
{
float Current_Error; //当前误差
float Last_Error; //上一次误差
float Previous_Error; //上上次误差
} ERROR;
// 位置式PID控制
float PID_Realize(ERROR *err, PID *pid, float feedback, float reference)
{
float iError, Realize; // 当前误差 实际输出
iError = reference - feedback; // 计算当前误差 期望值-当前值
err->Current_Error += pid->I * iError; // 误差积分
err->Current_Error = err->Current_Error > pid->limit ? pid->limit : err->Current_Error; //积分限幅
err->Current_Error = err->Current_Error < -pid->limit ? -pid->limit : err->Current_Error;
Realize = pid->P * iError //比例P
+ err->Current_Error //积分I
+ pid->D * (iError - err->Last_Error); //微分D
err->Last_Error = iError; // 更新上次误差
return Realize; // 返回实际值
}
// 增量式PID
float PID_Increase(ERROR *err, PID *pid, float feedback, float reference)
{
float iError, Increase; // 当前误差 最后得出的实际增量
iError = reference - feedback; // 计算当前误差
Increase = pid->P * (iError - err->Last_Error) //比例P
+ pid->I * iError //积分I
+ pid->D * (iError - 2 * err->Last_Error + err->Previous_Error); //微分D
err->Previous_Error = err->Last_Error; // 更新前次误差
err->Last_Error = iError; // 更新上次误差
return Increase; // 返回增量
}
PID pid = { 0.1, 0.01, 0.001, 100.0 };
ERROR err = { 0.0, 0.0, 0.0 };
float ref = 100; //参考值
float sample1 = 0, sample2 = 0; //采样值
int cnt = 0;
float value1[200];
float value2[200];
void main()
{
InitSysCtrl();
InitPieCtrl();
IER = 0x0000;
IFR = 0x0000;
InitPieVectTable();
while (1)
{
sample1 += PID_Realize(&err, &pid, sample1, ref);
sample2 += PID_Increase(&err, &pid, sample2, ref);
//DELAY_US(1000*200);
if (cnt < 200)
{
value1[cnt] = sample1;
value2[cnt] = sample2;
cnt++;
}
else
cnt = 0;
}
}