RailFX Jahrmarkt-Modul

Das erste Modul der RailFX-Serie ist das Jahrmarkt-Modul. Es handelt sich dabei um verschiedene Effekte, die man auf einem Jahrmarkt erwarten darf:

  • Riesenrad
  • Karussell
  • Lauflicht
  • Straßenbeleuchtung
  • Stroboskop
  • Soundmodul

Video – RailFX, Jahrmarkt-Modul

Wir benötigen Ihre Zustimmung um den Inhalt von YouTube laden zu können.

Mit dem Klick auf das Video werden durch den mit uns gemeinsam Verantwortlichen Youtube [Google Ireland Limited, Irland] das Video abgespielt, auf Ihrem Endgerät Skripte geladen, Cookies gespeichert und personenbezogene Daten erfasst. Damit kann Google Aktivitäten im Internet verfolgen und Werbung zielgruppengerecht ausspielen. Es erfolgt eine Datenübermittlung in die USA, diese verfügt über keinen EU-konformen Datenschutz. Weitere Informationen finden Sie hier.

PGlmcmFtZSB0aXRsZT0iQXJkdWlubyB1bmQgTW9kZWxsYmFobiDigJMgRWZmZWt0ZSBtaXQgZGVtIFJhaWxGWC1TeXN0ZW0iIHdpZHRoPSI2NDAiIGhlaWdodD0iMzYwIiBzcmM9Imh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL2VtYmVkL1gwLU5DNnhMWXdVP2ZlYXR1cmU9b2VtYmVkIiBmcmFtZWJvcmRlcj0iMCIgYWxsb3c9ImFjY2VsZXJvbWV0ZXI7IGF1dG9wbGF5OyBjbGlwYm9hcmQtd3JpdGU7IGVuY3J5cHRlZC1tZWRpYTsgZ3lyb3Njb3BlOyBwaWN0dXJlLWluLXBpY3R1cmUiIGFsbG93ZnVsbHNjcmVlbj48L2lmcmFtZT4=

Bauteile

Schaltplan

RailFX Jahrmarkt Modul Schaltplan

Wie alle RailFX-Module benötigt auch das Jahrmarkt-Modul Verbindungen zum Control-Modul (Davon benötigt man nur ein einziges Modul im gesamten System!). Dabei handelt es sich um eine Verbindung aller A4 Pins (benutzt wird dieser Pin als digitaler Input 18) und eine Verbindung zur Stromversorgung (5V+ und GND). Das grüne Kabel zwischen Control-Modul und Jahrmarkts-Modul ist überflüssig – sorry.

Achtung: Am Pin A4 ist wird das Control-Signal des RailFX Control-Moduls angelegt. Es steuert die Tageszeit und ist zwingend erforderlich.

RailFX Effekte für Modellbahn und Modellbau mit Arduino Nano  Jahrmarkt-Modul Überblick

Bei dem Riesenrad und Karussell handelt es sich um zwei Schrittmotoren, die die Modelle der jeweiligen Fahrgeschäfte antreiben. Beide Schrittmotoren werden über jeweils einen Schrittmotortreiber gesteuert.

Die LEDs für die Lauflichter, die Straßenbeleuchtung als auch das Stroboskop sind über einen ULN2803A Darlington-Transistor-Array verstärkt. Dadurch lassen sich theoretisch bis zu 500mA pro Kanal schalten. In der Realität ist das sicherlich weniger. Trotzdem lassen sich hier pro Output mehrere LEDs in Reihe schalten. So kann man z.B. ein längeres Lauflicht realisieren. Wie viele genau musst du entweder ausprobieren oder berechnen (und mir dann bitte bescheid sagen.) Wenn es wesentlich mehr LEDs sein sollen, muss das Netzteil dementsprechend groß dimensioniert sein.

Für die Tonausgabe wird der DFPlayer Mini verwendet. Er spielt MP3- und WAV-Dateien von einer SD-Karte ab. Die SD-Karte muss im FAT16 oder FAT32-Dateisystem formatiert sein. Darüber hinaus müssen sich die Dateien im Ordner »01« befinden und die Dateinamen müssen aufeinander folgend 001.mp3, 002.mp3, 003.mp3 usw. benannt sein. Wie erwähnt, dürfen auch WAV-Dateien verwendet werden: 001.mp3, 002.wav, 003.mp3 …

