Introduction
Control DC motor speed and direction using PWM signals with L298N H-bridge and closed-loop PID controller. This comprehensive guide covers everything from design through implementation, testing, and deployment.
Control DC motor speed and direction using PWM signals with L298N H-bridge and closed-loop PID controller.
Control DC motor speed and direction using PWM signals with L298N H-bridge and closed-loop PID controller. This comprehensive guide covers everything from design through implementation, testing, and deployment.
PWM (Pulse Width Modulation) controls the average voltage delivered to the motor by rapidly switching a fixed supply voltage on and off. At 50% duty cycle, the motor receives 6V average from a 12V supply. Increasing duty cycle increases average voltage and therefore speed. The L298N H-bridge allows current to flow in either direction through the motor, enabling both forward and reverse direction control by toggling the IN1/IN2 logic inputs.
10 components required for this project.
| # | Component | Purpose | Qty |
|---|---|---|---|
| 1 | Arduino Uno | PWM signal generation and PID control | x1 |
| 2 | L298N H-Bridge Module | Motor driver capable of bidirectional control | x1 |
| 3 | DC Motor (12V, 3000 RPM) | Controlled load | x1 |
| 4 | Rotary Encoder (600 PPR) | Speed feedback for closed-loop control | x1 |
| 5 | 10kΩ Potentiometer | Speed setpoint input | x1 |
| 6 | 12V 2A Power Supply | Motor power | x1 |
| 7 | 0.1µF Decoupling Capacitors | Motor noise suppression | x4 |
| 8 | 1N4007 Flyback Diodes | Back-EMF protection (if using discrete H-bridge) | x4 |
| 9 | OLED Display 0.96" | Speed display | x1 |
| 10 | Heatsink for L298N | Thermal management | x1 |
Follow these 6 steps carefully.
PWM (Pulse Width Modulation) controls the average voltage delivered to the motor by rapidly switching a fixed supply voltage on and off. At 50% duty cycle, the motor receives 6V average from a 12V supply. Increasing duty cycle increases average voltage and therefore speed. The L298N H-bridge allows current to flow in either direction through the motor, enabling both forward and reverse direction control by toggling the IN1/IN2 logic inputs.
Connect L298N: ENA pin to Arduino PWM pin (pin 9), IN1 to pin 7, IN2 to pin 8 for motor A direction control. Connect 12V motor supply to L298N power input (remove 5V jumper). Connect encoder VCC to 5V, GND to GND, and A/B channels to Arduino interrupt pins 2 and 3. Wire potentiometer middle pin to A0 for speed setpoint.
Attach interrupt to encoder channel A: attachInterrupt(0, encoder_ISR, RISING). In ISR, increment pulse counter. Calculate RPM every 100ms: RPM = (pulse_count × 60) / (PPR × time_seconds). For 600 PPR encoder with quadrature decoding: RPM = (pulses × 60 × 1000) / (600 × dt_ms). This gives real-time speed feedback for the PID controller.
Error = setpoint_RPM - actual_RPM. P_output = Kp × error. I_output += Ki × error × dt (integrate error over time). D_output = Kd × (error - prev_error) / dt. PID_output = P + I + D, clamped to 0–255 for analogWrite. Start tuning: Kp=0.5, Ki=0.01, Kd=0.05. Increase Kp until oscillation, then reduce 30%, add Ki to eliminate steady-state error, add Kd to reduce overshoot.
Set forward: IN1=HIGH, IN2=LOW, ENA=PWM. Set reverse: IN1=LOW, IN2=HIGH, ENA=PWM. Stop: IN1=LOW, IN2=LOW (coast) or IN1=HIGH, IN2=HIGH (brake). When changing direction, ramp speed down to 0 first to prevent motor and driver stress. Implement software braking by briefly applying reverse PWM to decelerate quickly — useful for precise position applications.
Never command full speed instantly from stop — the inrush current can trip the power supply and stress gears. Implement a soft-start: ramp the PWM from 0 to setpoint over 2 seconds. Set a maximum RPM limit in code: if actual RPM > max_RPM (e.g., 2800), override PID output and reduce duty cycle. Monitor L298N temperature; if the module is too hot to touch, reduce maximum duty cycle by 10%.
Core code for motor_pid.ino:
volatile long encoderCount = 0; float kp = 0.8, ki = 0.02, kd = 0.05; float setpoint = 1000; float integral = 0, prevError = 0; void encoderISR() { encoderCount++; } float getRPM() { static long lastCount = 0; static unsigned long lastTime = 0; unsigned long now = millis(); long count = encoderCount; float rpm = ((count - lastCount) * 60000.0) / (600.0 * (now - lastTime)); lastCount = count; lastTime = now; return rpm; } void loop() { setpoint = map(analogRead(A0), 0, 1023, 0, 2800); float actual = getRPM(); float error = setpoint - actual; integral += error * 0.1; integral = constrain(integral, -100, 100); float derivative = (error - prevError) / 0.1; float output = constrain(kp*error + ki*integral + kd*derivative, 0, 255); prevError = error; analogWrite(9, (int)output); delay(100); }
Test Motor Speed Control using PWM by verifying each subsystem individually before full integration.
Verify power voltages, check ground connections, use serial monitor for debug.
An interactive simulator will be available here — simulate circuits and run code in-browser without hardware.