delete homing. Have to redo
This commit is contained in:
@@ -1 +1 @@
|
|||||||
You didn't finish writing home for web. Analyse it properly as it was quota reached. I can see that there is definitely wron position on map at the start. I do not know what else but you should properly finish homing sequence it was done for mcu already
|
Implement mechanical stall homing
|
||||||
+1
-96
@@ -50,20 +50,6 @@
|
|||||||
#define DEFAULT_SPEED 600.0f // steps/sec
|
#define DEFAULT_SPEED 600.0f // steps/sec
|
||||||
#define DEFAULT_ACCEL 100.0f // steps/sec²
|
#define DEFAULT_ACCEL 100.0f // steps/sec²
|
||||||
|
|
||||||
// ─── Homing configuration ─────────────────────────────────────────
|
|
||||||
// TUNE these after first hardware test.
|
|
||||||
// SEEK_DIR: +1 = CW (positive steps), -1 = CCW toward stop
|
|
||||||
// SEEK_STEPS: must exceed maximum arm travel (~240° = 1365 steps)
|
|
||||||
// OP_OFFSET: steps from the mechanical stop to the operating home
|
|
||||||
// (derived from original PositionControl.cpp homing offsets)
|
|
||||||
#define M1_HOME_DIR (-1) // ← TUNE: flip to +1 if M1 moves wrong way
|
|
||||||
#define M2_HOME_DIR (+1) // ← TUNE: flip to -1 if M2 moves wrong way
|
|
||||||
#define HOME_SEEK_STEPS 1600 // ← TUNE: > max travel; 1600 ≈ 281° of a rev
|
|
||||||
#define HOME_SPEED 180.0f // ← TUNE: slow seek to avoid missing steps
|
|
||||||
#define HOME_ACCEL 50.0f // ← TUNE: gentle accel during seek
|
|
||||||
#define M1_OP_OFFSET 550 // ← TUNE: steps from M1 stop → operating home
|
|
||||||
#define M2_OP_OFFSET -530 // ← TUNE: steps from M2 stop → operating home
|
|
||||||
|
|
||||||
// ─── BLE UUIDs ────────────────────────────────────────────────────
|
// ─── BLE UUIDs ────────────────────────────────────────────────────
|
||||||
#define SERVICE_UUID "a0b1c2d3-e4f5-6789-abcd-ef0123456700"
|
#define SERVICE_UUID "a0b1c2d3-e4f5-6789-abcd-ef0123456700"
|
||||||
#define CMD_UUID "a0b1c2d3-e4f5-6789-abcd-ef0123456701"
|
#define CMD_UUID "a0b1c2d3-e4f5-6789-abcd-ef0123456701"
|
||||||
@@ -87,7 +73,6 @@ const unsigned long NOTIFY_INTERVAL_MS = 200;
|
|||||||
// ─── Forward declarations ─────────────────────────────────────────
|
// ─── Forward declarations ─────────────────────────────────────────
|
||||||
void parseCommand(const String& cmd);
|
void parseCommand(const String& cmd);
|
||||||
void sendPosition();
|
void sendPosition();
|
||||||
void runHoming();
|
|
||||||
|
|
||||||
// ─── BLE Server Callbacks ─────────────────────────────────────────
|
// ─── BLE Server Callbacks ─────────────────────────────────────────
|
||||||
class ServerCallbacks : public BLEServerCallbacks {
|
class ServerCallbacks : public BLEServerCallbacks {
|
||||||
@@ -145,13 +130,7 @@ void parseCommand(const String& cmd) {
|
|||||||
Serial.printf("[CFG] Acceleration → %.0f steps/sec²\n", acc);
|
Serial.printf("[CFG] Acceleration → %.0f steps/sec²\n", acc);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// HOME – full stall-seek homing sequence
|
// HOME1
|
||||||
if (cmd == "HOME") {
|
|
||||||
Serial.println("[HOME] Received HOME command");
|
|
||||||
runHoming();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// HOME1 / HOME2 – manual zero (soft zero, no motion)
|
|
||||||
if (cmd == "HOME1") {
|
if (cmd == "HOME1") {
|
||||||
stepper1.setCurrentPosition(0);
|
stepper1.setCurrentPosition(0);
|
||||||
Serial.println("[S1] Zeroed");
|
Serial.println("[S1] Zeroed");
|
||||||
@@ -173,80 +152,6 @@ void parseCommand(const String& cmd) {
|
|||||||
Serial.printf("[CMD] Unknown command: '%s'\n", cmd.c_str());
|
Serial.printf("[CMD] Unknown command: '%s'\n", cmd.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Stall-based homing sequence ─────────────────────────────────
|
|
||||||
/**
|
|
||||||
* Drives each motor slowly into its mechanical hard stop (the arm
|
|
||||||
* folds against the motor housing). The 28BYJ-48 stalls harmlessly
|
|
||||||
* for a fraction of a second at the stop. After both stops are
|
|
||||||
* found, the arm backs off to the operating home position and both
|
|
||||||
* step counters are zeroed.
|
|
||||||
*
|
|
||||||
* This is a BLOCKING call — BLE is not serviced during homing
|
|
||||||
* (~15-20 s total). The browser shows "Homing…" until the
|
|
||||||
* HOMED notify arrives.
|
|
||||||
*
|
|
||||||
* Tune M1_HOME_DIR, M2_HOME_DIR, HOME_SEEK_STEPS, and OP_OFFSETs
|
|
||||||
* after the first hardware test.
|
|
||||||
*/
|
|
||||||
void runHoming() {
|
|
||||||
Serial.println("[HOME] Starting homing sequence…");
|
|
||||||
|
|
||||||
// Reduce speed for the seek phase
|
|
||||||
stepper1.setMaxSpeed(HOME_SPEED);
|
|
||||||
stepper1.setAcceleration(HOME_ACCEL);
|
|
||||||
stepper2.setMaxSpeed(HOME_SPEED);
|
|
||||||
stepper2.setAcceleration(HOME_ACCEL);
|
|
||||||
|
|
||||||
// ── Seek M1 to its mechanical stop ───────────────────────────
|
|
||||||
Serial.println("[HOME] Seeking M1 stop…");
|
|
||||||
stepper1.move(M1_HOME_DIR * HOME_SEEK_STEPS);
|
|
||||||
while (stepper1.isRunning()) {
|
|
||||||
stepper1.run();
|
|
||||||
stepper2.run(); // keep M2 coils energised during M1 seek
|
|
||||||
}
|
|
||||||
stepper1.setCurrentPosition(0);
|
|
||||||
Serial.println("[HOME] M1 stop found → zeroed");
|
|
||||||
delay(200); // brief pause before M2 seek
|
|
||||||
|
|
||||||
// ── Seek M2 to its mechanical stop ───────────────────────────
|
|
||||||
Serial.println("[HOME] Seeking M2 stop…");
|
|
||||||
stepper2.move(M2_HOME_DIR * HOME_SEEK_STEPS);
|
|
||||||
while (stepper2.isRunning()) {
|
|
||||||
stepper1.run();
|
|
||||||
stepper2.run();
|
|
||||||
}
|
|
||||||
stepper2.setCurrentPosition(0);
|
|
||||||
Serial.println("[HOME] M2 stop found → zeroed");
|
|
||||||
delay(200);
|
|
||||||
|
|
||||||
// ── Move to operating home (restore normal speed) ─────────────
|
|
||||||
stepper1.setMaxSpeed(DEFAULT_SPEED);
|
|
||||||
stepper1.setAcceleration(DEFAULT_ACCEL);
|
|
||||||
stepper2.setMaxSpeed(DEFAULT_SPEED);
|
|
||||||
stepper2.setAcceleration(DEFAULT_ACCEL);
|
|
||||||
|
|
||||||
Serial.println("[HOME] Moving to operating home…");
|
|
||||||
stepper1.moveTo(M1_OP_OFFSET);
|
|
||||||
stepper2.moveTo(M2_OP_OFFSET);
|
|
||||||
while (stepper1.isRunning() || stepper2.isRunning()) {
|
|
||||||
stepper1.run();
|
|
||||||
stepper2.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Declare operating home as the new step zero
|
|
||||||
stepper1.setCurrentPosition(0);
|
|
||||||
stepper2.setCurrentPosition(0);
|
|
||||||
Serial.println("[HOME] Homing complete — at operating home (0, 0)");
|
|
||||||
|
|
||||||
// Notify the browser so it can sync its step counters
|
|
||||||
if (bleConnected && pStatusChar) {
|
|
||||||
pStatusChar->setValue("HOMED");
|
|
||||||
pStatusChar->notify();
|
|
||||||
}
|
|
||||||
delay(50);
|
|
||||||
sendPosition(); // immediately follow with P:0,0
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Send current positions via BLE NOTIFY ────────────────────────
|
// ─── Send current positions via BLE NOTIFY ────────────────────────
|
||||||
void sendPosition() {
|
void sendPosition() {
|
||||||
if (!bleConnected || !pStatusChar) return;
|
if (!bleConnected || !pStatusChar) return;
|
||||||
|
|||||||
+26
-26
@@ -33,11 +33,11 @@
|
|||||||
|
|
||||||
// ── Exact constants from PositionControl.cpp ─────────────────────
|
// ── Exact constants from PositionControl.cpp ─────────────────────
|
||||||
const ARM = {
|
const ARM = {
|
||||||
d: 25.8, // full motor separation (mm)
|
d: 25.8, // full motor separation (mm)
|
||||||
d2: 12.9, // half separation; M1 at (+d2, 0), M2 at (-d2, 0)
|
d2: 12.9, // half separation; M1 at (+d2, 0), M2 at (-d2, 0)
|
||||||
l1: 85.0, // proximal link length (mm)
|
l1: 85.0, // proximal link length (mm)
|
||||||
l2: 110.0, // distal link length (mm)
|
l2: 110.0, // distal link length (mm)
|
||||||
STEPS_PER_REV: 2048,
|
STEPS_PER_REV: 2048,
|
||||||
STEP_ANGLE_DEG: 360 / 2048, // ≈ 0.17578125 °/step
|
STEP_ANGLE_DEG: 360 / 2048, // ≈ 0.17578125 °/step
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -48,16 +48,16 @@ const LIMITS = {
|
|||||||
// ── Outer board boundary ───────────────────────────────────────
|
// ── Outer board boundary ───────────────────────────────────────
|
||||||
// The EE cannot be requested outside this rectangle.
|
// The EE cannot be requested outside this rectangle.
|
||||||
X_MIN: -150, // ← TUNE: left edge of board
|
X_MIN: -150, // ← TUNE: left edge of board
|
||||||
X_MAX: 150, // ← TUNE: right edge of board
|
X_MAX: 150, // ← TUNE: right edge of board
|
||||||
Y_MIN: -30, // ← TUNE: bottom (numbers reach ~44 mm; sign coords go lower)
|
Y_MIN: -30, // ← TUNE: bottom (numbers reach ~44 mm; sign coords go lower)
|
||||||
Y_MAX: 145, // ← TUNE: top (highest letter is ~128 mm)
|
Y_MAX: 145, // ← TUNE: top (highest letter is ~128 mm)
|
||||||
|
|
||||||
// ── Centre mechanism exclusion box ────────────────────────────
|
// ── Centre mechanism exclusion box ────────────────────────────
|
||||||
// Rectangular zone centred on (0, 0) where the motor housing sits.
|
// Rectangular zone centred on (0, 0) where the motor housing sits.
|
||||||
// The EE and both elbows must stay outside this area.
|
// The EE and both elbows must stay outside this area.
|
||||||
BOX_HALF_W: 30, // ← TUNE: half-width in X (motors at ±12.9, housing wider)
|
BOX_HALF_W: 30, // ← TUNE: half-width in X (motors at ±12.9, housing wider)
|
||||||
BOX_Y_MIN: -10, // ← TUNE: bottom of housing
|
BOX_Y_MIN: -10, // ← TUNE: bottom of housing
|
||||||
BOX_Y_MAX: 40, // ← TUNE: top of housing
|
BOX_Y_MAX: 40, // ← TUNE: top of housing
|
||||||
|
|
||||||
// ── Elbow exclusion zone (prevents arm crossing near the box) ─
|
// ── Elbow exclusion zone (prevents arm crossing near the box) ─
|
||||||
// Each elbow has a separate rectangular exclusion box.
|
// Each elbow has a separate rectangular exclusion box.
|
||||||
@@ -66,7 +66,7 @@ const LIMITS = {
|
|||||||
// If ELBOW_BOX_X_INNER is 5, the left elbow's X must be > +5 mm
|
// If ELBOW_BOX_X_INNER is 5, the left elbow's X must be > +5 mm
|
||||||
// (can never cross to the other side of the box mid-point).
|
// (can never cross to the other side of the box mid-point).
|
||||||
ELBOW_BOX_X_INNER: 5, // ← TUNE: inner X margin from centre for each elbow
|
ELBOW_BOX_X_INNER: 5, // ← TUNE: inner X margin from centre for each elbow
|
||||||
ELBOW_BOX_Y_MAX: 50, // ← TUNE: Y below which elbow crossing is forbidden
|
ELBOW_BOX_Y_MAX: 50, // ← TUNE: Y below which elbow crossing is forbidden
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── Letter / number position lookup table ────────────────────────
|
// ── Letter / number position lookup table ────────────────────────
|
||||||
@@ -132,23 +132,23 @@ function solve(x, y) {
|
|||||||
// ── Motor 1 (pivot at +d2, 0 = +12.9 mm) ─────────────────────
|
// ── Motor 1 (pivot at +d2, 0 = +12.9 mm) ─────────────────────
|
||||||
// xmd = x - d2 is the X component of (target – M1_pivot).
|
// xmd = x - d2 is the X component of (target – M1_pivot).
|
||||||
const xmd = x - d2;
|
const xmd = x - d2;
|
||||||
const s = Math.sqrt(xmd * xmd + y * y);
|
const s = Math.sqrt(xmd * xmd + y * y);
|
||||||
if (s < 1e-6) return { theta1: 0, theta2: 0, reachable: false };
|
if (s < 1e-6) return { theta1: 0, theta2: 0, reachable: false };
|
||||||
const cosW1 = (l2 * l2 - s * s - l1 * l1) / (-2 * l1 * s);
|
const cosW1 = (l2 * l2 - s * s - l1 * l1) / (-2 * l1 * s);
|
||||||
if (cosW1 < -1 || cosW1 > 1) return { theta1: 0, theta2: 0, reachable: false };
|
if (cosW1 < -1 || cosW1 > 1) return { theta1: 0, theta2: 0, reachable: false };
|
||||||
const q = Math.atan2(y, xmd);
|
const q = Math.atan2(y, xmd);
|
||||||
const w1 = Math.acos(cosW1);
|
const w1 = Math.acos(cosW1);
|
||||||
const theta1 = q - w1;
|
const theta1 = q - w1;
|
||||||
|
|
||||||
// ── Motor 2 (pivot at -d2, 0 = -12.9 mm) ─────────────────────
|
// ── Motor 2 (pivot at -d2, 0 = -12.9 mm) ─────────────────────
|
||||||
// xpd = x + d2 is the X component of (target – M2_pivot).
|
// xpd = x + d2 is the X component of (target – M2_pivot).
|
||||||
const xpd = x + d2;
|
const xpd = x + d2;
|
||||||
const t = Math.sqrt(xpd * xpd + y * y);
|
const t = Math.sqrt(xpd * xpd + y * y);
|
||||||
if (t < 1e-6) return { theta1: 0, theta2: 0, reachable: false };
|
if (t < 1e-6) return { theta1: 0, theta2: 0, reachable: false };
|
||||||
const cosW2 = (l2 * l2 - t * t - l1 * l1) / (-2 * l1 * t);
|
const cosW2 = (l2 * l2 - t * t - l1 * l1) / (-2 * l1 * t);
|
||||||
if (cosW2 < -1 || cosW2 > 1) return { theta1: 0, theta2: 0, reachable: false };
|
if (cosW2 < -1 || cosW2 > 1) return { theta1: 0, theta2: 0, reachable: false };
|
||||||
const r = Math.atan2(y, xpd);
|
const r = Math.atan2(y, xpd);
|
||||||
const w2 = Math.acos(cosW2);
|
const w2 = Math.acos(cosW2);
|
||||||
const theta2 = r + w2;
|
const theta2 = r + w2;
|
||||||
|
|
||||||
return { theta1, theta2, reachable: true };
|
return { theta1, theta2, reachable: true };
|
||||||
@@ -180,16 +180,16 @@ function forward(theta1, theta2) {
|
|||||||
|
|
||||||
// Elbow 1 — tip of Motor 1 proximal link (motor at +d2)
|
// Elbow 1 — tip of Motor 1 proximal link (motor at +d2)
|
||||||
const e1x = +d2 + l1 * Math.cos(theta1);
|
const e1x = +d2 + l1 * Math.cos(theta1);
|
||||||
const e1y = l1 * Math.sin(theta1);
|
const e1y = l1 * Math.sin(theta1);
|
||||||
|
|
||||||
// Elbow 2 — tip of Motor 2 proximal link (motor at -d2)
|
// Elbow 2 — tip of Motor 2 proximal link (motor at -d2)
|
||||||
const e2x = -d2 + l1 * Math.cos(theta2);
|
const e2x = -d2 + l1 * Math.cos(theta2);
|
||||||
const e2y = l1 * Math.sin(theta2);
|
const e2y = l1 * Math.sin(theta2);
|
||||||
|
|
||||||
// End-effector: intersection of the two distal-link circles
|
// End-effector: intersection of the two distal-link circles
|
||||||
// (radius l2, centred on each elbow). Pick the "upward" solution.
|
// (radius l2, centred on each elbow). Pick the "upward" solution.
|
||||||
const dx = e2x - e1x;
|
const dx = e2x - e1x;
|
||||||
const dy = e2y - e1y;
|
const dy = e2y - e1y;
|
||||||
const dist = Math.sqrt(dx * dx + dy * dy);
|
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
if (dist < 1e-6 || dist > 2 * l2) {
|
if (dist < 1e-6 || dist > 2 * l2) {
|
||||||
@@ -203,15 +203,15 @@ function forward(theta1, theta2) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const a = dist / 2;
|
const a = dist / 2;
|
||||||
const h = Math.sqrt(Math.max(0, l2 * l2 - a * a));
|
const h = Math.sqrt(Math.max(0, l2 * l2 - a * a));
|
||||||
const mx = (e1x + e2x) / 2;
|
const mx = (e1x + e2x) / 2;
|
||||||
const my = (e1y + e2y) / 2;
|
const my = (e1y + e2y) / 2;
|
||||||
|
|
||||||
// Two intersection candidates
|
// Two intersection candidates
|
||||||
const px1 = mx + h * ( dy / dist);
|
const px1 = mx + h * (dy / dist);
|
||||||
const py1 = my + h * (-dx / dist);
|
const py1 = my + h * (-dx / dist);
|
||||||
const px2 = mx - h * ( dy / dist);
|
const px2 = mx - h * (dy / dist);
|
||||||
const py2 = my - h * (-dx / dist);
|
const py2 = my - h * (-dx / dist);
|
||||||
|
|
||||||
// Always pick the candidate with higher Y (EE above the elbow line)
|
// Always pick the candidate with higher Y (EE above the elbow line)
|
||||||
@@ -247,15 +247,15 @@ function armsCrossed(theta1, theta2) {
|
|||||||
|
|
||||||
// Elbow positions (same formula as forward())
|
// Elbow positions (same formula as forward())
|
||||||
const e1x = +d2 + l1 * Math.cos(theta1);
|
const e1x = +d2 + l1 * Math.cos(theta1);
|
||||||
const e1y = l1 * Math.sin(theta1);
|
const e1y = l1 * Math.sin(theta1);
|
||||||
const e2x = -d2 + l1 * Math.cos(theta2);
|
const e2x = -d2 + l1 * Math.cos(theta2);
|
||||||
const e2y = l1 * Math.sin(theta2);
|
const e2y = l1 * Math.sin(theta2);
|
||||||
|
|
||||||
// Elbow1 (from M1 on the RIGHT) must not appear far to the LEFT at low height
|
// Elbow1 (from M1 on the RIGHT) must not appear far to the LEFT at low height
|
||||||
// Elbow2 (from M2 on the LEFT) must not appear far to the RIGHT at low height
|
// Elbow2 (from M2 on the LEFT) must not appear far to the RIGHT at low height
|
||||||
// Both conditions together catch the "arms have swapped sides" scenario.
|
// Both conditions together catch the "arms have swapped sides" scenario.
|
||||||
const e1_crossed = e1x < -XI && e1y < YM; // M1's elbow went too far left
|
const e1_crossed = e1x < -XI && e1y < YM; // M1's elbow went too far left
|
||||||
const e2_crossed = e2x > XI && e2y < YM; // M2's elbow went too far right
|
const e2_crossed = e2x > XI && e2y < YM; // M2's elbow went too far right
|
||||||
|
|
||||||
return e1_crossed || e2_crossed;
|
return e1_crossed || e2_crossed;
|
||||||
}
|
}
|
||||||
@@ -285,7 +285,7 @@ function checkWorkspace(x, y) {
|
|||||||
|
|
||||||
// 2. Centre mechanism exclusion box
|
// 2. Centre mechanism exclusion box
|
||||||
if (x > -L.BOX_HALF_W && x < L.BOX_HALF_W &&
|
if (x > -L.BOX_HALF_W && x < L.BOX_HALF_W &&
|
||||||
y > L.BOX_Y_MIN && y < L.BOX_Y_MAX)
|
y > L.BOX_Y_MIN && y < L.BOX_Y_MAX)
|
||||||
return { ok: false, reason: `(${x.toFixed(1)}, ${y.toFixed(1)}) is inside the mechanism housing` };
|
return { ok: false, reason: `(${x.toFixed(1)}, ${y.toFixed(1)}) is inside the mechanism housing` };
|
||||||
|
|
||||||
// 3. IK geometric reachability
|
// 3. IK geometric reachability
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ const PX_D2 = IK.ARM.d2 * SV.SCALE;
|
|||||||
// ── Section state ─────────────────────────────────────────────────
|
// ── Section state ─────────────────────────────────────────────────
|
||||||
let steps1 = 0;
|
let steps1 = 0;
|
||||||
let steps2 = 0;
|
let steps2 = 0;
|
||||||
let isHoming = false; // blocks all controls during homing sequence
|
|
||||||
let eventCleanup = [];
|
let eventCleanup = [];
|
||||||
|
|
||||||
// ── SVG element refs ──────────────────────────────────────────────
|
// ── SVG element refs ──────────────────────────────────────────────
|
||||||
@@ -226,58 +225,9 @@ async function zeroMotor(motor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────
|
||||||
// Homing
|
// Manual jog
|
||||||
// ─────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/** Block / unblock all interactive controls while homing is running. */
|
|
||||||
function setHomingActive(active) {
|
|
||||||
isHoming = active;
|
|
||||||
const root = document.querySelector('.section-page');
|
|
||||||
if (!root) return;
|
|
||||||
if (active) {
|
|
||||||
root.classList.add('homing-active');
|
|
||||||
} else {
|
|
||||||
root.classList.remove('homing-active');
|
|
||||||
}
|
|
||||||
// Update the button label
|
|
||||||
const btn = document.getElementById('btn-find-home');
|
|
||||||
const spin = document.getElementById('home-spinner');
|
|
||||||
const lbl = document.getElementById('home-label');
|
|
||||||
if (!btn) return;
|
|
||||||
btn.disabled = active;
|
|
||||||
if (spin) spin.style.display = active ? '' : 'none';
|
|
||||||
if (lbl) lbl.textContent = active ? 'Homing…' : 'Find Home';
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Called when the firmware sends HOMED or when simulating. */
|
|
||||||
function onHomingComplete() {
|
|
||||||
steps1 = 0;
|
|
||||||
steps2 = 0;
|
|
||||||
setHomingActive(false);
|
|
||||||
renderArmFromSteps();
|
|
||||||
UI.log('✓ Homing complete — step counters reset to zero.', 'success');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function startHoming() {
|
|
||||||
if (isHoming) return;
|
|
||||||
setHomingActive(true);
|
|
||||||
UI.log('⧐ Homing started — arm moving to mechanical stops…', 'warn');
|
|
||||||
|
|
||||||
if (BLE.isConnected()) {
|
|
||||||
try {
|
|
||||||
await BLE.write('HOME');
|
|
||||||
// onHomingComplete() will be called when HOMED notify arrives
|
|
||||||
} catch (e) {
|
|
||||||
UI.log(`BLE error during home: ${e.message}`, 'error');
|
|
||||||
setHomingActive(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Simulation: instant reset
|
|
||||||
UI.log('[sim] HOME command sent — simulating instant home', 'info');
|
|
||||||
setTimeout(onHomingComplete, 600);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────
|
||||||
// SVG workspace zones (pre-computed)
|
// SVG workspace zones (pre-computed)
|
||||||
// ─────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────
|
||||||
@@ -401,19 +351,7 @@ function buildHTML() {
|
|||||||
.quick-steps button { flex:1; min-width:30px; padding:4px 2px; font-size:0.68rem;
|
.quick-steps button { flex:1; min-width:30px; padding:4px 2px; font-size:0.68rem;
|
||||||
border-radius:var(--radius-xs); }
|
border-radius:var(--radius-xs); }
|
||||||
|
|
||||||
/* Disable all interactive elements during homing */
|
|
||||||
.homing-active button:not(#btn-find-home),
|
|
||||||
.homing-active input,
|
|
||||||
.homing-active #scara-svg {
|
|
||||||
pointer-events: none;
|
|
||||||
opacity: 0.4;
|
|
||||||
}
|
|
||||||
/* Spinner animation for homing button */
|
|
||||||
@keyframes spin { to { transform: rotate(360deg); } }
|
|
||||||
.home-spin { display:inline-block; width:13px; height:13px;
|
|
||||||
border:2px solid currentColor; border-top-color:transparent;
|
|
||||||
border-radius:50%; animation:spin 0.7s linear infinite;
|
|
||||||
vertical-align:middle; margin-right:5px; }
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="section-page fade-in">
|
<div class="section-page fade-in">
|
||||||
@@ -614,19 +552,6 @@ function buildHTML() {
|
|||||||
<span style="font-size:0.72rem;color:var(--text-muted)">Incremental step control — bypasses IK</span>
|
<span style="font-size:0.72rem;color:var(--text-muted)">Incremental step control — bypasses IK</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Find Home -->
|
|
||||||
<div style="padding:4px 0 14px;border-bottom:1px solid var(--border);margin-bottom:12px">
|
|
||||||
<div style="font-size:0.70rem;color:var(--text-muted);margin-bottom:8px;line-height:1.5">
|
|
||||||
⚠️ Drives each arm slowly into its mechanical stop then backs off to
|
|
||||||
operating home. Takes ~15–20 s. All controls are locked during homing.
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-full" id="btn-find-home"
|
|
||||||
style="background:rgba(255,152,0,0.12);border:1px solid rgba(255,152,0,0.35);
|
|
||||||
color:var(--accent-amber);font-weight:700;font-size:0.85rem;padding:10px">
|
|
||||||
<span class="home-spin" id="home-spinner" style="display:none"></span>
|
|
||||||
<span id="home-label">⌂ Find Home</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="jog-compact">
|
<div class="jog-compact">
|
||||||
<!-- Motor 1 -->
|
<!-- Motor 1 -->
|
||||||
@@ -818,11 +743,6 @@ const StepperTestSection = {
|
|||||||
// ── BLE NOTIFY position feedback ──────────────────────────────
|
// ── BLE NOTIFY position feedback ──────────────────────────────
|
||||||
function onBLEStatus(e) {
|
function onBLEStatus(e) {
|
||||||
const msg = e.detail;
|
const msg = e.detail;
|
||||||
if (msg === 'HOMED') {
|
|
||||||
// Firmware completed homing — sync browser counters
|
|
||||||
onHomingComplete();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (msg.startsWith('P:')) {
|
if (msg.startsWith('P:')) {
|
||||||
const [s1, s2] = msg.slice(2).split(',').map(Number);
|
const [s1, s2] = msg.slice(2).split(',').map(Number);
|
||||||
steps1 = s1; steps2 = s2;
|
steps1 = s1; steps2 = s2;
|
||||||
@@ -831,9 +751,6 @@ const StepperTestSection = {
|
|||||||
}
|
}
|
||||||
document.addEventListener('ble:status', onBLEStatus);
|
document.addEventListener('ble:status', onBLEStatus);
|
||||||
|
|
||||||
// ── Find Home button ──────────────────────────────────────
|
|
||||||
document.getElementById('btn-find-home').addEventListener('click', startHoming);
|
|
||||||
|
|
||||||
eventCleanup = [
|
eventCleanup = [
|
||||||
() => BLE.off('connected', updateBadge),
|
() => BLE.off('connected', updateBadge),
|
||||||
() => BLE.off('disconnected', updateBadge),
|
() => BLE.off('disconnected', updateBadge),
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
# WijiBoard Wiring Diagram
|
||||||
|
|
||||||
|
This document outlines the hardware connections for the WijiBoard.
|
||||||
|
|
||||||
|
## Hardware Components
|
||||||
|
- **Microcontroller**: Waveshare ESP32-S3-Zero
|
||||||
|
- **Motors**: 2× 28BYJ-48 (5V Stepper Motors)
|
||||||
|
- **Motor Drivers**: 2× ULN2003 Driver Boards
|
||||||
|
- **Power System**:
|
||||||
|
- 4S LiPo Battery
|
||||||
|
- XH-M609 Battery Low-Voltage Disconnect Module
|
||||||
|
- 2× Buck Regulators (step-down converters tuned to 5V)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Power Connections
|
||||||
|
The system is powered by a 4S LiPo battery. To protect the battery from over-discharge, it is connected to an **XH-M609** module. The output of the XH-M609 is split into two separate **Buck Regulators**, both tuned to output 5V.
|
||||||
|
|
||||||
|
1. **Buck Regulator 1 (MCU Power)**: Dedicated to powering the ESP32-S3-Zero.
|
||||||
|
2. **Buck Regulator 2 (Motor Power)**: Dedicated to powering both ULN2003 driver boards to prevent voltage drops or electrical noise from affecting the MCU.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> **Common Ground**: You MUST connect the Ground (GND) from the ESP32 to the Ground (GND) of the ULN2003 drivers. Even though they have separate 5V power supplies, they need a common ground reference for the GPIO logic signals to work correctly.
|
||||||
|
|
||||||
|
| Component | Connection | Destination |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **4S LiPo** | `BAT+` / `BAT-` | XH-M609 `VIN+` / `VIN-` |
|
||||||
|
| **XH-M609** | `VOUT+` / `VOUT-` | Split to **IN+** and **IN-** on both Buck Regulators |
|
||||||
|
| **Buck 1 (MCU)** | `OUT+ (5V)` | ESP32-S3-Zero `5V` (used as power input) |
|
||||||
|
| **Buck 1 (MCU)** | `OUT- (GND)` | ESP32-S3-Zero `GND` |
|
||||||
|
| **Buck 2 (Motors)**| `OUT+ (5V)` | ULN2003 (M1) `+ / 5V` and ULN2003 (M2) `+ / 5V` |
|
||||||
|
| **Buck 2 (Motors)**| `OUT- (GND)` | ULN2003 (M1) `- / GND` and ULN2003 (M2) `- / GND` |
|
||||||
|
| **ESP32-S3-Zero** | `GND` | Tie to the motor ground network (Common Ground) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Motor 1 (Right Motor) Logic
|
||||||
|
**Physical Location**: Right side (pivot at +12.9mm)
|
||||||
|
|
||||||
|
| ESP32-S3-Zero Pin | ULN2003 (Motor 1) Pin |
|
||||||
|
| :--- | :--- |
|
||||||
|
| **GPIO 4** | `IN1` |
|
||||||
|
| **GPIO 5** | `IN2` |
|
||||||
|
| **GPIO 6** | `IN3` |
|
||||||
|
| **GPIO 7** | `IN4` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Motor 2 (Left Motor) Logic
|
||||||
|
**Physical Location**: Left side (pivot at -12.9mm)
|
||||||
|
|
||||||
|
| ESP32-S3-Zero Pin | ULN2003 (Motor 2) Pin |
|
||||||
|
| :--- | :--- |
|
||||||
|
| **GPIO 8** | `IN1` |
|
||||||
|
| **GPIO 9** | `IN2` |
|
||||||
|
| **GPIO 10** | `IN3` |
|
||||||
|
| **GPIO 11** | `IN4` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Stepper to Driver Connections
|
||||||
|
Simply plug the white 5-pin JST connectors from the 28BYJ-48 motors into the corresponding white sockets on their respective ULN2003 driver boards. The connector is keyed and only fits one way.
|
||||||
|
|
||||||
|
## Schematic Overview
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
subgraph Power Delivery
|
||||||
|
BATT[4S LiPo Battery] -->|14.8V| XHM609[XH-M609 Protector]
|
||||||
|
XHM609 -->|14.8V| BUCK1[Buck Regulator 1]
|
||||||
|
XHM609 -->|14.8V| BUCK2[Buck Regulator 2]
|
||||||
|
end
|
||||||
|
|
||||||
|
BUCK1 -->|5V Power| ESP[ESP32-S3-Zero]
|
||||||
|
BUCK2 -->|5V Power| M1_Driver[ULN2003 M1]
|
||||||
|
BUCK2 -->|5V Power| M2_Driver[ULN2003 M2]
|
||||||
|
|
||||||
|
%% Common Ground
|
||||||
|
ESP -. Common Ground .- M1_Driver
|
||||||
|
ESP -. Common Ground .- M2_Driver
|
||||||
|
|
||||||
|
%% Logic Signals
|
||||||
|
ESP -- Pins 4,5,6,7 --> M1_Driver
|
||||||
|
ESP -- Pins 8,9,10,11 --> M2_Driver
|
||||||
|
|
||||||
|
%% Motors
|
||||||
|
M1_Driver == 5-wire cable ==> M1[Motor 1 - Right]
|
||||||
|
M2_Driver == 5-wire cable ==> M2[Motor 2 - Left]
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user