RailFX Effekte für Modellbahn und Modellbau mit Arduino Nano Schaltplan des Jahrmarkt-Moduls Close-Up

Einstellungen im Code


int lauflichtTimeout = 100;              // Lauflicht Geschwindigkeit (ms)
int strobeFlashTimeout = 100;            // Stroboskop Impulsdauer (ms)
int strobePauseTimeout = 1000;           // Stroboskop Pausendauer (ms)
int riesenradPauseTimeout = 3;           // Riesenrad Pausendauer (s)
int karussellPauseTimeout = 2;           // Karussell Pausendauer (s)
int strassenlampenTimeout = 100;         // Einschaltzeit der Straßenlampen (ms)
int lautstaerke = 10;                    // Lautstärke des DFPlayers (0 – 30);

int mp3Anzahl = 7;                       // Menge der MP3 Dateien auf der SD-Karte
int mp3Dauer[] = {28, 9, 20, 2, 4, 17, 23}; // Dauer der MP3 Dateien in Sekunden
int mp3Wahrscheinlichkeit = 10;          // Wahrscheinlichkeit, mit der MP3s abgespielt werden, 10 oft, 100 selten

Im Code lassen sich unterschiedliche Einstellungen treffen:

  • Lauflicht Geschwindigkeit in Millisekunden
  • Stroboskop Impulsdauer in Millisekunden
  • Stroboskop Pausendauer in Millisekunden
  • Riesenrad Pausendauer in Sekunden
  • Karussell Pausendauer in Sekunden
  • Einschaltzeit der Straßenlampen in Millisekunden
  • Lautstärke des DFPlayers (0 – 30);
  • Menge der MP3 Dateien auf der SD-Karte
  • Dauer der MP3 Dateien in Sekunden (kann kürzer als die MP3s sein)
  • Wahrscheinlichkeit, mit der MP3s abgespielt werden: 10 oft, 100 selten

Gesamter Programm-Code

/*
     Rail-FX Jahrmarkt-Modul
     StartHardware.org
*/

#include <CheapStepper.h>
#include "SoftwareSerial.h"              // Wird für den DFPlayer benötigt
#include "DFRobotDFPlayerMini.h"         // Wird für den DFPlayer benötigt

/* ***** ***** Einstellungen ***** ***** ***** *****  ***** ***** ***** *****  ***** ***** ***** ***** */

int meineAdresse = 2;                    // muss im System einzigartig sein! Darf es also nur einmal geben.

int lauflichtTimeout = 100;              // Lauflicht Geschwindigkeit (ms)
int strobeFlashTimeout = 100;            // Stroboskop Impulsdauer (ms)
int strobePauseTimeout = 1000;           // Stroboskop Pausendauer (ms)
int riesenradPauseTimeout = 3;           // Riesenrad Pausendauer (s)
int karussellPauseTimeout = 2;           // Karussell Pausendauer (s)
int strassenlampenTimeout = 100;         // Einschaltzeit der Straßenlampen (ms)
int lautstaerke = 10;                    // Lautstärke des DFPlayers (0 – 30);

int mp3Anzahl = 7;                       // Menge der MP3 Dateien auf der SD-Karte
int mp3Dauer[] = {28, 9, 20, 2, 4, 17, 23}; // Dauer der MP3 Dateien in Sekunden
int mp3Wahrscheinlichkeit = 10;          // Wahrscheinlichkeit, mit der MP3s abgespielt werden, 10 oft, 100 selten

/* ***** ***** Ab hier beginnt der Programmcode, der nicht angepasst werden muss ***** ***** ***** ***** */

int strassenlampenPin = 9;              // an diesem Pin sind die Straßenlampen angeschlossen
int lauflicht1Pin = 8;                  // an diesem Pin sind die Lauflicht A Lampen 2 angeschlossen
int lauflicht2Pin = 7;                  // an diesem Pin sind die Lauflicht A Lampen 3 angeschlossen
int lauflicht3Pin = 6;                  // an diesem Pin sind die Lauflicht B Lampen 1 angeschlossen
int lauflicht4Pin = 5;                  // an diesem Pin sind die Lauflicht B Lampen 2 angeschlossen
int strobePin = 4;                      // an diesem Pin ist ein Stroboskop (weiße LED?) angeschlossen

