If you’re looking to get into robotics with the Raspberry Pi, the first thing you need to master is motor control. Here, I’ll show you how to interface the Raspberry Pi 4(Model B) with an L298N dual H-bridge motor driver to control two DC motors using Python. We’ll even add push-buttons for manual forward and reverse control.
First, let’s look at the logic pins on the L298N. We are using BCM pin numbering for our Raspberry Pi.
ENA, IN1, and IN2 control Motor A. We’ve connected ENA to GPIO 18—this is a hardware PWM pin which allows for smooth speed control. IN1 goes to GPIO 27 and IN2 to GPIO 22 for direction.
For Motor B, we use IN3 on GPIO 23, IN4 on GPIO 24, and the speed controller ENB on GPIO 13.
Power is critical. The L298N has a 3-pin power block:
12V Terminal: Connect your external battery pack here (usually 7V to 12V).
5V Terminal: On most L298N modules, if the 12V jumper is in place, this pin acts as an output. You can use it to power the Raspberry Pi, or if you're powering the Pi separately, leave this disconnected.
GND Terminal: This is vital—you must connect the ground of your battery pack to a Ground pin on the Raspberry Pi. Without a common ground, the logic signals won't work.
For manual control, we have two buttons connected to GPIO 17 and GPIO 4. We’re using the Pi’s internal pull-up resistors, so the buttons should connect the pin to Ground when pressed.
Finally, connect your left motor to the OUT1/OUT2 terminals and your right motor to OUT3/OUT4. If your motor spins the wrong way later, just flip these wires!
When I press the Forward button, IN1 and IN3 go HIGH, and the PWM sets our speed to 80%. Release the buttons, and the stop motors function pulls all pins LOW to halt the robot.
The following is python script to control the motors using Raspberry Pi 4 and L298N motor driver module:
import RPi.GPIO as GPIO
from time import sleep
# --- Pin Definitions (Your Hardware PWM Optimized Mapping) ---
ENA, IN1, IN2 = 18, 27, 22
IN3, IN4, ENB = 23, 24, 13
BTN_FWD, BTN_REV = 17, 4
# --- Setup ---
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
# Setup Output Pins
for pin in [ENA, IN1, IN2, IN3, IN4, ENB]:
GPIO.setup(pin, GPIO.OUT)
# Setup Input Pins with Internal Pull-ups
GPIO.setup(BTN_FWD, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(BTN_REV, GPIO.IN, pull_up_down=GPIO.PUD_UP)
# --- PWM Initialization ---
# 1000Hz is smoother for L298N simulation than 100Hz
pwm_a = GPIO.PWM(ENA, 1000)
pwm_b = GPIO.PWM(ENB, 1000)
pwm_a.start(0)
pwm_b.start(0)
# Track the state to prevent redundant commands in the loop
current_state = "STOP"
def move_forward():
GPIO.output(IN1, GPIO.HIGH)
GPIO.output(IN2, GPIO.LOW)
GPIO.output(IN3, GPIO.LOW)
GPIO.output(IN4, GPIO.HIGH)
pwm_a.ChangeDutyCycle(80)
pwm_b.ChangeDutyCycle(80)
print("Action: Moving Forward")
def move_reverse():
GPIO.output(IN1, GPIO.LOW)
GPIO.output(IN2, GPIO.HIGH)
GPIO.output(IN3, GPIO.HIGH)
GPIO.output(IN4, GPIO.LOW)
pwm_a.ChangeDutyCycle(80)
pwm_b.ChangeDutyCycle(80)
print("Action: Moving Reverse")
def stop_motors():
pwm_a.ChangeDutyCycle(0)
pwm_b.ChangeDutyCycle(0)
# Turn off direction pins to save power/reduce noise
GPIO.output([IN1, IN2, IN3, IN4], GPIO.LOW)
print("Action: Stopped")
# --- Main Loop ---
print("Simulation Ready. GPIO 15: FWD | GPIO 14: REV")
try:
while True:
# Read button states (LOW = Pressed)
fwd_pressed = GPIO.input(BTN_FWD) == GPIO.LOW
rev_pressed = GPIO.input(BTN_REV) == GPIO.LOW
# Logic with State Tracking
if fwd_pressed and current_state != "FORWARD":
move_forward()
current_state = "FORWARD"
elif rev_pressed and current_state != "REVERSE":
move_reverse()
current_state = "REVERSE"
elif not fwd_pressed and not rev_pressed and current_state != "STOP":
stop_motors()
current_state = "STOP"
sleep(0.05) # Optimized for Proteus simulation speed
except KeyboardInterrupt:
print("\nCleaning up GPIO...")
pwm_a.stop()
pwm_b.stop()
GPIO.cleanup()
Now for the code. We start by setting up our pins and initializing PWM at 1000Hz. This frequency ensures our motors don't make too much high-pitched noise while running.
In our main loop, we check the state of our buttons. Because we used PUD_UP, the logic is 'active low'—meaning when the button is pressed, the value goes to LOW. We use a current_state variable to make sure we aren't spamming the motor driver with commands, which keeps the simulation and the hardware running efficiently.
The following video shows how the motor control using Raspberry Pi and L298N motor driver module works:
If you found this helpful, leave your comments below and subscribe for more Raspberry Pi robotics tutorials. Thanks for reading and Happy building!
📥 Download Free Proteus Raspberry Pi 4 Library/Model
Download Raspberry Pi 4 Library for Proteus
📥 Download Free Proteus L298N Motor Driver Module Library/Model
Download L298N Motor Driver Module Proteus
