DC Motor Speed Control
with Encoder – Arduino UNO
Control and monitor motor speed in real time using PWM output, encoder pulse feedback, potentiometer input, and RPM display on a 16×2 LCD — the foundation of PID motor control.
This project demonstrates a closed-loop DC motor speed control system using Arduino UNO. The user sets desired speed via a potentiometer, the Arduino generates a PWM signal to drive the motor through a 2N2222 transistor, and an encoder feeds back the actual speed — which is calculated as RPM and displayed on a 16×2 LCD.
Simulated in Tinkercad or built on hardware, this system teaches signal conditioning, transistor switching, encoder pulse counting, and RPM calculation — core skills for robotics and automation.
🧩 Components Required
🔁 Closed-Loop Control Concept
How Closed-Loop Motor Control Works
Desired Speed
analogRead() → map()
PWM Switching
Rotates at speed
Shows RPM
RPM = pulses × 60 / PPR
Pulse feedback
Physical rotation
⚡ PWM Speed Control
Low Speed
Low duty cycle (~25%)
analogWrite(pin, 64)
Medium Speed
Medium duty cycle (~60%)
analogWrite(pin, 153)
Full Speed
High duty cycle (~95%)
analogWrite(pin, 242)
RPM Calculation Formula
RPM = (pulse_count × 60) / (PPR × elapsed_seconds)
Where PPR = Pulses Per Revolution of your specific encoder (commonly 20, 100, 360 or 1000). Measure elapsed time using millis() every 1 second for stable readings.