//int motorPins1[4] = {10, 11, 12, 13};    // Pins des Stepper-Motors 1
//int motorPins2[4] = {14, 15, 16, 17};    // Pins des Stepper-Motors 2

/* Speicher-Variablen */
int lauflichtPosition = 0;              // speichert die Position des Lauflichts A
int strassenlampenHelligkeit = 0;       // speichert, wie hell die Straßenlampen leuchten

/* Timer Variablen */
long strassenlampenTimer = 0;            // Timer der Straßenlampen
long lauflichtTimer = 0;                 // Timer Lauflicht
long strobeTimer = 0;                    // Timer des Stroboskop
long riesenradTimer = 0;                 // Timer des Risenrads
long karussellTimer = 0;                 // Timer des Karussells
long soundTimer = 0;                     // Timer des DFPlayers
long soundTimeout = 0;                   // Speichert die Abspieldauer des aktuellen MP3
long moveStartTime1 = 0; // this will save the time (millis()) when we started each new move
long moveStartTime2 = 0; // this will save the time (millis()) when we started each new move

/* State Variablen */
int myState = 3;                         // Status der Haupt-Statemachine, startet bei 3, da hier alles abgeschaltet ist
int riesenradState = 0;                  // Status des Riesenrads
int karussellState = 0;                  // Status des Karussell
int strobeState = 0;                     // Status des Stroboskops
int soundState = 0;                      // Status des DFPlayers
bool moveClockwise21 = true;
bool moveClockwise22 = true;
int stepsLeft1 = 0;
int stepsLeft2 = 0;

int soundRandom;
int theSound;
int soundPlaying = false;

bool moveClockwise1 = true;
bool moveClockwise2 = true;

String stateName;

/* Variablen vom Controlmodul um die Uhrzeit festzustellen*/
boolean receive = false;
boolean receiveStarted = false;
int receiveTimeout = 10;
long receiveTimer = 0;
int receivedTime = 0;
int receivePulse = 0;
int lastReceivePulse = 0;
int receivePin = 18;
int myTime = 0;

/* Objekte anlegen */
SoftwareSerial mySoftwareSerial(3, 2);   // RX, TX für den DFPlayer
DFRobotDFPlayerMini myDFPlayer;          // DFPlayer Objekt

CheapStepper stepper1 (14, 15, 16, 17);  // Erstellen eines Stepperobjekts für das Risenrad
CheapStepper stepper2 (10, 11, 12, 13);  // Erstellen eines Stepperobjekts für das Karussell

#define PAYLOAD_SIZE 2                   // nötig für die Kommunikation zum Master
int uhrzeit = 0;                         // speichert die uhrzeit vom Master-Modul (0 und 255)
byte nodePayload[PAYLOAD_SIZE];          // speichert die Daten vom Master-Modul zwischen

void setup() {
  Serial.begin(115200);                  // started die serielle Kommunikation
  pinMode(receivePin, INPUT);            // Empfangspin vom Control-Modul
  mySoftwareSerial.begin(9600);          // started die serielle Kommunikation für den DFPlayer
  pinMode(lauflicht1Pin, OUTPUT);        // Lauflicht als Output festlegen
  pinMode(lauflicht2Pin, OUTPUT);        // Lauflicht als Output festlegen
  pinMode(lauflicht3Pin, OUTPUT);        // Lauflicht als Output festlegen
  pinMode(lauflicht4Pin, OUTPUT);        // Lauflicht als Output festlegen
  pinMode(strobePin, OUTPUT);            // Strobe als Output festlegen
  lauflichtAus();
  strobeAus();
  strassenlampenAus();
  stepper1.setRpm(16);
  stepper2.setRpm(12);


  /* DFPlayer Setup */
  Serial.println(F("Initializing DFPlayer ... "));
  if (!myDFPlayer.begin(mySoftwareSerial)) {  // nutze softwareSerial um mit dem DFPlayer zu sprechen
    Serial.println(F("Fehler: Prüfe Verbindung zum DFPlayer und SD-Karte"));
    /*while (true) {
      delay(0); // Code to compatible with ESP8266 watch dog.
      }*/
  }
  Serial.println(F("DFPlayer Mini online."));
  myDFPlayer.volume(lautstaerke);       // Lautstärke wird zugewiesen
}
/*
  void receiveEvent(int bytes) {
  while (Wire.available()) {
    uhrzeit = Wire.read();                // speichert die empfangenen Zeitdaten in der Variable uhrzeit
    if (uhrzeit > 22) {
      stateName = "später Abend";
      myState = 0; // später Abend
    } else if (uhrzeit > 18) {
      myState = 5; // Abend
      stateName = "Abend";
    } else if (uhrzeit > 12) {
      myState = 4; // Nachmittag
      stateName = "Nachmittag";
    } else if (uhrzeit > 9) {
      myState = 3; // Vormittag
      stateName = "Vormittag";
    } else if (uhrzeit > 7) {
      myState = 2;  // Morgen
      stateName = "Morgen";
    } else {
      myState = 1;  // Nacht
      stateName = "Nacht";
    }

    Serial.print(millis()); Serial.print("\t uhrzeit: \t"); Serial.print(uhrzeit);
    Serial.print("\t myState: \t"); Serial.print(myState); Serial.print(" — "); Serial.println(stateName);
  }
  }*/

