Discover our locomotive, a 1:32 scale reproduction of the iconic EMD F7 diesel-electric engine, engineered to bring realistic train behavior to a compact and fully functional model. Designed through detailed CAD work and supported by custom electronics, this miniature locomotive blends mechanical precision with intelligent embedded control. Powered by an ESP32 microcontroller, it manages speed, acceleration, and braking in real time while detecting stations or track markers to perform automated stop–wait–go sequences. It supports both autonomous operation and Wi-Fi remote control, dynamically selecting routes by commanding turnout switches to follow programmed itineraries. With rail-based power collection and isolated supply systems for logic and motors, the project unites mechanical design, power electronics, sensing, and software into a cohesive small-scale railway platform.
Start by downloading all the STL files provided:
Bogie Wheel Housing, Connector.stl.zip
Front piece locomotive.stl.zip
- Print two bogies along with four wheel housings/connectors for each, which will later be screwed directly onto the bogies.
- Print the plain bearings in PETG for durability and low friction.
- Print the locomotive body.
- Print all pieces of the removable roof to allow access to the electronics.
- Print two horns and six ladders for decoration
- Print the gears in PETG: for each bogie, one with a 6.3 mm hole and five with a 5.3 mm hole .
- Use the provided file to laser-cut the base plate that supports the internal components: Base Plate.stl.zip
The wheels used in this project are metal wheels, manufactured in the mechanical workshop. To reproduce the locomotive, you have two options: machine your own metal wheels, or purchase compatible model railway metal wheels, ensuring that their dimensions match the bogie design. The wheels must be designed with a slight conical profile (angled tread). This angle is essential: it helps the wheels naturally self-center on the rails and ensures smooth rolling through curves without derailment. Flat wheels are strongly discouraged, as they lead to unstable motion and increased friction. Inside each wheel, a plastic bushing is used. These bushings electrically isolate the wheel from the axle while reducing friction during rotation. The bushings are press-fit into the wheel: they are pushed into place using a press or workshop tool so that they remain firmly fixed during operation. Once inserted, the metal axle passes through the bushing, allowing the wheel to rotate freely while maintaining electrical isolation where needed.
Follow the assembly animations, (they can be found further down), carefully to mount the bogies, drivetrain, motors, and the full mechanical structure onto the base. The animation shows the correct order and positioning of all parts and should be followed step by step.
For each bogie, three metal rods with a diameter of 5 mm are required. Two of these rods are used as wheel axles, while the third one serves as the main axle for the drivetrain. The rods must be cut to length before assembly. The wheel axles should be long enough to correctly position the wheels on the rails, ensuring proper alignment and smooth rolling. The main axle must be cut to a length that allows the gears to be correctly positioned and aligned with the motor shaft. All critical dimensions and placements are shown in the animation.
When assembling the drivetrain, make sure to insert the appropriate nuts into the nut traps provided in the gears before tightening the screws. This ensures a secure and rigid fixation of the gears on their axles. If any gear is not perfectly stable on its shaft (this is most common for the gear mounted on the motor shaft) you can improve the fit by adding thin plastic shims (for example small plastic bands) around the shaft or axle. This helps take up the play and prevents slipping during operation.
Once assembled, verify that all gears rotate freely, the bearings are properly seated, and no excessive friction is present. A smooth and consistent rotation is essential for reliable motion.
- Wire the electrical system using the diagrams in the repository.
- Pay attention to ESP32 pin mappings, motor driver connections, and power-rail isolation.
- The final wiring layout should match the reference diagram which is further down.
All the firmware required to run the locomotive is provided in this repository.
- Open the Arduino IDE on your computer.
- In Tools → Board, select WEMOS D1 R32.
- Open the main Arduino project file from this repository in the Arduino IDE.
- Connect the WEMOS D1 R32 to your computer using a micro-USB cable.
Before uploading the code, it is very important that the ESP32 is powered only via USB:
- Do not connect the battery
- Do not connect the VIN and GND from the buck converter
- Do not connect the VIN and GND of the rail power
- Upload the code to the board using the Arduino IDE.
- Wait until the upload completes successfully.
Once the code has been uploaded, disconnect the USB cable and reconnect the power lines in the following order:
- Connect VIN and GND from the buck converter to the ESP32
- Connect the battery
- Connect the + and − terminals of the rail power input
Following this order ensures stable power-up of the system and prevents unintended current paths through the ESP32 during startup.
After this, the locomotive is ready to be powered and controlled.
-
Power on the locomotive and wait for the ESP32 to initialize.
-
Connect your device to the Wi-Fi network locomotive
- Password:
12345678
- Password:
-
Open a browser and go to 192.168.4.1.
-
Use the interface to:
- Trigger the sounds
- Move the locomotive forward
- Move it backward
- Adjust speed
- Configure stops
- 1 ESP32 microcontroller
- 1 IBT-4 motor driver
- 1 buck converter
- 1 bridge rectifier
- 1 Hall-effect magnetic sensor
- 1 VL53L1X time-of-flight sensor
- 1 MAX98357A audio amplifier and speaker
- 2 white LEDs (front lights)
- 2 red LEDs (rear lights)
- 5 resistors (330 Ω)
- Wiring and connectors
- 2 brushed DC motors (Igarashi type)
- Ball bearings
- Plastic bushings
- Metal wheelsets and axles
- 3D-printed bogies, gearbox, roof, and locomotive body
- Laser-cut base plate
- Inserts, screws, and fasteners
Our main mechanical objective was to design a locomotive capable of moving forward and backward smoothly while remaining compact and mechanically robust. To achieve this, we first focused on the drivetrain architecture and the way motion would be transmitted from the motor to the wheels.
We took inspiration from a LEGO train model presented during the course, which helped us understand how gears could be arranged efficiently in a confined space. Because our motor is mounted vertically while the wheel axles are horizontal, we needed to change the direction of rotation. This led us to design a drivetrain based on conical gears, which allow power to be transmitted between perpendicular axes. This choice was essential to keep the motor inside the locomotive body while maintaining a realistic bogie layout.
Once the drivetrain principle was defined, we focused on designing the bogie, which is one of the most important mechanical parts of the locomotive. The bogie had to be strong, compact, easy to assemble, and able to hold all mechanical and electrical elements.
Aesthetically, we were inspired by the real EMD F7 bogie, but we quickly realized that practicality was also important. The final design resembles an H-shaped structure, which provides good rigidity while leaving enough space for the gears, ball bearings, axles, and motor.
The bogie was carefully designed with assembly in mind. We added:
- Mounting holes for the motor screws
- Openings for routing motor and pickup cables
- Dedicated mounting points for the power pickup tongues
- A central opening that allows access with a wrench to tighten the motor gear
The wheel housings were designed as separate parts. This was necessary because the wheels and the plastic bushings inside them must be press-fit onto the axles using workshop machinery. This operation cannot be done once the axles are already mounted on the bogie. Therefore, the wheels are first mounted on the axles, and the complete axle assembly is then fixed onto the bogie using the wheel housings.
In order for the locomotive to go through curves, the bogies must be able to rotate freely relative to the locomotive body, while still supporting the full weight of the train.
To achieve this, we designed a cylindrical interface on top of each bogie. This cylindrical part is fixed to the bogie and surrounds the motor at its upper section. Its role is to guide and stabilize the rotation while keeping the bogie correctly aligned under the locomotive.
On top of each bogie, we added plain bearings, which act as a low-friction rotational interface between the bogie and the locomotive base plate. These bearings allow the bogie to rotate smoothly while supporting vertical loads, preventing unwanted tilting or mechanical stress.
The locomotive body is a 3D-printed reproduction inspired by the EMD F7. Due to 3D printer size limitations, we printed the structure in two main parts and glued them together. The roof was designed to be removable, allowing easy access to the electronics for debugging and maintenance.
We added holes in the front and rear of the structure to mount the lighting system: two white LEDs at the front and two red LEDs at the rear, matching the appearance of a real locomotive. One of the circular roof openings was also reused to place the audio amplifier, allowing sound to be emitted clearly while remaining integrated into the design.
The connection between the bogies and the locomotive structure is ensured by a laser-cut base plate. This base plate plays a central role: it connects both bogies and also supports most of the electronic components.
The base plate was designed with multiple screw holes so that electronic boards and components can be securely fixed. Since space inside the locomotive body was limited, we added a second small floor, attached to the base plate, to hold additional components. The width of the base plate was intentionally made slightly smaller than the locomotive body so that it remains hidden once the structure is mounted on top.
The locomotive body is screwed directly onto the base plate, ensuring a rigid and reliable assembly. The plain bearings placed on top of each bogie interface directly with the base plate, allowing smooth rotation while keeping the entire structure aligned.
To power the locomotive through the rails, we designed a simple and reliable current pickup system integrated directly into each bogie. The rails are powered by an external power supply, and the electrical current is collected from the wheels using flexible pickup tongues. For this purpose, we used thin phosphor bronze sheet, a material commonly used in model railway applications. Phosphor bronze offers a good balance between electrical conductivity, mechanical flexibility, and wear resistance, allowing continuous contact with the wheels without excessive friction.
Each bogie has four wheels, and therefore requires four pickup tongues, one per wheel. The pickup tongues are cut from the phosphor bronze sheet into small rectangular strips. Their dimensions are chosen so that each tongue gently presses against the inner surface of its corresponding wheel, ensuring constant electrical contact while allowing the wheel to rotate freely.
To mount the pickup tongues onto the bogie, a hole is drilled in each strip to allow it to be fixed using a screw. Once screwed to the bogie, the elasticity of the phosphor bronze provides a spring effect, maintaining reliable contact with the wheel.
This approach provides a simple, robust, and easily adjustable power pickup system, while keeping the design compact and compatible with the mechanical constraints of the bogie.
The electronics of the locomotive are responsible for motor control, sensing, lighting, sound generation, and wireless communication. Our main objective was to design a reliable and compact system capable of operating continuously while being powered directly from the rails, in a way that resembles real railway systems.
The core of the system is an ESP32 microcontroller, which handles motor commands, sensor processing, sound generation, and the embedded web interface. Around it, we designed a power distribution and protection scheme to safely supply all components from a single external power source.
The locomotive is powered through the rails using an external laboratory power supply set to 12 V, with a current limit of approximately 5 A. This power is injected into the rails and collected through pickup tongues mounted on the wheels.
Since the onboard electronics cannot operate directly at 12 V, the collected voltage is regulated using a buck converter. This converter provides a stable 5 V rail, which is used to power:
- The ESP32 (via its 5 V input)
- Logic-level electronics
- Sensors and peripheral modules
Internally, the ESP32 regulates this 5 V input down to 3.3 V, which is the operating voltage of the microcontroller itself. This architecture allows us to power the entire system from the rails while respecting the voltage requirements of each component.
The locomotive uses two brushed DC motors mounted in the bogies. These motors are driven by an H-bridge motor driver, allowing:
- Forward motion
- Backward motion
- Electronic stopping using PWM control
The motor driver is powered directly from the regulated supply, while control signals are provided by the ESP32. This separation ensures that high motor currents do not pass through the microcontroller, improving reliability and reducing electrical noise.
Several sensors extend the locomotive beyond simple remote control:
- A Hall-effect sensor detects magnets, allowing the locomotive to identify stations.
- A VL53L1X time-of-flight distance sensor measures the distance to obstacles in front of the locomotive and can automatically stop the train if an object is detected too close.
These sensors operate at logic-level voltages and communicate with the ESP32 using digital I/O and I²C, making them easy to integrate into the system.
To improve realism and provide visual feedback, the locomotive is equipped with directional lighting, similar to a real diesel locomotive.
The lighting system consists of:
- Two white LEDs at the front
- Two red LEDs at the rear
The LEDs are controlled directly by the ESP32 using GPIO pins.
Each group of LEDs (front and rear) is wired as follows:
- All front white LEDs are connected in parallel
- All rear red LEDs are connected in parallel
- Each LED has its own current-limiting resistor connected in series
Using individual resistors ensures that the current is evenly limited for each LED, preventing brightness imbalance or damage due to small manufacturing differences between LEDs.
Each LED group is controlled by a single GPIO pin:
- One GPIO pin controls all front LEDs
- One GPIO pin controls all rear LEDs
This means that all front lights turn on or off together, and the same applies to the rear lights.
To add sound feedback, we integrated a small audio system based on an I2S digital audio interface. The ESP32 generates audio samples and streams them to a MAX98357A amplifier, which drives a small speaker mounted inside the locomotive body.
This system allows the locomotive to:
- Play a horn sound on command
- Emit station-related audio cues
- Provide audible feedback during operation
By using I2S instead of analog audio output, we achieved better sound quality and reduced noise from the motors.
IMG_3809.2.MOV
In addition to rail power, we added a small onboard battery dedicated to the ESP32. This battery ensures that the microcontroller can remain powered during brief contact losses between the wheels and the rails, preventing unwanted resets.
All electronic components are mounted on a laser-cut base plate, which also serves as the structural interface between the bogies and the locomotive body. A second elevated platform was added to accommodate components that could not fit on a single layer.
The base plate was designed slightly narrower than the locomotive shell so that the structure fully covers the electronics once assembled. Cable routing holes and mounting points were carefully planned to keep wiring compact and accessible.
Although the wiring density is relatively high, grouping all electronics in a single area simplifies debugging, maintenance, and future modifications.
The software architecture of the locomotive was designed to remain modular, readable, and easy to extend. Instead of placing all the logic inside a single Arduino file, the code is split into dedicated modules, each responsible for a specific subsystem. This structure made debugging easier and allowed us to reason clearly about how mechanical actions, sensors, and user commands interact.
At a high level, the software is organized around four main components:
- a web-based user interface served directly by the ESP32
- a motor and motion control layer
- sensor handling for autonomous behavior
- an audio system for sound feedback and announcements
These components are coordinated from the main loop but remain largely independent.
Rather than using an external mobile application, the ESP32 hosts its own lightweight web interface. When powered on, the ESP32 creates a Wi-Fi access point and starts an HTTP server. Any device connected to this network can control the locomotive by opening the provided IP address in a browser.
Each button or control in the web interface corresponds to a specific HTTP route handled by the WebUI class. For example, pressing the “Forward” button triggers a request that calls the following function:
void WebUI::handleForward() {
motors_->setSpeed((uint8_t)(*dutyCycle_));
motors_->setDirection(Direction::Forward);
lights_->set(Direction::Forward);
server_.send(200, "text/plain", "Forward");
}This function illustrates the general software philosophy of the project:
- the web interface does not directly manipulate hardware
- it delegates actions to the appropriate subsystem (motors, lights)
- it remains simple and easy to understand
The same structure is used for backward motion, stopping, and speed updates. The speed slider sends a value between 0 and 255, which is constrained and stored as a shared duty cycle:
void WebUI::handleSpeed() {
if (server_.hasArg("d")) {
*dutyCycle_ = constrain(server_.arg("d").toInt(), 0, 255);
motors_->setSpeed((uint8_t)(*dutyCycle_));
server_.send(200, "text/plain", String("Speed=") + *dutyCycle_);
}
}This approach keeps the web layer thin while ensuring consistent behavior across the system.
The locomotive uses two DC motors driven by H-bridge. The MotorControl class abstracts this hardware layer and exposes simple high-level functions such as setting direction, setting speed, or stopping.
Internally, the class stores the current direction and speed, and applies them consistently through a single function:
void MotorControl::apply() {
if (dir_ == Direction::Forward) {
analogWrite(in1_, speed_);
analogWrite(in2_, 0);
analogWrite(in3_, speed_);
analogWrite(in4_, 0);
} else if (dir_ == Direction::Backward) {
analogWrite(in1_, 0);
analogWrite(in2_, speed_);
analogWrite(in3_, 0);
analogWrite(in4_, speed_);
} else {
analogWrite(in1_, 0);
analogWrite(in2_, 0);
analogWrite(in3_, 0);
analogWrite(in4_, 0);
}
}By centralizing all PWM updates in this function, we avoid duplicated logic and ensure that direction changes and speed updates remain synchronized.
The lighting system is intentionally simple but tightly integrated with motion. Two sets of LEDs are used: white lights at the front and red lights at the rear.
The Lights class updates the LEDs automatically depending on the current direction of travel:
void Lights::set(Direction dir) {
if (dir == Direction::Forward) {
digitalWrite(front_, HIGH);
digitalWrite(rear_, LOW);
} else if (dir == Direction::Backward) {
digitalWrite(front_, LOW);
digitalWrite(rear_, HIGH);
} else {
off();
}
}This ensures that the visual behavior of the locomotive always matches its motion, without requiring additional logic in the web interface or main loop.
To add a layer of autonomy and safety, the locomotive uses a VL53L1X time-of-flight distance sensor mounted at the front. The LaserDistance class handles initialization, measurement, and decision-making.
During operation, the sensor is polled periodically. If an obstacle is detected closer than a predefined threshold while the locomotive is moving forward, the motors are stopped automatically:
bool LaserDistance::updateAndAutoStop(MotorControl& motors) {
if (!vl53_.dataReady()) return false;
int16_t dist = vl53_.distance();
vl53_.clearInterrupt();
if (motors.direction() == Direction::Forward &&
dist > 0 && dist < (int16_t)stopThresholdMm_) {
motors.stop();
return true;
}
return false;
}This feature allows the locomotive to react independently of user input and prevents collisions during demonstrations.
Stations are detected using a Hall-effect sensor placed near the rails, with magnets embedded in the track. Each magnet corresponds to a station.
The HallSensor class implements a debounced detection system. It ensures that each magnet is counted only once and prevents false triggers caused by vibrations or prolonged magnetic fields.
bool HallSensor::update() {
int state = digitalRead(pin_);
if (armed_ && lastState_ == HIGH && state == LOW) {
armed_ = false;
return true;
}
lastState_ = state;
return false;
}This logic enables the following feature: the user can decide whether or not the train will stop at the next station it will encounter. By default,the train never stops when arriving at a station.
Sound feedback is handled by a dedicated audio subsystem based on I2S and a MAX98357A amplifier. Audio samples are stored as PCM data and streamed directly from the ESP32.
The AudioI2S class manages playback using DMA buffers and supports both single sounds and short sequences. For example, a station arrival announcement is implemented as a sequence of two audio clips:
void AudioI2S::startArrivalWithName(const int16_t* nameData, size_t nameSamples) {
Clip clips[2] = {
{ station_pcm, station_pcm_NUM_SAMPLES },
{ nameData, nameSamples }
};
startSequence(clips, 2);
}This allows us to combine a generic announcement sound with a specific station name. The audio system runs independently of motion and sensing, ensuring smooth playback without blocking other tasks.
IMG_3808.2.MOV
By separating responsibilities into clear modules and keeping each subsystem focused on a single role, the software remains robust, readable, and scalable. This architecture made it possible to combine manual control, autonomous behaviors, and multimedia feedback into a single embedded system that closely mimics the behavior of a real locomotive.











