一、前言
Marlin 在解析完 G-Code 命令之后,会得到各轴要移动的距离,然后又会把这个距离转成各轴电机需要走的总步数,根据这个总步数,又会根据预设的参数,比如电机以梯形加减速运动,计算出来加速阶段走的步数,匀速阶段走的步数和减速阶段走的步数。
二、解析
主要计算集中在下面这个函数中。
_buffer_steps(target
OPTARG(HAS_POSITION_FLOAT, target_float)
OPTARG(HAS_DIST_MM_ARG, cart_dist_mm)
进入到函数里面,首先会等待分配一个空闲的 Block,因为最终在电机的定时器中断里面执行的是事先填充的一个个 Block。Block 的总数量是固定的,已经执行的就会清除里面的信息变成空闲的 Block 等待填充。
Block 是一个结构体,里面主要保存的信息有:
(1)当前这个 Block 中各个轴电机的匀速阶段的速度,可以说是最大速度
(2)初速度和最大初速度,加速度
(3)当前这个 Block 要移动的空间直线距离
(4)各个轴各自要移动的总步数
(5)加速阶段结束的那一步的步数索引和减速阶段开始的那一步的步数索引,这里简单解释一下,每个轴要跑的总步数是明确的, 比如说 X 轴在当前这个 Block 需要跑 600 步,它在前面 100 步处于加速状态,101 步到 550 步处于匀速状态,551 到 600 步处于减速状态,那这里的 100 就是当前 X 轴加速阶段结束时候的步数索引,550 就是当前 X 轴减速阶段开始的步数索引。
(6)各轴的运动方向
拿到一个空闲的 Block 之后,就会根据命令的往这个 Block 填充信息。
负责填充的主要是下面这个函数。
_populate_block(block, target
OPTARG(HAS_POSITION_FLOAT, target_float)
OPTARG(HAS_DIST_MM_ARG, cart_dist_mm)
, fr_mm_s, extruder, hints
)
首先会计算各轴移动的距离
int32_t LOGICAL_AXIS_LIST(
de = target.e - position.e,
da = target.a - position.a,
db = target.b - position.b,
dc = target.c - position.c,
di = target.i - position.i,
dj = target.j - position.j,
dk = target.k - position.k,
);
根据距离的正负设定好各轴移动的方向
block->direction_bits = dm;
然后是设定各轴的总步数。
根据距离换算各轴要移动的直线距离,以 mm 为单位。
steps_dist_mm.a = da * mm_per_step[A_AXIS],
steps_dist_mm.b = db * mm_per_step[B_AXIS],
steps_dist_mm.c = dc * mm_per_step[C_AXIS]
根据 XYZ 轴要移动的直线距离计算空间直线距离。
float distance_sqr =XYZ_GANG(sq(steps_dist_mm.x),+sq(steps_dist_mm.y),+ sq(steps_dist_mm.z))
block->millimeters = SQRT(distance_sqr);
计算当前这个 Block 要移动的最大总步数。
block->step_event_count = _MAX(LOGICAL_AXIS_LIST(esteps,
block->steps.a, block->steps.b, block->steps.c,
block->steps.i, block->steps.j, block->steps.k,
block->steps.u, block->steps.v, block->steps.w
));
注意这里是 XYZ 轴中移动步数最大的那个步数作为这个 Block 要完成的步数。
然后就使能各轴电机的使能开关。
NUM_AXIS_CODE(
if (block->steps.x) stepper.enable_axis(X_AXIS),
if (block->steps.y) stepper.enable_axis(Y_AXIS),
if (TERN(Z_LATE_ENABLE, 0, block->steps.z)) stepper.enable_axis(Z_AXIS),
if (block->steps.i) stepper.enable_axis(I_AXIS),
if (block->steps.j) stepper.enable_axis(J_AXIS),
if (block->steps.k) stepper.enable_axis(K_AXIS),
if (block->steps.u) stepper.enable_axis(U_AXIS),
if (block->steps.v) stepper.enable_axis(V_AXIS),
if (block->steps.w) stepper.enable_axis(W_AXIS)
);
接着会计算这个 Block 的最大速度,nominal_speed,直译就是额定速度。
可以简单理解一下这里面的原理,匀速运动的公式是
S = V*T
那么这里面的路程就是空间直线距离 millimeters,fr_mm_s 就是速度,是设定好的。
const float inverse_millimeters = 1.0f / block->millimeters;
inverse_secs = fr_mm_s * inverse_millimeters;
然后,这里相当于是 V = S*(1/T),速度的单位是 mm/s
block->nominal_speed = block->millimeters * inverse_secs;
计算完速度之后,再计算另外一个速率,就是单位不一样,原理是一样的。
block->nominal_rate = CEIL(block->step_event_count*inverse_secs);
这里的单位是 step/s
然后再分别计算各轴各自的速度
LOOP_NUM_AXES(i) {
current_speed[i] = steps_dist_mm[i] * inverse_secs;
const feedRate_t cs = ABS(current_speed[i]),
max_fr = settings.max_feedrate_mm_s[i];
if (cs > max_fr) NOMORE(speed_factor, max_fr / cs);
}
计算梯形加减速的加速度,注意单位,第一个是 steps/s^2,第二个是 mm/s^2
block->acceleration_steps_per_s2 = accel;
block->acceleration = accel / steps_per_mm;
下面这个也是加速度计算,不过会与定时器的时钟频率关联上
block->acceleration_rate = (uint32_t)(accel * (float(1UL << 24) / (STEPPER_TIMER_RATE)));
计算最大初速度,这里是什么意思,当前这个 Block 的初速度可以等于上一个 Block 的结束时候的速度,就是上一个 Block 不必速度完全减到 0 才开始当前这个 Block,而且直接就以上一个 Block 结束时候的速度作为当前这个 Block 的初速度。这样更高效。
block->max_entry_speed_sqr = vmax_junction_sqr;
_populate_block() 就大致完成以上的工作。
填充完 Block 之后,就可计算梯形加减速的参数了。
recalculate(TERN_(HINTS_SAFE_EXIT_SPEED, hints.safe_exit_speed_sqr));
主要通过这个函数去执行。主要计算的参数有总的步数,加速度,加速的步数,开始减速的步数,匀速的步数,初速度,末速度。
然后就使能定时器中断去脉冲了。
三、总结
本文主要介绍了 Marlin 开发过程中拿到位置命令之后大致是怎么计算得到相关的速度参数和脉冲参数的。
评论