做数控和3D打印固件开发的人,几乎绕不开GRBL。一个不到一万行的开源运动控制器,能把三个轴的步进电机跑到你设定的极限附近,还能保证不丢步、不过冲,靠的就是它那套非常精炼的速度前瞻算法。很多人看GRBL代码,第一步卡住就在这里:planner.c里那些循环、速度更新和梯形剖面计算,看起来每个函数都能看懂,但合起来就不知道它们到底在干什么。其实整个规划器的核心就两个方向:反向规划和正向规划。搞懂这两个词,GRBL源码里最“劝退”的部分基本就通关了。
这篇文章我按源码阅读顺序来写。先说GRBL为什么需要前瞻缓冲区,再讲block、入口速度、出口速度这些关键概念,然后一步一步拆解反向规划和正向规划的推导过程,最后用一个连续运动实例把整条速度剖面串起来,顺便聊一些调参和排查问题的经验。适合正在啃GRBL源码的嵌入式开发者,也适合想搞清楚运动控制速度规划原理的CNC玩家。
1. 速度前瞻在GRBL中到底解决什么问题
1.1 为什么逐行执行G代码会“抖”还会“过切”
先回到最原始的问题。假设你有一串G01直线段,比如从(0,0)到(50,0),再到(60,5),再到(60,5)后面的圆弧等等。如果控制器拿到一行G代码就立刻完整执行一行,每一条线段都按“从零加速到设定进给率,再减速到零”的流程跑,会发生什么?
机器每到一个线段终点就完全停下来,再重新起步。停下来的瞬间机械结构有惯性冲击,重新起步时摩擦力和切削力方向突变,整个机身会抖一下。更麻烦的是,很多走空行程或者成型表面的路径由大量小线段组成,像样条曲线正距离切割出来的轮廓,动辄几百行上千行。如果每行都启停一次,加工时间会成倍拉长,工件表面也会留下明显停顿痕迹,尤其在轮廓拐角处,停顿的时间已经足够让刀具在工件上多切一刀,形成过切或者圆角。
真正理想的运动控制是:提前几行就预判到下一条路径要转弯,在这个转弯点前留出足够的减速距离,让实际到达转弯点的速度降到一个安全值,转过弯后再继续加速。也就是说,控制器必须“看向未来”。这就是速度前瞻,也叫lookahead。GRBL的规划器就是在干这件事:它不等当前运动块执行完,而是拿着后续的一串运动块,提前计算每个块之前该用什么速度、之后该用什么速度。
1.2 前瞻缓冲区,把“规划”和“执行”拆成两个节奏
GRBL直接在固件里维护一个环形缓冲区,专门放规划好的运动块(block)。扫描G代码的程序每解析出一行可执行的运动指令,就调用函数把这个运动块追加进缓冲区,然后立即触发一次重新规划。缓冲区里最多能放16个块,也可以根据芯片内存调整到更大,比如32个块。这个缓冲区的长度决定了控制器能“看”多远。
规划层和执行层是完全分开的。规划层跑在主循环或者空闲任务里,负责更新缓冲区里那些还没执行到的块的速度参数;真正的脉冲发给电机驱动器的动作,是放在步进中断里完成的。步进中断每触发一次,就按当前块的速度剖面决定这一拍该走多少步、该不该加速。因为规划早就提前做完了,中断里不用做任何高耗时计算,只需要查表、计数、发脉冲。这个架构最大的好处是:主循环卡顿一点,脉冲输出仍然可以非常稳定。而如果规划完全放实时执行里做,CPU只要被串口数据打断一会儿,电机波形就会变“毛”,直接影响加工质量。
1.3 一个缓冲区里的“双向规划”到底是什么关系
说回正题。GRBL的重新规划过程,大致可以分成三个动作:反向扫描一遍、正向扫描一遍、最后给每个块生成具体速度剖面。很多人看代码时一直想不通,为什么非要扫两遍,不能一遍搞定吗?
难点在于:每个块受加速度约束,它既受前面块影响,又受后面块影响。单纯从前往后算,最多只能保证当前块能加速到下一位要求的入口速度,但你不知道后面的块是不是还能刹得住。反过来单纯从后往前算,你能保证刹得住,但又可能把一个块的速度压得过低。GRBL的做法就是两次扫描互相修正,反向规划负责把“因为后面刹不住而必须降低的速度”向前传递,正向规划负责把“因为加速能力允许而可以提升的速度”向后传递。两个方向约束都满足以后,再生成梯形剖面,这时候得到的速度包络才同时满足加速度上限和终点停止这两个条件。
2. 规划器的数据结构与关键参数
2.1 block:GRBL缓冲区里的“最小运动单元”
在GRBL源码里,运动块用block_t表示。每个块本质上描述了一条线段:x、y、z轴分别要走多少步,其中步数最多的那个轴决定了整个块的执行时长,叫主导轴(Step Event Count)。块的物理长度、各轴方向、设定的进给率、轴加速度、当初来自哪一行G代码等消息也都会留在这个结构体里。
因为我这里要讲速度前瞻,你只需要先记住几个字段:steps_x、steps_y、steps_z代表三个轴的步数增量;step_event_count代表执行这个块所需的总步进事件数,它是后面算距离、算加减速的基础;acceleration是系统设置里每个轴的最大加速度;rate_delta是每步进事件速度的变化量,相当于梯形剖面里爬坡段的斜率。
这些字段是后面反向规划和正向规划所有的数值基础。GRBL在做加减速运算时,并没有直接把物理速度和加速度套公式,而是把速度和加速度都统一转换成“每步进事件步数”的单位。比如进给率F定义成“每分钟走多少毫米”,换算成“每分钟步数”,再除以step_event_count,就得到一个单位步进事件里应该发生的步数变化。这样做的好处是,中断执行时只需要做一个简单的浮点累加,不需要每一步都重新算毫米、算时间、算速度,效率极高。
2.2 三个关键速度:entry_speed、exit_speed、nominal_speed
GRBL规划器里最常看到的三个速度字段,是理解整个算法的钥匙:
entry_speed:进入当前块时的速度。exit_speed:离开当前块时的速度。nominal_speed:当前块在不超出设定进给率的前提下,理论上能达到的最高速度。
相邻两个块之间,前一个块的exit_speed和后一个块的entry_speed会被规划器强制拉平。这是速度前瞻能够“平滑衔接”的关键:如果一条路径从头到尾都是同方向直线,那速度根本不需要降下来,每个块的入口和出口速度都可以接近最大速度;但如果路径中间有个尖角,那尖角两侧的块就只能在通过尖角时降到安全速度,这个安全速度会由速度前瞻算法算出来,然后反向规划把这个低速向尖角以前所有块传递。
nominal_speed决定一个块最多能跑多快,它受两个因素限制:一是看G代码里给的目标进给率是多少;二是看这个块的路径长度,路径太短时可能没时间到达目标速度,那它的实际峰值速度就不可能跑到nominal_speed。
2.3 为什么GRBL要把规划拆成“反向”和“正向”两个阶段
单个块的速度规划很简单,但多个块连起来就不简单了。每两个相邻块之间存在一个速度衔接约束:前一个块出来的速度慢,后一个块进去的速度也慢;前一个块如果冲得太快,后一个块又必须在很短的距离内减速,那就必须提前知道这个“太快”的信号。
如果只做正向规划,从头向后扫描,你可以保证每个块都有能力按加速度上限从入口速度加速到出口速度。但你不知道缓冲区很远的地方有没有一个必须停下来的下料结束点。等到执行到那个结束点才发现刹不住,前面的加速就已经白白做完了。
反过来如果只做反向规划,从尾部向前扫描,你可以保证每个块都能在终点前刹得住,但生产时速度轮廓会过于保守:明明前面还有很长距离可以加速,反向规划却因为某个局部限制把整个入口速度都压低了,这时就需要正向规划在保证加速度安全的前提下,把速度重新“提回来”。
所以GRBL把两次扫描结合起来,反向先给一个“安全上限”,正向在这个上限里再尽量把速度拉高,最终得到一条既安全又尽可能快的速度包络线。这也是现代运动控制里很典型的思想:先满足安全性约束,再优化性能指标。
3. 反向规划:从路径尽头往回推,保证每个块都刹得住
3.1 反向规划的本质:知道终点要停,就得提前多少距离准备
先看一个最简单的场景。缓冲区最后一个块的运动走到终点时,电机通常要求停下,也就是末尾速度是0。如果这个块的长度很短,只有2毫米,但进给率很高,比如每分钟3000毫米,加速度又不大,能不能在2毫米内从3000降到0?当然不行。运动学公式告诉你,从速度v减速到0,需要的最小距离是v²除以2倍加速度。距离不够?那就只有一个办法:进入这个块之前就必须减速,把入口速度降到安全值。这个“向前一个块传递减速需求”的过程,就是反向规划。
GRBL的反向规划就是从缓冲区尾部开始,从最后一个块往缓冲区头部方向扫描。内核任务是针对每个块,根据它下一个块的入口速度限制,计算出当前块入口速度的上限。如果算出来的上限比当前块原来的入口速度小,就降低当前块入口速度,还要继续往更前面的块传递这个减速需求;如果算出来的上限不比当前块原有速度小,说明这个块不再限制整条路径了,循环就可以提前停掉。
3.2 减速公式推导:v_entry到底怎么算
假设当前块长度为S,加速度上限为a,它的出口速度等于下一个块的入口速度,记作v_exit。在理想匀加速/匀减速模型下,要在这段距离内稳稳落到v_exit,入口速度最大可以为多大?
匀变速运动有公式:
v_exit² = v_entry² - 2 * a * S
也就是说,反向规划允许的最大入口速度为:
v_entry = sqrt(v_exit² + 2 * a * S)
这个公式里a取正数,代表“允许的最大加速度绝对值”。算出来的v_entry如果超过当前块的名义速度或者系统设置的最大轴速度,还要再取一次最小值,因为总不能因为数学允许就超速。所以最后是:
v_entry_new = min(v_entry_max, nominal_speed, axis_max_speed, junction_speed_limit)
放到连续块链上,反向规划的过程就是:先把最后一个块的出口速度定成0,然后往前一个块一推,算出前一个块允许的最大入口速度;发现它比前一个块原来的入口速度低,于是更新前一个块入口速度,并以这个新速度作为再往前一个块计算的“出口速度”,继续往更前面推,直到某个块不再需要减速、或者已经到缓冲区头部为止。
3.3 对应到GRBL源码,大概是这么一段逻辑
GRBL源码里负责这个流程的函数叫planner_recalculate,里面会调用一个反向遍历的回调。下面我写一个示意性的伪代码,帮助你建立和源码的对应关系,实际源码里的单位换算和字段判断会多一些:
// 从缓冲区尾部向头部扫描 for (int i = tail_index; i > head_index; i--) { block_t *prev = &block_buffer[i - 1]; block_t *next = &block_buffer[i]; // 已知next块的入口速度,作为当前块的出口速度 float v_exit = next->entry_speed; // 用减速距离反推当前块最大入口速度 float v_entry_max = sqrt(v_exit * v_exit + 2.0f * prev->acceleration * prev->step_event_count); // 不能超过名义速度 float v_entry_new = min(prev->nominal_speed, v_entry_max); if (v_entry_new < prev->entry_speed) { prev->entry_speed = v_entry_new; // 继续往前传:下一个循环里,这个块会变成“next”,影响它前面的块 } else { // 当前块不需要减速,继续往前传没有意义,提前终止 break; } }这个反向循环有一个非常关键的地方:不是每次都要从头扫到尾。GRBL有一个头指针和尾指针,只有新加入缓冲区、还没执行到的块才会参与重新规划。已经执行完或者正在执行的块,速度剖面已经定死了,不能动了。另一个关键是,反向扫描一旦遇到一个块不需要改速度,就可以立刻停止,因为更前面的块面临的下游条件没变,不会受到减速限制影响。这样每次重新规划都只处理缓冲区的末尾一小段,计算开销很低,这也是GRBL在8位单片机上也能跑出不错效果的原因之一。
3.4 反向规划时很容易忽略的尖角限速
除了重力加速度和名义速度,GRBL在每个块之间还检查了一组“方向约束”。如果相邻两条线段的走刀方向夹角很大,说明轮廓上有尖角,为了不把尖角切圆,控制器希望经过尖角时速度尽可能低。GRBL会计算相邻三个轴动作方向之间的余弦值,角度越大余弦越小,允许的连接速度就越低。这个约束在反向规划里同样被压缩到出口速度限制里。
举个例子,一个面包板上的小方孔,四个角都很尖。虽然四条直线各自可以跑很快,但每条边的出口速度一旦遇上直角拐角,就会被压得很低。反向规划会把这个“低速转弯”的要求向每一条边的入口方向传递:如果一条边长度足够,可以前半段高速、后半段减速;如果边太短,就只能整段都低速跑。你实际看到的现象就是,铣一个正方形轮廓时,四条边中间速度飞快,到了四个角附近会明显慢下来,这个慢下来的区间和反向规划算出来的减速距离完全对应。
4. 正向规划:在加速度限制内,把该拉起来的速度拉起来
4.1 正向规划的作用,反向规划为什么还不够
反向规划跑完后,缓冲区里每个块的入口速度都已经被压到“绝对不会刹不住”的水平。但也仅仅是“安全”而已,不代表“高效”。因为反向规划是从尾往前推的,它天然只关心减速,不关心加速。一条很长的直线走完后要转一个非常小幅度的弯,理论上直线段完全可以根据剩余距离提前加速跑满速,在弯道前再降下来。但反向规划为了保证拐弯低速,可能会把整条直线的入口速度都压得很低。
正向规划就是来解决这个过度保守问题的。它从缓冲区头部向尾部扫描,核心任务是:在当前块已知入口速度的前提下,按照加速度上限计算它最多能加速到多快,并把该速度作为下一个块的入口速度传递下去。如果下一个块原有的入口速度比这个值低,那就说明它被反向规划压得太狠了,可以适当提高它;提高之后,再继续向后传递,看后面的块能不能承接这个更大的入口速度。
4.2 正向更新的计算逻辑与反向公式是对称的
正向规划的计算公式和反向刚好相反。已知当前块入口速度v_entry,当前块长度S,加速度上限a,那么当前块最多能加速到的出口速度是:
v_exit_max = sqrt(v_entry² + 2 * a * S)
这个出口速度就是下一个块入口速度的上限。下一个块新的入口速度取这个值、它自己的名义速度、方向角限速之间的最小值:
next->entry_speed_new = min(v_exit_max, next->nominal_speed, next->junction_speed_limit)
如果next->entry_speed_new大于next->entry_speed,就更新它,然后继续向后扫描。如果某个块不再需要提高速度,循环一样可以提前终止。伪代码大概是:
for (int i = head_index; i < tail_index; i++) { block_t *cur = &block_buffer[i]; block_t *nxt = &block_buffer[i + 1]; // 当前块在给定入口速度下,最多能跑到的出口速度 float v_exit_max = sqrt(cur->entry_speed * cur->entry_speed + 2.0f * cur->acceleration * cur->step_event_count); if (v_exit_max < nxt->entry_speed) { nxt->entry_speed = v_exit_max; } else { break; } }等等,你这个伪代码的if方向是不是反了?这里要特别说清楚。正向规划的目标不是“把能提的都得提上来”,而是“把不可能达到的高入口速度压下去”。很多初学者看正向规划,默认它一定会提高速度,实际上它同时还要做一件更基础的事:从头扫描,保证前一个块的真实加速能力足够支撑后一个块想要的入口速度。如果后一个块要求的入口速度太高,当前块根本没能力加速到那么快,那你必须把后一个块的入口速度强制压下来,否则执行到中间就会“规划的速度和实际能力对不上”。
所以说,正向规划其实有一个双向效果:如果下一个块原来的入口速度太乐观,就把它压到当前块能加速到的上限;如果下一个块原来的入口速度太低,那确实可以把它提上来。两种判断的数学基础是同一个加速公式,最终扫到某处发现下一个块不需要再改动,就可以停。
4.3 反向正向交替,GRBL是怎么收敛的
GRBL的规划器在实际运行时会怎么组织这两个方向?我梳理一下完整流程:
- 新的运动指令到达,
plan_buffer_line把新块追加到缓冲区尾部。 - 调用
planner_recalculate,从缓冲区尾部开始执行一次反向规划,把减速约束向前传播。 - 紧接着执行一次正向规划,从缓冲区头部开始,在反向规划得到的保守速度基础上,允许速度提升到加速能力可满足的范围。
- 两个方向都跑完后,调用生成梯形剖面的函数,为每个受影响的块算出实际的加速区、匀速区、减速区边界。
- 真正执行到块头时,步进中断里就按这个梯形剖面发脉冲。
这里注意一点:反向规划和正向规划并不是各自只跑一遍就绝对最优化。GRBL在缓冲区中会标记哪些块可能还需要重新规划,比如新块追加进来后,只有从某个块开始往后的部分才需要重新修正。反向扫描一旦提前终止,正向扫描也常常能在这个基础上快速收敛。因为问题本身是两两相邻块之间的约束传播,数学上是凸函数约束,整个算法迭代很少,实际运行中几乎感受不到规划延迟。
4.4 梯形剖面生成:两个方向算完以后,最后一步才是“形状”
反向和正向跑完,缓冲区里每个块都确定了三个值:入口速度、出口速度、名义速度。但这还不等于最终速度轮廓,因为你还得知道:这个块是先加速、再匀速、再减速,还是根本没时间匀速,直接“加速到中间立刻减速”,形状像三角形?
GRBL里calculate_trapezoid_for_block这类函数就干这个。它判断逻辑其实很直观:
- 如果从入口速度加速到名义速度需要的距离,加上从名义速度减速到出口速度需要的距离,加起来还小于等于块总长度,那就说明能完成完整梯形:加速段、匀速段、减速段都有。
- 如果加速距离加上减速距离超过块总长度,说明没有时间跑到名义速度,只能做一个三角形剖面,在块的一半位置达到某个峰值速度,然后马上开始减速。
- 如果入口速度和出口速度都等于名义速度,且路径足够长,那整个块就是匀速段,没有加速也没有减速。
这部分的计算会生成一个decelerate_after字段,记录从块的起点开始,执行多少个步进事件后进入减速段。执行层拿到这个字段,就知道前多少步按加速斜率加,后面多少步按减速斜率减。你看到的那条梯形速度图,其实是在这一步才真正定型的。
5. 从G代码行到最终速度剖面,完整看一遍联动
5.1 一个三块连续运动的数值例子
把前面这些概念串到一起,我用一个简化版例子演算一下。设三段连续直线,方向都一样,方便忽略尖角限速:
- 块A:长度5mm,名义速度45mm/s
- 块B:长度3mm,名义速度30mm/s
- 块C:长度2mm,名义速度20mm/s,最后要停到0
统一加速度a=100mm/s²。这里不严格换算成步进事件,只展示算法思路。
步骤一:从块C反向推。块C末尾速度0,长度2mm,则块C最大入口速度是sqrt(0² + 2×100×2) = 20mm/s。块C名义速度正好20mm/s,所以入口取20mm/s。
步骤二:反向看块B。块B出口速度为块C入口20mm/s,块B长度3mm,则块B最大入口速度是sqrt(20² + 2×100×3) = sqrt(400+600) = sqrt(1000) ≈ 31.6mm/s。但块B名义速度只有30mm/s,所以入口只能取30mm/s。
步骤三:反向看块A。块A出口速度为块B入口30mm/s,块A长度5mm,则块A最大入口速度是sqrt(30² + 2×100×5) = sqrt(900+1000) ≈ 43.6mm/s。块A名义速度45mm/s,所以反向阶段块A入口被定在43.6mm/s。如果块A前面还有块,它会继续向前传。
步骤四:正向扫描。假设块A之前的速度是0,那块A入口就是0。块A在5mm内能否加到30mm/s?可以。块B入口30mm/s,名义速度30mm/s,这说明块B整段都以速度30mm/s跑,不加速也不减速。块C入口20mm/s,出口0,正好走一个减速三角形。
这一步相当于得到了最终规划:块A从0加速到43.6,但到块B前已经减到30;块B稳速30;块C从30减速到0的前提下,入口被反向规划压到了20,所以在块B和块C之间实际是先减速再进块C,最后块C末端到0。
你可以看到反向规划和正向规划是怎么配合的。没有反向规划,块A可能一直到高速都很乐观;没有正向规划,块A入口可能还被上一轮的保守值压着,速度起不来。
5.2 尖角、圆弧路径和进给率覆盖之间的三角关系
实际加工中很少是一长串完全同方向的直线,更多是复杂轮廓和拐角。尖角限速对速度前瞻影响非常大。GRBL在plan_buffer_line时就会计算相邻块之间的方向变化,方向变化越大,允许的连接速度越低。这个连接速度参与反向规划的约束,于是尖角前很长的路径都会被“通知”到:要提前减速了,不然到拐角刹不住。
圆弧路径也一样。GRBL本身不支持真正的圆弧前瞻插补到极小误差级别,但G02/G03会被解析成很多段微小直线。如果这些微小直线长度非常短,比如0.1mm,而转角又不大,速度前瞻就可以在它们之间用很高的速度平滑通过,这就是为什么圆弧加工能比传统启停式的控制器快很多。如果转角大,速度就会被压下来,圆弧半径越大越接近直线,所以大圆弧能跑得更快,小圆弧只能慢速过弯。
还有个特殊场景是进给率覆盖。GRBL运行中可以通过串口命令直接改变进给率,这意味着实时运行时,当前块的名义速度变了,规划器需要尽快修正后续块的速度参数。你不能直接改当前正在执行的块,那样会破坏已经算好的梯形剖面,所以只能从当前块之后重新触发反向和正向规划。这也是GRBL对实时性要求很高的一个重要原因:规划不仅要预想,还要响应外部指令。
5.3 源码阅读顺序建议:不要从头到尾读planner.c
如果你已经在啃GRBL源码,我建议你按下面的顺序去看,不要一上来就通读整个planner.c,很容易被一堆#ifdef和单位换算绕晕。
先看planner.h里的结构体定义和宏,搞清楚block_t、plan_buffer_line、PLAN_BUFFER_SIZE这些是什么。然后在planner.c里找到plan_buffer_line的入口,看它怎么把一条直线拆成steps并追加进缓冲区。接着重点看planner_recalculate函数,这是整个速度前瞻的总调度,会发现它内部有两个方向循环,一个从尾往头,一个从头往尾。看懂这两个循环里的核心判断,再去看calculate_trapezoid_for_block,这时候你对“反向、正向、梯形剖面”三个阶段就会有完整认识。最后才去参考stepper.c,看看中断是怎么消费这些规划数据的。
如果只是学算法思想,其实不用把GRBL每一步单位换算都吃透。你只要抓住每个块轴向步数、三步加速度、入口出口速度这些概念,把公式推导清楚,然后用Python写个模拟器跑跑,同样能得到和GRBL差不多形状的速度剖面图。很多时候,自己写一遍代码理解比读十遍源码更有效。
6. 前瞻相关的常见问题、调参心得与避坑经验
6.1 现象:速度上不去、拐角异响、过冲失步,先从规划找原因
我接触过不少从GRBL入门做雕刻机和激光雕刻机的朋友,遇到问题第一反应就是调加速度参数或者换电机驱动,其实很多问题出在速度前瞻和数学约束上。你对照这几个典型现象:
| 现象 | 常见原因 | 优先排查方向 |
|---|---|---|
| 速度一直上不去,即使设了很高F值 | 最大进给率、最大加速度设置,或路径太短导致反向减速占满整段 | 查轴最大速度、轴最大加速度设置;看是否小线段太多,减小线段切分误差 |
| 走到尖角处有刺耳异响 | 尖角限速后,速度在几十毫秒内暴降,机械冲击放大 | 尽量让CAM处理成圆弧过渡,或在GRBL里调大允许的拐角余弦阈值 |
| 过冲、丢步、结尾停不准 | 加速度设置过大,反向规划算出的减速距离超出缓冲区覆盖范围 | 拉低加速度,或增大前瞻缓冲区,检查是否缓冲区被读满导致规划提前截断 |
| 缓冲区不连续性停顿 | 串口发送速度跟不上,规划的块消耗完,机器人进入实时急停 | 检查串口波特率、流控和上位机发送节奏,或增加每块距离 |
这里有个很基本的经验:加速度设置不是越大越好。GRBL的反向规划公式里面,加速度越大,意味着单位距离允许的入口速度越高,好处是整体吞吐上去了,代价是减速需求也越高,一旦路径中某个块距离不够,速度就会快速被压下来,机械上就是一次猛烈冲击。实际加工时,加速度参数通常需要在“能上去”和“不冲击”之间反复试。
6.2 GRBL常见参数速查,和它们怎么影响前瞻
GRBL 1.1里和速度规划直接相关的参数,比较重要的是这些:
$110、$111、$112:X、Y、Z轴最大速率。单位是mm/min,这个值限定了名义速度的上限。如果你把F值设得比它高,规划器也会被截断到这个速度。$120、$121、$122:X、Y、Z轴最大加速度。单位是mm/min²,注意不是每秒,而是每分钟。换算时很多人踩坑,实际换算成mm/s²时要除以3600。$1:步进空闲延迟,影响电机停止后的等待时间,和前瞻没有直接关系,但会影响频繁启停的节奏。$13:步进脉冲宽度相关设置,影响高速时脉冲是否稳定,省略号略,实际要看你的GRBL版本。$30、$31:主轴最低和最高转速,主轴启动停机会占用运动规划时序,会影响连续运动的节奏。
调参之前先确认你的单位。很多人设$120时把电脑上写惯的mm/s²直接填进去,结果慢了60倍,速度当然上不去。GRBL的加速度单位是mm/min²,比如你想设1000mm/s²,需要换算成1000×3600=3600000,填进$120。这个细节在前瞻计算里尤其致命,因为反向规划的减速距离公式对加速度值高度敏感,填错一两个数量级,规划出来的速度包络完全不同。
6.3 我建议的调参顺序和踩坑记录
如果你拿到一台新的GRBL设备,想让它跑得又快又稳,我建议的调试顺序是:先固定一个较低速度,比如200mm/min,确认机械和驱动都没有问题;然后把轴最大加速度调高到你能接受的范围,再用一段很长的直线测试最高速度能不能到设定值;之后再测试带尖角的方形路径,注意听拐角是否异响,异响就适当降低加速度;最后再用真正的加工G代码跑,看缓冲区有没有断流停顿。
我自己在调试一台雕刻机时就遇到过一个问题:在PC端打开图形预览时速度飞快,一实跑就一顿一顿的。后来发现是串口波特率虽然设了115200,但上位机发送的G代码每行之间带了太多注释和空行,导致缓冲区的块到不了满负荷状态,前瞻窗口经常只能看到一个块,结果每个块都被反向规划压成独立启停。解决方法是开启串口流控,上位机及时填充缓冲区,并且把注释行去掉。这个问题和算法无关,但实际使用中远比想象中常见。
还有一次是使用激光雕刻时出现每段圆弧内部有很细的停顿线。逐层排查后发现是GRBL的尖角限速太严格,小线段构成的圆弧被判定成有很多小尖角,速度不断升降。后来我把路径输出精度调低一点,让CAM生成的线段更长、方向变化更平滑,问题就消失了。所以真要追求高速,源头上的路径优化往往比固件调参更有效。
6.4 源码学习时的几个实战细节
最后说几个源码上容易踩的细节,看分支版本不同时特别容易困惑。
第一,GRBL有很多第三方分支,比如加上了S形加减速、加入了更多前瞻缓冲区、甚至改为多轴联动。不同的分支规划器实现可能完全不同,一些分支不再用“梯形剖面”而改用“S形剖面”,反向正向规划的实现也会变复杂。你读代码前先确认你手里是原版grbl 0.9/1.1还是某个二开版本。同一个函数名,在不同版本里的行为都可能不一样。
第二,原版GRBL在规划器里大量使用整数和定点数思维,很多速度变量存放的单位是“每分钟步数”而不是“每秒钟毫米”。你在推导公式时,先用数学模型理清方向,再回代码里做单位变换,不然会被float和整型混用搞得头大。
第三,真正的执行层不一定按“一个块”为最小单位发脉冲。GRBL把每个块又切成了多个“步进段”(step segment),这是因为要支持实时进给率覆盖和加速度变化。你在stepper.c里会看到,它把梯形剖面切成一串小段,每段有自己恒定的速度和步数。这个概念和速度前瞻并不是一回事,但如果你只盯着planner.c,不配合stepper.c看,会以为整条梯形剖面是一次性按块执行的,那样理解就偏差了。
老实说,我更愿意把GRBL这套反向规划和正向规划当成一个压缩过的运动控制教学案例。它没有复杂到让人不敢碰,却真实包含了多约束、双向传播、前瞻窗口和实时执行,这些概念放在任何一台工业数控系统里也一样成立。你如果能把GRBL的这两次扫描彻底读懂,再回头看你自己的运动控制项目,很多“为什么这里慢”“为什么那里刹不住”的问题,答案其实早就在速度包络里了。