This module of the RaylFX system controls an Arduino train station for the model railway. The functions include:
- OLED-Departure display with changing content
- Sound-module with verbal announcements
- Train station lighting with fluorescent tube switch on effect
Wiring Diagram
- D2, D3: DFPlayer MP3 module
- D4 – D12: LEDs as fluorescent tubes
- A3: connection of the time signal from the conrol module
- A4, A5: I2C connection to the OLED-display
Attention: At pin A3 (normally A4, but in this case A3) the control signal of the RaylFX control module is applied. It controls the time of day and is mandatory.
Components
- 1x Arduino Nano*
- 1x Breadboard and jumper wires*
- 1x LED-Set*
- 1x OLED-display*
- 1x Electrolytic capacitor* (100 – 470µF)
- DFPlayer-Mini* MP3 player module
- Speaker*
- 1x 1kOhm resistor
Code Settings
This module also offers a number of adjustment options. The lighting of the train station can flicker like fluorescent tubes when switched on. The time that all lamps take to switch on can be set with the variable flickerTimeLights. The higher the value, the longer the lights flicker. The intensity of flickering can be set with the variable flickerSpeed.
The announcements on the OLED display change randomly. The minimum duration per announcements can be set with the variable announcementChangeMin. A time between 0 and the value set in the variable announcementChangeVariableTime is added to it randomly.
The texts on the display are defined in the textSegments array. It can be extended by further texts. For this purpose, the dimensions in the array and the variable announcementCount must be adjusted.
The display automatically shows the current model railway time when switching. The follow-up trains are displayed at 30-minute intervals. So if the current time is 17:21, the departure times of the following trains are displayed 17:51 and 18:21.
The display will also randomly show a message. The text can be adjusted in the variable message.
The DFPlayer Mini is used for the sound output. It plays MP3 and WAV files from an SD card. The SD card must be formatted in the FAT16 or FAT32 file system. In addition, the files must be in the folder “01” and the file names must be sequentially named 001.mp3, 002.mp3, 003.mp3, and so on. As mentioned, WAV files may also be used: 001.mp3, 002.wav, 003.mp3 …
Sample files can be downloaded here:
Further info about the DF-Player on this page: DFPlayer Mini-MP3-Player for Arduino.
int flickerTimeLights = 1000; // the higher the value, the more the fluorescent tubes flicker when switched on int flickerSpeed = 100; // flickering speed of the fluorescent tubes int announcementChangeMin = 2000; // announcement change every x milliseconds int announcementChangeVariableTime = 3000; // the announcement change varies by this value (milliseconds) int mp3Count = 10; // amount of MP3 files on the SD card int mp3Duration[] = {16,16,15,17,17,18,19,18,19,9}; // duration of MP3 files in seconds int mp3Likelihood = 20; // likelihood of MP3s being played, 10 often, 100 rarely int volume = 10; // volume of the DFPlayer (0 – 30); /* Announcements */ int numberOfAnnouncements = 6; // Must be as large as the number of announcement texts from the array textSegments const char* textSegments[6][7] = { {"12", "", "Train passing through!", "RB31", "RB31", "Elsterw-Biehal", "from Coswig (b. Dresden)"}, {"12", "Leipzig Central - Halle (Saale)", "Hannover Central", "RB31", "RB31", "Elsterw-Biehal", "from Coswig (b. Dresden)"}, {"12", "Leipzig Central - Halle (Saale)", "Berlin Central", "RB31", "RB31", "Elsterw-Biehal", "from Coswig (b. Dresden)"}, {"12", "Leipzig Central - Halle (Saale)", "Dresden Central", "RB31", "RB31", "Elsterw-Biehal", "from Coswig (b. Dresden)"}, {"12", "Leipzig Central - Halle (Saale)", "Leipzig Central", "RB31", "RB31", "Elsterw-Biehal", "from Coswig (b. Dresden)"}, {"12", "Leipzig Central - Halle (Saale)", "Hamburg Central", "RB31", "RB31", "Elsterw-Biehal", "from Coswig (b. Dresden)"} }; const char* message[] = { "Due to a signal fault ", "the ICE100 will unfortunately have approx. 25 minutes delay. ", "We appreciate your understanding." };
Code for the RaylFX Train Station Module
Libraries
This project requires the SSD1306Ascii library from Bill Greiman. You can add it by searching for “SSD1306Ascii” in the Arduino menu > Sketch > Include Library > Manage Libraries and installing the current version..
In addition, the library DFRobotDFPlayerMini from DFRobot is used. You can also install this library via Manage Libraries. Here you can simply search for dfplayer.
When uploading you have to make sure that the correct board is selected in the Arduino menu. To do this, “ATmega328P (Old Bootlaoder)” must also be selected in the Processor subitem of the Tools menu. Otherwise you can simply copy the following program code with the above mentioned changes and load it onto the Arduino nano.
#include <Wire.h> #include "SSD1306Ascii.h" #include "SSD1306AsciiWire.h" #include "SoftwareSerial.h" // required for the DFPlayer #include "DFRobotDFPlayerMini.h" // required for the DFPlayer /* Rayl-FX Train Station StartHardware.org/en */ /* ***** ***** Settings ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** ***** */ int flickerTimeLights = 1000; // the higher the value, the more the fluorescent tubes flicker when switched on int flickerSpeed = 100; // flickering speed of the fluorescent tubes int announcementChangeMin = 2000; // announcement change every x milliseconds int announcementChangeVariableTime = 3000; // the announcement change varies by this value (milliseconds) int mp3Count = 10; // amount of MP3 files on the SD card int mp3Duration[] = {16,16,15,17,17,18,19,18,19,9}; // duration of MP3 files in seconds int mp3Likelihood = 20; // likelihood of MP3s being played, 10 often, 100 rarely int volume = 10; // volume of the DFPlayer (0 – 30); /* Announcements */ int numberOfAnnouncements = 6; // Must be as large as the number of announcement texts from the array textSegments const char* textSegments[6][7] = { {"12", "", "Train passing through!", "RB31", "RB31", "Elsterw-Biehal", "from Coswig (b. Dresden)"}, {"12", "Leipzig Central - Halle (Saale)", "Hannover Central", "RB31", "RB31", "Elsterw-Biehal", "from Coswig (b. Dresden)"}, {"12", "Leipzig Central - Halle (Saale)", "Berlin Central", "RB31", "RB31", "Elsterw-Biehal", "from Coswig (b. Dresden)"}, {"12", "Leipzig Central - Halle (Saale)", "Dresden Central", "RB31", "RB31", "Elsterw-Biehal", "from Coswig (b. Dresden)"}, {"12", "Leipzig Central - Halle (Saale)", "Leipzig Central", "RB31", "RB31", "Elsterw-Biehal", "from Coswig (b. Dresden)"}, {"12", "Leipzig Central - Halle (Saale)", "Hamburg Central", "RB31", "RB31", "Elsterw-Biehal", "from Coswig (b. Dresden)"} }; const char* message[] = { "Due to a signal fault ", "the ICE100 will unfortunately have approx. 25 minutes delay. ", "We appreciate your understanding." }; /* ***** ***** From here begins the program code, which does not need to be adjusted ***** ***** ***** ***** */ // 0X3C+SA0 - 0x3C or 0x3D #define I2C_ADDRESS 0x3C uint32_t tickTime = 0; int tickerPosition = 0; #define RTN_CHECK 1 int fluorescentTubesPins[9] = {4, 5, 6, 7, 8, 9, 10, 11, 12}; /* Memory Variables */ int fluorescentTubesState[9] = {1, 1, 1, 1, 1, 1, 1, 1, 1}; TickerState state; // stores the current text pointer of the OLED ticker int thereIsDelay = 0; // is determined at random; 1 = delay, 0 = no delay int announcementChangeVariableTimeAuxiliaryVariable = 0; /* Timer Variables */ long fluorescentTubesTimer[9]; long announcementTimer; /* Variables for the OLED */ SSD1306AsciiWire oled; /* Variables for the MP3 Player*/ long soundTimer = 0; // timer of the DFPlayer long soundTimeout = 0; // stores the playing time of the current MP3 int soundState = 0; // state of the DFPlayers int soundRandom; int theSound; int soundPlaying = false; SoftwareSerial mySoftwareSerial(3, 2); // RX, TX for the DFPlayer DFRobotDFPlayerMini myDFPlayer; // DFPlayer object /* Variables from the control module to determine the time of day */ boolean receive = false; boolean receiveStarted = false; int receiveTimeout = 10; long receiveTimer = 0; int receivedTime = 0; int receivePulse = 0; int lastReceivePulse = 0; int receivePin = 17; int myTime = 8; int myMinutes = 0; long lastTimeSignal; int timeToHour; #define PAYLOAD_SIZE 2 // mandatory for communication with master-module int timeOfDay = 0; // stores timeOfDay of master-module (0 and 255) byte nodePayload[PAYLOAD_SIZE]; // temporarily stores data of master-module void setup() { Serial.begin(115200); // starts the serial communication Wire.begin(); // I2C connection to OLED Wire.setClock(400000L); // I2C connection to OLED mySoftwareSerial.begin(9600); // starts the serial communication for the DFPlayer oled.begin(&Adafruit128x64, I2C_ADDRESS); // start of the OLED for (int i = 0; i < 9; i++) { pinMode(fluorescentTubesPins[i], OUTPUT); digitalWrite(fluorescentTubesPins[i], HIGH); } randomSeed(A2); oled.tickerInit(&state, Adafruit5x7, 0, false, 27, 100); // ticker for the departure display drawInfo(1); /* DFPlayer Setup */ Serial.println(F("Initializing DFPlayer ... ")); if (!myDFPlayer.begin(mySoftwareSerial)) { // use softwareSerial to communicate with the DFPlayer 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(volume); // volume is assigned } void loop() { receiveFunction(); // execute instructions for reception if (receiveStarted == false) { // if no data is currently being received: if (myTime > 22) { // ***** late evening ***** fluorescentTubesOn(); // turn on fluorescent tubes soundOn(); // turn on sound } else if (myTime > 18) { // ***** evening ***** fluorescentTubesOn(); // turn on fluorescent tubes soundOn(); // turn on sound } else if (myTime > 12) { // ***** noon ***** fluorescentTubesOff(); // turn off fluorescent tubes soundOn(); // turn on sound } else if (myTime > 9) { // ***** forenoon ***** fluorescentTubesOff(); // turn off fluorescent tubes soundOn(); // turn on sound } else if (myTime > 7) { // ***** morning ***** fluorescentTubesOff(); // turn on fluorescent tubes soundOn(); // turn on sound } else if (myTime > 0) { // ***** night ***** fluorescentTubesOn(); // turn on fluorescent tubes soundOff(); // turn off sound } if (myMinutes < 40) departureDisplay(); } } void departureDisplay() { int i; if (announcementTimer + announcementChangeMin + announcementChangeVariableTimeAuxiliaryVariable < millis()) { // the second part prevents that a display change blocks the reception of the time signal announcementTimer = millis(); i = random(numberOfAnnouncements); drawInfo(i); thereIsDelay = random(2); announcementChangeVariableTimeAuxiliaryVariable = random(announcementChangeVariableTime ); } if ((thereIsDelay > 0) && (i != 0)) { if (tickTime <= millis()) { tickTime = millis() + 30; oled.setInvertMode(1); int8_t rtn = oled.tickerTick(&state); if (rtn <= RTN_CHECK) { oled.tickerText(&state, message[(tickerPosition++) % 3]); } } } } void drawInfo(int i) { // OLED output oled.clear(); oled.setInvertMode(0); oled.setCursor(0 , 0); oled.setFont(Callibri15); oled.println(textSegments[i][0]); oled.clearToEOL(); oled.setCursor(104 , 0); oled.setFont(Callibri10); oled.println(makeTime()); oled.clearToEOL(); oled.setFont(Callibri10); oled.setCursor(27 , 1); oled.println(textSegments[i][1]); oled.setFont(Callibri14); oled.setCursor(27 , 2); oled.println(textSegments[i][2]); oled.setFont(Callibri10); oled.setCursor(0 , 5); oled.println(makeTime1()); oled.setCursor(0 , 6); oled.println(makeTime2()); oled.setCursor(27 , 5); oled.println(textSegments[i][3]); oled.setCursor(27 , 6); oled.println(textSegments[i][4]); oled.setCursor(54 , 5); oled.println(textSegments[i][5]); oled.setCursor(54 , 6); oled.println(textSegments[i][6]); } String makeTime() { String myTimeString = ""; int myHoursSub = myTime; int myMinutesSub = myMinutes; if (myHoursSub < 10) myTimeString = " " + String(myHoursSub); else myTimeString = String(myHoursSub); if (myMinutesSub < 10) myTimeString += ":0" + String(myMinutesSub); else myTimeString += ":" + String(myMinutesSub); return myTimeString; } String makeTime1() { // next train String myTimeString = ""; int myHoursSub = myTime; int myMinutesSub = myMinutes; // int myMinutesSub1=random(30); if (myMinutesSub > 29) { if (myHoursSub > 22) myHoursSub = 0; else myHoursSub++; } else { myMinutesSub += 30; } if (myHoursSub < 10) myTimeString = " " + String(myHoursSub); else myTimeString = String(myHoursSub); if (myMinutesSub < 10) myTimeString += ":0" + String(myMinutesSub); else myTimeString += ":" + String(myMinutesSub); return myTimeString; } String makeTime2() { // train after the next train String myTimeString = ""; int myHoursSub = myTime; int myMinutesSub = myMinutes; if (myHoursSub >= 23) myHoursSub = 0; else myHoursSub++; if (myHoursSub < 10) myTimeString = " " + String(myHoursSub); else myTimeString = String(myHoursSub); if (myMinutesSub < 10) myTimeString += ":0" + String(myMinutesSub); else myTimeString += ":" + String(myMinutesSub); return myTimeString; } void fluorescentTubesOn() { for (int i = 0; i < 9; i++) { if (random(flickerTimeLights) == 0) fluorescentTubesState[i] = 0; if (fluorescentTubesState[i] != 0) { // when the fluorescent tube is not switched on if (fluorescentTubesState[i] + fluorescentTubesTimer[i] < millis()) { fluorescentTubesState[i] = random(flickerSpeed) + 20; // creates a timeout variable fluorescentTubesTimer[i] = millis(); int myOutput = random(2); digitalWrite(fluorescentTubesPins[i], myOutput); } } else { digitalWrite(fluorescentTubesPins[i], 0); } } } void fluorescentTubesOff() { for (int i = 0; i < 9; i++) { fluorescentTubesState[i]=1; digitalWrite(fluorescentTubesPins[i], HIGH); } /*if (random(switchOffProbability) == 1) { // switching on the fluorescent tubes at random int thePin = random(10); digitalWrite(fluorescentTubesPins[thePin], HIGH); fluorescentTubesState[thePin] = 0; }*/ } void soundOn() { switch (soundState) { case 0: soundRandom = random(mp3Likelihood); if (soundRandom < 1) { soundState = 1; theSound = random(mp3Count ) + 1; soundTimer = millis(); soundTimeout = mp3Duration[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()) { myDFPlayer.pause(); soundPlaying = false; soundState = 0; } break; } } void soundOff() { if (soundPlaying == true) { Serial.println(soundPlaying); myDFPlayer.pause(); soundPlaying = false; } } void receiveFunction() { // receives time of day from control module receivePulse = digitalRead(receivePin); // read out the receive pin if ((receiveTimer + receiveTimeout < millis()) && (receiveStarted == true)) { // on timeout and active reception receiveStarted = false; // end active reception myTime = receivedTime - 1; // store received time receivedTime = 0; // reset the auxiliary variable for time reception timeToHour = millis() - lastTimeSignal; lastTimeSignal = millis(); } // if a pulse is detected at the receive pin that was not there before if ((receivePulse == 0) && (lastReceivePulse == 1)) { receiveTimer = millis(); // restart timer if (receiveStarted == false) receiveStarted = true; // start active reception, if not already done receivedTime++; // there was a pulse, so increase the auxiliary variable to receive time } lastReceivePulse = receivePulse; // remember current state at pin for next pass if (receiveStarted == false) { if (millis() % 100 < 2) { int myNewMinutes = map(millis() - lastTimeSignal, 0, timeToHour, 0, 59); myNewMinutes = constrain(myNewMinutes, 0, 59); if (myNewMinutes != myMinutes) { Serial.print(myTime); Serial.print(":"); Serial.print(myMinutes); Serial.print("\t"); Serial.println(myTime); } myMinutes = myNewMinutes; } } }
Hello sir
I loaded the code and got this error
TRAIN_STATION_OLED_MP3:43: error: ‘TickerState’ does not name a type
TickerState state; // stores the current text pointer of the OLED ticker
^
C:\Users\MIMI\Desktop\TRAIN_STATION_OLED_MP3\TRAIN_STATION_OLED_MP3.ino: In function ‘void setup()’:
TRAIN_STATION_OLED_MP3:87: error: ‘class SSD1306AsciiWire’ has no member named ‘tickerInit’
oled.tickerInit(&state, Adafruit5x7, 0, false, 27, 100); // ticker for the departure display
^
TRAIN_STATION_OLED_MP3:87: error: ‘state’ was not declared in this scope
oled.tickerInit(&state, Adafruit5x7, 0, false, 27, 100); // ticker for the departure display
^
C:\Users\MIMI\Desktop\TRAIN_STATION_OLED_MP3\TRAIN_STATION_OLED_MP3.ino: In function ‘void departureDisplay()’:
TRAIN_STATION_OLED_MP3:145: error: ‘class SSD1306AsciiWire’ has no member named ‘tickerTick’
int8_t rtn = oled.tickerTick(&state);
^
TRAIN_STATION_OLED_MP3:145: error: ‘state’ was not declared in this scope
int8_t rtn = oled.tickerTick(&state);
^
TRAIN_STATION_OLED_MP3:147: error: ‘class SSD1306AsciiWire’ has no member named ‘tickerText’
oled.tickerText(&state, message[(tickerPosition++) % 3]);
21:04
Please help me
Thank you so much
duylong78@gmail.com