FPGA实战:用Verilog手搓电机PID控制器(附完整代码)

张开发
2026/4/17 15:26:19 15 分钟阅读

分享文章

FPGA实战:用Verilog手搓电机PID控制器(附完整代码)
FPGA实战用Verilog手搓电机PID控制器附完整代码在工业自动化和机器人控制领域电机转速的精确调节一直是个经典难题。传统单片机方案虽然简单易用但在高速响应和并行处理方面存在天然瓶颈。本文将带您深入FPGA的硬件逻辑世界从零构建一个完整的电机PID控制器所有代码均采用纯Verilog实现无需依赖软核处理器。1. PID控制器的硬件思维转换PID算法在软件中实现时开发者通常不会特别关注计算时序问题。但在FPGA中我们需要彻底转换思维——每个乘法、积分运算都需要精确的时钟周期控制。定点数处理成为关键因为FPGA对浮点运算并不友好。1.1 定点数精度设计工业级PID控制器通常需要0.1%以上的控制精度我们采用Q16.16格式的32位定点数parameter SCALE_FACTOR 65536; // 2^16 reg signed [31:0] kp 32d6554; // 0.1 in Q16.16 reg signed [31:0] ki 32d655; // 0.01 reg signed [31:0] kd 32d65536; // 1.0注意定点数运算后需要右移16位还原真实值但为了减少逻辑资源消耗我们保持运算全程使用Q16.16格式。1.2 并行计算架构FPGA的最大优势在于可以并行处理P、I、D三个分量的计算always (posedge clk) begin // 比例项计算 p_term error * kp; // 积分项计算带抗饱和 if(integral MAX_INTEGRAL integral MIN_INTEGRAL) integral integral error; // 微分项计算 d_term (error - prev_error) * kd; // 综合输出 pid_out (p_term (integral * ki) d_term) 16; end2. 电机接口的硬件优化2.1 PWM生成器的改进方案传统PWM模块占空比调节范围有限我们设计了一个带死区控制的高分辨率PWM发生器module pwm_gen ( input clk, input [15:0] duty_cycle, output pwm_out ); reg [31:0] counter; reg [15:0] duty_reg; always (posedge clk) begin counter (counter 32d65535) ? 0 : counter 1; duty_reg duty_cycle; // 双缓冲避免毛刺 end assign pwm_out (counter duty_reg) ? 1b1 : 1b0; endmodule关键参数对比参数传统方案本设计方案分辨率8位16位更新延迟10周期2周期死区控制无可编程2.2 编码器接口的四倍频技术标准正交编码器接口会丢失大量位置信息我们采用状态机实现4倍频解码always (posedge clk) begin case({enc_a, enc_b}) 2b00: begin if(prev_state 2b01) position position 1; else if(prev_state 2b10) position position - 1; end 2b01: begin if(prev_state 2b11) position position 1; else if(prev_state 2b00) position position - 1; end // 其他状态转换... endcase prev_state {enc_a, enc_b}; end3. 速度环与位置环的协同设计3.1 双闭环控制架构外环位置环计算目标与当前位置的偏差内环速度环根据位置偏差输出目标速度// 位置环计算 always (posedge pos_clk) begin pos_error target_pos - current_pos; target_speed pos_error * pos_gain; end // 速度环计算 always (posedge vel_clk) begin vel_error target_speed - current_speed; pwm_duty pid_calculate(vel_error); end3.2 抗饱和处理技巧积分项饱和是PID控制的常见问题我们采用条件积分法if(pid_out MAX_OUTPUT pid_out MIN_OUTPUT) begin integral integral error; end else begin integral integral; // 保持当前值 end4. 实战调试与性能优化4.1 SignalTap实时调试Intel FPGA的SignalTap工具可以实时捕获内部信号设置采样深度至少1024点关键信号error、pid_out、pwm_duty触发条件error绝对值超过阈值4.2 时序约束关键点必须为PID计算路径添加适当约束create_clock -period 20 [get_clocks clk] set_max_delay -from [get_pins pid_reg*] -to [get_pins pid_out] 10ns4.3 资源优化策略当FPGA资源紧张时可以考虑将32位运算降为24位时分复用乘法器使用CORDIC算法替代常规乘法5. 完整系统集成将所有模块整合为顶层设计module motor_pid_top ( input clk, input enc_a, input enc_b, output pwm, output dir ); // 编码器接口 encoder_4x enc(.clk(clk), .a(enc_a), .b(enc_b), .pos(position)); // PID计算核心 pid_controller pid( .clk(clk), .setpoint(set_pos), .feedback(position), .out(speed_cmd) ); // 速度转换 speed_mapper speed( .speed_in(speed_cmd), .pwm_duty(pwm_val), .direction(dir) ); // PWM输出 pwm_gen pwm( .clk(clk), .duty_cycle(pwm_val), .pwm_out(pwm) ); endmodule实际部署时发现电机在低速段会出现抖动。通过增加死区补偿模块将小于5%的PWM输出强制归零问题得到解决// 死区补偿 assign final_pwm (pwm_val 16d3276) ? 0 : pwm_val - 16d3276;电机控制是个需要反复调试的过程。建议先用仿真验证各个模块功能再逐步提高闭环增益。记得保存每个版本的比特流文件方便快速回退到稳定版本。

更多文章