Benutzer-Werkzeuge

Webseiten-Werkzeuge


mbot_streckenfahren

mBot Streckenfahren

Der mBot kann, gesteuert über die Infrarot-Fernbedienung, eine zuvor eingegebene Strecke vorwärts oder rückwärts fahren. Zunächst wird ein allgemeines Grundgerüst des Programmcodes vorgestellt, gefolgt vom kompletten Programmcode. Das fertige Programm benötigt eine vordefinierte Strecke (in cm), die durch schwarze Linien abgegrenzt wird. Diese Linien dienen der Kalibrierung des Roboters. Nach der Kalibrierung kann der mBot eine beliebig eingegebene Strecke vorwärts oder rückwärts fahren.

Grundgerüst

Dieses Abschnitt stellt ein Grundgerüst vor, um den mBot mit einer IR-Fernbedienung zu steuern. Es stellt eine funktionierende Steuerlogik bereit, in der verschiedene Zustände und Befehle verarbeitet werden, um komplexe Abläufe zu ermöglichen.
Die Kernstruktur ist vollständig funktionsfähig und kann flexibel erweitert werden.
Die spezifischen Action-Funktionen (z. B. für Bewegung oder Sensoren) müssen jedoch noch individuell implementiert werden.

☛ Im nächsten Abschnitt werden die Methoden implementiert, um das vollständige Programm zu erhalten.

Um eine elegante Steuerung zu ermöglichen, sollten die Methoden nicht-blockierend implementiert werden.

siehe Abschnitt ☛ Blockierend vs. Nichtblockierende Mehoden

🎮 Bedienung des Programms

Die Steuerung erfolgt über fünf Tasten (E, C, D, F, B) sowie eine Zahleneingabe mit Bestätigung über die Setting-Taste

Taste Funktion Beschreitung
E (Escape) 🛑 Stopp – Beendet aktuelle Aktion Jederzeit aufrufbar
C (Calibration) ⚙️ Kalibrierung starten Erforderlich, um anschließend die gewünschte Strecke fahren zu können.
D (Distance) 📏 Distanz eingeben (1–3 Stellen) Erweiterter Eingabemodus!
F (Forward) ▶️ Vorwärts fahren (eingestellte Distanz) Nach Distanz-Eingabe und Kalibrierung
B (Backward) ◀️ Rückwärts fahren (eingestellte Distanz) Nach Distanz-Eingabe und Kalibrierung
Bedienungsbeispiel: 150 cm vorwärts fahren

Starten Sie das Programm und beobachten Sie den seriellen Monitor, um das Zusammenspiel der Steuerlogik mit der Fernbedienung und den Action-Funktionen zu studieren. Dieses Grundgerüst simuliert die Logik nur.

  1. 🛠️ C drücken → Kalibrierung startet
  2. 📏 D drücken → Distanz-Eingabe aktiv
  3. 🔢 1 5 0 eingeben → 150 Einheiten setzen
  4. Setting drücken → Eingabe bestätigen
  5. 🚀 F drücken → mBot fährt 150 Einheiten (cm) vorwärts

👉 Falls nötig: E drücken um jederzeit eine Aktion abzubrechen!

Die Distanz kann 1- bis 3-stellig eingegeben werden.

Quellcode (engl. Sourcecode)

Listing 1:MinimalesProgramm.ino

#include "MeMCore.h"

// Hardware
MeIR ir;
MeBuzzer buzzer;
MeLineFollower lineFinder(PORT_2);
MeRGBLed led(0, 2);   // must be fixed!
MeDCMotor motor1(M1);
MeDCMotor motor2(M2);

// IR-Entprellen
static uint8_t lastCode = 0xFF;
static unsigned long lastTime = 0;
const unsigned long COOL_DOWN = 1000; // in Millisekunden, also 1 Sekunde

//FSM
enum State {
  STATE_OFF,
  STATE_BACKWARD,
  STATE_CALIBRATION,
  STATE_DISTANCE,
  STATE_FORWARD
};

State state     = STATE_OFF;
State lastState = STATE_OFF;

