This commit is contained in:
PROFERIS - Mi³osz Stocki
2026-07-15 14:13:46 +02:00
9 changed files with 61 additions and 58 deletions
+8 -8
View File
@@ -25,11 +25,11 @@ only BLE between the ESP32 firmware and a local web SPA.
| **MCU** | Tenstar Robot ESP32-C3 Super Mini |
| **Framework** | PlatformIO + Arduino core |
| **Motors** | 2× 28BYJ-48 (unipolar stepper via ULN2003 driver) |
| **Motor mode** | `AccelStepper::FULL4WIRE`2048 steps/rev |
| **Motor 1 pins** | IN1=0, IN2=1, IN3=3, IN4=4 |
| **Motor 2 pins** | IN1=5, IN2=6, IN3=7, IN4=10 |
| **Motor mode** | `AccelStepper::HALF4WIRE`4096 steps/rev |
| **Motor 1 pins** | IN1=0, IN2=3, IN3=1, IN4=4 |
| **Motor 2 pins** | IN1=5, IN2=7, IN3=6, IN4=10 |
| **USB Serial** | Native USB CDC (`-D ARDUINO_USB_MODE=1 -D ARDUINO_USB_CDC_ON_BOOT=1`) |
| **Default speed** | 600 steps/s, accel 100 steps/s² |
| **Default speed** | 800 steps/s, accel 400 steps/s² |
### Mechanism geometry (critical — do NOT change without hardware verification)
@@ -62,8 +62,8 @@ d = 25.8 mm (full motor separation)
d2 = 12.9 mm (half separation)
l1 = 85.0 mm (proximal link)
l2 = 110.0 mm (distal link)
STEPS_PER_REV = 2048
STEP_ANGLE = 360/2048 ≈ 0.17578125 °/step
STEPS_PER_REV = 4096
STEP_ANGLE = 360/4096 ≈ 0.087890625 °/step
```
---
@@ -454,7 +454,7 @@ To add a new section:
assume a specific physical motor orientation. If the real arm moves in the wrong direction
when clicking a target, negate the step delta for that motor in `moveToXY()`.
- **Home position**: Stall homing is fully implemented matching the original C++ routine but adjusted to align with physical motor mappings. Homing pushes the motors against their physical limits and sets `steps1 = -1024` (-180°, points left) and `steps2 = 0` (0°, points right). This corresponds to the arms folded OUTWARDS.
- **Home position**: Stall homing is fully implemented matching the original C++ routine but adjusted to align with physical motor mappings. Homing pushes the motors against their physical limits and sets `steps1 = 2048` (+180°, points left) and `steps2 = 0` (0°, points right). This corresponds to the arms folded OUTWARDS.
- The initial web simulation starts with these coordinates, meaning the UI assumes the arm has already been homed before the browser connects.
- Homing can be triggered individually per-motor (`HOME1`, `HOME2`) or combined (`HOMEALL`) from the web UI.
@@ -463,7 +463,7 @@ To add a new section:
- `BOX_HALF_W`, `BOX_Y_MAX`: size of the actual motor housing
- `ELBOW_BOX_X_INNER`, `ELBOW_BOX_Y_MAX`: elbow clearance above the housing
- **Step speed / acceleration**: `DEFAULT_SPEED = 600`, `DEFAULT_ACCEL = 100` in firmware.
- **Step speed / acceleration**: `DEFAULT_SPEED = 800`, `DEFAULT_ACCEL = 400` in firmware.
28BYJ-48 motors are slow; these may need adjustment. Adjustable at runtime via `SPD:<n>`
and `ACC:<n>` BLE commands.
+5 -3
View File
@@ -15,9 +15,11 @@
#define M2_IN4 10
// ─── Stepper constants ────────────────────────────────────────────
#define STEPS_PER_REV 2048
#define DEFAULT_SPEED 600.0f
#define DEFAULT_ACCEL 100.0f
#define STEPS_PER_REV 4096
#define MAX_SPEED 1200.0f
#define MAX_ACCEL 500.0f
#define DEFAULT_SPEED 800.0f
#define DEFAULT_ACCEL 400.0f
#define IDLE_TIMEOUT_MS (30 * 60 * 1000)
#define NOTIFY_INTERVAL_MS 200
+20 -20
View File
@@ -2,8 +2,8 @@
#include "Config.h"
MotorController::MotorController()
: stepper1(AccelStepper::FULL4WIRE, M1_IN1, M1_IN2, M1_IN3, M1_IN4),
stepper2(AccelStepper::FULL4WIRE, M2_IN1, M2_IN2, M2_IN3, M2_IN4),
: stepper1(AccelStepper::HALF4WIRE, M1_IN1, M1_IN3, M1_IN2, M1_IN4),
stepper2(AccelStepper::HALF4WIRE, M2_IN1, M2_IN3, M2_IN2, M2_IN4),
currentMaxSpeed(DEFAULT_SPEED),
currentAccel(DEFAULT_ACCEL),
lastMotorActive(0),
@@ -95,35 +95,35 @@ void MotorController::setAcceleration(float acc) {
void MotorController::homeAll() {
Serial.println("[SYS] Homing all: Phase 1 (Stall OUTWARD)");
stepper1.setMaxSpeed(1500);
stepper2.setMaxSpeed(1500);
stepper1.setAcceleration(1500);
stepper2.setAcceleration(1500);
stepper1.move(-2000);
stepper2.move(2000);
stepper1.setMaxSpeed(MAX_SPEED);
stepper2.setMaxSpeed(MAX_SPEED);
stepper1.setAcceleration(MAX_ACCEL);
stepper2.setAcceleration(MAX_ACCEL);
stepper1.move(-4000);
stepper2.move(4000);
homingState = HOME_ALL_PHASE1;
}
void MotorController::home1() {
Serial.println("[S1] Homing Motor 1: Phase 1 (Stall OUTWARD)");
stepper1.setMaxSpeed(1500);
stepper1.setAcceleration(1500);
stepper1.move(-2000);
stepper1.setMaxSpeed(MAX_SPEED);
stepper1.setAcceleration(MAX_ACCEL);
stepper1.move(-4000);
homingState = HOME_1_PHASE1;
}
void MotorController::home2() {
Serial.println("[S2] Homing Motor 2: Phase 1 (Stall OUTWARD)");
stepper2.setMaxSpeed(1500);
stepper2.setAcceleration(1500);
stepper2.move(2000);
stepper2.setMaxSpeed(MAX_SPEED);
stepper2.setAcceleration(MAX_ACCEL);
stepper2.move(4000);
homingState = HOME_2_PHASE1;
}
void MotorController::zeroAll() {
stepper1.setCurrentPosition(1024);
stepper1.setCurrentPosition(2048);
stepper2.setCurrentPosition(0);
Serial.println("[SYS] Position blindly reset to home (1024, 0).");
Serial.println("[SYS] Position blindly reset to home (2048, 0).");
}
void MotorController::disableMotors() {
@@ -136,9 +136,9 @@ void MotorController::disableMotors() {
void MotorController::processHoming() {
if (homingState == HOME_ALL_PHASE1) {
if (stepper1.distanceToGo() == 0 && stepper2.distanceToGo() == 0) {
stepper1.setCurrentPosition(1024);
stepper1.setCurrentPosition(2048);
stepper2.setCurrentPosition(0);
stepper1.moveTo(1024);
stepper1.moveTo(2048);
stepper2.moveTo(0);
homingState = HOME_ALL_PHASE2;
Serial.println("[SYS] Homing all: Phase 2 (Hold Outward Home)");
@@ -154,8 +154,8 @@ void MotorController::processHoming() {
}
} else if (homingState == HOME_1_PHASE1) {
if (stepper1.distanceToGo() == 0) {
stepper1.setCurrentPosition(1024);
stepper1.moveTo(1024);
stepper1.setCurrentPosition(2048);
stepper1.moveTo(2048);
homingState = HOME_1_PHASE2;
}
} else if (homingState == HOME_1_PHASE2) {
+8 -8
View File
@@ -38,9 +38,9 @@ const ARM = {
d2: 12.9, // half separation; M1 at (-d2, 0), M2 at (+d2, 0)
l1: 85.0, // proximal link length (mm)
l2: 110.0, // distal link length (mm)
STEPS_PER_REV: 2048,
STEP_ANGLE_DEG: 360 / 2048, // ≈ 0.17578125 °/step
HOME_STEPS: { m1: 1024, m2: 0 }, // M1 (Left) at +180°, M2 (Right) at 0° (arms folded outward)
STEPS_PER_REV: 4096,
STEP_ANGLE_DEG: 360 / 4096, // ≈ 0.087890625 °/step
HOME_STEPS: { m1: 2048, m2: 0 }, // M1 (Left) at +180°, M2 (Right) at 0° (arms folded outward)
};
// ── WORKSPACE CONSTRAINTS ─────────────────────────────────────────
@@ -279,13 +279,13 @@ function radToSteps(rad) {
function radToStepsAbsolute(rad, motor) {
let steps = radToSteps(rad);
// Motor 1 (Left) is constrained near 1536 steps (90 deg, UP)
// Motor 2 (Right) is constrained near -512 steps (90 deg, UP)
const center = motor === 1 ? 1536 : -512;
// Motor 1 (Left) is constrained near 3072 steps (90 deg, UP)
// Motor 2 (Right) is constrained near -1024 steps (90 deg, UP)
const center = motor === 1 ? 3072 : -1024;
// Snap the step position to the closest equivalent angle around the center.
// This guarantees we always take the "top" physical path and never wrap through the bottom.
while (steps - center > 1024) steps -= 2048;
while (steps - center < -1024) steps += 2048;
while (steps - center > 2048) steps -= 4096;
while (steps - center < -2048) steps += 4096;
return steps;
}
function stepsToDeg(steps) {
+2 -2
View File
@@ -146,13 +146,13 @@ function buildHTML() {
<label class="form-label" style="margin:0;">Speed</label>
<span id="speed-val" style="font-size: 0.8rem; color: var(--text-muted);">${Settings.speed}</span>
</div>
<input type="range" id="bc-slider-speed" min="100" max="2000" step="100" value="${Settings.speed}" style="width:100%; margin-bottom: 8px;">
<input type="range" id="bc-slider-speed" min="100" max="1200" step="100" value="${Settings.speed}" style="width:100%; margin-bottom: 8px;">
<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">
<label class="form-label" style="margin:0;">Accel</label>
<span id="accel-val" style="font-size: 0.8rem; color: var(--text-muted);">${Settings.accel}</span>
</div>
<input type="range" id="bc-slider-accel" min="50" max="2000" step="50" value="${Settings.accel}" style="width:100%;">
<input type="range" id="bc-slider-accel" min="50" max="500" step="50" value="${Settings.accel}" style="width:100%;">
</div>
<div class="form-group" style="margin-bottom: 12px;">
+7 -6
View File
@@ -166,13 +166,14 @@ function buildHTML() {
<label class="form-label" style="margin:0;">Speed</label>
<span id="speed-val" style="font-size: 0.8rem; color: var(--text-muted);">${Settings.speed}</span>
</div>
<input type="range" id="slider-speed" min="100" max="2000" step="100" value="${Settings.speed}" style="width:100%; margin-bottom: 8px;">
<div class="setting-row">
<label class="form-label" style="margin:0;">Accel</label>
<span id="accel-val" style="font-size: 0.8rem; color: var(--text-muted);">${Settings.accel}</span>
<input type="range" id="slider-speed" min="100" max="1200" step="100" value="${Settings.speed}" style="width:100%; margin-bottom: 8px;">
</div>
<div class="form-group" style="margin-bottom:0;">
<div style="display:flex; justify-content:space-between; margin-bottom:4px;">
<label class="form-label" style="margin:0;">Acceleration</label>
<span id="val-accel" style="font-size:0.8rem; color:var(--accent-blue);">${Settings.accel}</span>
</div>
<input type="range" id="slider-accel" min="50" max="2000" step="50" value="${Settings.accel}" style="width:100%; margin-bottom: 8px;">
<input type="range" id="slider-accel" min="50" max="500" step="50" value="${Settings.accel}" style="width:100%; margin-bottom: 8px;">
<div class="setting-row">
<label class="form-label" style="margin:0;">Global Delay (ms)</label>
+2 -2
View File
@@ -298,13 +298,13 @@ function buildHTML() {
<label class="form-label" style="margin:0;">Speed</label>
<span id="speed-val" style="font-size: 0.8rem; color: var(--text-muted);">${Settings.speed}</span>
</div>
<input type="range" id="slider-speed" min="100" max="2000" step="100" value="${Settings.speed}" style="width:100%; margin-bottom: 8px;">
<input type="range" id="slider-speed" min="100" max="1200" step="100" value="${Settings.speed}" style="width:100%; margin-bottom: 8px;">
<div class="setting-row">
<label class="form-label" style="margin:0;">Accel</label>
<span id="accel-val" style="font-size: 0.8rem; color: var(--text-muted);">${Settings.accel}</span>
</div>
<input type="range" id="slider-accel" min="50" max="2000" step="50" value="${Settings.accel}" style="width:100%; margin-bottom: 8px;">
<input type="range" id="slider-accel" min="50" max="500" step="50" value="${Settings.accel}" style="width:100%; margin-bottom: 8px;">
<div class="setting-row">
<label class="form-label" style="margin:0;">Global Delay (ms)</label>
+7 -7
View File
@@ -56,9 +56,9 @@ function syncStateFromStorage() {
steps2 = isHomed ? parseInt(localStorage.getItem('wiji_steps2') || IK.ARM.HOME_STEPS.m2) : IK.ARM.HOME_STEPS.m2;
// Auto-migrate the old buggy negative home position to the new positive one
if (steps1 === -1024) {
steps1 = 1024;
localStorage.setItem('wiji_steps1', 1024);
if (steps1 === -2048 || steps1 === -1024 || steps1 === 1024) {
steps1 = 2048;
localStorage.setItem('wiji_steps1', 2048);
}
Motion.setSteps(steps1, steps2);
}
@@ -570,16 +570,16 @@ function buildHTML() {
<label class="form-label">Speed (steps/s)</label>
<div class="range-row">
<input class="form-range" type="range" id="speed-slider"
min="100" max="2000" step="50" value="600"/>
<span class="range-value" id="speed-val">600</span>
min="100" max="1200" step="50" value="800"/>
<span class="range-value" id="speed-val">800</span>
</div>
</div>
<div class="form-group">
<label class="form-label">Accel (steps/s²)</label>
<div class="range-row">
<input class="form-range" type="range" id="accel-slider"
min="50" max="2000" step="25" value="100"/>
<span class="range-value" id="accel-val">100</span>
min="50" max="500" step="25" value="400"/>
<span class="range-value" id="accel-val">400</span>
</div>
</div>
</div>
+2 -2
View File
@@ -6,8 +6,8 @@
*/
const DEFAULTS = {
speed: 600,
accel: 100,
speed: 800,
accel: 400,
delay: 2500
};