foc中,其实foc算法并不是最难理解的,反而是在其中使用的PID算法,之前我只会套用别人的代码,但并不理解其中的各参数含义,导致在实际调整PI参数的时候,很难调到合适的值。
在实际理解什么是PID算法以及各参数的实际作用意义后,再去调整PI参数时就比较容易了。
只有P参数
图片截取自慧驱动的讲解视频。
上图列出的是一个电机启动到设定转速的过程,假设整个过程只有P参数作用,且P的值为0.01。设定的目标转速为300r/min。
需要关注的是第6秒的280r/min这个参数,该转速为当提供0.5A电流的时候,电机在克服负载阻力的情况下的实际转速,如果以后的负载不再改变,则在接下来的第7秒,由于第6秒的误差计算只有20了,所以此刻给的输出电流只有0.2A。那么此时的0.2是比0.5小的,在负载不变的情况下,此时是不能维持280r/min的转速的,所以当前转速假设会下降到250r/min。
则在此刻计算出来的误差值变成50,然后到第8秒,转速又上去了,后面就会一直维持这样的情况不变,转速始终是在280的附近波动,不能达到300r/min,就会一直存在这样的误差,也就是稳态误差。
P参数和I参数同时作用
同样我们分析电机转速的问题。只不过这里是加入了I参数。Ki就是一个积分参数,加入积分运算的目的就是为了消除稳态误差,具体怎么实现的可以参考上图。
假设Kp=0.01;Ki=0.002
第1秒作用的时候,积分累积为0,所以输出只有P作用,输出值为300×0.01=3;
第2秒作用的时候,积分作用,输出有P和I同时作用,输出值为250×0.01+250×0.002=3;可以看到,同样为第2秒,如果只有P作用,则输出是2.5,当PI同时作用,输出变成3,这里的转速也比之前更高。
第3秒作用的时候,积分作用的结果需要加上上一次积分的结果,而不仅仅是此次的积分结果,这里的输出结果同样比只有P作用时的输出高,所以电机继续加速。假设在此时就达到了280r/min。这个280在只有P作用时可是一个稳态转速呦。
第4秒作用的时候,输出值为1.004。然后我们看下只有P作用的时候,达到280r/min的转速时,给出的输出值是0.5左右的,而这里的输出值达到了1.004,那么转速势必会比280r/min高了,所以很轻松的这个稳态误差被缩小了吧!别急,继续分析。
假设第4秒时的转速突破300r/min达到310r/min。那么误差err值就变成负的了。
第5秒作用的时候,输出值变成0.74,此时转速为301,那么误差值就只有1了。这就是I积分作用能实现的效果了。
积分抗饱和(Anti-Windup)
积分抗饱和是一个很重要的部分,往往应用在有积分限幅的场景中。我们先来看下框图
上图是一个添加了积分限幅的PI计算。从temp到输出out_temp是经过了一个积分限幅。红框部分就是所说的积分抗饱和。
先说说为什么要加这个积分限幅电路
假设有一个电机在正常负载情况下,提供3A的电流转速为3000r/min。现在增加负载,如果要维持3000r/min的转速,假设这个负载很大,那么计算出来的电流值可能会达到100A,那么这个电流值已经超过实际电路器件可以承受的电流值了,为了避免出现这种非正常情况,就增加了这个积分限幅,哪怕实际转速达不到要求,也不能出现故障。
那么,为什么要用这个积分抗饱和呢?
在上面讲解的基础上,假设有一个电机在正常负载情况下,提供3A的电流转速为3000r/min。现在增加负载,此时的转速变成1500r/min,如果要达到3000r/min的转速,电流需要达到5A,此时的积分限幅限制到了3A。所以会出现输出实际达不到5A。而转速会一直有1500的误差,也就是err值会一直为1500,这个err值在每次积分运算中都会参与计算,也就是意味着,积分池的累加会一直增加,这就导致temp的值会一直增加,积分池也无限增加,这个就是不合理的情况。
再看看这里的积分抗饱和,将PI计算输出的temp和积分限幅输出的temp_out作差,这个值再返回到积分池中。
可见,当输出的temp在积分限幅参考之内时,anti-windup是不起作用的,因为temp=temp_out;当temp>temp_out时,会有一个负值累加到积分池中,将积分池削弱。
实现代码参考如下:
/***************************************
current PID
B is Integral windup gain ,
usually, it is about I gain
***************************************/
#if 1
void Current_PID_Calc(float ref_temp,float fdb_temp,float* out_temp,CURRENT_PID_DEF* current_pid_temp)
{
float error;
float temp;
error = ref_temp - fdb_temp;
temp = current_pid_temp->P_Gain * error + current_pid_temp->I_Sum;//这样的一次PID计算不会立即有效,可能需要经过几个反馈循环才能达到目标
if (temp > current_pid_temp->Max_Output)
{
*out_temp = current_pid_temp->Max_Output;
}
else if (temp < current_pid_temp->Min_Output)
{
*out_temp = current_pid_temp->Min_Output;
}
else
{
*out_temp = temp;
}
current_pid_temp->I_Sum += ((*out_temp - temp) * current_pid_temp->B_Gain + current_pid_temp->I_Gain * error) *FOC_PERIOD;//FOC_PERIOD
}