Teacup_Firmware/bed_leveling.c

200 lines
4.7 KiB
C

/** \file
\brief Dynamic Z-height compensation for out-of-level print bed.
*/
#include "bed_leveling.h"
#ifdef BED_LEVELING
#include <stdint.h>
#include <stdlib.h>
#include "dda_maths.h"
#include "debug.h"
#include "sersendf.h"
#include "config_wrapper.h"
struct point {
int32_t x, y, z;
};
// Accept exactly three points for bed leveling
static uint8_t level_index = 0;
static struct point bed_level_map[3];
// Alias the three points
static struct point const * const P = &bed_level_map[0];
static struct point const * const Q = &bed_level_map[1];
static struct point const * const R = &bed_level_map[2];
int bed_plane_calculate(void);
// Reset the bed level values to "unknown"
void bed_level_reset() {
level_index = 0;
}
// Scale x and y down to tenths of mm to keep math from overflowing
#define SCALE 100
static int32_t scale_to_dum(int32_t a) {
if (a<0) return (a-SCALE/2)/SCALE;
return (a+SCALE/2)/SCALE;
}
// Register a point on the bed plane; coordinates in um
void bed_level_register(int32_t x, int32_t y, int32_t z) {
// Scale x and y to tenths of mm; keep z in um
x = scale_to_dum(x);
y = scale_to_dum(y);
// Find a previous registered point at the same location or use a new location
struct point * p = bed_level_map;
int i = 0;
for (; i < level_index; i++, p++) {
if (p->x == x && p->y == y)
break;
}
// We can only handle three points
if (i >= 3)
return;
p->x = x;
p->y = y;
p->z = z;
// Bump the index if we just used a new location
if (i == level_index)
++level_index;
// Nothing more to do until we have three points
if (level_index < 3)
return;
// We have three points. Try to calculate the plane of the bed.
if (!bed_plane_calculate())
--level_index;
}
// Pre-scaled coefficients of the planar equation,
// Ax + By + Cz + K= 0
//
// When we have these coefficients, we're only going to use them relative to -1/C, so
// Ac = -A/C; Bc = -B/C; Kc = 0 (because we translate a point to the origin)
// f(x,y) = z = Ac*x + Bc*y + Kc
static uint32_t Aq, Ar, Bq, Br, C;
static int8_t Asign, Bsign;
int bed_leveling_active() {
// No adjustment if not calibrated yet
return level_index == 3;
}
void bed_level_report() {
sersendf_P(PSTR("Bed leveling status:"));
if (!bed_leveling_active()) {
sersendf_P(PSTR(" not"));
}
sersendf_P(PSTR(" active (%d) positions registered\n"), level_index);
for (int i = 0; i < level_index; i++) {
sersendf_P(PSTR(" %d: G29 S1 X%lq Y%lq Z%lq\n"),
i+1, bed_level_map[i].x * SCALE, bed_level_map[i].y * SCALE, bed_level_map[i].z);
}
}
int32_t bed_level_adjustment(int32_t x, int32_t y) {
int32_t za, zb;
// No adjustment if not calibrated yet
if (!bed_leveling_active())
return 0;
x = scale_to_dum(x);
y = scale_to_dum(y);
x -= P->x;
y -= P->y;
za = muldivQR(x, Aq, Ar, C);
if (Asign)
za = -za;
zb = muldivQR(y, Bq, Br, C);
if (Bsign)
zb = -zb;
return P->z - za - zb;
}
int bed_plane_calculate() {
// Coefficients of the planar equation,
// Ax + By + Cz + K = 0
int32_t a, b, c;
// Draw two vectors on the plane, u = B-A and v = C-A
int32_t Ui, Uj, Uk, Vi, Vj, Vk;
// U = vector(QP)
Ui = Q->x - P->x;
Uj = Q->y - P->y;
Uk = Q->z - P->z;
// V = vector(RP)
Vi = R->x - P->x;
Vj = R->y - P->y;
Vk = R->z - P->z;
// Find normal vector (a,b,c) = (U x V) and is perpendicular to the plane
a = Uj*Vk - Uk*Vj;
b = Uk*Vi - Ui*Vk;
c = Ui*Vj - Uj*Vi;
// Notes:
// * Ignore K (constant) by translating plane to pass through origin at P (0,0,0)
// * if a==0 and b==0, the bed is already level; z-offset is still important
// * if c==0 the bed is perpendicular or the points are colinear
if (c == 0)
return 0;
if (DEBUG_DDA && (debug_flags & DEBUG_DDA))
sersendf_P(PSTR("Coefficients: A:%ld B:%ld C:%ld\n"), a, b, c);
// muldiv requires positive parameters
// remember the signs separately
Asign = a < 0;
Bsign = b < 0;
if (Asign) a = -a;
if (Bsign) b = -b;
// Combine A/C and B/C, so combine their signs, too
if (c < 0) {
c = -c;
Asign = !Asign;
Bsign = !Bsign;
}
// Pre-calc the coefficients A/C and B/C
Aq = a / c;
Ar = a % c;
Bq = b / c;
Br = b % c;
C = c;
int ret = 1;
// Sanity check
for (int i = 0; i < level_index; i++) {
int32_t x=bed_level_map[i].x * SCALE, y=bed_level_map[i].y * SCALE;
int32_t validation = bed_level_adjustment(x, y);
if (labs(validation - bed_level_map[i].z) > 10) {
sersendf_P(PSTR("!! Bed plane validation failed: Point %d: X:%lq Y:%lq Expected Z:%lq Calculated Z:%lq\n"),
i+1, x, y, bed_level_map[i].z, validation);
ret = 0; // invalidate results on error
}
}
return ret;
}
#endif /* BED_LEVELING */