enum ExState {
  EX_IDLE,
  EX_WAIT_FOR_INPUT
};
ExState exState       = EX_IDLE;
int     inputNumber   = 0;    // Anzahl der eingegebenen Stellen
String  inputBuffer   = "";   // Sammeln der Ziffern als Text
int     inputDistance = 0;    // Konvertierte Eingabe

void setup() {
  led.setpin(13);
  Serial.begin(9600);
  ir.begin();
}

void loop() {
  // step: command (IR-Taste einlesen)
  int cmd = read();
  // step: state bestimmen
  state  = decode(cmd);
  // step: zustandsabhängige Aktion
  switch (state) {
    case STATE_CALIBRATION:
      actionCalibration();
      break;
    case STATE_FORWARD:
      actionForward();
      break;
    case STATE_BACKWARD:
      actionBackward();
      break;
    case STATE_DISTANCE:
      actionDistance();
      break;
    case STATE_OFF:
      actionOff();
      break;
  }
}

/*
  ---------------------------
  Funktionen
  ---------------------------
*/

int read() {
  if (ir.decode()) {
    uint32_t code = getIRCode();
    // Vermeidung wiederholter Eingaben
    unsigned long now = millis();
    if (code == lastCode && (now - lastTime < COOL_DOWN))
      return -1;
    lastCode = code;
    lastTime = now;
    //
    switch (code) {
      case IR_BUTTON_E:
        buzzer.tone(1200, 200);
        return 0;  // STATE_OFF
        break;
      case IR_BUTTON_B:
        buzzer.tone(1200, 200);
        return 1;  // STATE_BACKWARD
        break;
      case IR_BUTTON_C:
        buzzer.tone(1200, 200);
        return 2;  // STATE_CALIBRATION
        break;
      case IR_BUTTON_D:
        initInput();
        return 3;  // STATE_DISTANCE
        break;
      case IR_BUTTON_F:
        buzzer.tone(1200, 200);
        return 4;  // STATE_FORWARD
        break;

      // Ziffern 0–9
      case IR_BUTTON_0: handleInput(0); break;
      case IR_BUTTON_1: handleInput(1); break;
      case IR_BUTTON_2: handleInput(2); break;
      case IR_BUTTON_3: handleInput(3); break;
      case IR_BUTTON_4: handleInput(4); break;
      case IR_BUTTON_5: handleInput(5); break;
      case IR_BUTTON_6: handleInput(6); break;
      case IR_BUTTON_7: handleInput(7); break;
      case IR_BUTTON_8: handleInput(8); break;
      case IR_BUTTON_9: handleInput(9); break;

      // Taste 'Setting' = Eingabe abschließen
      case IR_BUTTON_SETTING:
        finalizeInput();
        return -1;  // Kein direkter Zustandswechsel
        break;
    }
  }
  return -1; // keine bekannte/verwertbare Taste
}

uint32_t getIRCode() {
  uint32_t value = ir.value;
  value = (value >> 16) & 0xff;
  return value;
}

State decode(int cmd) {
  switch (cmd) {
    case 0:
      lastState = STATE_OFF;
      return STATE_OFF;
    case 1:
      lastState = STATE_BACKWARD;
      return STATE_BACKWARD;
    case 2:
      lastState = STATE_CALIBRATION;
      return STATE_CALIBRATION;
    case 3:    
      lastState = STATE_DISTANCE;
      return STATE_DISTANCE;
    case 4:
      lastState = STATE_FORWARD;
      return STATE_FORWARD;
  }
 
  return lastState;
}

/*
  ---------------------------
  Eingabemodus für EX-Befehle
  ---------------------------
*/
void initInput() {
  buzzer.tone(1000, 200);
  exState     = EX_WAIT_FOR_INPUT;
  inputNumber = 0;
  inputBuffer = "";
}

void handleInput(int number) {

  if (exState != EX_WAIT_FOR_INPUT) return;

  // Maximal 3 Stellen erlauben
  if (inputNumber >= 3) {
    buzzer.tone(300, 300);
    delay(200);
    buzzer.tone(300, 300);
    return;
  }

  // Zahl anhängen
  inputBuffer += String(number);
  inputNumber++;
  buzzer.tone(1200, 80);
}

void finalizeInput() {
  if (exState == EX_WAIT_FOR_INPUT) {
    exState = EX_IDLE;
    buzzer.tone(1200, 200);
  }
}