void loop() {

  receiveFunction();              // Führe Anweisungen für Empfang aus
  if (receiveStarted == false) {
    if (myTime > 22) {              // ***** Später Abend *****
      strassenlampenAn();           // Straßenlampen an
      lauflichtAn();                // Lauflicht an
      strobeAn();                   // Stroboskop an
      soundAn();                    // Sound An
      riesenradmotorAn();           // Risenradmotor an
      karussellmotorAn();           // Karussellmotor an
    } else if (myTime > 18) {       // ***** Abend *****
      strassenlampenAn();          // Straßenlampen an
      lauflichtAn();               // Lauflicht an
      strobeAn();                  // Stroboskop an
      soundAn();                   // Sound An
      riesenradmotorAn();          // Risenradmotor an
      karussellmotorAn();          // Karussellmotor an
    } else if (myTime > 12) {       // ***** Nachmittag *****
      strassenlampenAus();          // Straßenlampen aus
      lauflichtAus();               // Lauflicht aus
      strobeAus();                  // Stroboskop aus
      soundAus();                   // Sound aus
      riesenradmotorAn();           // Risenradmotor an
      karussellmotorAus();          // Karussellmotor aus

    } else if (myTime > 9) {        // ***** Vormittag *****
      strassenlampenAus();          // Straßenlampen aus
      lauflichtAus();               // Lauflicht aus
      strobeAus();                  // Stroboskop aus
      soundAus();                   // Sound aus
      riesenradmotorAus();          // Risenradmotor aus
      karussellmotorAus();          // Karussellmotor aus

    } else if (myTime > 7) {        // ***** Morgen *****
      strassenlampenAn();           // Straßenlampen an
      lauflichtAus();               // Lauflicht aus
      strobeAus();                  // Stroboskop aus
      soundAus();                   // Sound aus
      riesenradmotorAus();          // Risenradmotor aus
      karussellmotorAus();          // Karussellmotor aus

    } else {                        // ***** Nacht *****
      strassenlampenAn();           // Straßenlampen an
      lauflichtAus();                // Lauflicht aus
      strobeAus();                  // Stroboskop aus
      soundAus();                   // Sound aus
      riesenradmotorAus();          // Risenradmotor aus
      karussellmotorAus();          // Karussellmotor aus
    }
  }
}

void strassenlampenAn() {
  if (strassenlampenTimer + strassenlampenTimeout < millis()) {
    strassenlampenHelligkeit++;
    if (strassenlampenHelligkeit > 255) strassenlampenHelligkeit = 255;
    strassenlampenTimer = millis();
    analogWrite(strassenlampenPin, strassenlampenHelligkeit);
  }
}

void strassenlampenAus() {
  strassenlampenHelligkeit = 0;
  analogWrite(strassenlampenPin, 0);
  strassenlampenTimer = millis();
}

