How to Program an Arduino to Control Your RC Car
If you’ve ever ripped open a toy-grade RC car, you know the feeling: a clunky circuit board, a cheap DC motor, and a steering mechanism that feels like it’s powered by a rubber band. But here’s the secret—underneath that plastic shell lies a perfect robotics testbed. By ripping out the stock electronics and dropping in an Arduino, you can transform that $20 Walmart special into a programmable, sensor-laden, micro-servo-driven machine that responds to your code, not just your thumb.
And the star of this show? The micro servo motor. Not the big, bulky standard servos you see on robot arms, but the tiny 9g SG90 or MG90S units that fit in the palm of your hand. These little workhorses are the key to crisp, responsive steering on small-scale RC platforms. In this guide, we’ll go deep into the wiring, the code, and the physics of why a micro servo beats a DC motor with a steering rack every single time.
Why a Micro Servo, Not a DC Motor, for Steering?
Most toy RC cars use a simple DC motor connected to a gearbox that pushes a steering linkage left or right. The problem? There’s no feedback. You can’t tell the car “turn 15 degrees” and have it stick there. The motor just spins until you cut power, and the wheels flop back to center via a spring.
A micro servo motor changes the game entirely. Inside that tiny plastic case, you get:
- A DC motor with a gear reduction train
- A potentiometer that reports the output shaft’s absolute position
- A control circuit that compares your commanded pulse width to the potentiometer’s voltage
When you send a 1.5ms pulse on a 50Hz signal, the servo snaps to 90 degrees (center). Send 1.0ms and it goes to 0 degrees; 2.0ms and it hits 180 degrees. For an RC car, you rarely use the full sweep. You’ll typically limit the range to ±30 degrees from center, which gives you sharp, proportional steering that holds its position even when the car is stationary.
Key spec to remember: The SG90 micro servo produces about 1.8 kg·cm of torque at 5V. That’s plenty for a 1/16 or 1/18 scale car chassis. If your car is heavier or has stiff suspension, step up to the MG90S (metal gears) or even the MG996R if you’re building a 1/10 scale monster.
The Hardware: What You’ll Actually Need
Let’s assume you’re starting from a broken or cheap RC car. Here’s your shopping list—most of it you probably already own:
- Arduino board – Uno or Nano is fine. The Nano is great because it’s small enough to hide inside the car body.
- Micro servo – SG90 for light cars, MG90S for anything with real weight.
- Motor driver – L298N or TB6612FNG for the main drive motor. Or, if you’re using a differential-drive tank-style RC, you’ll need two channels.
- RC car chassis – Any toy with a separate steering servo mount (or one you can 3D print).
- Battery – 2S LiPo (7.4V) for the motor driver, and a 5V BEC or a separate 4xAA pack for the Arduino and servo.
- NRF24L01 or HC-05 Bluetooth module – For wireless control. Or just use a wired pot for bench testing.
- Jumper wires, a breadboard, and zip ties – The holy trinity of hobbyist engineering.
Critical note on power: Never power the micro servo directly from the Arduino’s 5V pin if you’re also driving the motor from the same source. The servo’s stall current can spike to 500mA or more, which will brown-out your ATmega328P. Use a separate 5V UBEC or a dedicated servo power line from the motor driver’s 5V output (if it’s a switching regulator, not linear).
Wiring Diagram: The Lazy Man’s Guide to Clean Connections
Here’s the exact pinout I use for a standard rear-wheel-drive RC car with front steering:
Arduino Nano Micro Servo (SG90) ----------- ------------------ D9 (PWM) -------> Orange (Signal) 5V (from BEC) -------> Red (Power) GND -------> Brown (Ground)
Arduino Nano L298N Motor Driver ----------- ------------------- D5 (PWM) -------> ENA (Enable A) D6 (PWM) -------> ENB (Enable B) [if dual motor] D7 -------> IN1 D8 -------> IN2 D10 -------> IN3 D11 -------> IN4 GND -------> GND (common ground with battery negative)
For the servo, I strongly recommend using a dedicated 5V 2A UBEC connected to the 2S LiPo’s balance lead or main power. The L298N’s onboard 5V regulator is a linear one—it gets scorching hot and can’t supply clean current to a servo during rapid left-right flicks.
The Code: Writing Servo Control That Feels Buttery Smooth
Here’s where the magic happens. The Arduino’s Servo.h library makes it stupid easy to move a micro servo, but you need to think beyond simple write() commands if you want your car to handle well.
Basic Sweep vs. Proportional Steering
Let’s start with the raw basics. If you just want to test your wiring, upload this:
cpp
include <Servo.h>
Servo steeringServo; const int servoPin = 9;
void setup() { steeringServo.attach(servoPin); steeringServo.write(90); // center delay(500); }
void loop() { // Sweep left to right for (int angle = 60; angle <= 120; angle += 1) { steeringServo.write(angle); delay(10); } for (int angle = 120; angle >= 60; angle -= 1) { steeringServo.write(angle); delay(10); } }
This works, but it feels robotic. Real RC cars don’t jerk—they ease into turns. Also, the write() function uses a default pulse range of 544 to 2400 microseconds, which can over-drive some micro servos, causing them to buzz at the extremes. You should calibrate your specific servo’s endpoints.
Calibrating Your Micro Servo’s Endpoints
Every SG90 is slightly different. Some center at exactly 90 degrees; others might be off by 3-5 degrees. Here’s a better approach using writeMicroseconds():
cpp
include <Servo.h>
Servo steer; int centerus = 1500; // typical 90 degree point int leftus = 1250; // adjust this until wheels just stop at full left int right_us = 1750; // adjust this for full right
void setup() { steer.attach(9); steer.writeMicroseconds(center_us); delay(1000); }
void loop() { // Test left steer.writeMicroseconds(leftus); delay(1000); // Test center steer.writeMicroseconds(centerus); delay(1000); // Test right steer.writeMicroseconds(right_us); delay(1000); }
Upload this, then manually tweak left_us and right_us until you hear no buzzing and the steering linkage doesn’t bind. Write those values down—you’ll need them for the full controller code.
Sub-Headline: Making the Car Drive Itself (Line Following or Obstacle Avoidance)
Now that you’ve got basic servo control, let’s make the car do something useful. The easiest upgrade is a line-following RC car. You’ll need two or three IR reflectance sensors mounted on the front bumper. The logic is simple:
- If the left sensor sees black (off the line), steer slightly right.
- If the right sensor sees black, steer slightly left.
- If both sensors see white (on the line), go straight.
Here’s the full code with a micro servo steering controller and a PID-inspired smoothing function:
cpp
include <Servo.h>
Servo steer; const int servoPin = 9; const int leftSensor = A0; const int rightSensor = A1;
int centerus = 1500; int leftus = 1250; int right_us = 1750;
int leftThreshold = 500; int rightThreshold = 500; int currentSteer = center_us;
// Simple low-pass filter for steering float alpha = 0.6;
void setup() { pinMode(leftSensor, INPUT); pinMode(rightSensor, INPUT); steer.attach(servoPin); steer.writeMicroseconds(center_us); Serial.begin(9600); }
void loop() { int leftValue = analogRead(leftSensor); int rightValue = analogRead(rightSensor);
int desiredSteer = center_us;
if (leftValue > leftThreshold && rightValue < rightThreshold) { // Turn right (car drifted left) desiredSteer = rightus; } else if (rightValue > rightThreshold && leftValue < leftThreshold) { // Turn left desiredSteer = leftus; } else if (leftValue > leftThreshold && rightValue > rightThreshold) { // Both sensors see line – probably a crossroad. Go straight. desiredSteer = center_us; }
// Smooth the transition to avoid servo jerk currentSteer = (int)(alpha * desiredSteer + (1 - alpha) * currentSteer);
// Constrain to calibrated range currentSteer = constrain(currentSteer, leftus, rightus);
steer.writeMicroseconds(currentSteer); delay(5); // small delay for stability }
The alpha filter is critical. Without it, the micro servo will slam from full-left to full-right instantly, which not only stresses the plastic gears but also causes the car to fishtail. A value of 0.6 means the servo moves 60% of the way to the new target each loop iteration, giving you a natural, progressive steering feel.
Sub-Headline: Adding a Throttle Curve for Realistic Acceleration
Most hobbyists ignore throttle mapping. They just set the motor PWM to a fixed value and let the car go. But if you’re using a micro servo for steering, you already know that fine control matters. The same applies to the drive motor.
Instead of a linear throttle response, use a piecewise curve:
cpp int mapThrottle(int input, int minInput, int maxInput) { // input from 0 to 1023 (analog stick) // output 0 to 255 (PWM) if (input < 512) { // Reverse zone – map 0 to 1023 linearly return map(input, 0, 512, 255, 0); } else { // Forward zone – give more resolution at low speeds float normalized = (float)(input - 512) / 511.0; float curved = pow(normalized, 1.5); // exponential curve return (int)(curved * 255); } }
Why does this matter? When you slam the throttle from a standstill, a raw 255 PWM value will break traction on loose surfaces. With the curve, you get gentle initial acceleration that ramps up aggressively at high speed. The micro servo steering can then keep up with the car’s actual grip level.
Sub-Headline: Wireless Control with an NRF24L01 + Joystick Transmitter
The ultimate RC experience is holding a controller in your hand. Here’s how to pair two Arduinos via 2.4GHz radio.
Transmitter (your hand): - Arduino Nano - NRF24L01 module - Two 10k potentiometers (one for steering, one for throttle) - 2x AA battery pack
Receiver (in the car): - Arduino Nano - NRF24L01 module - Micro servo (steering) - L298N + drive motor
The code for the transmitter:
cpp
include <SPI.h> include <nRF24L01.h> include <RF24.h>
include <RF24.h>
RF24 radio(9, 10); // CE, CSN const byte address[6] = "CAR01";
void setup() { radio.begin(); radio.openWritingPipe(address); radio.setPALevel(RF24PALOW); }
void loop() { int steerVal = analogRead(A0); // 0-1023 int throttleVal = analogRead(A1); // 0-1023
// Send as ints int data[2] = {steerVal, throttleVal}; radio.write(&data, sizeof(data)); delay(5); }
Receiver code (the important part with servo control):
cpp
include <SPI.h> include <nRF24L01.h> include <RF24.h> include <Servo.h>
include <RF24.h> include <Servo.h>
RF24 radio(9, 10); const byte address[6] = "CAR01"; Servo steer;
int centerus = 1500; int leftus = 1250; int right_us = 1750;
void setup() { radio.begin(); radio.openReadingPipe(0, address); radio.setPALevel(RF24PALOW); radio.startListening(); steer.attach(6); steer.writeMicroseconds(center_us); }
void loop() { if (radio.available()) { int data[2]; radio.read(&data, sizeof(data));
int steerVal = data[0]; // 0-1023 int throttleVal = data[1]; // 0-1023 // Map joystick to servo angle range // Steering: 0 (left) to 1023 (right), center 511 int mappedAngle = map(steerVal, 0, 1023, left_us, right_us); mappedAngle = constrain(mappedAngle, left_us, right_us); // Add dead zone in center to prevent jitter if (abs(steerVal - 511) < 15) { mappedAngle = center_us; } steer.writeMicroseconds(mappedAngle); // Control motor (simplified – you'd use a motor driver class) int motorPWM = map(abs(throttleVal - 511), 0, 511, 0, 255); if (throttleVal < 511) { // Reverse – set direction pins accordingly } else { // Forward } analogWrite(5, motorPWM); } }
Notice the dead zone in the steering. Potentiometers are noisy near center, and a micro servo will twitch if you feed it a constantly changing 1502us vs 1498us signal. Adding a ±15 count dead zone saves your servo from endless micro-corrections.
Sub-Headline: Advanced Tuning – Servo Speed Limiting and Acceleration
Here’s a pro tip that separates your build from a toy. By default, a micro servo moves at its maximum speed (about 0.1s per 60 degrees for an SG90). That’s actually too fast for realistic RC steering at low vehicle speeds. You can limit the servo’s angular velocity in software:
cpp int currentPosition = centerus; int targetPosition = centerus; int maxStepPerLoop = 3; // microseconds per 5ms loop = 600us/sec
void updateServo() { if (targetPosition > currentPosition) { currentPosition += maxStepPerLoop; if (currentPosition > targetPosition) currentPosition = targetPosition; } else if (targetPosition < currentPosition) { currentPosition -= maxStepPerLoop; if (currentPosition < targetPosition) currentPosition = targetPosition; } steer.writeMicroseconds(currentPosition); }
With maxStepPerLoop = 3 and a 5ms loop time, your servo takes about 1 second to sweep from full left to full right. That’s perfect for a scale crawler. For a drift car, you’d crank it up to 8 or 10.
Sub-Headline: Protecting Your Micro Servo from Stall Damage
Micro servos are cheap, but they burn out if you stall them for more than a few seconds. Here are three ways to protect yours:
- Mechanical stops – Don’t rely on software to limit travel. Design your steering linkage so the servo horn hits a physical stop before the internal potentiometer hits its end.
- Current sensing – Put a 0.1Ω resistor in series with the servo power line and read the voltage drop with an analog pin. If the current exceeds 800mA for more than 200ms, cut power via a MOSFET.
- Soft limits in code – Always use
constrain()on your final pulse width. If you set your max left at 1250us, never write below that, even if the joystick is slammed.
Here’s a simple stall detection snippet:
cpp const int currentSensorPin = A2; float currentLimit = 0.8; // amps unsigned long lastStallTime = 0;
void checkServoStall() { int sensorValue = analogRead(currentSensorPin); float voltage = sensorValue * (5.0 / 1023.0); float current = voltage / 0.1; // R = 0.1 ohm
if (current > currentLimit) { if (lastStallTime == 0) lastStallTime = millis(); if (millis() - lastStallTime > 200) { // Disable servo by sending no pulse steer.detach(); digitalWrite(9, LOW); } } else { lastStallTime = 0; } }
Sub-Headline: The 3D-Printed Mount – Because Zip Ties Are Ugly
You can’t just tape a micro servo to the RC car’s chassis. The torque will rip it free on the first hard turn. You need a rigid mount. If you have a 3D printer, design a bracket that:
- Clamps the servo body on both sides (not just one screw hole)
- Aligns the servo horn with the existing steering linkage
- Allows for 5 degrees of adjustment to set toe-in
If you don’t have a printer, use a piece of 1/8” aluminum L-bracket. Drill two holes for the servo ears and one hole for the linkage ball joint. Epoxy the bracket to the chassis after roughing up the plastic.
Pro tip: Use a servo saver (a spring-loaded horn) between the servo and the linkage. When you crash into a wall, the spring absorbs the shock instead of stripping the servo gears. For micro servos, the little plastic “T” horns often break before the servo does—so carry spares.
Sub-Headline: Testing Methodology – From Bench to Backyard
Don’t just upload the code and pray. Follow this sequence:
- Bench test (no wheels): Power the Arduino and servo. Slowly sweep the servo through its full range. Listen for buzzing—if you hear it at the endpoints, back off 20us.
- Lift test (wheels off ground): Hold the car in the air. Give full steering input while spinning the wheels. Watch for any binding in the linkage.
- Low-speed carpet test: Run the car at 10% throttle on a low-grip surface. Turn the steering to 50% and observe if the car understeers or oversteers.
- Full-speed asphalt test: This is where your throttle curve and servo speed limiting get tuned. If the car spins out when you turn at speed, reduce your steering range (e.g., go from ±500us to ±400us).
Sub-Headline: What If Your Micro Servo Feels Weak? (Upgrade Path)
If you’re running a heavier car or bigger wheels, an SG90 will struggle. Symptoms include: slow response, buzzing under load, and the wheels not returning to center. You have two options:
Option A: MG90S – Metal gears, same form factor, about 2.2 kg·cm torque. Direct drop-in.
Option B: DS3218 – This is a 20kg servo that’s way overkill, but it has one insane advantage: it runs at 6V-8.4V and uses a digital signal. If you pair it with a 2S LiPo directly (via a BEC), you get lightning-fast response with zero dead band. You’ll need to remount it because it’s much thicker.
For most 1/16 to 1/14 scale cars, the MG90S is the sweet spot. The metal gears handle the shock loads from curb impacts, and the extra torque means you can run a tighter steering linkage without flex.
Sub-Headline: Putting It All Together – A Complete Code Template
Here’s a final, modular code that combines everything we’ve discussed: radio control, throttle curve, servo speed limiting, and stall detection. This is the code I run in my personal RC drift car (FWD, rear steering deleted, front micro servo).
cpp
include <Servo.h> include <SPI.h> include <nRF24L01.h> include <RF24.h>
include <nRF24L01.h> include <RF24.h>
// ---------- PIN DEFINITIONS ----------
define SERVO_PIN 6 define MOTOR_PWM 5 define MOTOR_IN1 7 define MOTOR_IN2 8 define CURRENT_SENSE A2
define MOTOR_IN1 7 define MOTOR_IN2 8 define CURRENT_SENSE A2
define CURRENT_SENSE A2
// ---------- RADIO ---------- RF24 radio(9, 10); const byte address[6] = "CAR01";
// ---------- SERVO ---------- Servo steer; int centerus = 1500; int leftus = 1250; int rightus = 1750; int currentservous = centerus; int targetservous = centerus; int maxstepperloop = 3;
// ---------- THROTTLE ---------- int motorpwm = 0; bool motorforward = true;
// ---------- STALL PROTECTION ---------- float motorcurrent = 0; unsigned long stallstart = 0; bool servo_disabled = false;
void setup() { steer.attach(SERVOPIN); steer.writeMicroseconds(centerus);
pinMode(MOTORPWM, OUTPUT); pinMode(MOTORIN1, OUTPUT); pinMode(MOTOR_IN2, OUTPUT);
radio.begin(); radio.openReadingPipe(0, address); radio.setPALevel(RF24PALOW); radio.startListening();
Serial.begin(9600); }
void loop() { // ---- RECEIVE DATA ---- if (radio.available()) { int data[2]; radio.read(&data, sizeof(data)); int steerraw = data[0]; int throttleraw = data[1];
// Map steering with dead zone if (abs(steer_raw - 511) < 15) { target_servo_us = center_us; } else { target_servo_us = map(steer_raw, 0, 1023, left_us, right_us); target_servo_us = constrain(target_servo_us, left_us, right_us); } // Map throttle with curve if (throttle_raw < 511) { motor_forward = false; float norm = (511.0 - throttle_raw) / 511.0; motor_pwm = (int)(pow(norm, 1.5) * 255); } else { motor_forward = true; float norm = (throttle_raw - 511.0) / 511.0; motor_pwm = (int)(pow(norm, 1.5) * 255); } }
// ---- UPDATE SERVO WITH SPEED LIMIT ---- if (!servodisabled) { if (targetservous > currentservous) { currentservous += maxstepperloop; if (currentservous > targetservous) currentservous = targetservous; } else if (targetservous < currentservous) { currentservous -= maxstepperloop; if (currentservous < targetservous) currentservous = targetservous; } steer.writeMicroseconds(currentservo_us); }
// ---- UPDATE MOTOR ---- digitalWrite(MOTORIN1, motorforward ? HIGH : LOW); digitalWrite(MOTORIN2, motorforward ? LOW : HIGH); analogWrite(MOTORPWM, motorpwm);
// ---- STALL DETECTION ---- int sensorval = analogRead(CURRENTSENSE); float voltage = sensorval * (5.0 / 1023.0); motorcurrent = voltage / 0.1;
if (motorcurrent > 0.8) { if (stallstart == 0) stallstart = millis(); if (millis() - stallstart > 300) { servodisabled = true; steer.detach(); digitalWrite(SERVOPIN, LOW); Serial.println("SERVO STALLED – DISABLED"); } } else { stall_start = 0; }
delay(5); }
Upload this, and you’ll have a car that steers like it has power steering, accelerates with a torque curve, and won’t self-destruct when you hit a curb.
Sub-Headline: Troubleshooting Common Micro Servo Issues in RC Cars
Symptom: Servo twitches when car is sitting still.
Cause: Radio noise or unstable power. Solution: Add a 100µF electrolytic capacitor across the servo power pins. Also, make sure your radio module has its own 3.3V regulator (NRF24L01 modules often need one).
Symptom: Car turns left fine but struggles to turn right.
Cause: Mechanical binding on one side. Check the linkage for a rough spot or a ball joint that’s too tight. Also, your servo horn might be off-center—remove it, set the servo to center_us, then reattach the horn pointing straight.
Symptom: Servo gets hot after 2 minutes of driving.
Cause: You’re holding it at the end stops. The SG90 will pull 400mA continuously when stalled. Back off your left_us and right_us values until the buzzing stops.
Symptom: Car drifts to one side even with steering centered.
Cause: Your center_us is off. Use writeMicroseconds(1500) and then manually adjust the linkage so the wheels are straight. If they’re not, change center_us by ±10us increments until they are.
Sub-Headline: The Next Level – Adding a Gyro for Drift Stability
If you’ve mastered the basics, throw an MPU6050 gyro into the mix. Instead of directly setting the servo position from the joystick, you use a PID controller that adjusts steering based on the car’s yaw rate. This is how real RC drift cars work—the gyro counter-steers automatically when the rear end steps out.
The code gets complex, but the concept is simple:
cpp float yawRate = readGyroZ(); float error = desiredYawRate - yawRate; float correction = pidUpdate(error); int finalServo = target_servo_us + correction;
This is beyond the scope of this article, but it’s the natural evolution of your micro servo project. Once you have the hardware working, adding a gyro is just another I2C sensor.
Final Thoughts on the Micro Servo Journey
Programming an Arduino to control an RC car is more than just wiring a motor to a transistor. It’s about understanding proportional control, mechanical feedback, and power management. The micro servo is the perfect teaching tool because it forces you to think in terms of pulse widths, dead zones, and stall currents. And when you finally get that little car to drift around a corner, with the servo humming smoothly and the throttle curve feeling just right, you’ll realize that the $25 you spent on an SG90 and an Arduino clone was the best robotics investment you ever made.
Now go rip apart that old RC car. The micro servo is waiting.
Copyright Statement:
Author: Micro Servo Motor
Link: https://microservomotor.com/building-remote-controlled-cars/program-arduino-rc-car.htm
Source: Micro Servo Motor
The copyright of this article belongs to the author. Reproduction is not allowed without permission.
Recommended Blog
- How to Build a Remote-Controlled Car with LED Lights
- How to Build a Remote-Controlled Car with Working Headlights
- Exploring the Use of LiPo Batteries in RC Cars
- How to Build a Remote-Controlled Car with a Servo Steering System
- How to Build a Remote-Controlled Car with a Horn Sound
- How to Build a Remote-Controlled Car with an Aerodynamic Body
- How to Build a Remote-Controlled Car with Telemetry Sensors
- How to Build a Remote-Controlled Car with a Rack and Pinion Steering System
- How to Build a Remote-Controlled Car with a Smartphone App
- Building Your First Remote-Controlled Car: A Beginner's Guide
About Us
- Lucas Bennett
- Welcome to my blog!
Hot Blog
- Micro Servos with Metal vs Plastic Gears: Impacts on Drone Durability
- The Future of Micro Servo Motors in Smart Educational Systems
- Micro Servos with Minimal Dead Band
- The Role of Thermal Management in Motor Cost Reduction
- How to Build a Remote-Controlled Car with Working Headlights
- Citizen Chiba Precision's Micro Servo Motors: Trusted by Professionals
- Building a Micro Servo Robotic Arm with a Custom PCB
- How to Build a Remote-Controlled Car with LED Lights
- Exploring the SG90 Micro Servo Motor: Features and Specifications
- How to Implement Environmental Testing in Control Circuits
Latest Blog
- How to Build a Micro Servo Robotic Arm for a Robotics Workshop
- Aluminium Body Micro Servos vs Plastic Body
- How to Program an Arduino to Control Your RC Car
- Micro Servo Motors in Laboratory Automation Systems
- The Use of PWM in Signal Processing: Applications and Techniques
- The Role of Gear Materials in Servo Motor Performance Under Varying Amplitudes
- Advances in Power Density for Micro Servo Motors
- Touch-Activated Servo Gadgets: Push Plates, Pop-Out Controls
- Voltage Requirements: Micro vs Standard Servos Compared
- How to Connect a Micro Servo Motor to Arduino MKR FOX 1200
- How to Repair and Maintain Your RC Car's Chassis
- Top Micro Servo Motors for Arduino Projects
- Micro Servos with Feedback beyond Potentiometer (optical, magnetic)
- Standard Micro Servos for Model Aircraft
- The Best Micro Servo Motors for Robotics: A Brand Comparison
- How to Design PCBs for IoT Applications
- Creating a Servo-Controlled Automated Plant Watering System with Arduino
- How to Protect Motors from Thermal Expansion Damage
- The Future of Micro Servo Motors in Smart Educational Systems
- Micro Servos for Micro-Manufacturing / Micromachining Tools