/*
  ---------------------------
  Funktionen ...actionXXX
  ---------------------------
*/

void actionCalibration() {
  Serial.println("...actionCalibration");
}

void actionForward() {
  Serial.println("...actionForward");
}

void actionBackward() {
  Serial.println("...actionBackward");
}

void actionDistance() {

  Serial.println("...actionDistance");
  // noch in der Eingabe?
  if (exState == EX_WAIT_FOR_INPUT) {
    return;
  }
  // step: ...calculate distance
  inputDistance = inputBuffer.toInt();
  if (inputDistance > 200) {
    inputDistance = 200;
  }
  Serial.print("Eingegebene Distance: ");
  Serial.println(inputDistance);
}

void actionOff() {
  Serial.println("...actionOff");
}

Erklärungen zum Quellcode

Hauptprogramm (loop)

loop() führt den Hauptablauf des Programms aus:

  1. Befehl einlesenread()
  2. Zustand bestimmendecode(cmd)
  3. Aktion ausführen → Je nach Zustand eine actionXXX()-Funktion

Die FSM sorgt dafür, dass nur gültige Zustandswechsel erfolgen.

IR-Befehl auslesen (read)

read() verarbeitet Signale der Fernbedienung:

  1. IR-Code empfangen
  2. Entprellung → Mehrfacher Tastendruck wird ignoriert
  3. Befehl in Zustand umwandeln
  4. Falls Zahleneingabe aktivhandleInput()
    1. Bestätigt eine Zahleneingabe mit der Setting-Taste.

Zustandssteuerung (decode)

decode(cmd) weist dem Befehl einen FSM-Zustand zu:

  1. STATE_FORWARD → Vorwärts fahren
  2. STATE_BACKWARD → Rückwärts fahren
  3. STATE_CALIBRATION → Kalibrierung starten
  4. STATE_DISTANCE → Distanz-Eingabe starten
  5. STATE_OFF → Stoppt den Roboter

Bleibt der Befehl unverändert, bleibt der letzte Zustand aktiv.

Zahleneingabe

  1. initInput() → Startet Eingabemodus
  2. handleInput(n) → Fügt Ziffer zur Eingabe hinzu (max. 3 Stellen)
  3. finalizeInput() → Bestätigt die Eingabe

Die eingegebene Zahl wird in inputDistance gespeichert und auf max. 200 Einheiten begrenzt.

Aktionen (Platzhalter)

Die Action-Funktionen enthalten bisher nur serielle Ausgaben:

  1. actionForward() → Gibt „…actionForward“ auf dem Serial Monitor aus
  2. actionBackward() → Gibt „…actionBackward“ aus
  3. actionDistance() → Berechnet und zeigt die eingegebene Distanz
  4. actionCalibration() → Startet Kalibrierung
  5. actionOff() → Setzt den Roboter in den Stopp-Zustand

Um den Roboter tatsächlich zu bewegen, müssen die action-Funktionen implementiert werden.

Programm Steckenfahren

🎮 Verwendung des Programms

Hier ist das vollständige Programm. Die Bedienung bleibt dieselbe wie beim Grundgerüst. Der einzige Unterschied besteht darin, dass die Action-Funktionen nun implementiert wurden.
Das Programm erfordert, dass die Variable calibrationDistance an die vorgegebene Kalibrierungsstrecke angepasst wird. Die Kalibrierungsstrecke muss zuvor noch erstellt werden (siehe Abbildung).

Step 1: Kalibrierung

Die Kalibrierung erfolgt, indem eine bestimmte Strecke (calibrationDistance im Quellcode) abgefahren wird, die durch zwei schwarze Streifen begrenzt ist. calibrationDistance ist im Quellcode fest einprogrammiert (hardcodiert), daher müssen die schwarzen Streifen natürlich dazu passen. 🙂

🔹 Vorgehensweise:

  • 1️⃣ Stellen Sie den mBot auf die erste schwarze Linie.
  • 2️⃣ Kontrollieren Sie, dass die Sensoren des Liniensensors auf der schwarzen Linie befinden!
  • 3️⃣ Drücken Sie C, um die Kalibrierung zu starten.

