前言
最近给客户开发了一款关于控温的冲洗机项目,正好来记录下PID算法实现的过程
原理
学术概念网上很多,一查一大把。简单来说就是通过误差来控制被控量。控制器就是比例,积分,微分三个环节的加和。公式和流程图如下:
流程图待补充
公式待补充
- 比例系数Kp
- 积分系数Ki
- 微分系数Kd
- 误差err(t)
- 输出量u(t)
为了便于理解流程,我把参数直接带入到具体环境。
- 输入量是预设定温度;
- 输出量为实际温度;
- 传感器为高精度NTC探头;
- 执行器是可控硅
所以得出结论:误差就是输入值和输出值的差值,输入的是预期要达到的水温,输出量为实际水温,实际水温由探头获取。
那么,u(x)作为公式的输出量,和执行器输出的电压又是什么关系?执行器输出电压和温度又有什么关系?可控硅的控制原理不再赘述,网上也有很多,只要知道G极有电流流过,可控硅就导通。无电流,过零时,可控硅关断。
第一个问题:公式输出量其实就是一个占空比,可控硅斩波输出的电压和电网电压的比值等于这个占空比。有了输出电压的变化,可控硅就可以控制加热丝的功率,自然水温的温度上升速率就可以控制。
另外,PID还分为位置型和增量型,本项目使用的是位置型。
- 位置型顾名思义就是输出值和当前位置(时间)对应,适用于舵机,控温系统等。位置型由于存在累计误差,所以输出量u(x)和过去所有状态有关。公式和上面推到的公式相同,需要积分限幅和输出限幅。
增量型适用于步进电机这类控制单元。每次执行器的输入信号决定本次执行动作终点位置相对于上一次动作重点位置的改变量。所以增量型不需要累加误差,控制量只与最近三次采样值有关。只需输出限幅。
公式待补充
实现
变量
typedef struct
{
float Kp, Ki, Kd; // 三个比例系数
float setVal, targetVal; // 设定值和目标值
float err; // 偏差值
float errLast; // 上个偏差值
float integral; // 定义积分值
float pwmMax;
float pwmMin;
float pwm;
}Pid_t;
主要实现
gsPidParameter.pwm = gsPidParameter.Kp*gsPidParameter.err+index*gsPidParameter.Ki*gsPidParameter.integral+gsPidParameter.Kd*(gsPidParameter.err-gsPidParameter.errLast);
gsPidParameter.errLast = gsPidParameter.err;
抗积分饱和
积分饱和(Integral windup / integral saturation)是指如果执行机构已经到极限位置,仍然不能消除偏差时,由于积分作用,尽管PID差分方程式所得的运算结果继续增大或减小,但执行机构已无相应的动作。
所以抗积分饱和就是计算u(x)时,首先判断上一时刻的控制量是否已经超过极限范围:若u(x-1) > umax则只累计负偏差,若u(x-1) < umax则只累计正误差
相关部分完整代码
void PidInit(void)
{
gsPidParameter.Kp = PID_KP;
gsPidParameter.Ki = PID_KI;
gsPidParameter.Kd = PID_KD;
gsPidParameter.err = 0.0;
gsPidParameter.errLast = 0.0;
gsPidParameter.integral = 0.0;
gsPidParameter.pwm = 0;
gsPidParameter.pwmMax = 100; //更改100,可控硅为单独控制,不和继电器一起
gsPidParameter.pwmMin = 0;
gsPidParameter.setVal = 0;
gsPidParameter.targetVal = 0;
}
u16 PidHandle(u8 setVal, u8 targetTemperature)
{
u8 index;
gsPidParameter.targetVal = (float)targetTemperature; // 出水温度
gsPidParameter.setVal = (float)setVal;
gsPidParameter.err = gsPidParameter.setVal - gsPidParameter.targetVal;
if(gsPidParameter.pwm > gsPidParameter.pwmMax) // 当超过最大值时,只积累负误差,可以加快退出饱和位置
{
if(((gsPidParameter.err > 0) && (gsPidParameter.err > gsPidParameter.setVal)) || ((gsPidParameter.err < 0) && (-gsPidParameter.err > gsPidParameter.setVal)))
//if(abs(gsPidParameter.err) > gsPidParameter.setVal) // 超过设定温度的一般,表示差值过大,需要积分累加,提高精度
{
index = 0;
}
else
{
index = 1;
if(gsPidParameter.err < 0)
{
gsPidParameter.integral += gsPidParameter.err;
}
}
}
else if(gsPidParameter.pwm < gsPidParameter.pwmMin) // 当低于最小值时,只积累正误差,可以加快退出饱和位置
{
if(((gsPidParameter.err > 0) && (gsPidParameter.err > gsPidParameter.setVal)) || ((gsPidParameter.err < 0) && (-gsPidParameter.err > gsPidParameter.setVal)))
//if(abs(gsPidParameter.err) > gsPidParameter.setVal) // 超过设定温度的一般,表示差值过大,需要积分累加,提高精度
{
index = 0;
}
else
{
index = 1;
if(gsPidParameter.err > 0)
{
gsPidParameter.integral += gsPidParameter.err;
}
}
}
else
{
if(((gsPidParameter.err > 0) && (gsPidParameter.err > gsPidParameter.setVal)) || ((gsPidParameter.err < 0) && (-gsPidParameter.err > gsPidParameter.setVal)))
//if(abs(gsPidParameter.err) > gsPidParameter.setVal) // 超过设定温度的一般,表示差值过大,需要积分累加,提高精度
{
index = 0;
}
else
{
index = 1;
gsPidParameter.integral += gsPidParameter.err;
}
}
gsPidParameter.pwm = gsPidParameter.Kp*gsPidParameter.err+index*gsPidParameter.Ki*gsPidParameter.integral+gsPidParameter.Kd*(gsPidParameter.err-gsPidParameter.errLast);
gsPidParameter.errLast = gsPidParameter.err;
if(gsPidParameter.pwm > gsPidParameter.pwmMax)
{
return gsPidParameter.pwmMax;
}
else if(gsPidParameter.pwm < gsPidParameter.pwmMin)
{
return gsPidParameter.pwmMin;
}
else
{
return (u16)gsPidParameter.pwm;
}
}
结语
总的来说PID算法是工业应用中最广泛的算法之一,它让你不需要了解被控对象的精确数学模型,仅仅调节"P", "I", "D"三个参数就可以得到满意的效果。可以称之为万能算法。
这篇文章也主要是为了记录下调试过程,以便下次遇到类似项目时不需要再重新思考,可以快速使用。
参考资料
本文作者:HelloGakki
本文链接:https://pinaland.cn/archives/PID-C.html
版权声明:所有文章除特别声明外均系本人自主创作,本文遵循署名 - 非商业性使用 - 禁止演绎 4.0 国际许可协议,转载请注明出处。