void lauflichtAn() {
  if (lauflichtTimer + lauflichtTimeout < millis()) {
    lauflichtTimer = millis();
    lauflichtPosition++;
    if (lauflichtPosition > 3) {
      lauflichtPosition = 0;
    }
    if (lauflichtPosition == 0) {
      digitalWrite(lauflicht1Pin, HIGH);
      digitalWrite(lauflicht2Pin, HIGH);
      digitalWrite(lauflicht3Pin, HIGH);
      digitalWrite(lauflicht4Pin, LOW);
    } else if (lauflichtPosition == 1) {
      digitalWrite(lauflicht1Pin, HIGH);
      digitalWrite(lauflicht2Pin, HIGH);
      digitalWrite(lauflicht3Pin, LOW);
      digitalWrite(lauflicht4Pin, HIGH);
    } else if (lauflichtPosition == 2) {
      digitalWrite(lauflicht1Pin, HIGH);
      digitalWrite(lauflicht2Pin, LOW);
      digitalWrite(lauflicht3Pin, HIGH);
      digitalWrite(lauflicht4Pin, HIGH);
    } else if (lauflichtPosition == 3) {
      digitalWrite(lauflicht1Pin, LOW);
      digitalWrite(lauflicht2Pin, HIGH);
      digitalWrite(lauflicht3Pin, HIGH);
      digitalWrite(lauflicht4Pin, HIGH);
    }
  }
}

void lauflichtAus() {
  lauflichtTimer = millis();
  digitalWrite(lauflicht1Pin, LOW);
  digitalWrite(lauflicht2Pin, LOW);
  digitalWrite(lauflicht3Pin, LOW);
  digitalWrite(lauflicht4Pin, LOW);
}

void strobeAn() {
  switch (strobeState) {
    case 0:
      digitalWrite(strobePin, HIGH);
      if (strobeTimer + strobeFlashTimeout < millis()) {
        strobeTimer = millis();
        strobeState = 1;
      }
      break;
    case 1:
      digitalWrite(strobePin, LOW);
      if (strobeTimer + strobePauseTimeout < millis()) {
        strobeState = 0;
        strobeTimer = millis();
      }
      break;
  }
}

void strobeAus() {
  digitalWrite(strobePin, LOW);
  strobeTimer = millis();
}

void riesenradmotorAn() {
  stepper1.run();

  switch (riesenradState) {
    case 0: // fahren
      stepsLeft1 = stepper1.getStepsLeft();
      if (stepsLeft1 == 0) {
        moveClockwise1 = !moveClockwise1;
        if (random(3) <= 1) {
          riesenradState = 1;
          riesenradTimer = millis();
        } else {
          stepper1.newMoveDegrees (moveClockwise1, 360 + random(360));
        }
      }
      break;
    case 1: // pause
      if (riesenradTimer + (riesenradPauseTimeout * 1000) < millis()) {
        riesenradState = 0;
      }
      break;
  }
}

void riesenradmotorAus() {
  /*stepsLeft1 = stepper1.getStepsLeft();
    if (stepsLeft1>0) stepper1.stop();*/
}

void karussellmotorAn() {
  stepper2.run();
  switch (karussellState) {
    case 0: // fahren
      stepsLeft2 = stepper2.getStepsLeft();
      if (stepsLeft2 == 0) {
        moveClockwise2 = !moveClockwise2;
        if (random(5) <= 1) {
          karussellState = 1;
          karussellTimer = millis();
        } else {
          stepper2.newMoveDegrees (moveClockwise2, 90 + random(360));
        }
      }
      break;
    case 1: // pause
      if (karussellTimer + (karussellPauseTimeout * 1000) < millis()) {
        karussellState = 0;
      }
      break;
  }
}

void karussellmotorAus() {
  /*stepsLeft2 = stepper2.getStepsLeft();
    if (stepsLeft2>0) stepper2.stop();*/
}

void soundAn() {
  switch (soundState) {
    case 0:
      Serial.println("soundTimer 0");
      soundRandom = random(mp3Wahrscheinlichkeit);
      if (soundRandom < 1) {
        soundState = 1;
        theSound = random(mp3Anzahl) + 1;
        soundTimer = millis();
        soundTimeout = mp3Dauer[theSound - 1] * 1000;
        Serial.print("Playsound: \t\t\t"); Serial.print(theSound); Serial.print("\t\t\t"); Serial.println(soundTimeout);
        myDFPlayer.playFolder(1, theSound); //play specific mp3 in SD:/15/004.mp3; Folder Name(1~99); File Name(1~255)
        soundPlaying = true;
      } else {
        soundTimer = millis();
        soundTimeout = 500;
        soundState = 1;
      }
      break;
    case 1:
      if (soundTimer + soundTimeout < millis()) {
        soundPlaying = false;
        soundState = 0;
      }
      break;
  }
}


