草庐IT

基于FPGA的PID控制器设计

Moon_3181961725 2023-05-07 原文

1 知识背景

PID控制应该算是应用非常广泛的控制算法了。常见的比如控制环境温度,控制无人机飞行高度速度等。PID我们将其分成三个参数,如下:
P-比例控制,基本作用就是控制对象以线性的方式增加,在一个常量比例下,动态输出,缺点是会产生一个稳态误差。
I-积分控制,基本作用是用来消除稳态误差,缺点是会产生超调现象
D-微分控制,基本作用是减弱超调现象,加大惯性响应速度。

PID控制系统原理框图

PID公式

总的来说,当得到系统的输出后,将输出经过比例,积分,微分三种运算方式再叠加到输入中,从而形成一个闭环控制系统。在真正的实践中,最难的是如何确定三个项的系数,这就需要大量的实验以及经验来确定了,通过不断的尝试和思考,就能选取合适的参数,从而做出一个优良的PID控制器。

2 系统框架

理论说得再多,不如亲自动手实践一下,我做了一个简单的PID控制模型,因为采用的是用PID控制PWM占空比,输出的PWM占空比和采集到的占空比基本不会存在误差,所以和真正的PID闭环控制还有一些差别。仅以此例说明如何用FPGA做一个简易的PID算法,然后可通过Modelsim仿真观察到调节占空比的曲线变化,最后在开发板上验证,用示波器测试输出的PWM占空比,与我们设置的目标占空比一致。假如我们要控制电机速度,有摩擦阻力以及速度采集误差,此时就需要我们对Kp,Ki,Kd进行调节,以达到最佳的控制效果。模型框图如下所示:

targe:目标值
actual:实际值

3 实验需求及目的

产生频率固定,占空比可通过按键进行调节的PWM信号。按下KEY1占空比加10%,按下KEY2占空比减10%,每个PWM周期进行一次PID计算

4 所需硬件

  1. ALOGIC_V4 FPGA开发板
  2. FPGA下载器
  3. 示波器

5 公式分析

前面我们列出了PID理论的公式,但是光看理论公式我们要用Verilog语言将其实现出来还是有点摸不着头脑,所以我们需要将公司稍做变化,转成方便用FPGA实现PID的公式:

Kp:比例项参数
Ki:积分项参数
Kd:微分项参数
error:误差,targe-actual
sum_error:error的总和
error-last_error:当前error减去上一次error

6 程序设计

程序框图如下:

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

7 仿真(通过Modelsim仿真观察调节效果)

我们设置的占空比目标值是980,即9.8%。

7.1 第一组PID参数

Kp=0.1,Ki=0.03,Kd=0;
设置如下图:

仿真如下图所示:

通过仿真可以看到,pid_ctrl模块输出的占空比值(pwm_zkb)是一条平滑的曲线,从0缓慢上升到98160,然后稳定下来。实验测出的占空比(actual)等于980,与目标值相等。在这个过程中误差(error)也慢慢减小,直到误差等于0,这就是一个闭环调节过程。

7.2 第二组PID参数

Kp=0.1,Ki=0.15,Kd=0;
参数设置如下:

仿真如下图所示:

我们将Ki参数调大了,发现信号上升的坡度变陡了,这样的好处是缩短了调节时间,便信号能更快的达到我们的目标值,但是信号出现了振荡(超调)现象,即先是超过了目标值(980),然后才慢慢趋于稳定。Kp参数越大,超调现象越严重,在实际使用中我们是不允许有严重振荡现象出现,因为这样会造成我们的控制系统出现问题。比如我们控制无人机,比如我们设置1000米的高度,如果振荡严重,那么无人机会突然一下上升到1000多米的高度,然后再降到1000米,在振荡过程中,如果1200米处有一个障碍物,那无人机就撞上障碍物了,导致严重的后果,所以我们要避免出现严重的超调现象。解决超调现象需要Ki和Kd两个参数来调节。

7.3 第三组PID参数

Kp=0.1,Ki=0.1,Kd=0.15;
设置如下图:

仿真如下图所示:

可明显观察到超调现象减弱了很多。

8 上板验证

用我们ALOGIC_V4开发板验证,占空比可以达到我们的目标值。如下图所示:

有关基于FPGA的PID控制器设计的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. Ruby Readline 在向上箭头上使控制台崩溃 - 2

    当我在Rails控制台中按向上或向左箭头时,出现此错误:irb(main):001:0>/Users/me/.rvm/gems/ruby-2.0.0-p247/gems/rb-readline-0.4.2/lib/rbreadline.rb:4269:in`blockin_rl_dispatch_subseq':invalidbytesequenceinUTF-8(ArgumentError)我使用rvm来管理我的ruby​​安装。我正在使用=>ruby-2.0.0-p247[x86_64]我使用bundle来管理我的gem,并且我有rb-readline(0.4.2)(人们推荐的最少

  3. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

  4. ruby-on-rails - 带 Spring 锁的 Rails 4 控制台 - 2

    我正在使用Ruby2.1.1和Rails4.1.0.rc1。当执行railsc时,它被锁定了。使用Ctrl-C停止,我得到以下错误日志:~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`gets':Interruptfrom~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`verify_server_version'from~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.

  5. ruby-on-rails - openshift 上的 rails 控制台 - 2

    我将我的Rails应用程序部署到OpenShift,它运行良好,但我无法在生产服务器上运行“Rails控制台”。它给了我这个错误。我该如何解决这个问题?我尝试更新ruby​​gems,但它也给出了权限被拒绝的错误,我也无法做到。railsc错误:Warning:You'reusingRubygems1.8.24withSpring.UpgradetoatleastRubygems2.1.0andrun`gempristine--all`forbetterstartupperformance./opt/rh/ruby193/root/usr/share/rubygems/rubygems

  6. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  7. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  8. C51单片机——实现用独立按键控制LED亮灭(调用函数篇) - 2

    说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时

  9. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  10. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

随机推荐