Files
ESP32-WijiBoard/src/main.cpp
T
PROFERIS - Mi³osz Stocki cf71803fc4 kinematics
2026-07-08 13:12:12 +02:00

348 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* WijiBoard BLE Stepper Controller Firmware
* src/main.cpp
*
* Board : Tenstar Robot ESP32-C3 Super Mini (or compatible ESP32-C3)
* Steppers: Two 28BYJ-48 via ULN2003 (AccelStepper, FULL4WIRE mode)
*
* BLE Command Characteristic (WRITE_NR):
* S1+<n> → Step motor 1 CW n steps
* S1-<n> → Step motor 1 CCW n steps
* S2+<n> → Step motor 2 CW n steps
* S2-<n> → Step motor 2 CCW n steps
* SPD:<n> → Set max speed for both motors (steps/sec)
* ACC:<n> → Set acceleration for both motors (steps/sec²)
* HOME1 → Home motor 1 (Right)
* HOME2 → Home motor 2 (Left)
* HOMEALL → Home both motors simultaneously
* POS → Request current positions (triggers NOTIFY)
*
* BLE Status Characteristic (NOTIFY):
* P:<s1>,<s2> → Current step positions for motor 1 & 2
*
* UUIDs must match web/js/ble.js exactly:
* Service : a0b1c2d3-e4f5-6789-abcd-ef0123456700
* Command : a0b1c2d3-e4f5-6789-abcd-ef0123456701
* Status : a0b1c2d3-e4f5-6789-abcd-ef0123456702
*/
#include <AccelStepper.h>
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
// ─── Motor pin mapping (28BYJ-48 / ULN2003) ──────────────────────
// Motor 1 Shoulder
#define M1_IN1 0
#define M1_IN2 1
#define M1_IN3 3
#define M1_IN4 4
// Motor 2 Elbow
#define M2_IN1 5
#define M2_IN2 6
#define M2_IN3 7
#define M2_IN4 10
// ─── Stepper constants ────────────────────────────────────────────
#define STEPS_PER_REV 2048
#define DEFAULT_SPEED 600.0f // steps/sec
#define DEFAULT_ACCEL 100.0f // steps/sec²
// ─── BLE UUIDs ────────────────────────────────────────────────────
#define SERVICE_UUID "18f3b235-9831-4c75-8ec0-210469b820a0"
#define CMD_UUID "cd083b06-4447-4cf3-a7c3-322ecf802ce4"
#define STATUS_UUID "82e38c5b-d3ab-41d1-861c-b84dc6bb1e03"
#define DEVICE_NAME "WijiBoard"
// ─── Stepper objects ──────────────────────────────────────────────
AccelStepper stepper1(AccelStepper::FULL4WIRE, M1_IN1, M1_IN2, M1_IN3, M1_IN4);
AccelStepper stepper2(AccelStepper::FULL4WIRE, M2_IN1, M2_IN2, M2_IN3, M2_IN4);
// ─── BLE globals ─────────────────────────────────────────────────
BLEServer *pServer = nullptr;
BLECharacteristic *pCmdChar = nullptr;
BLECharacteristic *pStatusChar = nullptr;
bool bleConnected = false;
// ─── Status notify throttle ───────────────────────────────────────
unsigned long lastNotify = 0;
const unsigned long NOTIFY_INTERVAL_MS = 200;
// ─── Forward declarations ─────────────────────────────────────────
void parseCommand(const String &cmd);
void sendPosition();
void performHomingAll();
void performHoming1();
void performHoming2();
// ─── Homing Routines ──────────────────────────────────────────────
void runSteppersWithNotify() {
stepper1.run();
stepper2.run();
if (bleConnected) {
unsigned long now = millis();
if (now - lastNotify >= NOTIFY_INTERVAL_MS) {
lastNotify = now;
sendPosition();
}
}
}
// ─── BLE Server Callbacks ─────────────────────────────────────────
class ServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer *) override {
bleConnected = true;
Serial.println("[BLE] Client connected");
}
void onDisconnect(BLEServer *) override {
bleConnected = false;
Serial.println("[BLE] Client disconnected restarting advertising");
BLEDevice::startAdvertising();
}
};
// ─── Command Characteristic Callbacks ────────────────────────────
class CmdCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pChar) override {
String val = pChar->getValue().c_str();
val.trim();
if (val.length() == 0)
return;
Serial.printf("[CMD] Received: '%s'\n", val.c_str());
parseCommand(val);
}
};
// ─── Command parser ───────────────────────────────────────────────
void parseCommand(const String &cmd) {
// S1+<n> or S1-<n> (Relative)
if (cmd.startsWith("S1") && cmd.length() > 2) {
long steps = cmd.substring(2).toInt();
stepper1.move(steps);
Serial.printf("[S1] Move relative %+ld steps\n", steps);
return;
}
// S2+<n> or S2-<n> (Relative)
if (cmd.startsWith("S2") && cmd.length() > 2) {
long steps = cmd.substring(2).toInt();
stepper2.move(steps);
Serial.printf("[S2] Move relative %+ld steps\n", steps);
return;
}
// X1:<n> (Absolute)
if (cmd.startsWith("X1:")) {
long target = cmd.substring(3).toInt();
stepper1.moveTo(target);
Serial.printf("[S1] Move absolute to %ld\n", target);
return;
}
// X2:<n> (Absolute)
if (cmd.startsWith("X2:")) {
long target = cmd.substring(3).toInt();
stepper2.moveTo(target);
Serial.printf("[S2] Move absolute to %ld\n", target);
return;
}
// SPD:<n>
if (cmd.startsWith("SPD:")) {
float spd = cmd.substring(4).toFloat();
stepper1.setMaxSpeed(spd);
stepper2.setMaxSpeed(spd);
Serial.printf("[CFG] Max speed → %.0f steps/sec\n", spd);
return;
}
// ACC:<n>
if (cmd.startsWith("ACC:")) {
float acc = cmd.substring(4).toFloat();
stepper1.setAcceleration(acc);
stepper2.setAcceleration(acc);
Serial.printf("[CFG] Acceleration → %.0f steps/sec²\n", acc);
return;
}
// HOME1
if (cmd == "HOME1") {
Serial.println("[S1] Homing Motor 1...");
performHoming1();
sendPosition();
return;
}
// HOME2
if (cmd == "HOME2") {
Serial.println("[S2] Homing Motor 2...");
performHoming2();
sendPosition();
return;
}
// HOMEALL
if (cmd == "HOMEALL") {
Serial.println("[SYS] Homing both motors...");
performHomingAll();
sendPosition();
return;
}
// POS explicit position request
if (cmd == "POS") {
sendPosition();
return;
}
Serial.printf("[CMD] Unknown command: '%s'\n", cmd.c_str());
}
// ─── Send current positions via BLE NOTIFY ────────────────────────
void sendPosition() {
if (!bleConnected || !pStatusChar)
return;
String pos = "P:" + String(stepper1.currentPosition()) + "," +
String(stepper2.currentPosition());
pStatusChar->setValue(pos.c_str());
pStatusChar->notify();
Serial.printf("[POS] %s\n", pos.c_str());
}
// ─── Setup ────────────────────────────────────────────────────────
void setup() {
Serial.setTxTimeoutMs(0);
Serial.begin(115200);
delay(1000); // Wait for USB CDC to enumerate
Serial.println("\n[BOOT] WijiBoard Stepper Controller");
// ── Init steppers ──────────────────────────────────────────
stepper1.setMaxSpeed(DEFAULT_SPEED);
stepper1.setAcceleration(DEFAULT_ACCEL);
stepper1.setCurrentPosition(0);
stepper2.setMaxSpeed(DEFAULT_SPEED);
stepper2.setAcceleration(DEFAULT_ACCEL);
stepper2.setCurrentPosition(0);
Serial.println("[STEP] Steppers initialized");
// ── Init BLE ───────────────────────────────────────────────
BLEDevice::init(DEVICE_NAME);
pServer = BLEDevice::createServer();
pServer->setCallbacks(new ServerCallbacks());
// Service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Command characteristic (Write without response)
pCmdChar = pService->createCharacteristic(CMD_UUID,
BLECharacteristic::PROPERTY_WRITE);
pCmdChar->setCallbacks(new CmdCallbacks());
// Status characteristic (Notify)
pStatusChar = pService->createCharacteristic(
STATUS_UUID, BLECharacteristic::PROPERTY_NOTIFY);
// pStatusChar->addDescriptor(new BLE2902()); // Removed due to
// deprecation/NimBLE conflicts
pService->start();
// Advertising
BLEAdvertising *pAdv = BLEDevice::getAdvertising();
pAdv->addServiceUUID(SERVICE_UUID);
pAdv->setScanResponse(true);
pAdv->setMinPreferred(0x06); // 7.5ms
pAdv->setMaxPreferred(0x0C); // 15ms - CRITICAL for Linux/BlueZ stability
BLEDevice::startAdvertising();
Serial.printf("[BLE] Advertising as '%s' ready!\n", DEVICE_NAME);
}
// ─── Loop ─────────────────────────────────────────────────────────
void loop() {
// Run steppers (non-blocking AccelStepper)
stepper1.run();
stepper2.run();
// Periodic position notify while motors are moving
if (bleConnected) {
unsigned long now = millis();
bool moving = stepper1.isRunning() || stepper2.isRunning();
if (moving && (now - lastNotify >= NOTIFY_INTERVAL_MS)) {
lastNotify = now;
sendPosition();
}
}
}
// ─── Homing Routines ──────────────────────────────────────────────
void performHomingAll() {
Serial.println("[SYS] Homing all: Phase 1 (Stall UP)");
// 1. Swing BOTH motors UP to hit the mechanism housing (stall point)
// M1 (Left) swings UP by moving CW (negative)
// M2 (Right) swings UP by moving CCW (positive)
stepper1.move(-2048);
stepper2.move(2048);
while (stepper1.distanceToGo() != 0 || stepper2.distanceToGo() != 0) {
runSteppersWithNotify();
}
// 2. At this point, BOTH are stalled against the mechanism housing.
// We know the physical angles of these stall points!
// M1 stall is at +474 steps (83.3 degrees).
// M2 stall is at +530 steps (93.1 degrees).
stepper1.setCurrentPosition(474);
stepper2.setCurrentPosition(530);
Serial.println("[SYS] Homing all: Phase 2 (Move to Outward Home)");
// 3. Move BOTH motors to their outward home positions
// M1 goes to +1024 (180 degrees, pointing Left)
// M2 goes to 0 (0 degrees, pointing Right)
stepper1.moveTo(1024);
stepper2.moveTo(0);
while (stepper1.distanceToGo() != 0 || stepper2.distanceToGo() != 0) {
runSteppersWithNotify();
}
// 4. Disable outputs to rest
stepper1.disableOutputs();
stepper2.disableOutputs();
Serial.println("[SYS] Homing all complete.");
}
void performHoming1() {
Serial.println("[S1] Homing Motor 1: Phase 1 (Stall UP)");
stepper1.move(-2048);
while (stepper1.distanceToGo() != 0) {
runSteppersWithNotify();
}
stepper1.setCurrentPosition(474);
Serial.println("[S1] Homing Motor 1: Phase 2 (Move Outward)");
stepper1.moveTo(1024);
while (stepper1.distanceToGo() != 0) {
runSteppersWithNotify();
}
stepper1.disableOutputs();
Serial.println("[S1] Homing complete.");
}
void performHoming2() {
Serial.println("[S2] Homing Motor 2: Phase 1 (Stall UP)");
stepper2.move(2048);
while (stepper2.distanceToGo() != 0) {
runSteppersWithNotify();
}
stepper2.setCurrentPosition(530);
Serial.println("[S2] Homing Motor 2: Phase 2 (Move Outward)");
stepper2.moveTo(0);
while (stepper2.distanceToGo() != 0) {
runSteppersWithNotify();
}
stepper2.disableOutputs();
Serial.println("[S2] Homing complete.");
}