4. 数字积分法(DDA):基本原理、直线DDA插补、圆弧DDA插补、溢出与累加器处理

数字积分法,圈里人习惯叫它 DDA(Digital Differential Analyzer)。

这名字听起来挺唬人,其实说白了就是——用累加代替乘法,用时间换精度。

我刚入行那会儿,厂里的老系统用的就是 DDA。那时候 CPU 慢得可怜,乘除法能省则省。DDA 正好,只需要加法器和寄存器,硬件成本低,速度还快。到现在,很多专用运动控制芯片里,DDA 依然是核心算法之一。

核心思想: 将刀具的移动轨迹,看作一个连续积分过程。用一系列小步长的累加,逼近理想轨迹。每走一步,累加器溢出一回,就发一个脉冲。

4.1 基本原理:从微分到累加

先回忆一下微积分。一个函数 y = f(t),它在 [0, t] 上的积分,就是曲线下的面积。

DDA 的做法很粗暴:把时间 t 切成无数小段 Δt,每段里用矩形面积 y × Δt 去近似。只要 Δt 足够小,误差就能接受。

放到插补里,就是:

  • 被积函数:各坐标轴的速度分量(Vx, Vy)
  • 自变量:时间 t
  • 积分结果:各轴的位移(Δx, Δy)

数学表达式长这样:

X = ∫ Vx dt ≈ Σ Vx × Δt
Y = ∫ Vy dt ≈ Σ Vy × Δt

每次累加,其实就是算一小步。累加器满了(溢出),就说明该走一步了。

我的经验: DDA 的精度取决于累加器的位数和 Δt 的大小。位数越多,步长越细,轨迹越平滑。但位数太多,溢出频率降低,速度会变慢。一般 16 位或 24 位比较常用,得在精度和速度之间找个平衡点。

4.2 直线 DDA 插补

直线插补是 DDA 最经典的应用。我当年调试第一台雕刻机,用的就是它。

4.2.1 原理与公式

假设要从起点 (0, 0) 走到终点 (Xe, Ye)。总步数 N 通常取 2^m(m 是累加器位数)。

各轴的速度分量就是:

Vx = Xe / N
Vy = Ye / N

每次累加:

Σx += Vx
Σy += Vy

当 Σx ≥ 1 时,X 轴走一步,Σx -= 1。Y 轴同理。

4.2.2 具体步骤

  1. 初始化:累加器 Σx = 0, Σy = 0。总步数 N = 2^m。
  2. 计算增量:Δx = Xe / N, Δy = Ye / N。实际用整数表示,比如左移 m 位。
  3. 循环累加:每来一个时钟脉冲,执行 Σx += Δx, Σy += Δy。
  4. 溢出判断:如果 Σx ≥ 1,X 轴发一个脉冲,Σx -= 1。Y 轴同理。
  5. 结束:累加 N 次后,插补完成。

注意: 如果 Xe 或 Ye 为 0,对应的累加器永远不会溢出。这时候要特殊处理,直接让该轴不动作。我曾经见过有人忘了这个,结果电机一直嗡嗡响,就是不走。

4.2.3 代码示例

// 直线 DDA 插补(16位累加器)
void line_dda(int Xe, int Ye) {
    int acc_x = 0, acc_y = 0;   // 累加器
    int step_x = Xe;            // 左移16位后的增量
    int step_y = Ye;
    int total_steps = 65536;    // 2^16

    for (int i = 0; i < total_steps; i++) {
        acc_x += step_x;
        acc_y += step_y;

        if (acc_x >= 65536) {   // 溢出
            output_pulse(X_AXIS);
            acc_x -= 65536;
        }
        if (acc_y >= 65536) {
            output_pulse(Y_AXIS);
            acc_y -= 65536;
        }
    }
}

4.3 圆弧 DDA 插补

圆弧比直线麻烦一点。因为速度方向一直在变。