Nach der Kalibrierung kann der mBot für die Distanzfahrt verwendet werden. Die Kalibrierung kann bei Bedarf jederzeit wiederholt werden.

Step 2: Roboter fahren lassen 8-)
Nach der Kalibrierung kann eine Distanz (D) eingegeben werden und anschließend beliebig oft mit F oder B vorwärts bzw. rückwärts gefahren werden. Drücken Sie E, um es vorzeitig zu beenden.

Falls der Roboter nicht sauber geradeaus fährt, lässt sich das Programm dank seiner modularen Struktur leicht erweitern. Es ist möglich, eine zusätzliche Kalibriermethode zu implementieren, die sicherstellt, dass der Roboter präzise geradeaus fährt.“

Quellcode (engl. Sourcecode)

Listing 1:MinimalesProgramm.ino

#include "MeMCore.h"

// Hardware
MeIR ir;
MeBuzzer buzzer;
MeLineFollower lineFinder(PORT_2);
MeRGBLed led(0, 2);   // must be fixed!
MeDCMotor motor1(M1);
MeDCMotor motor2(M2);

// IR-Entprellen
static uint8_t lastCode       = 0xFF;
static unsigned long lastTime = 0;
const unsigned long COOL_DOWN = 1000; // in Millisekunden, also 1 Sekunde

// Allgemein
unsigned long calibrationTravelTime;
unsigned long currentTravelTime = 100;

// ADJUST AREA START
int calibrationDistance = 40;  // in cm
int speed               = 200; // Motor speed
int dir                 = 1;   // change to -1 if forward drive backwards!
// ADJUST AREA END

// actionCalibration...
unsigned long ac_movingStartTimeForward  = 0;
unsigned long ac_movingTimeForward       = 0;
unsigned long ac_movingStartTimeBackward = 0;
unsigned long ac_movingTimeBackward      = 0;
bool ac_isMoving         = false;
bool ac_isForwardMoving  = false;
bool ac_isBackwardMoving = false;
bool ac_calibrationDone  = false;

// motor movement
bool mm_firstRunMotor = true;
bool mm_motorActive   = false;
unsigned long mm_motorStartTime = 0;

//FSM
enum State {
  STATE_OFF,
  STATE_BACKWARD,
  STATE_CALIBRATION,
  STATE_DISTANCE,
  STATE_FORWARD
};

State state     = STATE_OFF;
State lastState = STATE_OFF;

enum ExState {
  EX_IDLE,
  EX_WAIT_FOR_INPUT
};
ExState exState       = EX_IDLE;

int     inputNumber   = 0;    // Anzahl der eingegebenen Stellen
String  inputBuffer   = "";   // Sammeln der Ziffern als Text
int     inputDistance = 0;    // Konvertierte Eingabe

void setup() {
  led.setpin(13);
  //Serial.begin(9600);  // debugging
  ir.begin();
}

void loop() {
  // step: command (IR-Taste einlesen)
  int cmd = read();
  // step: state bestimmen
  state  = decode(cmd);
  // step: zustandsabhängige Aktion
  switch (state) {
    case STATE_CALIBRATION:
      actionCalibration();
      break;
    case STATE_FORWARD:
      actionForward();
      break;
    case STATE_BACKWARD:
      actionBackward();
      break;
    case STATE_DISTANCE:
      actionDistance();
      break;
    case STATE_OFF:
      actionOff();
      break;
  }
}

/*
  ---------------------------
  Funktionen
  ---------------------------
*/
void setLED(int r, int g, int b) {
  led.setColorAt(0, r, g, b);
  led.setColorAt(1, r, g, b);
  led.show();
}

