1、Bresenham直线插补(实现多轴同步)
1.1算法原理
Bresenham 是一个快速画直线的算法。在现实中,我们可以根据直线方程画出任意直线,但在计算机中却稍有不同。由于计算机屏幕是由很多栅格(像素点)组成,所以计算机中的直线实际上是通过一系列离散化的像素点来近似表现这条直线。如下a,b两图所示:
根据这样的原理,假设线段端点为,为偏移量,直线方程为。则有:
假设已经确定了直线上某个点正好在像素 $P_i(x_i,y_i)$,我们要确定下一个像素$P_{i+1}$是$(x_i+1,y_i)$还是$(x_i+1,y_i+1)$。此时的情况如下:
由上图可知,在处,直线上y的值,该点距离和点的距离分别为和,我们选取距离直线上理论点更近的像素点作为下一步要到达的地方,则有:
根据上述两个式子,两距离的差值为:
根据两点之间距离差值的大小判断,即?,可以作为计算机判断的决策,则有:
将i+1代入式子(15)中,其中$x_{i+1} = x_i + 1$得:
由,代入式子(17),化简得:
由于,对的大小判断无任何影响,所以,可以对式子(18)两边同时乘进行进一步化简, 令,得:
令直线上初始端点为像素点坐标,则将代入式子(17)中的可得到判断条件的初始值为:
由判断条件(16),递推公式(19),初始值(20)可得:
对平面坐标系所以象限进行bresenham的推导,结果如下所示:
1. x1 > x0, y1 > y0, dy <= dx : x = x0, y = y0, p = 2dy − dx
若 p < 0 : p = p + 2dy, y = y, x = x + 1
若 p > 0 : p = p + 2dy − 2dx, y = y + 1, x = x + 1
2. x1 > x0, y1 > y0, dy > dx : x = x0, y = y0, p = 2dx − dy
若 p < 0 : p = p + 2dx, x = x, y = y + 1
若 p > 0 : p = p + 2dx − 2dy, x = x + 1, y = y + 1
3. x1 > x0, y1 < y0, dy <= dx : x = x0, y = −y0, p = 2dy − dx
若 p < 0 : p = p + 2dy, y = y, x = x + 1
若 p > 0 : p = p + 2dy − 2dx, y = y + 1, x = x + 1
4. x1 > x0, y1 < y0, dy <= dx : x = x0, y = −y0, p = 2dx − dy
若 p < 0 : p = p + 2dx, x = x, y = y + 1
若 p > 0 : p = p + 2dx − 2dy, x = x + 1
5. x1 < x0, y1 > y0, dy <= dx : x = −x0, y = y0, p = 2dy − dx
若 p < 0 : p = p + 2dy, y = y, x = x + 1
若 p > 0 : p = p + 2dy − 2dx, y = y + 1, x = x + 1
6. x1 < x0, y1 > y0, dy > dx : x = −x0, y = y0, p = 2dx − dy
若 p < 0 : p = p + 2dx, x = x, y = y + 1
若 p > 0 : p = p + 2dx − 2dy, x = x + 1, y = y + 1
7. x1 < x0, y1 < y0, dy <= dx : x = −x0, y = −y0, p = 2dy − dx
若 p < 0 : p = p + 2dy, y = y, x = x + 1
若 p > 0 : p = p + 2dy − 2dx, y = y + 1, x = x + 1
8. x1 < x0, y1 < y0, dy > dx : x = −x0, y = −y0, p = 2dx − dy
若 p < 0 : p = p + 2dx, x = x, y = y + 1
若 p > 0 : p = p + 2dx − 2dy, x = x + 1, y = y + 1
1.2应用分析
Bresenham原理上是一个快速画直线的算法,Marlin根据其原理,进行了部分转化,从而实现多轴同步,实现打印机的打印操作。根据对Marlin中bresenham部分代码的分析,其中作了两点比较重要的转化。
1. 判断条件改为 P1 = Y − X/2 因为计算机中浮点数计算会占用比较多的资源, 所以 Marlin
中针对计算的优化采用了整数相加和移位的方法,实现快速计算。
2. 将长轴的判断整合在一起,当某个轴为长轴是,其轴的步进条件恒为真
1.3算法分析
//Marlin 1.x版本Bresenham算法 代码简化
/*----------------init---------------------*/
int step_even_count; // 步数计数
int step_x,step_y,step_z; //各轴输出的步数 ΔY
int count_x,count_y,count_z;//各轴的Pi
int step_completed;//步数计数器
step_even_count = max(step_x,step_y);
step_even_count = max(step_z,step_even_count);//取各轴最大值作为X(ΔX)
count_x = -(step_even_count>>1);//-ΔX/2 ΔX>>1,后边所有数据都是除2处理
count_y = count_x;
count_z = count_x;
/*-------------bresenham-------------------*/
counter_X += current_block->steps_x;
if(counter_x > 0) //pi>0?
{
WRITE(X_STEP_PIN,!INVERT_X_STEP_PIN); //脉冲输出
counter_x -= current_block->step_event_count;
count_position[X_AXIS] += count_direction[X+AXIS]; //由于Pi + ΔY已经计算,此处直接减去ΔX即可
WRITE(X_STEP_PIN,INVERT_X_STEP_PIN);//脉冲停止
}
counter_y +=current_block->steps_y;
if(counter_y > 0)
{
WRITE(Y_STEP_PIN,!INVERT_Y_STEP_PIN);
counter_y -= current_block->step_event_count;
count_position[Y_AXIS] += count_direction[Y_AXIS];
WRITE(Y_STEP_PIN,INVERT_Y_STEP_PIN);
}
counter_z += current_block->steps_z;
if(counter_z > 0)
{
WRITE(Z_STEP_PIN,!INVERT_Z_STEP_PIN);
#ifdef Z_DUAL_STEPPER_DRIVERS
WRITE(Z2_STEP_PIN,!INVERT_Z_STEP_PIN)
#endif
counter_z -= current_block->step_event_count;
count_position[Z_AXIS] += count_direction[Z+AXIS]
WRITE(Z_STEP_PIN,INVERT_Z_STEP_PIN)
#ifdef Z_DUAL_STEPPER_DRIVERS
WRITE(Z2_STEP_PIN,INVERT_Z_STEP_PIN)
#endif
}
#ifdef ADVANCE
counter_e +=current_block->steps_e;
if(counter_e > 0)
{
WRITE(!INVERT_E_STEP_PIN);
counter_e -= current_block->step_event_count;
count_position[E_AXIS] += count_direction[E_AXIS];
WRITE(INVERT_E_STEP_PIN);
}
#endif
/*--------------------Marlin 2.x Bresenham--------------------*/
//(本质上没什么变化,只是优化了结构,改变变量,进行封装)
#define _APPLY_STEP(AXIS, INV, ALWAYS) AXIS ##_APPLY_STEP(INV, ALWAYS)
#define _INVERT_STEP_PIN(AXIS) INVERT_## AXIS ##_STEP_PIN
//分成3个宏
#define PULSE_PREP(AXIS) do{\
delta_error[AXIS] += advance_dividend[AXIS];\
steep_needed[AXIS] = (delta_error[AXIS]>=0);\
if( steep_needed[AXIS])\
delta_error[AXIS] -= advance_divisor\
}while(0)
#define PULSE_START(AXIS) do{ \
if (step_needed[AXIS]) { \
count_position[AXIS] += count_direction[AXIS]; \
_APPLY_STEP(AXIS, !_INVERT_STEP_PIN(AXIS), 0); \
} \
}while(0)
#define PULSE_STOP(AXIS) do { \
if (step_needed[_AXIS(AXIS)]) { \
_APPLY_STEP(AXIS, _INVERT_STEP_PIN(AXIS), 0); \
} \
}while(0)
1.4.小结
marlin使用的bresenham算法实现的直线插补,在实际执行上对于具体的运动拟合程度比较差。例如,在对于一个45度直线进行插补时,x和y两个分量实际的插补和使用bresenham算法在微观上是存在较大的差异。