Schrittmotorsteuerung: Die Einführung in BLE

Dies ist ein Projekt aus dem Alltag. Für einen Futterspender möchte ich Trockenfutter von oben in ein T-Stück eines Rohres fallen lassen. Im T befindet sich ein Schieber, der durch einen Schrittmotor vorwärts und rückwärts bewegt wird.

​Über BLE kann er programmiert und überwacht werden.

Arduino stepper.ino
#include <Arduino.h>
#include <NimBLEDevice.h>
#include <Preferences.h>
#include <PowerLib.hpp>

// UUIDs für eigenen Service und Characteristics
#define SERVICE_UUID        "6e400001-b5a3-f393-e0a9-e50e24dcca9e"
#define CHAR_MOTOR_UUID     "6e400002-b5a3-f393-e0a9-e50e24dcca9e"
#define CHAR_TEMP_UUID      "6e400003-b5a3-f393-e0a9-e50e24dcca9e"
#define CHAR_WIFI_UUID      "6e400004-b5a3-f393-e0a9-e50e24dcca9e"

#define LED_PIN 46

static NimBLEServer*         pServer      = nullptr;
static NimBLECharacteristic* pCharMotor   = nullptr;
static NimBLECharacteristic* pCharTemp    = nullptr;
static NimBLECharacteristic* pCharWifi    = nullptr;

static bool deviceConnected = false;
static uint8_t motorPosition = 0;

Preferences prefs;

// Callback: Verbindung auf-/abbauen
class ServerCallbacks : public NimBLEServerCallbacks {
  void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) override {
    deviceConnected = true;
    Serial.println("BLE: Client verbunden");
  }
  void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) override {
    deviceConnected = false;
    Serial.printf("BLE: Client getrennt (reason=%d), Advertising neu starten\n", reason);
    NimBLEDevice::startAdvertising();
  }
};

// Callback: Motor-Position geschrieben
class MotorCallbacks : public NimBLECharacteristicCallbacks {
  void onWrite(NimBLECharacteristic* pChar, NimBLEConnInfo& connInfo) override {
    std::string value = pChar->getValue();
    if (value.length() >= 1) {
      uint8_t pos = (uint8_t)value[0];
      if (pos > 100) pos = 100;
      motorPosition = pos;
      Serial.printf("Motor-Sollposition: %u %%\n", motorPosition);
      // TODO: Zielposition an Motor-Task weiterreichen
    }
  }
};

// Callback: WiFi-Credentials geschrieben (Format: "SSID\0PASSWORD")
class WifiCallbacks : public NimBLECharacteristicCallbacks {
  void onWrite(NimBLECharacteristic* pChar, NimBLEConnInfo& connInfo) override {
    std::string value = pChar->getValue();
    size_t nullPos = value.find('\0');
    if (nullPos == std::string::npos || nullPos == value.length() - 1) {
      Serial.println("WiFi: ungueltiges Format, erwarte SSID\\0PASSWORT");
      return;
    }
    std::string ssid = value.substr(0, nullPos);
    std::string pass = value.substr(nullPos + 1);

    prefs.begin("wifi", false);
    prefs.putString("ssid", ssid.c_str());
    prefs.putString("pass", pass.c_str());
    prefs.end();

    Serial.printf("WiFi-Credentials gespeichert: SSID='%s'\n", ssid.c_str());
    // TODO: WiFi-Task anstossen, neu zu verbinden
  }
};

void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println("\n=== BLE-Schrittmotor-Steuerung startet ===");

  // T-FPGA Spannungsprofil programmieren
  if (Axp2101PwrOn() != ESP_OK) {
    Serial.println("FEHLER: T-FPGA Initialisierung fehlgeschlagen!");
  }

  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);

  // BLE initialisieren
  NimBLEDevice::init("ESP32-Stepper");
  NimBLEDevice::setPower(ESP_PWR_LVL_P9);

  pServer = NimBLEDevice::createServer();
  pServer->setCallbacks(new ServerCallbacks());

  NimBLEService* pService = pServer->createService(SERVICE_UUID);

  // Motor-Position: Write, uint8 (0..100)
  pCharMotor = pService->createCharacteristic(
      CHAR_MOTOR_UUID,
      NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR);
  pCharMotor->setCallbacks(new MotorCallbacks());
  uint8_t initMotor = 0;
  pCharMotor->setValue(&initMotor, 1);

  // Temperatur: Read + Notify, float (4 Byte, little-endian)
  pCharTemp = pService->createCharacteristic(
      CHAR_TEMP_UUID,
      NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY);
  float initTemp = 0.0f;
  pCharTemp->setValue((uint8_t*)&initTemp, sizeof(initTemp));

  // WiFi-Credentials: Write (SSID\0PASS)
  pCharWifi = pService->createCharacteristic(
      CHAR_WIFI_UUID,
      NIMBLE_PROPERTY::WRITE);
  pCharWifi->setCallbacks(new WifiCallbacks());

  pService->start();

  // Advertising konfigurieren
  NimBLEAdvertising* pAdv = NimBLEDevice::getAdvertising();
  pAdv->addServiceUUID(SERVICE_UUID);
  pAdv->setName("ESP32-Stepper");
  pAdv->enableScanResponse(true);
  NimBLEDevice::startAdvertising();

  Serial.println("BLE-Advertising laeuft, warte auf Client...");
}

void loop() {
  static uint32_t lastNotify = 0;
  uint32_t now = millis();

  // Dummy-Temperatur alle 1000 ms versenden (spaeter durch echten Sensor ersetzen)
  if (deviceConnected && (now - lastNotify >= 1000)) {
    lastNotify = now;
    float temp = 25.0f + (float)(now % 5000) / 1000.0f;  // Platzhalter
    pCharTemp->setValue((uint8_t*)&temp, sizeof(temp));
    pCharTemp->notify();
    digitalWrite(LED_PIN, !digitalRead(LED_PIN));
  }

  delay(10);
}
Zeile 1