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

数字积分法,圈内人一般叫 DDA(Digital Differential Analyzer)。这名字听着挺唬人,说白了就是「用累加代替积分」。我当年刚接触运动控制时,觉得这玩意儿不就是个加法器吗?后来在调试一台三轴雕刻机时,才真正体会到它的精妙之处——计算量小、脉冲均匀、硬件实现简单,至今仍是很多嵌入式运动控制方案的首选。

核心思想:将直线或圆弧的位移增量,分解为 X、Y 轴上的微小步长,通过累加器不断累加,当累加值超过某个阈值(通常为 1)时,就输出一个脉冲,驱动电机走一步。

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

先回忆一下高等数学里的积分。一个函数 f(t) 在 [0, T] 上的积分,几何意义是曲线下的面积。DDA 的做法很简单:把时间 T 切成 n 个小段 Δt,每段里用矩形面积近似。

公式长这样:

S = ∫₀ᵀ f(t) dt ≈ Σᵢ₌₁ⁿ f(tᵢ) · Δt

在运动控制里,f(t) 就是速度 v(t),积分结果 S 就是位移。我们让 Δt 等于一个固定的时间间隔(比如 1ms),每次累加 v(t)·Δt,累加器满了就输出一个脉冲。

嗯,这里要注意:累加器的位数决定了插补精度。我见过不少新手工程师,为了省资源用 8 位累加器,结果走出来的直线歪歪扭扭。我个人习惯,至少用 16 位,条件允许直接上 24 位。

4.2 直线 DDA 插补

直线插补是 DDA 最经典的应用场景。假设我们要从起点 (0,0) 走到终点 (Xₑ, Yₑ),总步数为 N。那么 X 轴和 Y 轴每步的增量分别是:

ΔX = Xₑ / N
ΔY = Yₑ / N

实际实现时,我们不用除法,而是用累加器。把 Xₑ 和 Yₑ 分别放到两个累加器里,每个时钟周期加一次,溢出就发脉冲。

具体流程如下:

  1. 初始化:设累加器 Jᵪ = 0, Jᵧ = 0;累加上限 M = 2ⁿ(n 为累加器位数)。
  2. 累加:每个周期 Jᵪ += Xₑ, Jᵧ += Yₑ。
  3. 溢出判断:若 Jᵪ ≥ M,则 X 轴发一个脉冲,Jᵪ -= M;同理处理 Y 轴。
  4. 结束:累计发出 N 个脉冲后停止。

这里有个关键点:N 取多少? 通常取 N = M,也就是累加器的最大值。这样每个周期累加一次,总共 M 个周期后,X 轴累计走了 Xₑ 步,Y 轴走了 Yₑ 步,刚好到达终点。

避坑指南:我曾经在一个项目中,直接用终点坐标值作为累加值,结果发现走出来的直线末端总是差几个脉冲。后来排查发现,是因为累加器位数和终点坐标不匹配。正确的做法是:将终点坐标左移 n 位(即乘以 2ⁿ),再作为累加值,这样能保证精度。

4.3 圆弧 DDA 插补

圆弧插补比直线复杂一些。为什么?因为圆弧上每一点的速度方向都在变。DDA 处理圆弧的思路很巧妙:用切线方向的速度分量来驱动坐标轴。

对于第一象限的逆圆,圆心在原点,半径为 R,当前点为 (X, Y)。速度方向是切线方向,X 轴分量为 -Y,Y 轴分量为 +X。所以累加公式变成:

Jᵪ += Yₑ(注意这里是 Y 坐标)
Jᵧ += Xₑ(注意这里是 X 坐标)

等等,你是不是觉得符号不对?我刚开始学的时候也迷糊。你想想看,圆弧上某点的切线方向,X 分量确实和 Y 坐标有关。具体推导我就不展开了,记住这个结论:圆弧 DDA 的累加值是交叉的,X 累加器加 Y 坐标,Y 累加器加 X 坐标。

圆弧插补的步骤:

  1. 初始化:设起点坐标 (X₀, Y₀),累加器 Jᵪ = 0, Jᵧ = 0。
  2. 累加:每个周期 Jᵪ += Yᵢ, Jᵧ += Xᵢ(Xᵢ, Yᵢ 为当前坐标)。
  3. 溢出判断:Jᵪ ≥ M 时 X 轴发脉冲,Xᵢ 减 1(逆圆方向);Jᵧ ≥ M 时 Y 轴发脉冲,Yᵢ 加 1。
  4. 终点判断:到达终点坐标 (Xₑ, Yₑ) 时停止。