int read() {
  if (ir.decode()) {
    uint32_t code = getIRCode();

    // Vermeidung wiederholter Eingaben
    unsigned long now = millis();
    if (code == lastCode && (now - lastTime < COOL_DOWN))
      return -1;
    lastCode = code;
    lastTime = now;

    //
    switch (code) {
      case IR_BUTTON_E:
        buzzer.tone(1200, 200);
        return 0;  // STATE_OFF
        break;
      case IR_BUTTON_B:
        buzzer.tone(1200, 200);
        return 1;  // STATE_BACKWARD
        break;
      case IR_BUTTON_C:
        buzzer.tone(1200, 200);
        return 2;  // STATE_CALIBRATION
        break;
      case IR_BUTTON_D:
        initInput();
        return 3;  // STATE_DISTANCE
        break;
      case IR_BUTTON_F:
        buzzer.tone(1200, 200);
        return 4;  // STATE_FORWARD
        break;

      // Ziffern 0–9
      case IR_BUTTON_0: handleInput(0); break;
      case IR_BUTTON_1: handleInput(1); break;
      case IR_BUTTON_2: handleInput(2); break;
      case IR_BUTTON_3: handleInput(3); break;
      case IR_BUTTON_4: handleInput(4); break;
      case IR_BUTTON_5: handleInput(5); break;
      case IR_BUTTON_6: handleInput(6); break;
      case IR_BUTTON_7: handleInput(7); break;
      case IR_BUTTON_8: handleInput(8); break;
      case IR_BUTTON_9: handleInput(9); break;

      // Taste 'Setting' = Eingabe abschließen
      case IR_BUTTON_SETTING:
        finalizeInput();
        return -1;  // Kein direkter Zustandswechsel
        break;
    }
  }
  return -1; // keine bekannte/verwertbare Taste
}

uint32_t getIRCode() {
  uint32_t value = ir.value;
  value = (value >> 16) & 0xff;
  return value;
}

State decode(int cmd) {
  switch (cmd) {
    case 0:
      lastState = STATE_OFF;
      return STATE_OFF;
    case 1:
      lastState = STATE_BACKWARD;
      return STATE_BACKWARD;
    case 2:
      lastState = STATE_CALIBRATION;
      return STATE_CALIBRATION;
    case 3:
      lastState = STATE_DISTANCE;
      return STATE_DISTANCE;
    case 4:
      lastState = STATE_FORWARD;
      return STATE_FORWARD;
  }
  return lastState;
}

/*
  ---------------------------
  Eingabemodus für EX-Befehle
  ---------------------------
*/
void initInput() {
  buzzer.tone(1000, 200);
  exState     = EX_WAIT_FOR_INPUT;
  inputNumber = 0;
  inputBuffer = "";
  setLED(0, 0, 255); // LED BLUE
}

void handleInput(int number) {

  if (exState != EX_WAIT_FOR_INPUT) return;

  // Maximal 3 Stellen erlauben
  if (inputNumber >= 3) {
    buzzer.tone(300, 300);
    delay(200);
    buzzer.tone(300, 300);
    return;
  }

  // Zahl anhängen
  inputBuffer += String(number);
  inputNumber++;
  buzzer.tone(1200, 80);
}

void finalizeInput() {
  if (exState == EX_WAIT_FOR_INPUT) {
    exState = EX_IDLE;
    buzzer.tone(1200, 200);
    setLED(0, 0, 0); // LED OFF
  }
}

/*
  ---------------------------
  Funktionen ...actionXXX
  ---------------------------
*/
void actionCalibration() {

  if (ac_calibrationDone)
    return;

  setLED(255, 0, 0); // RED ON
  int sensorState = lineFinder.readSensors();

  // step - start moving forward
  if (!ac_isMoving) {
    motor1.stop();
    motor2.stop();
    motor1.run(-dir * speed);
    motor2.run( dir * speed);

    ac_movingStartTimeForward = millis();
    ac_isMoving = true;
    ac_isBackwardMoving = true;
    do {
      sensorState = lineFinder.readSensors();
    } while ( sensorState != S1_OUT_S2_OUT);
  }

  // step - end moving forward ... start moving backward
  if (ac_isBackwardMoving && sensorState == S1_IN_S2_IN) {
    motor1.stop();
    motor2.stop();
    ac_movingTimeForward = millis() - ac_movingStartTimeForward;
    motor1.run( dir * speed);
    motor2.run(-dir * speed);
    ac_movingStartTimeBackward = millis();
    do {
      sensorState = lineFinder.readSensors();
    } while ( sensorState != S1_OUT_S2_OUT);
    ac_isBackwardMoving = false;
    ac_isForwardMoving  = true;
  }

  // step - end turing right
  if (ac_isForwardMoving && sensorState == S1_IN_S2_IN) {
    motor1.stop();
    motor2.stop();
    ac_movingTimeBackward = millis() - ac_movingStartTimeBackward;
    calibrationTravelTime = (ac_movingTimeForward + ac_movingTimeBackward) / 2.0;
    ac_calibrationDone = true;
    buzzer.tone(1200, 600);
    lastState = STATE_OFF;
  }
}

