PID闭环的FPGA实现
1 原理分析
最近小张同学在做项目的时候发现PI闭环的FPGA学习资料很少,秉持着“既然没有轮子,那么自己就造一个的原则”,于是乎自己写了个PI的Verilog程序。
FPGA中实现PI闭环与DSP、STM32、arm中都不一样,由于FPGA中没有数学库,需要从底层开始解决运算中产生的小数、除法等问题。因此FPGA中实现PI闭环相比来说有一点点难度。
首先,位置式PI和增量式PI的选择问题,由于位置式PI的实现原理包含历史误差的累计,并且启动瞬间或状态突然切换瞬间会有突变现象,突变现象会影响电机的动态性能,同时启动电流会很大,而这会对电机工作状态产生影响,因此采用增量式PI控制更优。当然,我觉得用位置式pi也能适用于电机的pi闭环。因为本质上来说,这两种pi实现机理并无差异,最终的控制效果也应该差异不大。
而增量式PI控制通常包括并联式PI控制和串联式PI控制,并联式PI控制可以实现 K p K_p Kp和 K i K_i Ki 的解耦,方便参数调试,所以采用并联式增量PI控制器。
上图是增量并联并联式PI框图,含义为参考值与实际值比较得到的差值分为两部分,一部分通过P的直接增益,另一部分通过积分的积分增益,合并输出。
下图是传递函数,只调节Kp相,可以调节幅值,对应的Ki值需要与之对应,可以调节控制系统的零点,Kp幅值增加对应的零点频率会下降,零点频率实际上是Kp和Ki的函数。
典型pid系统的输出信号表达式如(1)所示
u k = K p ( ( e t ) + 1 T i ∫ 0 t e ( t ) d ( t ) + T d d e ( k ) e ( k ) ) u_k = K_p((e_t)+\frac{1}{T_i}\int ^t_0 e(t){\rm d}(t)+T_d\frac{de(k)}{e(k)}) uk=Kp((et)+Ti1∫0te(t)d(t)+Tde(k)de(k))
采用PI控制,即无微分项,则
u k = K p ( ( e t ) + 1 T i ∫ 0 t e ( t ) d ( t ) ) u_k = K_p((e_t)+\frac{1}{T_i}\int ^t_0 e(t){\rm d}(t)) uk=Kp((et)+Ti1∫0te(t)d(t))
2 PI的硬件实现
在数字电路中,并无法实现连续时间的积分和微分,因此数字pid中,只能以固定采样周期来进行离散化,从而实现近似的连续时间的积分和微分,利用这个思想,将式(2)进行离散化,
- 用一系列采样时间kT来代替时间t
- 以和式来代替积分: ∫ 0 t e ( t ) d ( t ) ) = ∑ e ( j T ) T = T ∑ e ( k ) \int ^t_0 e(t){\rm d}(t)) = \sum e(jT)T = T\sum e(k) ∫0te(t)d(t))=∑e(jT)T=T∑e(k)
- 向后差分 来代替 微分 d e ( t ) d t = e ( k ) − e ( k − 1 ) Δ t = e ( k ) − e ( k − 1 ) T s \frac{de(t)}{dt}=\frac{e(k)-e(k-1)}{\Delta t}=\frac{e(k)-e(k-1)}{T_s} dtde(t)=Δte(k)−e(k−1)=Tse(k)−e(k−1)
可得位置式PID的离散化如下式
u k = K p ( e k ) + K i ∑ ( e k ) + K d ( e ( k ) − e ( k − 1 ) ) u_k = K_p(e_k)+K_i\sum(e_k)+K_d(e(k)-e(k-1)) uk=Kp(ek)+Ki∑(ek)+Kd(e(k)−e(k−1))
PI中令位置式 K d K_d Kd = 0 即可得 位置式PI表达式
u k = K p ( e k ) + K i ∑ ( e k ) u_k = K_p(e_k)+K_i\sum(e_k) uk=Kp(ek)+Ki∑(ek)
硬件中可以对误差的累计 S u m ( k ) = S u m ( k − 1 ) + ( k ) 。 Sum(k)=Sum(k-1)+(k)。 Sum(k)=Sum(k−1)+(k)。
增量式PID
增量为:
Δ u ( k ) = u ( k ) − u ( k − 1 ) = k p [ e ( k ) − e ( k − 1 ) ] + k i e ( k ) + k d [ e ( k ) − 2 e ( k − 1 ) + e ( k − 2 ) ] \Delta u(k) = u(k)-u(k-1) =k_p[e(k)-e(k-1)]+k_i\,\,e(k)+k_d\,[e(k)-2e(k-1)+e(k-2)] Δu(k)=u(k)−u(k−1)=kp[e(k)−e(k−1)]+kie(k)+kd[e(k)−2e(k−1)+e(k−2)]
令位置式 K d K_d Kd = 0 即可得
Δ u ( k ) = u ( k ) − u ( k − 1 ) = k p [ e ( k ) − e ( k − 1 ) ] + k i e ( k ) \Delta u(k) = u(k)-u(k-1) =k_p[e(k)-e(k-1)]+k_i\,\,e(k) Δu(k)=u(k)−u(k−1)=kp[e(k)−e(k−1)]+kie(k)
增量式PID的输出为
u ( k ) = u ( k − 1 ) + Δ u ( k ) u(k)=u(k-1)+\Delta u(k) u(k)=u(k−1)+Δu(k)
然而很多场合下需要的往往不只增量,还有上一拍的输出值,于是可知增量式PI调节器算法为
u ( k ) = u ( k − 1 ) + Δ u ( k ) = u ( k − 2 ) + Δ u ( k − 1 ) + Δ u ( k ) = u ( 0 ) + u ( 1 ) + . . . . . + u ( k − 1 ) u(k)=u(k-1)+\Delta u(k)=u(k-2)+\Delta u(k-1)+\Delta u(k)=u(0)+u(1)+.....+u(k-1) u(k)=u(k−1)+Δu(k)=u(k−2)+Δu(k−1)+Δu(k)=u(0)+u(1)+.....+u(k−1)
由于 u ( 0 ) = 0 u(0)=0 u(0)=0在具体编程操作中,对每一拍的 Δ u ( k ) \Delta u(k) Δu(k)进行累积,即为PI调节器的输出;同样地,为了避免超过允许值,仅需对输出限幅即可。
事实上,由增量式PI
u ( k ) = u ( k − 1 ) + Δ u ( k ) = u ( k − 1 ) + K P ∗ ( e ( k ) − e ( k − 1 ) ) + K i T s e ( k ) u(k)=u(k-1)+\Delta u(k)=u(k-1)+K_P*(e(k)-e(k-1))+K_iT_se(k) u(k)=u(k−1)+Δu(k)=u(k−1)+KP∗(e(k)−e(k−1))+KiTse(k)
可得
u ( k − 1 ) = u ( k − 2 ) + Δ u ( k − 1 ) = u ( k − 2 ) + K P ∗ ( e ( k − 1 ) − e ( k − 2 ) ) + K i T s e ( k − 1 ) u(k-1)=u(k-2)+\Delta u(k-1)=u(k-2)+K_P*(e(k-1)-e(k-2))+K_iT_se(k-1) u(k−1)=u(k−2)+Δu(k−1)=u(k−2)+KP∗(e(k−1)−e(k−2))+KiTse(k−1)
代入上式即可约去 e ( k − 1 ) e(k-1) e(k−1)项,不断迭代,由于 e ( 0 ) = 0 e(0)=0 e(0)=0,可发现其最终结果与位置式PI的表达式一致,也即两种PI算法完全相同(未超出限幅值的前提下)
因此,可以理解为无论用增量叠加的方式来计算位置式PI,还是直接计算,结果都是相同的。两者唯一的区别就是位置式PI需要同时设置积分限幅和输出限幅,而增量式PI只需输出限幅。 增量式的好处就是启动时和状态突然发生变化时不会产生突变,其控制效果与位置式PI基本相同。
3 如何在硬件中实现积分器
如何实现一个积分器(I)?
创建一个加法器是最简单的形式,PI环节如上述框图描述所示。Z-1代表延迟块,T代表采样周期。
O u t ( x ) = O u t ( x − 1 ) + I n ( x ) ∗ T Out(x) = Out(x-1) + In(x)*T Out(x)=Out(x−1)+In(x)∗T
新输出值 = 旧输出值 + 输入*采样周期
//C语言实现
Out += In*T;
4、FPGA的硬件实现
话不多说,直接贴代码
代码简单易懂 相信各位聪明的小伙伴一看就能看懂
`timescale 1 ns/1 ns
//增量式 PI
//仿真验证通过版本
// 至于k_p k_i
module pid_controller_delta #(
parameter logic [23:0] k_p=24'd30, //kP
parameter logic [23:0] k_i=24'd2 //Ki
)(
input wire signed [15:0] i_real,//实际电流值
input wire signed [15:0] i_aim, //输入给定电流值
input wire clk,
input wire rstn,
input wire pi_en, //park变换是否完成的信号,高定平即完成park变换
output wire signed [15:0] u_out, //输出电压值 u_alpha U_beta
output reg o_en //PI 结束信号
);
reg signed [31:0] error_1,error_2,delta_error;//误差值 error_1为上一时刻的误差值,error_2为当前的误差值
reg signed [31:0] multipy_p,multipy_i,multipy_i1,multipy_i2; //分别代表delta_u两部分的乘积
reg signed [31:0] u_out_temp,delta_u; //寄存上一时刻的输出值,delta_u代表输出的增量
reg en_s1,en_s2,en_s3,en_s4,en_s5;
assign u_out = u_out_temp[15:0];
function automatic logic signed [31:0] protect_add(input logic signed [31:0] a, input logic signed [31:0] b);
automatic logic signed [32:0] y;
y = $signed({a[31],a})+$signed({b[31],b}); //积分限幅
//积分限幅
if( y > $signed(33'h7fffffff) )//
return $signed(32'h7fffffff);
else if( y < -$signed(32'h7fffffff) )
return -$signed(32'h7fffffff);
else
return $signed(y[31:0]);
endfunction
function automatic logic signed [31:0] protect_mul(input logic signed [31:0] a, input logic signed [24:0] b);
automatic logic signed [57:0] y;
y = a * b; //积分限幅
//积分限幅
if( y > $signed(57'h7fffffff) )//
return $signed(32'h7fffffff);
else if( y < -$signed(57'h7fffffff) )
return -$signed(32'h7fffffff);
else
return $signed(y[31:0]);
endfunction
function automatic logic signed [31:0] protect_subtract(input logic signed [31:0] a, input logic signed [31:0] b);
automatic logic signed [32:0] y;
y = $signed({a[31],a}) - $signed({b[31],b});
//
if( y > $signed(33'h7fffffff) )
return $signed(32'h7fffffff);
else if ( y < -$signed(32'h7fffffff) )
return -$signed(32'h7fffffff);
else
return $signed(y[31:0]);
endfunction
// plpeline 1 计算e(k)
always@(posedge clk or negedge rstn)
if(~rstn) begin
en_s1 <= 1'b0;
error_1 <= 0;
end
else begin
en_s1 <= pi_en;
if(pi_en) begin
error_1 <= $signed({
{16{i_aim[15]}},i_aim}) - $signed({
{16{i_real[15]}},i_real}) ;
end
end
//pipeline 2 计算e(k-1)
always@(posedge clk or negedge rstn)
if(~rstn) begin
en_s2 <= 1'b0;
error_2 <= 0;
end
else begin
en_s2 <= en_s1;
if(en_s1) begin
error_2 <= error_1; //e(k-1)
multipy_i <= protect_mul(error_1,k_i);
end
end
always@(posedge clk or negedge rstn)
if(~rstn) begin
en_s3 <= 1'b0;
multipy_i1 <= 0;
delta_error <= 0;
end
else begin
en_s3 <= en_s2;
if(en_s2) begin
multipy_i1 <= multipy_i;
delta_error <= protect_subtract(error_1,error_2);
end
end
always@(posedge clk or negedge rstn)
if(~rstn) begin
en_s4 <= 1'b0;
multipy_i2 <= 0;
multipy_p <=0;
end
else begin
en_s4 <= en_s3;
if(en_s3) begin
multipy_i2 <= multipy_i1;
multipy_p <= protect_mul(delta_error,k_p);
end
end
always@(posedge clk or negedge rstn)
if(~rstn) begin
en_s5 <= 1'b0;
delta_u <= 0;
end
else begin
en_s5 <= en_s4;
if(en_s4) begin
delta_u <= protect_add(multipy_i2,multipy_p);
end
end
always@(posedge clk or negedge rstn)
if(~rstn) begin
o_en <= 1'b0;
u_out_temp <= 0;
end
else begin
o_en <= en_s5;
if(en_s5) begin
u_out_temp = protect_add(u_out_temp,delta_u);
end
end
endmodule
5、仿真波形
i_aim 给定值是350 实际值是调节后的曲线,从图中可以看出,PI调节可以快速达到闭环,并进入稳态。
如果感觉博主的博客写的还可以的话,欢迎一键三连,点个关注不迷路哦~
文章评论