第四讲:DDA法原理——数字积分思想与插补实现

各位同学,今天我们来聊聊DDA法。说实话,我刚接触这个算法时,觉得它挺绕的。但后来在FPGA上做了几版实现后,发现它其实特别适合硬件加速。说白了,DDA法就是把复杂的插补问题,转化成简单的累加操作。

4.1 数字积分思想

DDA的全称是Digital Differential Analyzer,数字微分分析器。名字听着高大上,但核心思想很简单:用累加代替积分

你想想看,在连续系统中,我们要计算一个曲线上的点,需要解微分方程。但在数字系统里,我们只能处理离散的数值。那怎么办?用累加器不断累加一个增量,每累加一次,就输出一个脉冲。这就是数字积分的基本思路。

核心公式:

假设我们要产生一个频率为f的脉冲序列,时钟频率为Fclk,那么每个时钟周期累加的增量为:

Δ = f / Fclk

当累加器溢出时,就输出一个脉冲。

我在项目中遇到过一个问题:直接用浮点数做累加,资源消耗太大。后来我改用定点数,把增量放大2^N倍,累加器也用N位寄存器。这样既保证了精度,又节省了资源。

4.2 直线DDA插补

直线DDA是最简单的应用。假设我们要从点A(x1,y1)走到点B(x2,y2),需要走多少步?

先算步长:

Δx = x2 - x1
Δy = y2 - y1
步数 N = max(|Δx|, |Δy|)

然后每个时钟周期累加:

累加器X += Δx / N
累加器Y += Δy / N

当累加器溢出时,对应的轴就走一步。

我的经验:实际实现时,我习惯把Δx和Δy都放大2^N倍,这样累加器就是整数运算。比如N=16,那么Δx' = Δx * 65536 / N。这样FPGA里只需要一个加法器和一个比较器,非常高效。

嗯,这里要注意:如果Δx和Δy都是正数,那很简单。但如果有一个是负数,就要处理方向问题。我一般用一个符号位来控制加减方向。

4.3 圆弧DDA插补

圆弧DDA比直线复杂一些。为什么?因为圆弧上每个点的切线方向都在变化。

圆弧DDA的核心思想是:用微小直线段逼近圆弧。每走一小步,就更新一下方向。

假设我们要画一个半径为R的圆弧,从角度θ1到θ2:

初始位置:x = R*cos(θ1), y = R*sin(θ1)
每个步长:Δθ = 1/R  (这个1是步长,R是半径)

每个时钟周期:
x += -y * Δθ
y += x * Δθ

等等,这个公式是不是看着眼熟?其实就是旋转矩阵的离散化形式。

我曾经踩过的坑:圆弧DDA的误差会累积!因为每次计算都有截断误差,走完一圈后,终点可能回不到起点。我的解决办法是:每走几步就做一次误差修正,或者用更高精度的累加器。

另外,圆弧DDA要处理象限问题。不同象限的加减方向不一样。我一般用一个状态机来管理象限切换。

4.4 积分累加器设计

累加器是DDA法的核心模块。一个好的累加器设计,直接决定了插补的精度和速度。

先看一个基本的累加器结构:

module dda_accumulator #(
    parameter WIDTH = 16
)(
    input clk,
    input rst_n,
    input [WIDTH-1:0] delta,   // 增量
    output reg overflow        // 溢出脉冲
);

    reg [WIDTH-1:0] acc;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            acc <= 0;
            overflow <= 0;
        end else begin
            {overflow, acc} <= acc + delta;
        end
    end

endmodule

这个设计很简单,但有个问题:如果delta很小,累加器要很久才溢出一次,导致输出脉冲不均匀。

改进方案:我建议用带余数的累加器。每次累加后,把余数保留下来,下次继续累加。这样脉冲分布更均匀。

还有一种情况:多轴联动时,每个轴都有自己的累加器。但它们的时钟是同步的。我一般用一个全局时钟,所有累加器同时更新。这样能保证各轴的运动是同步的。

来看看多轴累加器的设计要点:

参数 说明 我的建议
累加器位宽 决定精度 至少16位,推荐24位
增量格式 定点数还是浮点数 定点数,Q格式
溢出检测 如何判断溢出 用进位位,不用比较器
多轴同步 各轴如何协调 统一时钟,独立累加

我个人习惯用Q16格式,也就是16位整数部分加16位小数部分。这样累加器是32位,精度足够,资源消耗也合理。

最后,给大家看一个我实际项目中用过的圆弧DDA插补流程图:

圆弧DDA插补流程图 开始插补 初始化:设置起点、半径、方向 计算初始累加器值 是否到达终点? 插补结束 更新累加器:x += -y*Δθ, y += x*Δθ 检查溢出,输出脉冲 注:Δθ = 步长 / 半径 累加器溢出时对应轴走一步

这个流程图看起来简单,但实际实现时要注意时序。我一般用流水线设计,把更新累加器和检查溢出分成两个时钟周期。这样能提高时钟频率。

避坑指南:我曾经在圆弧插补时,忘记处理半径为零的情况。结果仿真时一切正常,上板后直接死机。后来加了保护逻辑:如果半径为零,直接输出直线插补。

好了,DDA法的核心内容就这些。总结一下:数字积分思想让复杂的插补变成了简单的累加;直线DDA是基础,圆弧DDA是扩展;累加器设计要兼顾精度和资源。这些都是在FPGA上实现多轴联动插补的关键。

记住一点:硬件加速的本质,就是把软件中的循环和判断,变成硬件中的并行累加和比较。DDA法就是这种思想的完美体现。

专注资料整理