void motorMovement(int go_forward ) {
  unsigned long currentMillis = millis();

  // Wenn der Motor aktuell läuft, prüfen wir, ob die Zeit abgelaufen ist
  if (mm_motorActive) {
    if (currentMillis - mm_motorStartTime >= currentTravelTime) {
      motor1.stop();
      motor2.stop();
      mm_motorActive = false;
      state          = STATE_OFF;
      lastState      = STATE_OFF;
    }
    return;
  }

  if (!mm_firstRunMotor)
    return;

  setLED(0, 255, 0); // LED GREEN
  motor1.run(-go_forward * speed);
  motor2.run( go_forward * speed);

  mm_motorStartTime = currentMillis;
  mm_motorActive    = true;
  mm_firstRunMotor  = false;
}

void actionForward() {
  motorMovement(dir);
}
void actionBackward() {
  motorMovement(-dir);
}

void actionDistance() {
  //Serial.println("...actionDistance");
  // noch in der Eingabe?
  if (exState == EX_WAIT_FOR_INPUT)
    return;

  // step: ...calculate distance
  inputDistance = max(0, min(200, inputBuffer.toInt()));

  // step: calculate the travel time (for non blocking-method)
  double v = (1.0 * calibrationDistance) / (1.0 * calibrationTravelTime);
  currentTravelTime = inputDistance / v;
  // Serial.print("Eingegebene Distance: ");
  // Serial.println(inputDistance);
}

void actionOff() {
  setLED(0, 0, 0); // LED OFF
  // reset
  motor1.stop();
  motor2.stop();

  // reset state calibration
  ac_isMoving         = false;
  ac_isForwardMoving  = false;
  ac_isBackwardMoving = false;
  ac_calibrationDone  = false;

  // reset movement
  mm_firstRunMotor    = true;
  mm_motorActive      = false;
  mm_motorStartTime   = 0;

  // reset state distance
  exState       = EX_IDLE;
}

Erklärungen zum Quellcode

⚙️ Setup (setup())

Initialisiert die IR-Fernbedienung und setzt den LED-Pin.

  • ir.begin() → Startet die IR-Signalverarbeitung.
  • led.setpin(13) → Setzt den LED-Steuerpin.

🔄 Loop (loop())

  1. Liest IR-Signale (read())
  2. Bestimmt den aktuellen Zustand (decode())
  3. Führt die passende Aktion aus (`actionXXX()`)

🚦 Zustandsbezogene Aktionen (actionXXX())

  • actionForward() / actionBackward() → startet Motoren für Vorwärts-/Rückwärtsfahrt.
  • actionDistance() → Berechnet die Fahrtzeit basierend auf eingegebener Distanz.
  • actionCalibration() → Bestimmt die Fahrzeit für eine feste Strecke mit dem Linienfolger.
  • actionOff() → Stoppt alle Motoren und setzt den Zustand zurück.

⌨️ Eingabeverarbeitung (handleInput(), finalizeInput())

  1. Ermöglicht die Eingabe einer numerischen Distanz per IR-Fernbedienung.
  2. handleInput(int number) → Fügt eine Zahl zur Eingabe hinzu.
  3. finalizeInput() → Wandelt den Eingabewert in eine Strecke um.

⚙️ Die ADJUST AREA

Hier können drei zentrale Parameter angepasst werden:

  • calibrationDistance = 40 → Standardstrecke für die Kalibrierung in cm.
  • speed = 200 → Geschwindigkeit der Motoren.
  • dir = 1 → Richtungskorrektur für Vorwärtsbewegung.
    • Falls das Fahrzeug rückwärts fährt, auf -1 setzen.

Die Werte beeinflussen das Fahrverhalten direkt und sollten je nach Umgebung angepasst werden.

mbot_streckenfahren.txt · Zuletzt geändert: 2025/03/02 14:38 von torsten.roehl