🔌 Circuit Diagram
📋 Pin Connection Table
| Component | Component Pin | Arduino Pin | Wire | Notes |
|---|---|---|---|---|
| Potentiometer | Middle (Wiper) | A0 | Yellow | Analog speed input 0–5V |
| Potentiometer | Pin 1 | 5V | Red | One outer terminal to 5V |
| Potentiometer | Pin 3 | GND | Black | Other outer terminal to GND |
| Encoder | Signal Out | D2 | Green | Interrupt pin for pulse counting |
| Encoder | VCC | 5V | Red | Encoder power |
| Encoder | GND | GND | Black | Common ground |
| 2N2222 Transistor | Base | D9 (via 1kΩ) | Orange | PWM signal through 1kΩ resistor |
| 2N2222 Transistor | Emitter | GND | Black | Common ground |
| DC Motor | Terminal (+) | Power Supply + | Red | Positive motor terminal |
| DC Motor | Terminal (–) | 2N2222 Collector | Black | Transistor controls motor current |
| Flyback Diode (1N4001) | Cathode | Motor (+) | Orange | Protects transistor from back-EMF |
| Flyback Diode (1N4001) | Anode | Motor (–) | Orange | Across motor terminals |
| 16×2 LCD | RS | D11 | Blue | Register select |
| 16×2 LCD | EN | D12 | Blue | Enable |
| 16×2 LCD | D4–D7 | D4–D7 | Blue | 4-bit data bus |
| 16×2 LCD | VSS | GND | Black | LCD ground |
| 16×2 LCD | VDD | 5V | Red | LCD power |
| 16×2 LCD | VO | Pot wiper | Yellow | Contrast via 10kΩ potentiometer |
Always use a Flyback Diode!
DC motors generate voltage spikes (back-EMF) when switching off. Always place a 1N4001 diode across the motor terminals (cathode to +, anode to –) to protect the 2N2222 transistor from damage.
📝 Step-by-Step Instructions
Install LiquidCrystal Library
The LiquidCrystal and Servo libraries are built into the Arduino IDE. Go to Sketch → Include Library → LiquidCrystal to confirm it's available. No additional install needed.
Wire the Potentiometer (Speed Input)
- Pot left outer pin → 5V
- Pot right outer pin → GND
- Pot middle wiper pin → Arduino A0
- This gives 0–1023 range from
analogRead(A0)
Wire the 2N2222 Transistor Motor Driver
- Transistor Base → 1kΩ resistor → Arduino D9
- Transistor Collector → Motor (–) terminal
- Transistor Emitter → GND
- Motor (+) terminal → External power supply (+)
- Place 1N4001 diode across motor terminals (cathode to +)
Wire the Encoder
- Encoder signal output → Arduino D2 (interrupt pin)
- Encoder VCC → 5V, GND → GND
- If signal is weak/noisy, use an op-amp (LM358) as a comparator to clean the pulse
- Note your encoder's PPR (Pulses Per Revolution) value for the RPM formula
Wire the 16×2 LCD
- LCD RS → D11, EN → D12, D4 → D4, D5 → D5, D6 → D6, D7 → D7
- LCD VSS → GND, VDD → 5V
- LCD VO → middle pin of 10kΩ potentiometer (for contrast)
- Pot outer pins to 5V and GND
Set PPR in the Code
Find your encoder's PPR (Pulses Per Revolution) in its datasheet. Common values: 20 PPR (cheap DC motors), 100 PPR, 360 PPR (precision). Update const int PPR = 20; in the code to match your encoder.
Upload and Test
- Select Tools → Board → Arduino UNO, choose COM port
- Upload the sketch — LCD should display "Set: 0 RPM / Actual: 0 RPM"
- Turn potentiometer slowly — motor speed should change and LCD RPM should update
- Connect oscilloscope to D9 to observe the PWM waveform changing with pot
- Connect oscilloscope to D2 to see encoder pulses at different speeds
💻 Arduino Code
/* * DC Motor Speed Control with Encoder Feedback * Arduino UNO + 2N2222 Transistor + Encoder + 16x2 LCD * * Potentiometer → A0 (speed setpoint) * Encoder Signal → D2 (interrupt for pulse counting) * PWM Motor Drive → D9 (through 1kΩ → 2N2222 base) * LCD: RS=D11, EN=D12, D4=D4, D5=D5, D6=D6, D7=D7 * * IMPORTANT: Add 1N4001 flyback diode across motor terminals * * Tutorial: https://www.makemindz.com */ #include <LiquidCrystal.h> // LCD: RS, EN, D4, D5, D6, D7 LiquidCrystal lcd(11, 12, 4, 5, 6, 7); // Pin definitions const int potPin = A0; // Potentiometer input const int motorPin = 9; // PWM output to 2N2222 base const int encoderPin = 2; // Encoder interrupt pin // Encoder settings — update PPR for your specific encoder const int PPR = 20; // Pulses Per Revolution volatile long pulseCount = 0; // Incremented by interrupt // Timing unsigned long lastTime = 0; const unsigned long interval = 1000; // Calculate RPM every 1 second int actualRPM = 0; int pwmValue = 0; // Interrupt Service Routine — called on each encoder pulse void countPulse() { pulseCount++; } void setup() { lcd.begin(16, 2); lcd.print("DC Motor Control"); lcd.setCursor(0, 1); lcd.print(" Initialising..."); delay(2000); pinMode(motorPin, OUTPUT); pinMode(encoderPin, INPUT_PULLUP); // Attach interrupt on D2 — RISING edge = one encoder pulse attachInterrupt(digitalPinToInterrupt(encoderPin), countPulse, RISING); lastTime = millis(); Serial.begin(9600); } void loop() { // Read potentiometer and map to PWM range int potValue = analogRead(potPin); // 0–1023 pwmValue = map(potValue, 0, 1023, 0, 255); // Map to 0–255 // Calculate desired RPM for display (pot position estimate) int setRPM = map(potValue, 0, 1023, 0, 300); // Drive motor via transistor with PWM analogWrite(motorPin, pwmValue); // Calculate RPM every 1 second unsigned long currentTime = millis(); if (currentTime - lastTime >= interval) { // Disable interrupt briefly to safely read pulseCount detachInterrupt(digitalPinToInterrupt(encoderPin)); long count = pulseCount; pulseCount = 0; attachInterrupt(digitalPinToInterrupt(encoderPin), countPulse, RISING); // RPM = (pulses per second × 60) / PPR actualRPM = (count * 60) / PPR; lastTime = currentTime; // Update LCD display lcd.clear(); lcd.setCursor(0, 0); lcd.print("Set: "); lcd.print(setRPM); lcd.print(" RPM "); lcd.setCursor(0, 1); lcd.print("Act: "); lcd.print(actualRPM); lcd.print(" RPM "); // Serial output for debugging / oscilloscope correlation Serial.print("PWM: "); Serial.print(pwmValue); Serial.print(" | RPM: "); Serial.println(actualRPM); } }
📐 diagram.json (Wokwi Simulation)
Using this in Wokwi
Go to wokwi.com → New Project → Arduino UNO. Replace diagram.json with this content and paste your sketch code. Turn the potentiometer in simulation to vary the PWM. Use the Serial Monitor to watch real-time RPM values.
{
"version": 1,
"author": "MakeMindz",
"editor": "wokwi",
"parts": [
{
"type": "wokwi-arduino-uno",
"id": "uno", "top": 80, "left": 180, "attrs": {}
},
{
"type": "wokwi-potentiometer",
"id": "pot", "top": -80, "left": -160,
"attrs": { "value": "10000", "label": "Speed Control" }
},
{
"type": "wokwi-dc-motor",
"id": "motor", "top": 80, "left": -200, "attrs": {}
},
{
"type": "wokwi-npn-transistor",
"id": "q1", "top": 240, "left": -80,
"attrs": { "type": "2N2222" }
},
{
"type": "wokwi-resistor",
"id": "r1", "top": 240, "left": -180,
"attrs": { "value": "1000", "label": "1k Base" }
},
{
"type": "wokwi-diode",
"id": "d1", "top": 80, "left": -140,
"attrs": { "label": "1N4001 Flyback" }
},
{
"type": "wokwi-lcd1602",
"id": "lcd", "top": -100, "left": 440, "attrs": {}
},
{
"type": "wokwi-potentiometer",
"id": "pot2", "top": 80, "left": 440,
"attrs": { "value": "10000", "label": "LCD Contrast" }
}
],
"connections": [
[ "pot:SIG", "uno:A0", "yellow", ["h0"] ],
[ "pot:VCC", "uno:5V", "red", ["h0"] ],
[ "pot:GND", "uno:GND.1", "black", ["h0"] ],
[ "motor:A", "uno:5V", "red", ["h0"] ],
[ "motor:B", "q1:C", "black", ["h0"] ],
[ "q1:B", "r1:2", "orange", ["h0"] ],
[ "r1:1", "uno:9", "orange", ["h0"] ],
[ "q1:E", "uno:GND.2", "black", ["h0"] ],
[ "d1:A", "motor:B", "orange", ["h0"] ],
[ "d1:K", "motor:A", "orange", ["h0"] ],
[ "motor:ENC", "uno:2", "green", ["h0"] ],
[ "lcd:RS", "uno:11", "blue", ["h0"] ],
[ "lcd:EN", "uno:12", "blue", ["h0"] ],
[ "lcd:D4", "uno:4", "blue", ["h0"] ],
[ "lcd:D5", "uno:5", "blue", ["h0"] ],
[ "lcd:D6", "uno:6", "blue", ["h0"] ],
[ "lcd:D7", "uno:7", "blue", ["h0"] ],
[ "lcd:VSS", "uno:GND.3", "black", ["h0"] ],
[ "lcd:VDD", "uno:5V", "red", ["h0"] ],
[ "lcd:VO", "pot2:SIG", "yellow", ["h0"] ],
[ "pot2:VCC", "uno:5V", "red", ["h0"] ],
[ "pot2:GND", "uno:GND.4", "black", ["h0"] ]
],
"dependencies": {}
}
🚀 Try the Live Simulations
Simulate the full motor speed control system — turn the virtual potentiometer to change PWM duty cycle and watch the RPM update on the LCD display!
✨ Key Features
Closed-Loop Control
Encoder feedback compares actual vs desired speed — foundation of PID systems
PWM Speed Control
Smooth motor speed variation from 0–100% using 8-bit PWM on D9
Real-Time RPM Display
LCD updates every second with actual measured RPM from encoder pulses
Transistor Driver Stage
2N2222 safely handles motor current — Arduino GPIO protected from high currents
Back-EMF Protection
1N4001 flyback diode prevents voltage spikes from damaging the transistor
Oscilloscope Integration
Monitor PWM waveform and encoder pulses for real-world signal analysis
Comments
Post a Comment