当前位置: 首页 > news >正文

基于FPGA的PID控制器设计

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开发板验证,占空比可以达到我们的目标值。如下图所示:
在这里插入图片描述

在这里插入图片描述

相关文章:

  • 程序设计与c语言笔记(一)
  • 【.Net实用方法总结】 整理并总结.NET 中的 System.IO.Pipelines(管道)
  • 深度学习10——卷积神经网络
  • Mybatis 实现原理
  • matplotlib入门
  • JavaScript设计模式——建造者模式
  • Roson的Qt之旅 #124 QNetworkConfigurationManager网络配置管理
  • 天池Python练习02-位运算
  • 国内主机整车EEA架构汇总
  • Java刷题面试系列习题(十三)
  • linux驱动35:工作队列
  • 句向量模型之SimCSE——Pytorch
  • 简单旅游景点HTML网页设计作品 DIV布局故宫介绍网页模板代码 DW家乡网站制作成品 web网页制作与实现
  • 图解redis(四)——高可用篇
  • LQ0048 交换瓶子【无标题】
  • jdbc就是这么简单
  • JS字符串转数字方法总结
  • Laravel 菜鸟晋级之路
  • PaddlePaddle-GitHub的正确打开姿势
  • Python 使用 Tornado 框架实现 WebHook 自动部署 Git 项目
  • Python利用正则抓取网页内容保存到本地
  • Swift 中的尾递归和蹦床
  • SwizzleMethod 黑魔法
  • 创建一种深思熟虑的文化
  • 解决jsp引用其他项目时出现的 cannot be resolved to a type错误
  • 紧急通知:《观止-微软》请在经管柜购买!
  • 入手阿里云新服务器的部署NODE
  • 说说动画卡顿的解决方案
  • 吴恩达Deep Learning课程练习题参考答案——R语言版
  • 小李飞刀:SQL题目刷起来!
  • 《天龙八部3D》Unity技术方案揭秘
  • #在 README.md 中生成项目目录结构
  • (html转换)StringEscapeUtils类的转义与反转义方法
  • (免费分享)基于springboot,vue疗养中心管理系统
  • (十八)三元表达式和列表解析
  • (转)linux自定义开机启动服务和chkconfig使用方法
  • (转)memcache、redis缓存
  • (转)Spring4.2.5+Hibernate4.3.11+Struts1.3.8集成方案一
  • .Net core 6.0 升8.0
  • .NET 使用 ILRepack 合并多个程序集(替代 ILMerge),避免引入额外的依赖
  • .net 使用ajax控件后如何调用前端脚本
  • @cacheable 是否缓存成功_让我们来学习学习SpringCache分布式缓存,为什么用?
  • @Mapper作用
  • @requestBody写与不写的情况
  • @RequestMapping用法详解
  • @vue/cli脚手架
  • @取消转义
  • [ vulhub漏洞复现篇 ] Grafana任意文件读取漏洞CVE-2021-43798
  • [ 渗透测试面试篇 ] 渗透测试面试题大集合(详解)(十)RCE (远程代码/命令执行漏洞)相关面试题
  • [2013AAA]On a fractional nonlinear hyperbolic equation arising from relative theory
  • [28期] lamp兄弟连28期学员手册,请大家务必看一下
  • [CUDA 学习笔记] CUDA kernel 的 grid_size 和 block_size 选择
  • [Flutter]设置应用包名、名称、版本号、最低支持版本、Icon、启动页以及环境判断、平台判断和打包
  • [Hive] INSERT OVERWRITE DIRECTORY要注意的问题
  • [IOI2007 D1T1]Miners 矿工配餐