void soundAus() {
  if (soundPlaying == true) {
    Serial.println(soundPlaying);
    myDFPlayer.pause();
    soundPlaying = false;
  }
}


void printDetail(uint8_t type, int value) {     // DF Player Fehlermeldungen
  switch (type) {
    case TimeOut:
      Serial.println(F("Time Out!"));
      break;
    case WrongStack:
      Serial.println(F("Stack Wrong!"));
      break;
    case DFPlayerCardInserted:
      Serial.println(F("Card Inserted!"));
      break;
    case DFPlayerCardRemoved:
      Serial.println(F("Card Removed!"));
      break;
    case DFPlayerCardOnline:
      Serial.println(F("Card Online!"));
      break;
    case DFPlayerUSBInserted:
      Serial.println("USB Inserted!");
      break;
    case DFPlayerUSBRemoved:
      Serial.println("USB Removed!");
      break;
    case DFPlayerPlayFinished:
      Serial.print(F("Number:"));
      Serial.print(value);
      Serial.println(F(" Play Finished!"));
      break;
    case DFPlayerError:
      Serial.print(F("DFPlayerError:"));
      switch (value) {
        case Busy:
          Serial.println(F("Card not found"));
          break;
        case Sleeping:
          Serial.println(F("Sleeping"));
          break;
        case SerialWrongStack:
          Serial.println(F("Get Wrong Stack"));
          break;
        case CheckSumNotMatch:
          Serial.println(F("Check Sum Not Match"));
          break;
        case FileIndexOut:
          Serial.println(F("File Index Out of Bound"));
          break;
        case FileMismatch:
          Serial.println(F("Cannot Find File"));
          break;
        case Advertise:
          Serial.println(F("In Advertise"));
          break;
        default:
          break;
      }
      break;
    default:
      break;
  }
}


void receiveFunction() {                      // Empfängt die Uhrzeit vom Control-Modul
  receivePulse = digitalRead(receivePin);     // Lies den Empfangspin aus
  
  if ((receiveTimer + receiveTimeout < millis()) && (receiveStarted == true)) {
    // Bei Timeout und aktivem Empfang
    receiveStarted = false;                   // beende aktiven Empfang
    myTime = receivedTime - 1;                // speichere die empfangene Zeit
    receivedTime = 0;                         // setze die Hilfsvariable zum Zeitempfang zurück
    Serial.println(myTime);                   // serielle Ausgabe
  }
  // falls ein Puls am Empfangspin erfasst wird, der vorher nicht da war
  if ((receivePulse == 0) && (lastReceivePulse == 1)) {
    receiveTimer = millis();                  // starte Timer neu
    if (receiveStarted == false) receiveStarted = true;  // starte aktiven Empfang, wenn noch nicht geschehen
    receivedTime++;                           // es gab einen Puls, also erhöhe die Hilfsvariable zum Zeitempfang
  }
  lastReceivePulse = receivePulse;            // aktuellen Zustand am Pin für nächsten Durchlauf merken
}

RailFX-Module

Zurzeit gibt es folgende Projekt-Module:


Wenn dir das Projekt gefallen hat und du von weiteren interessanten Projekten inspiriert werden willst, sieh dir doch mal mein neues E-Book an!

Darin findest du die beliebtesten Arduino-Projekte von StartHardware. Jedes Projekt umfasst Schaltplan, Bauteile, Beschreibung und Code. Für Einsteiger gibt es ein Arduino-Schnellstart-Kapitel und einen kompakten Programmierkurs. Zusätzlich findest du Zwischenkapitel mit Expertenwissen zu den Komponenten, die verwendet werden. Alle Code-Beispiele gibt es natürlich als Download.

Weitere Informationen findest du auf der Produktseite. Klicke jetzt auf den Button, um Details anzusehen.


Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

 

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.

2 Kommentare zu »RailFX Jahrmarkt-Modul«