自动调平与自动校正
首先要澄清一下,自动调平(auto-leveling)和自动校正(auto-calibration)是两个容易混淆的概念。自动调 平,一般是指调节热床的平整度;而自动校正是一个更复杂的过程,不但包括校正热床的平整度,还包括调整三个 塔(Towers)的对称性,限位开关的高度,杆长等等,是一个相当复杂的过程。
Marlin 固件对于自动调平和自动校正的支持现状
Marlin 固件的碎片化越来越严重了!如果上 Github, 你会发现目前 Marlin 至少有 1800 多个 fork。最最正宗的 Marlin 固件是由 Erik 维护的。不幸的是,尽管已经存在了很久,但是目前对于 Deltabot 的自动调平支持的还 不是很好(原因嘛~也许 Marlin 固件要做到大而全,尽量支持各种结构和机型,当然不可能在短时间内将所有事 情都做完美)。
目前支持 Deltabot 自动调平的固件可以参考 Jcrocholl 的 Marlin 固件;而『支持』自动校正的是 RichCattell 的 Marlin 固件。
Jcrocholl 的 Marlin 代码
G29 和 G30
阅读 Marlin 的代码,可以发现控制 Deltabot 运动的全部代码几乎都在 Marlin_main.cpp 这个文件的 Process_commands() 文件里。通过 switch 语句来处理不同的 G-Code, M-Code 指令。在 Jcrocholl 版本的 Marlin 中,G30 的作用是探测某一点的 Z-高度。让我们来研究一下代码:
case 30: // G30 Single Z Probe
{
engage_z_probe(); // Engage Z Servo endstop if available
/* 这个函数是在 marlin_main.cpp 里面定义的,作用是放下Z-探测探针(当然有各种办法放下,比
如用『伺服电机』或者干脆让 Z-探针接触皮带从而放下探针)。崩溃的是,让 Z-探针接触皮带的
坐标是写死在固件里的(并没有提供设置参数),因此 Jcrocholl 的 Marlin 注定是高度定制化
的。
*/
st_synchronize();
/* 这个函数的定义可以在 Stepper.cpp 这个文件里面找到,是直接控制步进电机用的。作用是完成
缓充区里暂存的命令,并负责管理热床的状态、更新 LCD 显示等等。
*/
// TODO: make sure the bed_level_rotation_matrix is identity or the planner will get set incorectly
setup_for_endstop_move();
/* 设定 feedrate 等等,但最重要的是 enable_endstops(true),即把一个检测 endstop 状态的变
量:check_endstops 的值设为 true。
*/
feedrate = homing_feedrate[Z_AXIS];
run_z_probe();
/* 这才是最重要的一句:开始 z-探测。这个函数也是 Jcrocholl 特有的,下面我们将分析这个函
数的功能。
*/
SERIAL_PROTOCOLPGM(MSG_BED);
SERIAL_PROTOCOLPGM(" X: ");
SERIAL_PROTOCOL(current_position[X_AXIS]);
SERIAL_PROTOCOLPGM(" Y: ");
SERIAL_PROTOCOL(current_position[Y_AXIS]);
SERIAL_PROTOCOLPGM(" Z: ");
SERIAL_PROTOCOL(current_position[Z_AXIS]);
SERIAL_PROTOCOLPGM("\n");
clean_up_after_endstop_move();
retract_z_probe(); // Retract Z Servo endstop if available
}
break;
run_z_probe() 的代码:
static void run_z_probe() {
plan_bed_level_matrix.set_to_identity();
#ifdef DELTA
enable_endstops(true);
float start_z = current_position[Z_AXIS];
// 当前的打印头位置的 z 坐标
long start_steps = st_get_position(Z_AXIS);
/* 这句很奇怪,得到 Z-柱的步进电机当前的步数,但是对于 Deltabot 来说,Z-柱的步数和打印头的 Z-坐
标之间有直接关系吗?
*/
feedrate = homing_feedrate[Z_AXIS]/10; // 让移动速度是 Homing 速度的十分之一
destination[Z_AXIS] = -10; // 设置打印头 z-坐标的预期值为 -10
prepare_move_raw(); // 规划运动
st_synchronize(); // 完成缓冲
endstops_hit_on_purpose(); // 设置三个轴 hit 的值为 false
enable_endstops(false); //设置 check_endstops 这个变量为 false
long stop_steps = st_get_position(Z_AXIS);
float mm = start_z - float(start_steps - stop_steps) / axis_steps_per_unit[Z_AXIS];
current_position[Z_AXIS] = mm;
calculate_delta(current_position);
plan_set_position(delta[X_AXIS], delta[Y_AXIS], delta[Z_AXIS], current_position[E_AXIS]);
#else
feedrate = homing_feedrate[Z_AXIS];
// move down until you find the bed
float zPosition = -10;
plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], zPosition, current_position[E_AXIS], feedrate/60, active_extruder);
st_synchronize();
// we have to let the planner know where we are right now as it is not where we said to go.
zPosition = st_get_position_mm(Z_AXIS);
plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], zPosition, current_position[E_AXIS]);
// move up the retract distance
zPosition += home_retract_mm(Z_AXIS);
plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], zPosition, current_position[E_AXIS], feedrate/60, active_extruder);
st_synchronize();
// move back down slowly to find bed
feedrate = homing_feedrate[Z_AXIS]/4;
zPosition -= home_retract_mm(Z_AXIS) * 2;
plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], zPosition, current_position[E_AXIS], feedrate/60, active_extruder);
st_synchronize();
current_position[Z_AXIS] = st_get_position_mm(Z_AXIS);
// make sure the planner knows where we are as it may be a bit different than we last said to move to
plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]);
#endif
}
prepare_move_raw() 的代码,在 Marlin_main.cpp 里定义:
void prepare_move_raw()
{
previous_millis_cmd = millis();
// 这是一个 Arduino 内部函数,得到 Arduino 运行到现在的时间(是个
// 无符号长整型),Arduino 将会在 50 天后重置时间
calculate_delta(destination);
// 输入打印头的笛卡尔坐标,得到打印头的 delta 坐标(三个滑车向下移动的距离)
plan_buffer_line(delta[X_AXIS], delta[Y_AXIS], delta[Z_AXIS],
destination[E_AXIS], feedrate*feedmultiply/60/100.0,
active_extruder);
// 将线性运动加入 buffer, x, y, z 是带符号的目标位置,单位是 mm
for(int8_t i=0; i < NUM_AXIS; i++) {
current_position[i] = destination[i];
}
// 将当前打印头位置位置设为目标位置,表明运动完成
}
以下是 RichCattell 的z_probe() 代码,在 Marlin_main.cpp 文件里定义:
float z_probe() {
feedrate = AUTOCAL_TRAVELRATE * 60;
prepare_move();
st_synchronize();
enable_endstops(true);
float start_z = current_position[Z_AXIS];
long start_steps = st_get_position(Z_AXIS);
feedrate = AUTOCAL_PROBERATE * 60;
destination[Z_AXIS] = -20;
prepare_move_raw();
st_synchronize();
endstops_hit_on_purpose();
enable_endstops(false);
long stop_steps = st_get_position(Z_AXIS);
//saved_position[X_AXIS] = float((st_get_position(X_AXIS)) / axis_steps_per_unit[X_AXIS]);
//saved_position[Y_AXIS] = float((st_get_position(Y_AXIS)) / axis_steps_per_unit[Y_AXIS]);
//saved_position[Z_AXIS] = float((st_get_position(Z_AXIS)) / axis_steps_per_unit[Z_AXIS]);
float mm = start_z -
float(start_steps - stop_steps) / axis_steps_per_unit[Z_AXIS];
current_position[Z_AXIS] = mm;
calculate_delta(current_position);
plan_set_position(delta[X_AXIS], delta[Y_AXIS], delta[Z_AXIS],
current_position[E_AXIS]);
for(int8_t i=0; i < 3; i++) {
saved_position[i] = float(st_get_position(i) / axis_steps_per_unit[i]);
}
destination[Z_AXIS] = mm + AUTOCAL_PROBELIFT;
prepare_move_raw();
st_synchronize();
return mm;
}
以下是 prepare_move() 的代码,在 Marlin_main.cpp 里定义:
void prepare_move()
{
clamp_to_software_endstops(destination);
previous_millis_cmd = millis();
#ifdef DELTA
float difference[NUM_AXIS];
for (int8_t i=0; i < NUM_AXIS; i++) {
difference[i] = destination[i] - current_position[i];
}
float cartesian_mm = sqrt(sq(difference[X_AXIS]) +
sq(difference[Y_AXIS]) +
sq(difference[Z_AXIS]));
if (cartesian_mm < 0.000001) { cartesian_mm = abs(difference[E_AXIS]); }
if (cartesian_mm < 0.000001) { return; }
float seconds = 6000 * cartesian_mm / feedrate / feedmultiply;
int steps = max(1, int(DELTA_SEGMENTS_PER_SECOND * seconds));
// SERIAL_ECHOPGM("mm="); SERIAL_ECHO(cartesian_mm);
// SERIAL_ECHOPGM(" seconds="); SERIAL_ECHO(seconds);
// SERIAL_ECHOPGM(" steps="); SERIAL_ECHOLN(steps);
for (int s = 1; s <= steps; s++) {
float fraction = float(s) / float(steps);
for(int8_t i=0; i < NUM_AXIS; i++) {
destination[i] = current_position[i] + difference[i] * fraction;
}
calculate_delta(destination);
adjust_delta(destination);
plan_buffer_line(delta[X_AXIS], delta[Y_AXIS], delta[Z_AXIS],
destination[E_AXIS], feedrate*feedmultiply/60/100.0,
active_extruder);
}
#else