#ifndef _PID_H
#define _PID_H
//定义一个PID结构体
typedef struct PID
{
double SetValue; //设定的目标值
double Kp; //比例系数
double Kd; //微分系数
double Ki; //积分系数
double Error1; //误差量,即为当前值和目标值的差
double Error2; //也是误差量,但是这里是Error1之后时间的当前值和目标值的差,假设Error1在前
double sum_error; //误差的总和,这个就是误差的积分
} PID;
#endif
#include "pid.h"
#include "string.h"
#include "stdlib.h"
//初始化PID结构,这里独立出来比较符合单片机的写法,哈哈,也可以写到主函数里面去
void PIDInit(PID *Stru) //这里就是随便弄了个结构体变量Stru,反正是传递地址,主函数里面随意
{
memset(Stru, 0, sizeof(PID)); /*这里把Stru的内存块中全部替换为0,据我的经验,一开始分配好内存后都是些无意义数据或者残留数据,这里memset函数的作用实际上把Stru里面所有字节最后“sizeof(PID)”位的字节全部替换为0,因为这里位数为sizeof(PID),因此是初始化全部字节为0 */
}
//PID的内部运算函数
double PID_OP(PID *Stru, double NewValue) //NewValue是新读取到的值
{
double dError,Error; //dError即为对误差微分,由于我们这里是处理离散数据,所以待会其实就是作差
//误差计算
//这里是目标值和当前读取值的偏差,这里吧,无论是当前值在前还是目标值在前都是无所谓的,因为对于整个PID而言,误差的比例算法、积分算法、微分算法都是统一用的。不过话说回来,
//我不知道使用者是打算用什么负号表示输出,姑且这么用吧,使用者用时根据自己的理解用吧,毕竟咱这里只是个大体框架。
Error = Stru->SetValue - NewValue;
//加入微分算法
dError = Stru->Error2 - Stru->Error1; //当前微分计算(离散)注意,这里如果误差越来越小的话无疑这里微分是负值
//加入积分算法
Stru->Sum_Error += Error; //积分运算
Stru->Error1 = Stru->Error2; //新误差值传递给旧误差,如此往复传递,为下个离散时间点PID的计算做准备
Stru->Error2 = Error; //这里一开始Error2应该是0,但无所谓,这里计算过程用时是循环,后面会有值堆上去。
return (Stru->Kp * Error + Stru->Ki * Stru->Sum_Error + Stru->Kd * dError); //按颜色不同,分别为比例、积分、微分。但是可以注意的是,这里并不是初始的PID系数。在这个PID框架里面,很明显,是把初始的PID数学式展开了,后面的微积分两个系数都是和比例系数发生了运算的,这不影响。
}
//输入函数和输出函数,叫输入输出系统好些
//这里输入的就是读取编码盘数据,输出就是“控制器的输出”,大致就是改变PWM啥的吧,这个之后再说也行。这里就不能具体写了,是单片机的特色部分,本来我是不打算写上去的,不过参考的罗世洲先生的代码里面提到了这个,鄙人思索之下还是加上去了,毕竟框架嘛,嚯哈哈
void Input() //采集编码盘或别的数据
{
//加油
}
void System_Out(PID_out) //注意这里不是PID输出,而且根据PID结果进而进行相应硬件输出
{
//加油
}
//定时器初始化函数
//设置定时中断作为控制周期
void time_interrupt_Init(这里可以放装填初值啥的,随意)
{
//这里进行一些启动定时器中断、设置工作方式、装填初值啥的骚操作
}
//中断函数
void Timer0() interrupt 1 //这里我当做51来做了
{
//定时器硬件的装初值或其它操作语句,具体硬件具体分析;
PID_in = Input(); //执行输入函数,读取输入
PID_out = PID_OP(&Squ, PID_in); //运行PID的运算函数,得出PID_out,好用于系统输出函数
System_Out(PID_out); //系统输出
}
//主函数
void main()
{
PID Squ; //Squirtle是杰尼龟的英文名,杰尼龟在咱们国家被亲切叫做憨批龟,开个玩笑,这个结构体变量就用憨批龟吧!
double PID_out, PID_in; //定义PID的数据来源和数据输出
PIDInit(&Squ); //执行PID初始化函数,其实这里就只是全部存成0,记得吧,之前说的那个memset函数。
Squ.Kp = 0.5; //设置比例系数,当然三个系数和目标值不一定得就这么赤裸裸写出来,换成读取感觉更灵活,随意
Squ.Ki = 0.5; //设置积分系数
Squ.Kd = 0.1; //设置微分系数,值得注意的是,物理进程里面这里起到阻尼作用,无论是对动态还是静态的阻尼,这都是一个“往回扳”的作用,变化越快阻尼也越大
Squ.SetValue = 100.0; //设置目标值,这里是随便弄的
void time_interrupt_Init(/*这里可以放装填初值啥的,随意*/); //初始化定时器
//程序停在这里等待中断发生
//主函数里面还可以放一些存储调试数据的语句,这种语句优先等级低,在不干扰正常操作情况下存储或者发送调试数据,个人认为对调试有一定帮助(强烈推荐eeprom);
while (1)
;
}
|