提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
FPGA:Verilog实现pid算法控制pwm
前言
本次使用verilog编写pid算法,并用于生成pwm波形,分别进行仿真和上板并用示波器验证
提示:以下是本篇文章正文内容,下面案例可供参考
一、PID公式
原理自行学习,网上资料多,主要就是下面的这个公式
其中Kp,Ki,Kd就是后续需要调整的参数,通常为固定值,而在本次使用中只是用到了p和i,没有使用到d。
二、verilog实现pid
主要思路就是固定个期望值,随便赋予个实际初值,然后再对这个实际值进行pid算法处理,pid计算的值与原来的实际值相加作为新的实际值
1.pid模块
由于Kp,Ki,Kd三个参数通常为小数或者浮点数,所以一般都是经过放大处理减小误差, 计算完后还需要通过位移缩小。
代码如下(示例):
parameter period_num = 1024;//pwm周期频率
reg [15:0] period_cnt;//pwm频率计数器
reg signed [15:0]inter_data;//寄存当前实际值
reg signed [15:0]error;//误差寄存器
reg signed [15:0]prev_error;//上次误差寄存器
reg signed [15:0]sum_error;//误差累加
reg signed [15:0] p ; //P算法计算结果
reg signed [15:0] d ; //I算法计算结果
reg signed [15:0] i ; //D算法计算结果(尽管未使用到)
reg signed [15:0] kp_reduce;//缩小计算结果
reg signed [15:0] ki_reduce;
reg signed [15:0] kd_reduce;
reg signed [16:0] prev_out;//上一次输出(未使用到)
reg [5:0]flow_cnt;//状态机计数器,用于控制状态跳转
wire [15:0]error_jian;//误差之差,即本次误差和上次误差的差值
assign error_jian = error - prev_error;
always @(posedge clk or negedge rst_n)begin
if (!rst_n) begin
out <= 'b0;
pid_vld <= 'b0;//输出使能
//---------状态机跳转计数器------------
flow_cnt <= 'b0;
xianfu_out <= 'b0;
error <= 'b0;
// prev_error <= 'b0;
sum_error <= 'b0;
inter_data <= 'b0;
kp_reduce <= 'b0;
ki_reduce <= 'b0;
// kd_reduce <= 'b0;
p <= 'b0;
i <= 'b0;
// d <= 'b0;
end
else begin
case(flow_cnt)
6'd0: begin
if(pid_en == 1'b1)begin
inter_data <= actual_value;//寄存当前实际值,用于和pid计算值相加
prev_error <= error; //将当前误差赋给上次误差
// prev_out <= out;
flow_cnt <= flow_cnt + 1'b1;//接收到数据,并完成误差累加和上次误差的赋值后进入下一个状态
//---------------------------------------积分限幅-----------------------------------------
if( sum_error > $signed('d3000) && ( expect_value - actual_value ) > $signed('d0) )
sum_error <= sum_error;
else if( sum_error < $signed(-'d3000) && ( expect_value - actual_value ) < $signed('d0) )
sum_error <= sum_error;
else
sum_error <= sum_error + ( expect_value - actual_value );//误差累加
end
else begin
inter_data <= inter_data;
prev_error <= prev_error;
sum_error <= sum_error;
end
end
6'd1: begin//该状态给当前误差赋值
error <= expect_value - actual_value ;
flow_cnt <= flow_cnt + 1'b1;
end
6'd2: begin//三种误差赋值完后,需要进行P I D三个算法的计算,公式怎么来的自行百度学习
/***************************************************************************
直接用*进行乘法在实际中容易出问题,本次使用只是为了用于仿真验证,实际使用时,
使用乘法器,则这个状态就只需要添加一个使能信号用于输出给乘法器。
***************************************************************************/
p <= Kp * error;
// d <= Kd * error_jian;//
i <= Ki * sum_error;
flow_cnt <= flow_cnt + 1'b1;
end
6'd3:begin //P I D三种算法计算完后还需要根据参数的放大倍数进行缩小,本次将Kp放大了100倍,Ki放大了1024倍,采用右移完成
kp_reduce <= (p >>> 7) + (p >>> 9);// p/128 + p/512 = 5*p/512 = p/102.4 有误差但影响小
ki_reduce <= i >>> 10;//(i >>> 7) + (i >>> 9);
// kd_reduce <= (d >>> 7) + (d >>> 9);
flow_cnt <= flow_cnt + 1'b1;
end
6'd4:begin //给pid输出赋值,将当前值以及三个算法的缩小结果相加
//pid_ack <= 1'b1;
flow_cnt <= flow_cnt + 1'b1;
out <= inter_data + kp_reduce + ki_reduce + kd_reduce;
end
6'd5:begin //对输出数据进行限幅处理,避免超过限制(根据实际情况使用,可有可无),并拉高输出有效信号使能
pid_vld <= 1'b1;
flow_cnt <= flow_cnt + 1'b1;
if(out > $signed(32'd1023))
xianfu_out <= $signed(32'd1023);
else if(out < $signed(-32'd1023))
xianfu_out <= $signed(-32'd1023);
else
xianfu_out <= out;
end
6'd6:begin
pid_vld <= 1'b0;//输出有效拉低,表示pid计算完成
flow_cnt <= 'b0;
end
default: ;
endcase
end
end
2.仿真文件
always #20 clk = ~clk;
initial begin
clk = 1'b0;
rst_n = 1'b0;
#100
rst_n = 1'b1;
expect_value = 'd200;
end
3.仿真波形
本次给Kp赋20,Ki赋1,Kd为0,如果使用Kd,则波形不会出现这种平滑不断接近期望值的波形,而是会超过期望值,然后在不断逼近,而我在使用中不能出现超过的情况
4.PWM生成
PWM生成就是通过定义一个pwm频率计数器,然后在将pid三种算法计算后和实际值相加所得的值作为PWM占空比的比较值,添加在pid模块内,状态机后面,最终得到的仿真波形如下
示波器采集到的波形如下(不一样是因为期望值不同,仿真懒得改)
总结
无