不同象限的符号规则不一样,我整理了一个表格:

象限 圆弧方向 X 轴脉冲方向 Y 轴脉冲方向 累加值符号
第一象限 逆圆 -X +Y Jᵪ+=Y, Jᵧ+=X
第一象限 顺圆 +X -Y Jᵪ-=Y, Jᵧ-=X
第二象限 逆圆 +X +Y Jᵪ+=Y, Jᵧ-=X
第二象限 顺圆 -X -Y Jᵪ-=Y, Jᵧ+=X

注意:圆弧插补的终点判断不能只看累加次数,必须实时比较当前坐标与终点坐标。我曾经遇到过因为终点判断逻辑写错,导致圆弧多走了 90° 的情况,差点把刀具撞坏。

4.4 溢出脉冲与累加器设计

溢出脉冲是 DDA 的核心输出机制。累加器本质上是一个 n 位寄存器,每次累加后,如果结果超过了 2ⁿ-1,就会产生一个进位,这个进位就是脉冲信号。

累加器的设计有几个关键参数:

  • 位数 n:决定插补精度。n 越大,累加步长越细,轨迹越平滑。16 位是底线,24 位是推荐值。
  • 累加频率 f:决定插补速度。f 越高,单位时间内发出的脉冲越多,电机跑得越快。
  • 累加值 K:决定脉冲密度。K 越大,每个周期累加的量越多,溢出越快。

溢出脉冲的生成逻辑:

// 伪代码:16位累加器
uint16_t accumulator = 0;
uint16_t increment = 100;  // 累加值
uint16_t threshold = 65535; // 2^16 - 1

void clock_tick() {
    uint16_t old = accumulator;
    accumulator += increment;
    if (accumulator < old) {  // 发生了溢出
        output_pulse();
    }
}

你可能会问:为什么用 accumulator < old 来判断溢出?因为无符号整数加法溢出后,结果会回绕,比原来的值小。这是嵌入式开发里常用的技巧,比比较阈值更高效。

实际项目中,我通常会把累加器设计成可配置的:

  • 累加器位数:通过寄存器选择 16/24/32 位。
  • 累加值:由插补算法实时计算并写入。
  • 溢出标志:每个轴独立,支持中断或轮询。

个人经验:在 FPGA 上实现 DDA 时,我习惯用移位寄存器代替乘法器。比如累加值左移 n 位,相当于乘以 2ⁿ,这样能省掉一个乘法器资源。对于资源紧张的芯片,这个优化很实用。

4.5 DDA 插补整体流程

下面这张图展示了 DDA 插补的完整数据流,从参数输入到脉冲输出:

DDA 插补数据流图 输入参数 (Xₑ, Yₑ, R) 象限与方向判断 累加值计算 (ΔX, ΔY) X 轴累加器 (Jᵪ) Y 轴累加器 (Jᵧ) X 轴脉冲输出 Y 轴脉冲输出 终点判断 说明:虚线表示终点判断未完成时,继续下一轮累加;实线表示正常数据流

从图上可以看出,DDA 插补是一个闭环迭代过程:每次累加后检查是否溢出,溢出则发脉冲并更新坐标,然后判断是否到达终点。没到就继续累加,到了就停止。

4.6 性能优化与工程实践

DDA 虽然简单,但用不好也会出问题。我总结了几条优化经验:

  • 累加器位数的选择:不是越大越好。位数太大,累加频率就得降低,否则脉冲输出太快,电机跟不上。我一般根据最高进给速度来反推累加器位数。
  • 多轴同步:直线插补时,X 和 Y 轴的累加器必须同步累加,否则轨迹会偏。我习惯用一个全局时钟驱动所有累加器,保证同时更新。
  • 速度控制:通过改变累加频率或累加值来实现加减速。比如加速阶段,逐步增大累加值;减速阶段,逐步减小累加值。
  • 误差补偿:圆弧插补存在累积误差,每走一段可以修正一次坐标。我通常在每 90° 弧长后做一次强制修正。

重要提醒:DDA 插补的脉冲输出是均匀的,但实际电机响应可能有滞后。如果发现电机运行时有抖动,先检查脉冲频率是否超过了电机的最大响应频率,而不是怀疑 DDA 算法本身。

好了,数字积分法的核心内容就这些。从直线到圆弧,从累加器到溢出脉冲,每一步都有它的物理意义和工程考量。你想想看,一个简单的加法器,配上合适的逻辑,就能驱动电机走出精确的轨迹——这就是 DDA 的魅力所在。