赞
踩
PID控制应该算是应用非常广泛的控制算法了。常见的比如控制环境温度,控制无人机飞行高度速度等。PID我们将其分成三个参数,如下:
P-比例控制,基本作用就是控制对象以线性的方式增加,在一个常量比例下,动态输出,缺点是会产生一个稳态误差。
I-积分控制,基本作用是用来消除稳态误差,缺点是会产生超调现象
D-微分控制,基本作用是减弱超调现象,加大惯性响应速度。
PID控制系统原理框图
PID公式
总的来说,当得到系统的输出后,将输出经过比例,积分,微分三种运算方式再叠加到输入中,从而形成一个闭环控制系统。在真正的实践中,最难的是如何确定三个项的系数,这就需要大量的实验以及经验来确定了,通过不断的尝试和思考,就能选取合适的参数,从而做出一个优良的PID控制器。
理论说得再多,不如亲自动手实践一下,我做了一个简单的PID控制模型,因为采用的是用PID控制PWM占空比,输出的PWM占空比和采集到的占空比基本不会存在误差,所以和真正的PID闭环控制还有一些差别。仅以此例说明如何用FPGA做一个简易的PID算法,然后可通过Modelsim仿真观察到调节占空比的曲线变化,最后在开发板上验证,用示波器测试输出的PWM占空比,与我们设置的目标占空比一致。假如我们要控制电机速度,有摩擦阻力以及速度采集误差,此时就需要我们对Kp,Ki,Kd进行调节,以达到最佳的控制效果。模型框图如下所示:
targe:目标值
actual:实际值
产生频率固定,占空比可通过按键进行调节的PWM信号。按下KEY1占空比加10%,按下KEY2占空比减10%,每个PWM周期进行一次PID计算
前面我们列出了PID理论的公式,但是光看理论公式我们要用Verilog语言将其实现出来还是有点摸不着头脑,所以我们需要将公司稍做变化,转成方便用FPGA实现PID的公式:
Kp:比例项参数
Ki:积分项参数
Kd:微分项参数
error:误差,targe-actual
sum_error:error的总和
error-last_error:当前error减去上一次error
程序框图如下:
key_xd:按键消抖模块,该模块直接调用我们开发板的消抖例程。
targe_gen:目标值生成模块,即生成想要的占空比数值。由于FPGA无法处理小数,所以为了方便处理将此数据扩大了100倍,假如设置的数值是980,那实际占空比就是9.8%。
module targe_gen( input clk, input rst_n, input key_add, input key_sub, output reg rst_n_out, output reg [15:0] targe ); reg [7:0] rst_cnt; always@(posedge clk or negedge rst_n)begin if(!rst_n)begin targe<=3500; rst_n_out<=0; end else if(key_add)begin targe<=targe+1000; rst_n_out<=0; end else if(key_sub)begin targe<=targe-1000; rst_n_out<=0; end else if(rst_cnt==100)begin rst_n_out <=1; end end always@(posedge clk or negedge rst_n)begin if(!rst_n) rst_cnt<=0; else if(key_add||key_sub) rst_cnt<=0; else if(rst_n_out==0) rst_cnt<=rst_cnt+1; else rst_cnt<=0; end endmodule
zkb_calc:占空比检测模块,计算实际输出的PWM占空比,即targe值。由于FPGA无法处理小数,所以为了方便处理将此数据扩大了100倍,假如设置的数值是980,那实际占空比就是9.8%。该模块需要用到除法,我们不能直接在代码里面用"/"来进行计算,而是需要调用除法器IPCORE来进行计算。
module zkb_calc( input clk , input rst_n , input pwm_in ,//反馈信号 output reg [15:0] pwm_zkb ,//计算的PWM占空比 output reg pwm_zkb_vld //PWM占空比有效标志 ); parameter ST0 =4'd0; parameter ST1 =4'd1; parameter CALC_ST =4'd2; parameter RESULT_ST =4'd3; parameter time_out_num=500;//采样时间 reg [3:0] curr_st; reg [31:0] pwm_hcnt; reg [31:0] pwm_hlcnt; reg [31:0] div_dividend; reg [31:0] div_divisor; reg div_ce; reg [31:0] time_out_cnt; reg pwm_in_ff1,pwm_in_ff2,pwm_in_ff3; wire[39:0] quotient; reg rdy; assign pwm_in_rise=pwm_in_ff2&&(pwm_in_ff3==0); always@(posedge clk)pwm_in_ff1<=pwm_in; always@(posedge clk)pwm_in_ff2<=pwm_in_ff1; always@(posedge clk)pwm_in_ff3<=pwm_in_ff2; always@(posedge clk)rdy<=div_ce; always@(posedge clk or negedge rst_n)begin if(!rst_n)begin pwm_zkb <=0; pwm_zkb_vld <=0; end else if(rdy)begin pwm_zkb <=quotient[15:0]; pwm_zkb_vld <=1; end else pwm_zkb_vld<=0; end always@(posedge clk or negedge rst_n)begin if(!rst_n)begin curr_st <=ST0; div_ce <=0; div_dividend<=0; div_divisor<=0; end else case(curr_st) ST0:begin if(time_out_cnt==1) curr_st<=ST1; else; end ST1:begin if(time_out_cnt==1) curr_st<=CALC_ST; else; end CALC_ST:begin curr_st<=RESULT_ST; div_dividend<={pwm_hcnt,13'h0}+{pwm_hcnt,10'h0}+{pwm_hcnt,9'h0}+{pwm_hcnt,8'h0}+{pwm_hcnt,4'h0};//x10000 div_divisor<=pwm_hlcnt; div_ce<=1; end RESULT_ST:begin div_ce<=0; curr_st<=ST0; end default:; endcase end always@(posedge clk or negedge rst_n)begin if(!rst_n) time_out_cnt<=0; else if(time_out_cnt==time_out_num-1) time_out_cnt<=0; else time_out_cnt<=time_out_cnt+1; end always@(posedge clk or negedge rst_n)begin if(!rst_n) pwm_hcnt<=0; else if(curr_st==RESULT_ST) pwm_hcnt<=0; else if(curr_st==ST1&&pwm_in_ff3) pwm_hcnt<=pwm_hcnt+1; else; end always@(posedge clk or negedge rst_n)begin if(!rst_n) pwm_hlcnt<=0; else if(curr_st==ST0) pwm_hlcnt<=0; else if(curr_st==ST1) pwm_hlcnt<=pwm_hlcnt+1; else; end DIV U_DIV( .denom ({8'b0,div_divisor}),//被除数 .numer ({8'b0,div_dividend}),//除数 .quotient(quotient), .remain () ); endmodule
pid_ctrl:pid计算模块,按照上面的公式,需要用到加法和乘法,加法我们可直接在代码里面用"+"来进行计算,乘法就需要调用乘法器的IPCORE(由于误差是有正负之分,所以我们的乘法器IPCORE也需要设置成有符号的,这一点一定要注意,否则计算会出问题),最终计算出占空比数值,由于FPGA无法处理小数,所以Kp,Ki,Kd都扩大了100倍,假如Kp=10,真实值即为0.1。根据公式我们知道最终的PWM占空比数值扩大了10000倍。
module pid_ctrl( input clk , input rst_n , input [15:0] targe ,//x100 input [15:0] actual ,//x100 input actual_vld , output reg [31:0] pwm_zkb , //x10000,因为targe,actual乘以100,Kp,Ki,Kd乘以100,所以结果放大了10000 output reg pwm_zkb_vld ); parameter IDLE =8'd0; parameter STEP1 =8'd1; parameter STEP2 =8'd2; parameter STEP3 =8'd3; parameter STEP4 =8'd4; parameter STEP5 =8'd5; parameter STEP6 =8'd6; parameter Kp =10;//x100; parameter Ki =10;//x100; parameter Kd =15;//x100; reg [7:0] curr_st; reg [31:0] sum_error; reg [31:0] last_error; reg [31:0] error ; reg [31:0] mul1_a,mul2_a,mul3_a; reg [31:0] mul1_b,mul2_b,mul3_b; reg mul1_ce,mul2_ce,mul3_ce; wire[31:0] mul1_result,mul2_result,mul3_result; always@(posedge clk or negedge rst_n)begin if(!rst_n)begin curr_st <=IDLE; sum_error <=0; last_error <=0; error <=0; pwm_zkb <=0; mul1_a <=0; mul1_b <=0; mul1_ce <=0; mul2_a <=0; mul2_b <=0; mul2_ce <=0; mul3_a <=0; mul3_b <=0; mul3_ce <=0; pwm_zkb_vld <=0; end else case(curr_st) IDLE:begin pwm_zkb_vld<=0; if(actual_vld)begin curr_st<=STEP1; end else; end STEP1:begin last_error<=error; curr_st<=STEP2; end STEP2:begin error<=targe-actual; curr_st<=STEP3; end STEP3:begin sum_error<=sum_error+error; curr_st<=STEP4; end STEP4:begin mul1_a<=Kp; mul1_b<=error; mul1_ce<=1; mul2_a<=Ki; mul2_b<=sum_error; mul2_ce<=1; mul3_a<=Kd; mul3_b<=error-last_error; mul3_ce<=1; curr_st<=STEP5; end STEP5:curr_st<=STEP6; STEP6:begin mul1_ce<=0; mul2_ce<=0; mul3_ce<=0; pwm_zkb<=mul1_result+mul2_result+mul3_result; pwm_zkb_vld<=1; curr_st<=IDLE; end default; endcase end MUL_SIGN_32X32 U_MUL1( .clock (clk ), // input clk .aclr (~rst_n ), .dataa (mul1_a ), // input [15 : 0] a .datab (mul1_b ), // input [15 : 0] b .clken (mul1_ce ), // input ce .result (mul1_result) // output [31 : 0] p ); MUL_SIGN_32X32 U_MUL2( .clock (clk ), // input clk .aclr (~rst_n ), .dataa (mul2_a ), // input [15 : 0] a .datab (mul2_b ), // input [15 : 0] b .clken (mul2_ce ), // input ce .result (mul2_result) // output [31 : 0] p ); MUL_SIGN_32X32 U_MUL3( .clock (clk ), // input clk .aclr (~rst_n ), .dataa (mul3_a ), // input [15 : 0] a .datab (mul3_b ), // input [15 : 0] b .clken (mul3_ce ), // input ce .result (mul3_result) // output [31 : 0] p ); endmodule
pwm_drv:根据pid_ctrl模块计算出的占空比,输出PWM信号,PWM信号一分为二,一路通过FPGA管脚输出,可以用示波器观测到波形,另一路直接传给zkb_calc模块,这样就构成了一个闭环系统。该模块需要用到乘法和除法计算,需要调用相应的IPCORE。
module pwm_drv( input clk, input rst_n, input [31:0] pwm_zkb,//x10000 input pwm_zkb_vld, output reg pwm ); parameter period_num=500;//FREQ 10K,频率越大,可调占空比精度越低,实测如果频率是100K,占空比只能精确到个位,频率是10K,可确到小数点后1位。 reg [19:0] period_cnt; reg [31:0] hcnt; reg [35:0] div_dividend; reg [31:0] div_divisor; reg div_ce; wire[31:0] quotient; reg rdy; reg [31:0] mul_a,mul_b; reg mul_ce; wire[63:0] mul_result; reg [3:0] curr_st; always@(posedge clk)rdy<=div_ce; always@(posedge clk or negedge rst_n)begin if(!rst_n) hcnt<=0; else if(rdy) hcnt<=quotient[31:0]; else; end always@(posedge clk or negedge rst_n)begin if(!rst_n) period_cnt<=0; else if(period_cnt==period_num-1) period_cnt<=0; else period_cnt<=period_cnt+1; end always@(posedge clk or negedge rst_n)begin if(!rst_n)begin curr_st<=0; mul_a<=0; mul_b<=0; mul_ce<=0; div_ce<=0; div_dividend<=0; div_divisor<=0; end else case(curr_st) 0:begin div_ce<=0; if(pwm_zkb_vld) curr_st<=1; else ; end 1:begin mul_a<=period_num; mul_b<=pwm_zkb; mul_ce<=1; curr_st<=2; end 2:begin mul_ce<=0; curr_st<=3; end 3:begin div_dividend<=mul_result[63:0]; div_divisor<=1000000;//占空比扩大100倍,360就是3.6%,0.036,此处引起误差 div_ce<=1; curr_st<=0; end default:; endcase end always@(posedge clk or negedge rst_n)begin if(!rst_n) pwm<=0; else if(period_cnt<hcnt[15:0]) pwm<=1; else pwm<=0; end MUL_UNSIGN_32X32 U_MUL( .clock (clk ), // input clk .aclr (~rst_n ), .dataa (mul_a ), // input [15 : 0] a .datab (mul_b ), // input [15 : 0] b .clken (mul_ce ), // input ce .result (mul_result) // output [31 : 0] p ); DIV U_DIV( .denom ({8'b0,div_divisor}), .numer ({4'b0,div_dividend[35:0]}), .quotient(quotient), .remain () ); endmodule
我们设置的占空比目标值是980,即9.8%。
Kp=0.1,Ki=0.03,Kd=0;
设置如下图:
仿真如下图所示:
通过仿真可以看到,pid_ctrl模块输出的占空比值(pwm_zkb)是一条平滑的曲线,从0缓慢上升到98160,然后稳定下来。实验测出的占空比(actual)等于980,与目标值相等。在这个过程中误差(error)也慢慢减小,直到误差等于0,这就是一个闭环调节过程。
Kp=0.1,Ki=0.15,Kd=0;
参数设置如下:
仿真如下图所示:
我们将Ki参数调大了,发现信号上升的坡度变陡了,这样的好处是缩短了调节时间,便信号能更快的达到我们的目标值,但是信号出现了振荡(超调)现象,即先是超过了目标值(980),然后才慢慢趋于稳定。Kp参数越大,超调现象越严重,在实际使用中我们是不允许有严重振荡现象出现,因为这样会造成我们的控制系统出现问题。比如我们控制无人机,比如我们设置1000米的高度,如果振荡严重,那么无人机会突然一下上升到1000多米的高度,然后再降到1000米,在振荡过程中,如果1200米处有一个障碍物,那无人机就撞上障碍物了,导致严重的后果,所以我们要避免出现严重的超调现象。解决超调现象需要Ki和Kd两个参数来调节。
Kp=0.1,Ki=0.1,Kd=0.15;
设置如下图:
仿真如下图所示:
可明显观察到超调现象减弱了很多。
用我们ALOGIC_V4开发板验证,占空比可以达到我们的目标值。如下图所示:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。