ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

FPGA直流电机数字PID闭环控制器:节拍与定点数实战

2026/9/19 14:02:42 拓冰建站 浏览量
FPGA直流电机数字PID闭环控制器:节拍与定点数实战 简介这是一份关于基于FPGA的直流电机数字PID闭环控制器设计文档面向FPGA开发工程师、自动化专业学生以及电机控制领域相关技术人员用于解决直流电机调速系统中数字PID算法实现与PWM脉宽调制硬件设计问题。资源包内仅含1个doc文档压缩后大小432KB篇幅精简但结构完整从模拟PID到数字PID的离散化推导、直流电机电压与转速传递函数建模、FPGA并行架构下PWM波形的产生原理到利用红外光电对管配合编码器完成闭环测速均有清晰说明。文档还以一款永磁直流齿轮减速电机为例给出电枢电阻、电感、转矩系数等具体参数并据此求得电压-转速传递函数进一步说明参数整定方向与近似简化方法。已有399人学习对希望快速掌握FPGA电机控制原理并联调实际系统的读者这份资料提供了一条从理论到硬件实现的完整路径。1. FPGA直流电机数字PID闭环控制器难的不是PID而是节拍把 PID 三行差分方程翻译成 Verilog一个 always 块就够真正让这个闭环控制器从仿真走到电机转起来难在编码器计数、采样节拍和 PWM 相位对齐这三件看起来和算法无关的事。FPGA 适合做直流电机数字PID闭环控制器是因为测速、算法、驱动逻辑可以在同一片芯片里按硬件时序并行跑控制延迟能压到微秒级这是 MCU 中断服务程序很难稳定保证的。这篇文章从增量式编码器速度反馈讲起落到数字 PID 的定点实现再讲到 H 桥 PWM 输出最后给出用 ILA 观测内部信号的调参方法想拿完整 FPGA 项目练手的人能照着搭被采样周期和整数溢出坑过的工程师也能找到对应的解法。2. FPGA直流电机闭环控制器的系统骨架先拆模块再定定点数2.1 顶层模块划分测速、PID、PWM 三者彻底解耦做 FPGA 直流电机控制器我一般不会一上来写 PID而是先画数据流。顶层拆成四个模块编码器四倍频计数、速度采样、数字 PID 计算、PWM 生成。模块间只传两个信号speed_fb有符号 16 位单位 RPM和pwm_duty有符号 16 位正负代表方向绝对值代表占空比。这个接口设计的收益在换电机时体现把编码器换成霍尔传感器或者把有刷直流电机换成永磁无刷直流电机PID 模块可以原封不动只改测速前端和功率级。这种全硬件实现和软核方案的关键差别在于控制节拍稳定。常见做法是让 PID 模块运行在 1kHz 左右的节拍上PWM 载波运行在 20kHz二者共用同一个 100MHz 系统时钟。PID 的run输入接一个由计数器分频产生的脉冲使能而不是把时钟直接切慢。门控时钟会造成时序约束混乱这是 FPGA 开发里很隐蔽的问题用时钟使能是更稳妥的做法。一个典型例化如下参数名和位宽在设计开始就要定死pid_speed #( .PW(16), .Q(14) ) u_pid ( .clk (clk_100m), .rst_n (rst_n), .run (pid_tick), // 1kHz 脉冲代表一个控制周期 .target (speed_set), // 目标转速RPM .actual (speed_fb), // 编码器反馈速度RPM .duty (pwm_duty) // 输出给 PWM 模块 );pid_tick由 100MHz 时钟分频得到每 100000 个时钟周期拉高一个周期。采样窗口、PID 计算、PWM 更新都对齐到这个节拍上闭环的相位裕度才不会因为软件调度抖动而劣化。2.2 FPGA定点数的选择速度用整数PID参数用Q14FPGA 上做浮点 PID 不是不行但一块浮点乘法器占用的 DSP 资源和延迟都偏高。做直流电机速度环完全没有必要定点数足够。先把所有物理量映射成有符号整数速度范围假设是 ±10000 RPM用 16 位有符号数表示分辨率约 0.3 RPM转速环足够的。PID 参数采用 Q14 格式含义是低 14 位为小数部分实际值等于整数值除以 16384。比如Kp 0.5在代码里写作16sd8192。误差信号是 16 位速度相减为了防止结果溢出位宽扩成 17 位。乘法输出用 32 位累加器。各信号位宽和格式如下信号位宽格式说明target / actual16 bit有符号整数单位 RPMerr17 bittarget - actual多 1 位防溢出KP / KI / KD16 bitQ14 定点实际值 整数 / 16384inc32 bit增量式 PID 的单步输出acc32 bit累加器也就是输出占空比的积分项duty16 bit有符号占空比正负代表方向FPGA 定点数最常出问题的地方不在格式定义而在乘法的位宽扩展。KP * derr两个有符号数相乘综合工具默认按最大操作数位宽处理结果只有 32 位。参数调大后乘积可能溢出表现为转速在目标值附近突然失控。保守做法是把乘法器输出声明成 48 位累加器用 40 位最后截位时再做饱和处理而不是让位宽自动截断。3. 速度反馈链路增量式编码器四倍频与FPGA采样窗口3.1 增量式编码器的AB相为什么要做成四倍频直流电机的速度闭环离不开测速。增量式编码器输出 A、B 两路正交方波每转一圈产生固定数量的脉冲。A 和 B 的相位差 90 度通过判断谁领先谁就能得到旋转方向。只用 A 相上升沿计数每圈只能得到线数个脉冲把 A、B 两相的上升沿和下降沿全部利用起来脉冲数是线数的 4 倍这就是四倍频。比如 1000 线的编码器四倍频后每圈产生 4000 个计数。闭环的最小可分辨转速由这个数字和采样窗口共同决定四倍频等于直接提高了测速分辨率成本只是多写几行状态判断。正交信号还有一个额外好处只要 A、B 不是合法顺序跳变就判定为毛刺可以丢弃。这个特性在电机电刷打火、信号线上有干扰时很有用。3.2 用Verilog写四倍频计数同步、判向、防毛刺编码器信号相对 FPGA 时钟是异步的必须先打两拍同步。相比经常被讨论的复位信号亚稳态这种真正异步的输入才是实际工程里更容易踩的坑。下面这个模块用两级触发器同步 AB 信号再用状态机判断方向module encoder_quad ( input wire clk, input wire rst_n, input wire ch_a, input wire ch_b, output reg signed [31:0] cnt ); reg [1:0] sync0, sync1, prev; always (posedge clk or negedge rst_n) begin if (!rst_n) begin sync0 2b00; sync1 2b00; prev 2b00; cnt 32sd0; end else begin sync0 {ch_a, ch_b}; // 第一级同步 sync1 sync0; // 第二级同步 prev sync1; // 上一拍的AB状态 // AB状态按 00-01-11-10 顺序跳变视为正转 if ((prev 2b00 sync1 2b01) || (prev 2b01 sync1 2b11) || (prev 2b11 sync1 2b10) || (prev 2b10 sync1 2b00)) begin cnt cnt 1sd1; end // 反向跳变视为反转 else if ((prev 2b00 sync1 2b10) || (prev 2b10 sync1 2b11) || (prev 2b11 sync1 2b01) || (prev 2b01 sync1 2b00)) begin cnt cnt - 1sd1; end // 其它跳变属于毛刺保持计数不变 end end endmodule这段代码里最重要的是sync0和sync1两拍同步它保证sync1和prev是同一个时钟域的稳定信号。方向判断用状态转移而不是分别抓 A、B 的边沿好处是不会把毛刺误判成有效脉冲。cnt声明为有符号数反转时能自然减到负数。后续采样模块只负责每隔固定时间读取cnt的差值不需要关心方向。3.3 M法采样窗口分辨率与响应速度怎么权衡速度采样最常用的是 M 法即固定时间窗口内数脉冲。转速公式为RPM 窗口内计数差值 * 60 / (编码器四倍频线数 * 采样窗口秒数)分母越大每个计数对应的转速越小分辨率越高。但窗口越长速度反馈的延迟越大PID 的带宽被迫降低。常见的减速电机测试环境窗口取 10ms编码器四倍频后每圈 4000 个计数分辨率约 1.5 RPM足够支撑 1000~3000 RPM 量程的速度环。不同配置的理论分辨率如下四倍频后脉冲数/圈采样窗口每个计数对应转速适用场景400010 ms1.5 RPM常规直流减速电机800010 ms0.75 RPM高分辨率编码器80001 ms7.5 RPM快速响应实验平台窗口从 10ms 缩短到 1ms分辨率明显变差但闭环响应更快。一个折中方案是低速时切到 T 法也就是测量相邻两个脉冲的时间间隔转速越低精度越高但 FPGA 里要同时维护一个高分辨率时间戳计数器。如果只是做入门级的直流电机 PID 闭环先固定 10ms 的 M 法采样把闭环调稳再回头优化低速性能。4. 数字PID的Verilog实现增量式公式、定点格式与参数整定4.1 位置式PID和增量式PID在FPGA上的差别数字 PID 有位置式和增量式两种。位置式的输出是占空比绝对值需要把误差的积分项单独累加积分饱和处理不好会 overshoot。增量式输出的是占空比的变化量核心公式为u[k] u[k-1] Kp*(e[k]-e[k-1]) Ki*e[k] Kd*(e[k]-2*e[k-1]e[k-2])u 是占空比e 是速度误差。这个形式天然适合 FPGA当前占空比由累加器保存每次 PID 节拍只需计算一个增量然后叠加上去。没有误差时增量归零输出保持不变不会因为误差累积出现大幅摆动。实现时还要把增量结果限幅防止某次大幅阶跃让占空比直接打满。4.2 可综合的增量式PID模块下面这段代码是速度环的核心参数和位宽与第 2 章的设计对应module pid_speed #( parameter PW 16, // 速度/占空比数据位宽 parameter Q 14 // 参数定点小数位数 )( input wire clk, input wire rst_n, input wire run, // 1kHz 控制节拍使能 input wire signed [PW-1:0] target, // 目标速度RPM input wire signed [PW-1:0] actual, // 反馈速度RPM output reg signed [PW-1:0] duty // 占空比输出 ); wire signed [PW:0] err target - actual; // 17bit 误差 localparam signed [15:0] KP 16sd8192; // Kp 0.5 localparam signed [15:0] KI 16sd4096; // Ki 0.25 localparam signed [15:0] KD 16sd2048; // Kd 0.125 reg signed [PW:0] e1, e2; // e[k-1], e[k-2] reg signed [31:0] acc; // 输出累加器 reg signed [31:0] acc_nxt; // 组合逻辑暂存 wire signed [PW:0] derr err - e1; wire signed [PW:0] derr2 err - (e1 1) e2; wire signed [31:0] inc KP * derr KI * err KD * derr2; always (posedge clk or negedge rst_n) begin if (!rst_n) begin e1 sd0; e2 sd0; acc sd0; duty sd0; end else if (run) begin acc_nxt acc inc; // 增量限幅防止输出占空比在阶跃时打满 if (acc_nxt 32sd50000) acc_nxt 32sd50000; if (acc_nxt -32sd50000) acc_nxt -32sd50000; acc acc_nxt; duty acc_nxt[Q PW - 1 -: PW]; // 右移14位取整 e2 e1; e1 err; end end endmoduleacc_nxt使用阻塞赋值只是为了算临时值最终写回寄存器仍是非阻塞赋值。derr和derr2分别是比例项和微分项的输入inc的位宽是 32 位三个乘积相加后综合工具会按最大结果位宽扩展。代码里的 Kp/Ki/Kd 只是初始参数实际整定时一般从更小的值开始后面细说。关键在duty的截取acc的格式是 Q14要输出 16 位有符号占空比需要右移 14 位。写成acc_nxt[Q PW - 1 -: PW]即取acc_nxt[29:14]相当于除以 16384。对于负数这种截位是向零取整百分比偏差不大控制环能接受。如果你希望更精确可以改成先加半个 LSB 再右移但大部分电机速度环场景没必要。4.2.1 定点乘法的溢出坑参数调大后出问题找这里FPGA 定点数最常见的坑不是格式不统一而是乘法器位宽不够。上面的KP * derr是 16 位乘 17 位有符号数乘积最大约 21 亿刚好贴近 32 位有符号上限。当 Kp 从 0.5 调大到 2.0乘积就可能溢出表现是转速在设定值附近来回跳甚至一次阶跃后占空比反号。遇到这种情况不是 PID 参数本身的问题而是位宽不够。把inc声明成 48 位acc声明成 40 位截位前再限幅可以在不重写逻辑的前提下把可调参数范围扩大很多。这个经验对任何涉及 FPGA 定点数的算法模块都适用。4.3 参数整定先开环测增益再闭环调三个值PID 参数整定不需要一上来就试。常见做法是先开环固定一个占空比比如 20%用第 3 章的测速模块记录电机稳态转速得到占空比到转速的静态增益。然后闭环先只给 Ki0、Kd0从较小的 Kp 开始逐步增加。小 Kp 系统响应慢但稳定太大则会在目标速度附近震荡。加入 Ki 后稳态误差被消除但 Ki 过大会造成超调。Kd 用于抑制超调然而编码器量化噪声会被微分项放大Kd 给的过大会让 PWM 输出明显抖动。参数调大后的典型现象调整方向Kp响应变快过大时持续震荡以 0.1 为步进试探Ki稳态误差消除过大时超调明显从 Kp 的十分之一开始加Kd抑制超调过大会放大测速噪声只在超调时少量增加实际工程我习惯把 Kp 设置在产生 10% 额定占空比对应的数值然后固定采样窗口 10ms先让系统稳定再去处理启动和负载突变。这个过程需要实时观察误差单靠示波器量电机电压效率不高下一章的 ILA 观测方法就是为这一步准备的。5. 从PID输出到电机转起来PWM生成与直流电机驱动5.1 互补PWM和死区时间怎么设PID 输出的是一个带符号的占空比数字要变成电机两端的电压最终靠 PWM。速度环常用一个定时器产生中心对齐的 PWM 载波频率 20kHz 是一个平衡点电机电感在这个频率下纹波电流可控同时听不见明显噪声。如果驱动电路是 H 桥上下桥臂需要互补 PWM并且两路信号之间必须有死区时间防止上下管直通。死区生成可以简单用一个移位寄存器延迟 PWM 信号。下面这段代码把 PWM 信号延迟DELAY拍再用组合逻辑合成两路互补输出module deadtime #( parameter DELAY 100 // 100MHz 下延迟 1us )( input wire clk, input wire pwm_in, output wire pwm_h, // 上管 output wire pwm_l // 下管 ); reg [DELAY-1:0] sr; always (posedge clk) begin sr {sr[DELAY-2:0], pwm_in}; end assign pwm_h pwm_in sr[DELAY-1]; assign pwm_l ~(pwm_in | sr[DELAY-1]); endmodule这个逻辑的效果是PWM 上升沿时上管延迟打开下管立即关断下降沿时上管立即关断下管延迟打开。死区时间等于DELAY / 系统时钟频率100MHz 下延迟 100 拍就是 1us对大多数直流电机驱动芯片足够。如果功率管是 MOS 管死区可以适当小到 500ns用 IGBT 则需要更大这个参数最终要和栅极驱动芯片匹配。5.2 驱动电路续流和母线电容比芯片本身更值得上心FPGA 输出的是 3.3V 逻辑信号驱动大功率直流电机需要一级功率放大。最省事的方案是用集成 H 桥驱动芯片比如 TB6612 这类内部集成了逻辑电平转换和死区FPGA 直接接 PWM 和方向引脚即可。如果电流需求超过几安培就要按分立 MOS 管加栅极驱动芯片来搭这时候重点是布局驱动芯片的栅极电阻靠近 MOS 管栅极电机端反并联续流二极管或依赖 MOS 体二极管母线靠近 H 桥放高频去耦电容。直流电机驱动电路最隐蔽的问题是地回路。PWM 电流在功率地和信号地之间流动如果 FPGA 的地和功率地直接大面积连通测速编码器的信号会被干扰。常见做法是单点接地或者用磁珠隔离数字地和功率地同时编码器信号线靠近 FPGA 侧加上拉电阻。这个环节表面上是模拟电路问题但直接决定数字 PID 闭环能不能收敛。5.3 闭环联调先标定开环占空比曲线PWM 控制直流电机转速的静态关系不是精确线性的负载和摩擦都会改变曲线。联调时不要直接给 PID 一个任意目标转速那样 Kp 和 Ki 初值难以判断。先在开环模式下输出 10%、20%、30% 占空比记录对应的稳定转速画一条占空比-转速曲线。这条曲线可以确定两个信息电机启动的死区占空比比如小于 8% 可能转不起来以及 PID 输出占空比初值。闭环联调的步骤按下面顺序做目标转速设为开环曲线的中点比如曲线显示 30% 占空比对应 1500RPM就把 1500 作为目标。Kp 从 0 开始逐渐增加直到速度响应不再有明显静差记录临界值。加 KiKi 初始取 Kp 的十分之一观察稳态误差是否归零。加少量 Kd目标只是平抑超调不要追求理论上最快的收敛。每次修改参数后重新启动电机并让转速从零爬升观察启动阶段有没有过流或堵转。启动阶段是闭环最容易出问题的场景。PID 输出在电机还没转起来时已经把占空比推到限幅值电机突然启动会造成电流冲击。常见补救是在 PID 输出端加一个上升速率限制让占空比以每控制周期最多几百 LSB 的速度爬升。这个斜坡其实就是给速度环一个相当于软启动的外挂不改变 PID 本身的整定结果但能显著改善运行体验。6. 用ILA在FPGA内部看PID闭环波形验证整定的三个技巧6.1 探针选择和采样时钟不要直接用100MHz抓1kHz的闭环Vivado 里叫 ILAQuartus 里叫 SignalTap用法基本一致。先把target、actual、err、acc、duty这五个信号加进探针。触发条件设成target上升沿也就是目标转速从 1000 跳到更高的时刻这样能抓到完整的阶跃响应。最容易犯的错是直接用 100MHz 系统时钟作为 ILA 采样时钟。4096 个采样点除以 100MHz 只有大约 41us而速度环的阶跃响应通常要几十毫秒抓到的波形几乎是一条直线。正确做法是把 PID 的 1kHz 节拍信号作为 ILA 的采样使能用系统时钟采样但只在run为高时记录一次数据。注意这里是用使能信号不是把run当作时钟去接 ILA 的clk门控时钟在 FPGA 调试里同样要避免。6.2 从ILA波形判断参数该往哪个方向动抓到一条完整的阶跃响应后主要看三个特征稳态误差、超调量、恢复时间。稳态误差表现为err在目标阶跃后长时间不为零并且acc稳定在一个固定值说明积分项不够加大 Ki。超调表现为actual越过target后又折返折返次数超过两次就要降 Kp或者微增 Kd。acc频繁顶到限幅值说明占空比饱和了不是增加 PID 增益而是应该减小速度阶跃的幅度或者提高限幅值。还有一个值得记录的现象如果err在目标附近做高频抖动而acc也在同步抖动这通常是测量噪声被微分项放大优先检查编码器计数毛刺而不是继续调 Kd。把 ILA 采回来的数据导出成 CSV在 Python 里算一下超调百分比和恢复时间比肉眼看波形更可复现。目标速度从 1000 阶跃到 1500RPM记录超调小于 5% 且恢复时间小于 200ms 时的参数把这组 Kp、Ki、Kd 固化到代码里再对这个闭环控制器做不同负载下的鲁棒性测试。本文还有配套的精品资源点击获取