Autonomous Pacer for Track Running (coded 1/10 Scale Car)
by anear141421 in Workshop > 3D Printing
623 Views, 5 Favorites, 0 Comments
Autonomous Pacer for Track Running (coded 1/10 Scale Car)
As students taking the IB diploma, we designed, printed, and wrote the code for the purpose of building an autonomous pacer robot (1/10 scale motor-run car) that can help runners maintain their paces throughout their track runs. Keep in mind that this is a CAS project that we were not able to complete due to time constraints—one of us relocated mid-project, limiting the time for the actual track testing. This is the reason why we weren't able to create mounts for the electronics (as the final design), and the components were printed in green PLA. Keep in mind that this document is only for reference and learning purpose only, showing how far we got and what we learned along the way.
Supplies
Hardware:
- Arduino Nano (CH340 clone)
- BTS7960 IBT-2 motor driver (HW-039)
- RS-550 21T brushed motor (7.4V, 26A max)
- 2S 7.4V LiPo battery (4200mAh, 35C+, XT60)
- LM2596 buck converter (7.4V → 5V)
- MG995 servo
- TCRT5000 3-channel IR line sensor module (KY-033 style)
- LM393 optical encoder module + slotted disc
- LiPo voltage alarm (1-8S)
- Breadboard + jumper wires
- 10 AWG silicone wire (battery → motor driver)
- 22 AWG silicone wire (logic connections)
- XT60 connector pair
- Heat shrink tubing
- MR117ZZ bearings ×6
- 10mm OD bearings (rear axle bearings)
- 12mm OD bearings (universal joint bearings)
- PLA filament (chassis, gearbox housing, suspension arms)
- M3, M4 screws and nuts
- 4mm steel hinge pins + E-clips (for suspension pivots)
- Duct tape (for temporary mounting)
- 96mm OD wheels ×4
- B6 V3 LiPo balance charger
- RC transmitter + receiver (for manual testing)
- soldering tools
- 1/10 RC shock absorbers
- universal joint drive shaft × 2 (rear)
- steering linkage × 2
Software:
- Fusion 360 (free education license)
- Visual Studio Code
- PlatformIO
Goals and Specs
PROJECT GOALS
- Follow a white line on a red track autonomously
- Maintain 15 km/h (adjustable via code)
- Complete a full 1500m run without human intervention
KEY SPECS
- Target speed - 15 km/h (4 min/km)
- Track - 250m red rubber track
- Scale - 1/10
- Wheel diameter - 96mm
- Gear ratio - 1:12.5 (3-stage herringbone)
- Motor - RS-550 21T brushed, 7.4V
- Battery - 2S 7.4V LiPo
- Steering - servo
- Line sensing - TCRT5000 IR reflectance, 3-channel
- Speed control - PID with encoder
Mechanical Design (using Fusion 360)
The mechanical part of our design has three main systems: steering, suspension, and the drivetrain. All parts were designed using Fusion 360 and printed in PLA using Bambu Lab A1 printer.
Steering
The front wheels were connected via tie rods/steering linkages we bought with ball joints that allow the headroom for the suspension, and a cap for the servo horn that connected the servo to the linkages was designed. During initial testing we used an MG90S micro servo, which proved too weak to turn the front wheels reliably under load. We upgraded to the MG995 which provides significantly more torque.
The MG995 servo requires three connections: power, ground, and a signal from the Arduino.
The servo signal was connected to D3 on the Arduino Nano. Since the servo draws considerably more current than an Arduino output pin can safely provide, it should not be powered directly from an Arduino GPIO pin. The power system therefore used a regulated supply, with the servo and Arduino sharing a common ground.
The common ground is important because the Arduino's control signal needs the same voltage reference as the servo.
The Arduino Servo library was used to control the steering angle. During testing, we first uploaded a separate servo program before combining steering with the other systems.
A simplified version is:
#include <Servo.h>
Servo steering;
const int SERVO_PIN = 3;
const int CENTER_ANGLE = 90;
void setup() {
steering.attach(SERVO_PIN);
steering.write(CENTER_ANGLE);
}
void loop() {
}
The exact centre, maximum left, and maximum right values need to be calibrated for the physical steering mechanism. Sending 90 degrees does not necessarily mean the wheels will be mechanically centred.
Once the IR sensors were added, the steering angle could be changed depending on where the white line was detected. Instead of the servo simply moving through predetermined angles, the sensor readings became the input used to decide the steering direction.
Suspension
Though for flat tracks, the full suspension was over-engineered for our case (since tracks are relatively flat and without obstacles), we kept it in the design for the engineering challenge and learning value, even if it wasn't strictly necessary.
Both the front and rear suspensions were designed in Fusion 360. The ball joint that connects the front suspension arms to the bearing holders (that are linked to the steering linkages) were especially difficult to design for the right fit. They were designed using the revolve function, following a tutorial by Engineer3D.
Our initial shock absorbers soon turned out to be too weak to hold up the weight of our electronics and battery, so we had to purchase larger ones (72-100mm) and our suspension holders had to be redesigned.
Drivetrain
The first thing we did for our project was to purchase the components that would take the longest to ship, and that happened to be the motor. We made a rushed purchase, and as a result, we had to reduce the spin speed from 13,000 RPM (max) to around 833 RPM at the wheel—requiring a 1:15.6 gear reduction. Had we calculated the required RPM at the very start, the gearbox would've been simpler. Time was running short, so we decided on using a 3 stage herringbone gearbox (for higher torque and strength), fully designed and printed. (tutorial by POWER STROKE)
In order for the rear suspension to work, we connected universal joint drive shafts to the final gear, allowing both the up/down motion and the rotation/drive motion.
An important lesson we learned during testing: running the motor at high throttle leaves very little room for the PID controller to make speed corrections. Our original gear ratio of 1:15.6 meant the robot needed to run at nearly 95% throttle to hit 15 km/h.
After installing the wheels and doing initial speed tests, we changed stage 2 from 12T/30T (1:2.5) to 14T/28T (1:2.0), bringing the combined ratio to 1:12.5. This dropped the operating throttle to around 80% — giving the PID comfortable headroom in both directions (slower and faster). Crucially, both gear pairs share the same center distance (21mm at module 1.0), so no changes to the gearbox housing were needed.
Because the RS-550 can draw far more current than the Arduino Nano can supply, the motor was controlled through a BTS7960 motor driver.
Our control connections were as follows:
RPWM → Arduino D9
LPWM → Arduino D10
REN → Arduino D7
LEN → Arduino D8
The motor was connected to the motor output of the BTS7960, while the 7.4V LiPo battery provided the high-current motor power.
The Arduino only sends control signals to the BTS7960. It does not directly provide the power required to run the motor.
A simplified motor test looked like this:
const int RPWM = 9;
const int LPWM = 10;
const int REN = 7;
const int LEN = 8;
void setup() {
pinMode(RPWM, OUTPUT);
pinMode(LPWM, OUTPUT);
pinMode(REN, OUTPUT);
pinMode(LEN, OUTPUT);
digitalWrite(REN, HIGH);
digitalWrite(LEN, HIGH);
}
void loop() {
analogWrite(RPWM, 140);
analogWrite(LPWM, 0);
}
The PWM value ranges from 0 to 255. Increasing the PWM increases the average power supplied to the motor.
However, PWM should not be treated as an exact speed setting. A PWM value of 140, for example, does not guarantee that the vehicle will always travel at the same speed. Battery voltage, friction, surface conditions, and load can all change the actual speed. This is why we added an encoder and PID speed control.
Other Components
Encoder — A 20-slot disc was designed and printed to work with the LM393 optical encoder. As the disc spins with the gear shaft, the encoder counts the slots passing through its gap and converts this to RPM and wheel speed for the PID loop.
IR Sensor — The TCRT5000 3-channel IR sensor required a printed shade cover to block sunlight, which otherwise prevented sensor from distinguishing the white line from the red track. A sensor mount was also designed to hold the sensor at the correct height above the track surface.
Motor Mount — A motor mount was designed specifically for our RS-550 motor, securing it to the chassis at the correct position relative to the gearbox input.
PID Speed Control
Once the encoder could provide feedback about the actual speed, we used PID control to automatically adjust the motor PWM. PID stands for Proportional, Integral, and Derivative. It is a type of feedback control system that continuously looks at the difference between the speed we want and the speed the vehicle is actually travelling at, then adjusts the motor PWM to reduce that difference.
This was necessary because giving the motor a fixed PWM value does not guarantee a constant speed of travel. Instead, changes to the battery voltage, friction, track conditions, or load on the motor could cause the vehicle to travel slightly faster or slower even when the PWM remains the same. By using the encoder together with PID control, the program could detect these changes and automatically compensate for them.
Proportional (P) responds to the current speed error. If the vehicle is too slow, it increases motor power; if it is too fast, it decreases it.
Integral (I) responds to error that continues over time. For example, if the vehicle consistently remains slightly below the target speed, the integral term gradually increases the correction.
Derivative (D) responds to how quickly the error is changing and can help prevent the controller from overcorrecting.
The PID controller then combines three terms:
output = Kp(error) + Ki(sum of previous errors) + Kd(change in error)
Here, the Kp, Ki, and Kd values determine how strongly each part of the PID controller affects the motor. These values have to be tuned experimentally.
To use PID for speed control, the program repeatedly measures the current speed using the encoder, compares it with the target speed, calculates the required correction, and changes the motor PWM.
The basic control loop was:
Target speed → Compare with measured speed → Calculate error → PID controller → Adjust → motor PWM → Measure speed again → repeat
The error is calculated as :
error = target speed - measured speed
A positive error means that the vehicle is travelling too slowly, while a negative error means that it is travelling too quickly.
The PID calculation can then be represented in code as:
error = targetSpeed - measuredSpeed;
integral += error * dt;
derivative = (error - previousError) / dt;
correction =
Kp * error +
Ki * integral +
Kd * derivative;
motorPWM += correction;
motorPWM = constrain(motorPWM, 0, 255);
previousError = error;
The calculated correction changes the motor PWM depending on the difference between the target and measured speeds. The PWM is then limited from between 0 and 255, which is the usable PWM range for the Arduino. This process then repeats continuously, allowing the vehicle to correct its speed while in motion by itself.
Developing a PID speed control system allowed us to appreciate one of the hidden benefits of changing the gearbox ratio from 1:15.6 to 1:12.5. If maintaining a speed of 15km/h requires around 95% of the throttle, the controller has almost no ability to increase the motor power when the vehicle falls below the target speed. Because we changed the gear ratio, we would be able to operate at around 80% instead which leaves room for additional PWM when the PID controller makes corrections.
Detecting the White Line
Our original approach to detecting the track lines was using the TCS34725 RGB color sensors. During indoor testing, the sensors could distinguish between different surface colors reasonably well. However, outdoor testing introduced a problem in direct sunlight. The sunlight made the bright red track and the faded white line much harder to distinguish reliably. Because of this, we eventually changed to a three-channel TCRT5000 infrared reflectance sensor.
The TCRT5000 works differently from an RGB color sensor. It emits infrared light and measures how much is reflected back from the surface. The white track line generally reflects more infrared light than the surrounding red surface.
Using three sensors also provides information about the position of the line:
LEFT CENTER RIGHT
sensor sensor sensor
↓ ↓ ↓
[ L ] [ C ] [ R ]
WHITE LINE
If the centre sensor detects the line, the vehicle continues approximately straight. If the left or right sensor detects it, the program changes the steering angle to bring the car back toward the centre.
Sensor Testing and Calibration
Before combining the sensors with the steering code, the raw sensor readings were checked separately.
For example:
const int LEFT_SENSOR = A0;
const int CENTER_SENSOR = A1;
const int RIGHT_SENSOR = A2;
void setup() {
Serial.begin(115200);
}
void loop() {
Serial.print("L: ");
Serial.print(analogRead(LEFT_SENSOR));
Serial.print(" C: ");
Serial.print(analogRead(CENTER_SENSOR));
Serial.print(" R: ");
Serial.println(analogRead(RIGHT_SENSOR));
delay(100);
}
We tested the readings over both the white line and red track rather than assuming a fixed threshold beforehand.
We also found that the physical distance between the sensor and track made a large difference to the readings. This meant that sensor calibration and sensor mounting had to be considered together.
A printed shade cover was also designed to reduce interference from sunlight, while a sensor mount was intended to maintain a consistent height above the track.
Combining Line Detection and Steering
Once the three sensors and servo worked separately, they could be combined.
The simplest control system uses conditions as follows:
if (centerWhite) {
steering.write(CENTER);
}
else if (leftWhite) {
steering.write(LEFT);
}
else if (rightWhite) {
steering.write(RIGHT);
}
In this code, if both the centre and left sensors detect the line, the vehicle only requires a small correction. If only the far-left sensor detects it, a stronger correction can be used.
This is why three sensors were preferable to a single line sensor: they provide information about not only whether the white line exists underneath the vehicle, but also approximately where it is relative to the centre of the car.
Combining the Electronics
The final electrical system brought together three main control systems:
TCRT5000 sensors → Arduino → Steering servo
↓
Encoder → Arduino → PID → BTS7960 → Motor
The Arduino therefore had two main jobs running at the same time:
- Read the three IR sensors and adjust the steering.
- Read the encoder, calculate the current speed, and use PID to adjust motor power.
One of the most important things we practiced while developing this project was not combining every component immediately. Instead we tested each individual component and made sure they worked. We made separate test programs for the motor, servo, sensors, and encoder. Only after confirming that an individual component worked did we begin combining it with the others.
This made troubleshooting much easier. If the complete vehicle does not work, there are many possible causes: wiring, power, sensor calibration, mechanical resistance, or programming. Testing each subsystem individually made it much easier to isolate the actual problem.
The full code we used is uploaded on GitHub.
Overview
Overall, although we weren't able to finish the project due to complicated personal reasons, we thought the process we took as students was worth sharing.
If you are replicating or have any suggestions on how we could improve our project in the future, feel free to leave a comment.