GRBL运动控制的灵魂:加减速算法深度拆解
你有没有遇到过这种情况?
一台刚组装好的CNC雕刻机,跑G代码时嗡嗡作响,直线还行,一到拐角就“咔哒”一声丢步;或者加工精细文字时,边缘毛糙不堪,像是被震出来的锯齿。问题可能不在于电机或驱动器——而是在于速度没控好。
在GRBL的世界里,真正决定一台设备是“玩具”还是“工具”的,不是它能转多快,而是它如何从静止加速到高速、又如何在毫厘之间平稳刹住。这就是我们今天要深挖的主题:GRBL的加减速处理机制。
这不是简单的“慢慢提速”,而是一套运行在8位单片机上、仅有2KB RAM可用的实时运动规划系统,在资源极限下实现工业级平滑性的智慧结晶。
为什么不能直接全速启停?
想象一辆赛车,从0瞬间飙到200km/h,再在下一个弯道前一脚踩停——结果只会是轮胎打滑、车身失控。同样的道理也适用于步进电机。
传统开环控制中,如果命令电机“立即以1000mm/min移动”,控制器会立刻发出高频脉冲。但电机转子有惯性,定子磁场变化太快时,转子跟不上,就会失步(missed step)。更严重的是,这种突变会产生剧烈振动,传递到机械结构上,轻则噪音大,重则影响精度甚至损坏导轨。
GRBL的解决方案很明确:让速度变化变得平滑。不是跳变,而是像汽车油门一样缓缓踩下、缓缓松开。
梯形速度曲线:小资源里的大智慧
GRBL采用的核心模型是梯形加减速(Trapezoidal Acceleration Profile)。虽然名字听起来复杂,其实逻辑非常直观:
- 第一阶段:加速—— 速度线性上升
- 第二阶段:匀速—— 维持最高速度前进(如果有足够距离)
- 第三阶段:减速—— 线性下降至目标出口速度
当行程太短,还没来得及达到最高速就该开始减速了,那就退化成三角形速度曲线——没有中间的平顶部分。
这看起来简单,但在嵌入式环境中要做到高效、低延迟、可衔接连续路径,挑战巨大。关键在于:每一段的速度起点和终点都必须与前后段匹配,否则就会出现“断崖式变速”。
加减速不是孤立行为:前瞻(Look-ahead)才是精髓
很多人以为GRBL只是对每条G代码单独做加减速。错。
真正的高手,看的不是眼前这一段路,而是未来的几个转弯。
GRBL内置了一个名为planner的模块(位于planner.c),它维护一个最多16个运动块(block)的缓冲队列。每当新指令进来,它不会马上执行,而是先“向前看”几段,分析路径走向,判断是否即将进入急弯。
比如两条直线夹角为90°,系统就知道这里必须提前降速;如果是170°的缓弯,则可以几乎不减速通过。这个过程叫做前向扫描(forward pass)。
然后再从后往前回溯一次,确保每个block的出口速度不超过下一段允许的入口速度——这叫反向扫描(backward pass)。
两轮扫描完成后,才最终确定每一段的真实速度 profile。整个流程就像一个微型交通调度中心,为每一辆车规划最优通行策略。
关键数据结构:Block 中藏着哪些秘密?
在GRBL中,每一段运动都被抽象为一个plan_block_t结构体。它的设计极具巧思,处处体现性能优化思维。
typedef struct { uint32_t steps_x, steps_y, steps_z; // 各轴需要走多少步 float entry_speed_sqr; // 入口速度平方 float max_entry_speed_sqr; // 最大允许入口速度平方 float exit_speed_sqr; // 出口速度平方 float max_junction_speed_sqr; // 节点处最大连接速度平方 float acceleration; // 当前段加速度 uint32_t nominal_step_count; // 匀速段步数 uint8_t recalculate_flag; // 是否需要重算 } plan_block_t;注意到没有?所有速度相关字段都是平方值!
为什么不用v而用v²?
因为开平方运算sqrt()在AVR这类无浮点单元的MCU上极其耗时。只要全程用平方比较,就能完全避开sqrt(),大幅提升计算效率。这是典型的“用数学换时间”的工程技巧。
如何判断一个拐角有多“急”?向量夹角说了算
GRBL怎么知道两个线段之间的夹角有多大?答案是:单位方向向量点积。
假设有前一段的方向向量 $\vec{u}$ 和当前段的方向向量 $\vec{v}$,它们的点积满足:
$$
\vec{u} \cdot \vec{v} = |\vec{u}||\vec{v}| \cos\theta
$$
由于是单位向量,模长为1,所以点积就是 $\cos\theta$。角度越大(越接近直角),$\cos\theta$ 越小,连接速度就越低。
下面是简化后的核心函数逻辑:
float plan_compute_max_junction_speed(float prev_vec[], float curr_vec[]) { float cos_theta = prev_vec[X] * curr_vec[X] + prev_vec[Y] * curr_vec[Y] + prev_vec[Z] * curr_vec[Z]; if (cos_theta > 0.9f) return MAX_JUNCTION_SPEED_SQR; // 几乎同向,不限速 if (cos_theta < -0.9f) cos_theta = -0.9f; // 防止极端情况 float sin_theta_div_2 = sqrt(0.5f * (1.0f - cos_theta)); return MAX_JUNCTION_SPEED_SQR * sin_theta_div_2; }这里用了个小技巧:用 $ \sin(\theta/2) $ 来近似表示转向剧烈程度。数值越小,说明转弯越缓,允许更高速度通过。
这个函数返回的是速度平方,可以直接和其他v²字段比较,无需开方。
实际是怎么生成脉冲的?DDA + Stepper ISR
有了速度 profile 还不够,最终还是要落实到每一个脉冲何时发出。
GRBL使用数字微分分析器(DDA, Digital Differential Analyzer)算法来生成各轴协调的脉冲序列。其本质是一个累加器机制:
// 简化示意 dda_counter += dda_increment; if (dda_counter >= 0x10000) { dda_counter -= 0x10000; step_pin_set(HIGH); delay_short(); step_pin_set(LOW); // 发出一个脉冲 }dda_increment的大小决定了脉冲频率,也就是当前速度。而在加减速过程中,这个增量值是动态调整的。
具体来说,在定时器中断(Stepper ISR)中,GRBL会根据当前已走过的步数,判断处于哪个阶段:
- 如果还在加速段:逐步增大
dda_increment - 如果进入匀速段:保持恒定
- 如果进入减速段:逐步减小
切换点由pl_calculate()提前计算好并存入 block 中,例如accelerate_until和decelerate_after字段,都是以“总步数比例”形式存储,便于快速查表。
一段真实G代码的旅程:从文本到平滑运动
来看这样一个例子:
G01 X10 Y0 F1000 ; 水平右移 G01 X10 Y10 ; 向上走,形成90°拐角 G01 X0 Y10 ; 左移,又一个90°拐角当第一条指令进入 planner:
- 起始速度为0,目标速度1000 mm/min
- 行程足够长,生成标准梯形曲线
第二条指令加入后,planner发现两段夹角为90°,计算出连接速度只能达到约300 mm/min。于是触发反向修正:
- 第一段的出口速度被强制设为300 mm/min
- 因此第一段的实际速度 profile 改为“未达峰值即开始减速”
接着处理第二段:
- 以300 mm/min为入口速度重新加速
- 若行程够长,仍可再次加速至1000 mm/min
第三段同理,再次触发减速。
最终效果是:机器在每个直角前自然缓速,转完弯后再提速,整个过程无需人工插入暂停或降速指令,全自动完成。
参数调优实战指南:别让硬件背锅
很多用户抱怨“GRBL丢步”,其实往往是参数设置不合理。以下是几个关键建议:
✅ 正确配置加速度($120-$122)
公式参考:
$$
a \leq \frac{\tau}{J}
$$
其中 $\tau$ 是电机扭矩,$J$ 是系统转动惯量。实测推荐范围:
| 机型类型 | 推荐加速度(mm/s²) |
|---|---|
| 小型激光雕刻机 | 100 – 200 |
| 金属铣床 | 30 – 80 |
| 大型龙门结构 | 20 – 50 |
过高会导致启动抖动甚至堵转。
✅ 合理利用 Junction Deviation(GRBL 1.1+)
新版GRBL引入JUNCTION_DEVIATION参数(默认0.02mm),取代旧版固定加速度限速方式。
它的含义是:“允许路径在拐角处偏离理想轨迹的最大距离”。数值越小越保守,越大越激进。
建议值:
- 精雕加工:0.01 ~ 0.02 mm
- 快速粗切:0.05 ~ 0.10 mm
可通过$ junction_deviation=命令动态调整。
✅ 避免微小线段堆积
CAM软件输出时常将圆弧拆成上百段极短线段,导致 planner 频繁启停,平均速度极低。
解决办法:
- 使用支持arc fitting的后处理器
- 或用预处理工具合并共线小段(如 ncorrect )
✅ 监控 planner buffer 状态
若 buffer 经常为空(可用$查看状态),说明主机发送G代码太慢或CPU负载过高,可能导致速度波动。建议使用串口缓存更大的上位机(如 bCNC、Universal Gcode Sender)。
写在最后:掌握加减速,才算真正懂GRBL
你可以说自己会调GRBL,会改接线、会刷固件、会调步距角……
但只有当你能解释清楚:“为什么这个拐角自动降速了?”、“哪几个参数会影响加速时间?”、“buffer满了会怎样?”——你才算真正走进了它的内核。
GRBL的伟大之处,不在于它功能多全,而在于它用最朴素的硬件,实现了最聪明的控制逻辑。它的加减速系统,是算法、数学、工程权衡三者结合的典范。
未来如果你想升级到S型加减速(七段S曲线)、 jerk-limited planning,甚至是基于RTOS的高级运动规划,今天的这套梯形+前瞻机制,就是你最好的起点。
如果你在调试中遇到了“速度忽高忽低”、“拐角振荡”、“长直线中途降速”等问题,不妨回头看看这篇文章里的 block 扫描逻辑和速度衔接机制——也许答案早就藏在里面了。
欢迎在评论区分享你的调参经验或遇到的运动异常问题,我们一起拆解。