4.3.1 原理与公式

圆弧插补的核心是:动点坐标的变化,满足圆的参数方程。

对于第一象限逆圆,从 (R, 0) 到 (0, R):

Vx = -Y
Vy =  X

注意这里的 X 和 Y 是动点的当前坐标,不是终点坐标。所以每次累加后,坐标值会变,增量也跟着变。

4.3.2 具体步骤

  1. 初始化:X = R, Y = 0。累加器 Σx = 0, Σy = 0。
  2. 计算增量:Δx = -Y, Δy = X。
  3. 累加:Σx += Δx, Σy += Δy。
  4. 溢出判断:同直线,溢出就走一步。
  5. 更新坐标:如果 X 轴走了,X -= 1。如果 Y 轴走了,Y += 1。
  6. 结束:走到终点 (0, R) 为止。

避坑指南: 圆弧 DDA 的误差会累积。因为每次更新坐标后,增量才变化,存在一个步长的滞后。我曾经在加工一个精密圆环时,发现接刀处有台阶,就是这个问题。后来加了误差补偿,才搞定。

4.3.3 象限处理

不同象限,速度分量的符号不一样。我习惯用查表法:

象限 顺圆 Vx 顺圆 Vy 逆圆 Vx 逆圆 Vy
一 +Y -X -Y +X
二 -X -Y +X +Y
三 -Y +X +Y -X
四 +X +Y -X -Y

4.4 溢出与累加器处理

溢出处理是 DDA 的精髓。处理不好,轨迹会跑偏。

4.4.1 溢出原理

累加器就像一个水桶。每次往里倒水(加增量),满了就溢出来(发脉冲)。

桶的容量就是 2^m。溢出的频率,决定了电机的速度。

4.4.2 常见问题与对策

  • 同时溢出:X 和 Y 同时溢出时,先处理哪个?我建议先处理 X,再处理 Y。顺序固定,避免抖动。
  • 多次溢出:一次累加,可能溢出多次(增量很大时)。要循环判断,直到累加器小于容量。
  • 负增量:反向运动时,增量是负的。累加器会减少,不会溢出。这时候要判断下溢(小于 0),发反向脉冲。

重要: 累加器溢出后,一定要减去容量值,而不是清零。否则会丢失余数,造成累积误差。我见过有人直接清零,结果加工出来的圆变成了多边形。

4.4.3 代码示例(带溢出处理)

// 带溢出处理的累加
void process_accumulator(int *acc, int step, int *pulse_count) {
    *acc += step;

    // 正向溢出
    while (*acc >= 65536) {
        (*pulse_count)++;
        *acc -= 65536;
    }

    // 负向下溢
    while (*acc < 0) {
        (*pulse_count)--;
        *acc += 65536;
    }
}

4.5 知识体系总览

下面这张图,把 DDA 的核心脉络串起来了。我每次培训新人,都会先让他们看这个。

数字积分法(DDA)知识体系 DDA 插补 直线 DDA 插补 圆弧 DDA 插补 增量计算:Δx=Xe/N, Δy=Ye/N 累加 → 溢出 → 发脉冲 增量依赖当前坐标 (X, Y) 累加 → 溢出 → 更新坐标 溢出与累加器处理 核心:累加器溢出频率决定速度,余数保留保证精度

4.6 小结与个人体会

DDA 是个老算法,但老不代表过时。它的思想——用累加代替乘法,用时间换精度——在很多嵌入式场景里依然好用。

我这些年调试过的系统,从三轴雕刻到五轴联动,底层或多或少都有 DDA 的影子。它简单、可靠、容易硬件化。

如果你刚开始学插补,我建议从 DDA 入手。把累加器和溢出的逻辑吃透,后面学逐点比较法、时间分割法,都会轻松很多。

最后说一句: 别小看这个“老古董”。很多新算法,都是在 DDA 的基础上改进的。基础打牢了,才能走得更远。