前言
在数控系统和运动控制领域,脉冲信号的精确生成是实现高精度位置控制的核心。GRBL作为一款高效、开源的嵌入式运动控制固件,其底层脉冲生成机制直接决定了步进电机的运动平滑性、响应速度及整体性能。而这一机制的核心,正是经典的Bresenham算法——一种最初为计算机图形学设计的直线插补算法,被巧妙移植到运动控制中,以整数运算高效分配脉冲序列,实现多轴联动的协调运动。
本文聚焦GRBL1.1的脉冲生成逻辑,通过深入解析Bresenham算法的原理、优化实现及其在运动控制中的具体应用,帮助读者理解。
一.Bresenham算法
1 . 斜率0 ≤ m ≤ 1 (x1 < x2)
在绘制(x,y) 时,我们将错误 ϵ \epsilon ϵ与每个 y y y 坐标关联, y y y 的实值应为 y + ϵ y + \epsilon y+ϵ,ε 误差范围为:
− 0.5 < ϵ < 0.5 -0.5 <\epsilon< 0.5 −0.5<ϵ<0.5
从 x x x 移动到 x + 1 x+1 x+1 时,其横坐标是 x + 1 x + 1 x+1,它对应的纵坐标是 y + m + ϵ y + m + \epsilon y+m+ϵ,判断:
( y + ϵ + m ) − y < 0.5 \begin{align*} (y + \epsilon + m) - y < 0.5 \\ \end{align*} (y+ϵ+m)−y<0.5
如果此新值与 y y y 之间的差值小于 0.5, 则 绘制 ( x , y + 1 ) (x , y + 1) (x,y+1),则新的 ϵ \epsilon ϵ 值:
ϵ new = ( y + ϵ + m ) − y = ϵ + m \begin{align*} \epsilon_{\text{new}} = (y + \epsilon + m) - y = \epsilon + m \\ \end{align*} ϵnew=(y+ϵ+m)−y=ϵ+m
如果此新值与 y y y之间的差值大于等于 0.5, 则绘制 ( x + 1 , y + 1 ) (x + 1, y + 1) (x+1,y+1),则新的 ϵ \epsilon ϵ 值:
ϵ new = ( y + ϵ + m ) − ( y + 1 ) = ϵ + m − 1 \begin{align*} \epsilon_{\text{new}} = (y + \epsilon + m) - (y + 1)= \epsilon + m - 1 \\ \end{align*} ϵnew=(y+ϵ+m)−(y+1)=ϵ+m−1
2 . 斜率 1 < m < ∞(y1 < y2)
在绘制(x,y) 时,我们将错误 ϵ \epsilon ϵ与每个 x x x 坐标关联, x x x的实值应为 x + ϵ x + \epsilon x+ϵ,ε 误差范围为:
− 0.5 < ϵ < 0.5 -0.5 <\epsilon< 0.5 −0.5<ϵ<0.5
从 y y y 移动到 y + 1 y+1 y+1 时,其横坐标是 x + m + ϵ x + m + \epsilon x+m+ϵ,它对应的纵坐标是 y + 1 y + 1 y+1,判断:
( x + ϵ + m ) − x < 0.5 \begin{align*} (x + \epsilon + m) - x < 0.5 \\ \end{align*} (x+ϵ+m)−x<0.5
如果此新值与 x x x 之间的差值小于 0.5, 则 绘制 ( x , y + 1 ) (x , y + 1) (x,y+1),则新的 ϵ \epsilon ϵ 值:
ϵ new = ( x + ϵ + m ) − x = ϵ + m \begin{align*} \epsilon_{\text{new}} = (x + \epsilon + m) - x = \epsilon + m \\ \end{align*} ϵnew=(x+ϵ+m)−x=ϵ+m
如果此新值与 x x x之间的差值大于等于 0.5, 则绘制 ( x + 1 , y + 1 ) (x + 1, y + 1) (x+1,y+1),则新的 ϵ \epsilon ϵ 值:
ϵ new = ( x + ϵ + m ) − ( x + 1 ) = ϵ + m − 1 \begin{align*} \epsilon_{\text{new}} = (x + \epsilon + m) - (x + 1)= \epsilon + m - 1 \\ \end{align*} ϵnew=(x+ϵ+m)−(x+1)=ϵ+m−1
3 . 斜率 -∞ < m < -1(y1 < y2)
在绘制(x,y) 时,我们将错误 ϵ \epsilon ϵ与每个 x x x 坐标关联, x x x的实值应为 x + ϵ x + \epsilon x+ϵ,ε 误差范围为:
− 0.5 < ϵ < 0.5 -0.5 <\epsilon< 0.5 −0.5<ϵ<0.5
从 y y y 移动到 y + 1 y+1 y+1 时,其横坐标是 x + m + ϵ x + m + \epsilon x+m+ϵ,它对应的纵坐标是 y + 1 y + 1 y+1,判断:
( x + ϵ + m ) − x < 0.5 \begin{align*} (x + \epsilon + m) - x < 0.5 \\ \end{align*} (x+ϵ+m)−x<0.5
如果此新值与 x x x 之间的差值小于 0.5, 则 绘制 ( x , y + 1 ) (x , y + 1) (x,y+1),则新的 ϵ \epsilon ϵ 值:
ϵ new = ( x + ϵ + m ) − x = ϵ + m \begin{align*} \epsilon_{\text{new}} = (x + \epsilon + m) - x = \epsilon + m \\ \end{align*} ϵnew=(x+ϵ+m)−x=ϵ+m
如果此新值与 x x x之间的差值大于等于 0.5, 则绘制 ( x − 1 , y + 1 ) (x - 1, y + 1) (x−1,y+1),则新的 ϵ \epsilon ϵ 值:
ϵ new = ( x + ϵ + m ) − ( x − 1 ) = ϵ + m + 1 \begin{align*} \epsilon_{\text{new}} = (x + \epsilon + m) - (x - 1)= \epsilon + m + 1 \\ \end{align*} ϵnew=(x+ϵ+m)−(x−1)=ϵ+m+1
4 . 斜率 -1 ≤ m ≤ 0(x2 < x1)
在绘制(x,y) 时,我们将错误 ϵ \epsilon ϵ与每个 y y y 坐标关联, y y y 的实值应为 y + ϵ y + \epsilon y+ϵ,ε 误差范围为:
− 0.5 < ϵ < 0.5 -0.5 <\epsilon< 0.5 −0.5<ϵ<0.5
从 x x x 移动到 x − 1 x-1 x−1 时,其横坐标是 x − 1 x - 1 x−1,它对应的纵坐标是 y + m + ϵ y + m + \epsilon y+m+ϵ,判断:
y − ( y + ϵ + m ) < 0.5 \begin{align*} y - (y + \epsilon + m) < 0.5 \\ \end{align*} y−(y+ϵ+m)<0.5
如果此新值与 y y y 之间的差值小于 0.5, 则 绘制 ( x − 1 , y ) (x - 1, y ) (x−1,y),则新的 ϵ \epsilon ϵ 值:
ϵ new = ( y + ϵ + m ) − y = ϵ + m \begin{align*} \epsilon_{\text{new}} = (y + \epsilon + m) - y = \epsilon + m \\ \end{align*} ϵnew=(y+ϵ+m)−y=ϵ+m
如果此新值与 y y y之间的差值大于等于 0.5, 则绘制 ( x − 1 , y + 1 ) (x - 1, y + 1) (x−1,y+1),则新的 ϵ \epsilon ϵ 值:
ϵ new = ( y + ϵ + m ) − ( y + 1 ) = ϵ + m − 1 \begin{align*} \epsilon_{\text{new}} = (y + \epsilon + m) - (y + 1)= \epsilon + m - 1 \\ \end{align*} ϵnew=(y+ϵ+m)−(y+1)=ϵ+m−1
5 . 斜率 0 ≤ m ≤ 1(x2 < x1)
在绘制(x,y) 时,我们将错误 ϵ \epsilon ϵ与每个 y y y 坐标关联, y y y 的实值应为 y + ϵ y + \epsilon y+ϵ,ε 误差范围为:
− 0.5 < ϵ < 0.5 -0.5 <\epsilon< 0.5 −0.5<ϵ<0.5
从 x x x 移动到 x − 1 x-1 x−1 时,其横坐标是 x − 1 x - 1 x−1,它对应的纵坐标是 y + m + ϵ y + m + \epsilon y+m+ϵ,判断:
( y + ϵ + m ) − y < 0.5 \begin{align*} (y + \epsilon + m) - y < 0.5 \\ \end{align*} (y+ϵ+m)−y<0.5
如果此新值与 y y y 之间的差值小于 0.5, 则 绘制 ( x − 1 , y ) (x - 1, y ) (x−1,y),则新的 ϵ \epsilon ϵ 值:
ϵ new = ( y + ϵ + m ) − y = ϵ + m \begin{align*} \epsilon_{\text{new}} = (y + \epsilon + m) - y = \epsilon + m \\ \end{align*} ϵnew=(y+ϵ+m)−y=ϵ+m
如果此新值与 y y y之间的差值大于等于 0.5, 则绘制 ( x − 1 , y − 1 ) (x - 1, y - 1) (x−1,y−1),则新的 ϵ \epsilon ϵ 值:
ϵ new = ( y + ϵ + m ) − ( y − 1 ) = ϵ + m + 1 \begin{align*} \epsilon_{\text{new}} = (y + \epsilon + m) - (y - 1)= \epsilon + m + 1 \\ \end{align*} ϵnew=(y+ϵ+m)−(y−1)=ϵ+m+1
6 . 斜率 1 < m < ∞(y2 < y1)
在绘制(x,y) 时,我们将错误 ϵ \epsilon ϵ与每个 x x x 坐标关联, x x x的实值应为 x + ϵ x + \epsilon x+ϵ,ε 误差范围为:
− 0.5 < ϵ < 0.5 -0.5 <\epsilon< 0.5 −0.5<ϵ<0.5
从 y y y 移动到 y − 1 y-1 y−1 时,其横坐标是 x + m + ϵ x + m + \epsilon x+m+ϵ,它对应的纵坐标是 y − 1 y - 1 y−1,判断:
( x + ϵ + m ) − x < 0.5 \begin{align*} (x + \epsilon + m) - x < 0.5 \\ \end{align*} (x+ϵ+m)−x<0.5
如果此新值与 x x x 之间的差值小于 0.5, 则 绘制 ( x , y − 1 ) (x , y - 1) (x,y−1),则新的 ϵ \epsilon ϵ 值:
ϵ new = ( x + ϵ + m ) − x = ϵ + m \begin{align*} \epsilon_{\text{new}} = (x + \epsilon + m) - x = \epsilon + m \\ \end{align*} ϵnew=(x+ϵ+m)−x=ϵ+m
如果此新值与 x x x之间的差值大于等于 0.5, 则绘制 ( x − 1 , y − 1 ) (x - 1, y - 1) (x−1,y−1),则新的 ϵ \epsilon ϵ 值:
ϵ new = ( x + ϵ + m ) − ( x − 1 ) = ϵ + m + 1 \begin{align*} \epsilon_{\text{new}} = (x + \epsilon + m) - (x - 1)= \epsilon + m + 1 \\ \end{align*} ϵnew=(x+ϵ+m)−(x−1)=ϵ+m+1
7 . 斜率 -∞ < m < -1(y2 < y1)
在绘制(x,y) 时,我们将错误 ϵ \epsilon ϵ与每个 x x x 坐标关联, x x x的实值应为 x + ϵ x + \epsilon x+ϵ,ε 误差范围为:
− 0.5 < ϵ < 0.5 -0.5 <\epsilon< 0.5 −0.5<ϵ<0.5
从 y y y 移动到 y − 1 y-1 y−1 时,其横坐标是 x + m + ϵ x + m + \epsilon x+m+ϵ,它对应的纵坐标是 y − 1 y - 1 y−1,判断:
( x + ϵ + m ) − x < 0.5 \begin{align*} (x + \epsilon + m) - x < 0.5 \\ \end{align*} (x+ϵ+m)−x<0.5
如果此新值与 x x x 之间的差值小于 0.5, 则 绘制 ( x , y − 1 ) (x , y - 1) (x,y−1),则新的 ϵ \epsilon ϵ 值:
ϵ new = ( x + ϵ + m ) − x = ϵ + m \begin{align*} \epsilon_{\text{new}} = (x + \epsilon + m) - x = \epsilon + m \\ \end{align*} ϵnew=(x+ϵ+m)−x=ϵ+m
如果此新值与 x x x之间的差值大于等于 0.5, 则绘制 ( x + 1 , y − 1 ) (x + 1, y - 1) (x+1,y−1),则新的 ϵ \epsilon ϵ 值:
ϵ new = ( x + ϵ + m ) − ( x + 1 ) = ϵ + m − 1 \begin{align*} \epsilon_{\text{new}} = (x + \epsilon + m) - (x + 1)= \epsilon + m - 1 \\ \end{align*} ϵnew=(x+ϵ+m)−(x+1)=ϵ+m−1
8 . 斜率 -1 ≤ m ≤ 0(x1 < x2)
在绘制(x,y) 时,我们将错误 ϵ \epsilon ϵ与每个 y y y 坐标关联, y y y 的实值应为 y + ϵ y + \epsilon y+ϵ,ε 误差范围为:
− 0.5 < ϵ < 0.5 -0.5 <\epsilon< 0.5 −0.5<ϵ<0.5
从 x x x 移动到 x + 1 x+1 x+1 时,其横坐标是 x + 1 x + 1 x+1,它对应的纵坐标是 y + m + ϵ y + m + \epsilon y+m+ϵ,判断:
y − ( y + ϵ + m ) < 0.5 \begin{align*} y - (y + \epsilon + m) < 0.5 \\ \end{align*} y−(y+ϵ+m)<0.5
如果此新值与 y y y 之间的差值小于 0.5, 则 绘制 ( x + 1 , y ) (x + 1, y ) (x+1,y),则新的 ϵ \epsilon ϵ 值:
ϵ new = ( y + ϵ + m ) − y = ϵ + m \begin{align*} \epsilon_{\text{new}} = (y + \epsilon + m) - y = \epsilon + m \\ \end{align*} ϵnew=(y+ϵ+m)−y=ϵ+m
如果此新值与 y y y之间的差值大于等于 0.5, 则绘制 ( x + 1 , y − 1 ) (x + 1, y - 1) (x+1,y−1),则新的 ϵ \epsilon ϵ 值:
ϵ new = ( y + ϵ + m ) − ( y − 1 ) = ϵ + m + 1 \begin{align*} \epsilon_{\text{new}} = (y + \epsilon + m) - (y - 1)= \epsilon + m + 1 \\ \end{align*} ϵnew=(y+ϵ+m)−(y−1)=ϵ+m+1
9 . 总结
综合所有图表:
斜率范围 | 递增轴 | 增量方向条件 | 误差更新公式 | 绘制点坐标调整 | 处理逻辑 |
---|---|---|---|---|---|
0 ≤ m ≤ 1 | x轴 | x1 < x2 | ε + m - 1 | (x+1, y+1) | 基础情况:沿x轴递增,根据误差决定是否增加y。 |
x2 < x1 | ε + m + 1 | (x-1, y-1) | 反转增量方向(x递减)。 | ||
1 < m < ∞ | y轴 | y1 < y2 | ε + m - 1 | (x+1, y+1) | 交换x和y轴,转化为0 ≤ m ≤ 1情况处理。 |
y2 < y1 | ε + m + 1 | (x-1, y-1) | 反转增量方向(y递减)。 | ||
-∞ < m < -1 | y轴 | y1 < y2 | ε + m - 1 | (x-1, y+1) | 交换x和y轴,斜率取倒数,结合负斜率调整方向(x递减)。 |
y2 < y1 | ε + m + 1 | (x+1, y-1) | 反转增量方向(y递减)。 | ||
-1 ≤ m ≤ 0 | x轴 | x1 < x2 | ε + m - 1 | (x-1, y+1) | 负斜率基础情况:沿x轴递增,y递减,根据误差调整。 |
x2 < x1 | ε + m + 1 | (x+1, y-1) | 反转增量方向(x递减)。 |
Bresenham直线算法斜率处理总结:
-
反转坐标轴:当斜率绝对值大于1时,交换x和y轴的角色,将问题转化为斜率在0到1之间的基础情况。
-
统一增量方向:通过比较起点和终点的坐标大小,确保增量方向一致(始终递增或递减),避免重复逻辑。
-
误差累积判断:通过误差项ε决定是否调整y坐标,保持整数运算效率。
二.GRBL脉冲生成
1 . 步数计算(planner.c)
遍历每一个轴:
for (idx=0; idx<N_AXIS; idx++)
坐标转换为步数:
target_steps[idx] = lround(target[idx]*settings.steps_per_mm[idx]);
计算当前坐标和目标坐标的步数差:
block->steps[idx] = labs(target_steps[idx]-pl.position[idx]);
统计步进次数的最大值(step_event_count):
block->step_event_count = max(block->step_event_count, block->steps[idx]);
计算每轴单位向量分量(方向 + 加速度规划用):
delta_mm = (target_steps[idx] - pl.position[idx])/settings.steps_per_mm[idx];
unit_vec[idx] = delta_mm;
block->millimeters += delta_mm * delta_mm; // 后面会开平方
设置方向位 :
if (delta_mm < 0 ) {
block->direction_bits |= get_direction_pin_mask(idx);
}
2 . 可选优化:AMASS优化机制(stepper.c)
// 预处理阶段数据放大
st_prep_block->steps[X_AXIS] = pl_block->steps[X_AXIS] << MAX_AMASS_LEVEL;
// 中断内处理
st.counter_x += st.steps[X_AXIS]; // st.steps已预乘放大系数
精度提升原理:
参数 | 无AMASS | AMASS Level=2(×4) |
---|---|---|
1.5步/中断 | 舍入为1 | 1.5×4=6 → 右移2位=1.5 |
效果:
避免低速时因整数截断丢失微步,最大支持2^MAX_AMASS_LEVEL倍细分
3 . Bresenham 算法脉冲生成机制(stepper.c)
初始化(ISR):
// 计数器初始化为step_event_count/2
st.counter_x = st.exec_block->step_event_count >> 1;
等价于误差项初始值e = step_event_count - 0.5 * step_event_count,右移实现快速整数除法,因为GRBL里面的counter是无符号数值,所以在实际代码中是做了1/2 event_step的初值偏移,为了不出现负值运算。
实时中断处理(ISR):
#ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING
st.counter_x += st.steps[X_AXIS];
#else
st.counter_x += st.exec_block->steps[X_AXIS];
#endif
if (st.counter_x > st.exec_block->step_event_count) {
st.step_outbits |= (1<<X_STEP_BIT); // 设置X轴的step引脚输出高电平
st.counter_x -= st.exec_block->step_event_count;
if (st.exec_block->direction_bits & (1<<X_DIRECTION_BIT)) { sys.position[X_AXIS]--; }// 方向为负,位置减
else { sys.position[X_AXIS]++; }// 方向为正,位置加
}
#ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING
st.counter_y += st.steps[Y_AXIS];
#else
st.counter_y += st.exec_block->steps[Y_AXIS];
#endif
if (st.counter_y > st.exec_block->step_event_count) {
st.step_outbits |= (1<<Y_STEP_BIT); // 设置Y轴的step引脚输出高电平
st.counter_y -= st.exec_block->step_event_count;
if (st.exec_block->direction_bits & (1<<Y_DIRECTION_BIT)) { sys.position[Y_AXIS]--; }// 方向为负,位置减
else { sys.position[Y_AXIS]++; }// 方向为正,位置加
}
#ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING
st.counter_z += st.steps[Z_AXIS];
#else
st.counter_z += st.exec_block->steps[Z_AXIS];
#endif
if (st.counter_z > st.exec_block->step_event_count) {
st.step_outbits |= (1<<Z_STEP_BIT); // 设置Z轴的step引脚输出高电平
st.counter_z -= st.exec_block->step_event_count;
if (st.exec_block->direction_bits & (1<<Z_DIRECTION_BIT)) { sys.position[Z_AXIS]--; }// 方向为负,位置减
else { sys.position[Z_AXIS]++; }// 方向为正,位置加
}
累加本轴的步数值,如果总计数器超过了总事件数,就意味着“该轴应该发一个脉冲”,发脉冲(设置 step bit),更新位置。
4 . 总结
GRBL脉冲生成流程图:
Bresenham算法就是一种改进的DDA算法,最大的特点就是计算过程不带浮点数,全用整型数。在Grbl的Bresenham直线插补算法实现中,由于步进电机的位移方向已通过独立的方向信号(DIR引脚)进行控制,因此算法只需处理各运动轴的绝对值位移量即可。(取绝对值位移量,解决斜率为负问题;取 step_event_count主导轴处理,解决斜率大于1问题;)
脉冲发生的处理在stepper.c文件里面,GRBL采用了DDA的插补方法,采用了“Bresenham”直线算法做多轴联动计算,该算法的具体实现在stepper.c文件的ISR函数里面,即定时器中断,其中一些参数如插补的总步数、每个轴输出脉冲的分配步数,是在st_prep_buffer()函数里面进行预处理的,预处理数据涉及到plan_buffer_line()函数里面的插补运动段pl_block的总steps和st_pre_buffer()函数里面的微小运动段segment_buffer的各轴steps来整合计算的,最后在ISR里面直接进行条件判断和步数累计。
三.GRBL多轴联动时序分析
1 .实例验证
案例:X轴需 6 步,Y轴需 3 步,Z 轴需 2 步;
结果:
X轴:6个等间隔脉冲(主导轴)
Y轴:第 2,4,6中断触发(3 次)
Z轴:第2,5中断触发(2次)
2 .与经典Bresenham的差异
特性 | 经典Bresenham | GRBL实现 |
---|---|---|
初始化值 | 误差项=0 | 误差项=step_event_count/2 |
主导轴处理 | 无显式主导轴 | 显式step_event_count控制 |
步进触发条件 | ≥step_event_count | >step_event_count |
硬件优化 | 无 | AMASS位操作优化 |
四.GRBL 定时器
定时器流程图:
Timer0:
在系统设计中,溢出中断用于实现脉冲输出管脚的复位电平控制。具体而言,在启动定时器(st_wake_up()函数)时,Timer0的计数器会被初始化为一个预设值,该值作为用户可配置参数存储在EEPROM中。这一预设值决定了脉冲输出的有效保持时间,即从T1输出有效脉冲电平到Timer0计数溢出之间的时间间隔。当Timer0发生溢出中断时,系统会在中断服务例程中将所有轴的脉冲管脚电平复位,从而形成脉冲信号的上升沿或下降沿。
匹配中断则用于实现脉冲延时功能(参考STEP_PULSE_DELAY宏定义)。该机制通过在Timer1中断中记录脉冲输出状态,待Timer0的匹配值到达时才实际输出脉冲信号,以此实现脉冲延时的效果。值得注意的是,管脚复位操作仍通过Timer0的溢出中断执行,这意味着脉冲延时和溢出复位两种机制在系统中是协同工作的。
Timer1:
在脉冲生成机制中,Timer1 中断作为核心的 I/O 翻转计算和脉冲输出触发点,负责执行 DDA(数字微分分析)算法,动态计算每个定时中断周期内各轴是否需要输出脉冲。当某轴满足脉冲输出条件时,系统会记录其目标管脚状态,并在下一个中断周期立即执行实际的 I/O 电平翻转。随后,系统会清除该轴的脉冲状态记录,并重新进行 DDA 计算,形成闭环控制流程。具体而言,Timer1 中断根据算法条件输出脉冲的有效电平(仅触发符合条件的轴),随后使能 Timer0 的溢出中断,最终由 Timer0 溢出中断负责复位脉冲电平,从而确保精确的脉冲宽度和时序控制。
参考文章
参考CSDN文章:DIY运动控制器——移植grbl(软件架构、脉冲产生)
参考文章:Bresenham 画线算法