3、加速器硬件架构:FPGA在运动控制中的优势、加速器核心模块(脉冲发生器、插补器、位置计数器)、硬件与软件的协同设计
这一章,我们聊聊加速器的硬件架构。
说实话,很多做运动控制的工程师,一开始都习惯用MCU或者DSP。我也一样。直到我第一次用FPGA做了一台三轴点胶机的控制板,才发现——原来运动控制可以这么「丝滑」。那感觉,就像从绿皮火车换成了高铁。
3.1 FPGA在运动控制中的优势
为什么是FPGA?说白了,就三个字:快、准、稳。
- 快:硬件并行执行。你想想看,MCU处理一个脉冲,得进中断、查表、算延时。FPGA呢?一个时钟周期,所有轴同时输出脉冲。我做过对比,同样跑100KHz的脉冲频率,MCU的CPU占用率已经飙到80%,FPGA几乎为零。
- 准:硬件定时,没有抖动。MCU的中断响应时间受代码分支影响,有时候差几个微秒。对于高精度插补来说,这就是灾难。FPGA的脉冲发生器是纯硬件逻辑,每个脉冲的间隔精确到纳秒级。
- 稳:不会死机。我见过太多MCU方案,跑着跑着看门狗复位了。FPGA的逻辑一旦综合好,只要电源稳定,它就能一直跑下去。我曾经有个项目,FPGA连续跑了三年没重启过。
核心观点:FPGA不是替代MCU,而是做MCU做不了的事。运动控制中,实时性要求高的部分(脉冲生成、插补计算、位置捕获)交给FPGA,人机交互、逻辑判断、通信协议交给MCU。各司其职,才是最佳方案。
3.2 加速器核心模块
一个运动控制加速器,核心就三个模块。我一个个说。
3.2.1 脉冲发生器
脉冲发生器,就是产生步进电机驱动信号的模块。常见的模式有脉冲+方向(Pulse/Dir)和正反转脉冲(CW/CCW)。
我个人习惯用脉冲+方向模式,接线简单,逻辑也清晰。脉冲发生器内部其实就是一个频率合成器。给定目标频率和脉冲数,它就能精确输出。
这里有个坑,我踩过。刚开始做的时候,我用的是直接计数分频法。比如系统时钟50MHz,要输出1KHz脉冲,就计50000个时钟翻转一次。但问题是,如果中途要改变频率,脉冲周期会突然跳变,电机就会「咯噔」一下。
避坑指南:我曾经因为频率切换没做平滑处理,导致一台三轴平台在换速时剧烈抖动,把工件都甩飞了。后来改用NCO(数控振荡器)结构,频率切换时相位连续,电机运行就顺滑多了。
NCO的核心代码其实很简单:
// NCO脉冲发生器核心逻辑
always @(posedge clk) begin
phase_acc <= phase_acc + freq_word; // 相位累加
pulse_out <= phase_acc[MSB]; // 取最高位作为脉冲
end
你看,就两行代码。freq_word就是频率控制字,改变它就能平滑调速。相位累加器的位宽决定了频率分辨率。我一般用32位,分辨率足够精细。
3.2.2 插补器
插补器,是运动控制的灵魂。没有它,你只能做单轴运动。有了它,才能走出直线、圆弧、甚至任意曲线。
最常用的插补算法是DDA(数字微分分析器)。它的思想很简单:把一条直线分解成X轴和Y轴两个方向上的步进。每个时钟周期,判断哪个轴该走一步。
我记得第一次在FPGA上实现DDA插补时,花了整整一周。调试的时候,示波器上看到两路脉冲完美地走出了45度斜线,那种成就感,到现在还记得。
DDA插补的硬件实现:
// DDA直线插补核心
always @(posedge clk) begin
if (enable) begin
acc_x <= acc_x + step_x; // X轴累加
acc_y <= acc_y + step_y; // Y轴累加
if (acc_x >= total) begin
pulse_x <= 1'b1;
acc_x <= acc_x - total;
end
if (acc_y >= total) begin
pulse_y <= 1'b1;
acc_y <= acc_y - total;
end
end
end
这里step_x和step_y是终点坐标,total是步数。每次累加溢出,就输出一个脉冲。硬件并行执行,X轴和Y轴的判断在同一时钟完成,效率极高。
经验之谈:实际项目中,插补器还要考虑加减速。我一般会在插补器前面加一个速度规划模块,实时调整插补步长。这样电机启动和停止时就不会「冲」出去。
3.2.3 位置计数器
位置计数器,用来记录电机实际走了多少步。它接收编码器的反馈信号(A相、B相),进行四倍频计数。
为什么需要四倍频?因为编码器一个周期内,A和B的上升沿和下降沿共有4个,每个沿都计数,分辨率就提高了4倍。比如1000线的编码器,四倍频后就是4000个脉冲每圈。
位置计数器的Verilog实现:
// 正交编码器四倍频计数
always @(posedge clk) begin
case ({a_prev, b_prev, a, b})
4'b0001, 4'b0111, 4'b1110, 4'b1000: counter <= counter + 1;
4'b0010, 4'b0100, 4'b1101, 4'b1011: counter <= counter - 1;
default: counter <= counter;
endcase
{a_prev, b_prev} <= {a, b};
end
这段代码用状态机判断旋转方向。注意,编码器信号一定要做去抖处理。我遇到过因为信号毛刺导致计数乱跳的情况,后来加了三级同步器和边沿检测,问题就解决了。
3.3 硬件与软件的协同设计
FPGA做得好,只能算成功了一半。另一半,是软硬件怎么配合。
我习惯把系统分成三层:
- 硬件层(FPGA):负责实时任务。脉冲生成、插补计算、位置捕获、IO控制。这些任务对时间敏感,必须硬件完成。
- 驱动层(MCU固件):负责配置FPGA寄存器、读取状态、处理中断。相当于硬件和应用的桥梁。
- 应用层(上位机/操作系统):负责运动规划、轨迹生成、人机交互。这部分对实时性要求不高,可以跑在Linux或者Windows上。
三层之间通过寄存器映射通信。FPGA内部定义一组寄存器,MCU通过总线(比如SPI、并行总线)读写这些寄存器。举个例子:
| 寄存器地址 | 功能 | 方向 |
|---|---|---|
| 0x00 | X轴目标位置 | MCU→FPGA |
| 0x04 | X轴目标速度 | MCU→FPGA |
| 0x08 | X轴当前位置 | FPGA→MCU |
| 0x0C | 控制命令(启动/停止/急停) | MCU→FPGA |
| 0x10 | 状态标志(忙/完成/报警) | FPGA→MCU |
这种设计的好处是:接口清晰,职责分明。MCU只管发命令、读状态,不用管脉冲怎么生成。FPGA只管干活,不用管上层逻辑。
设计原则:把「做什么」和「怎么做」分开。MCU告诉FPGA「去哪里、多快」,FPGA自己决定「怎么走、怎么停」。这样即使更换MCU,FPGA部分完全不用动。
嗯,这里要注意一点:通信带宽。我曾经在一个项目中,MCU每1ms就要更新一次位置指令,结果SPI通信成了瓶颈。后来改用并行总线,带宽提升了10倍,问题才解决。所以设计初期就要估算好数据量,选对通信方式。
最后,我画了一张架构图,把这三个核心模块的关系理清楚:
这张图把三层架构和三个核心模块的关系展示得很清楚。应用层发指令,驱动层做翻译,硬件层干活。三个核心模块之间通过内部总线交换数据,协同工作。
我的建议:刚开始做FPGA运动控制时,不要贪多。先把脉冲发生器调通,再慢慢加上插补器和位置计数器。每加一个模块,都要用示波器或者逻辑分析仪验证波形。硬件调试不像软件,改一行代码就能跑。一次综合可能要等几分钟,所以前期仿真一定要做足。
好了,这一章就到这里。FPGA加速器的硬件架构,说白了就是「用硬件的并行性,解决软件的实时性问题」。三个核心模块各司其职,再加上合理的软硬件分工,一套高性能的运动控制系统就搭起来了。