Raspberry Pi Pico 2 L298N Motor Driver Control Using MicroPython

Hey everyone! In this Raspberry Pi Pico 2 tutorial, I will demonstrating a complete robotics drive circuit. I will be using the powerful new Raspberry Pi Pico 2 alongside a classic L298N H-Bridge module to drive two independent DC motors.

Raspberry Pi Pico 2 L298N DC motor diagram

Let’s start with the control setup. We have two momentary push buttons connected to the Pico 2 on pins GPIO 14 and 15. Since they're wired directly to the ground, we initialize them with internal pull-up resistors in our code. This means when a button is pressed, the signal drops to logic LOW, which triggers our movement actions.

To interface the Pico 2 with the high-current demands of our motors, we are utilizing the L298N driver module. We've dedicated six GPIO pins on the Pico to manage it. Pins GP7 and GP2 are connected to Enable A and Enable B, handling our hardware Pulse Width Modulation, or PWM, to control motor speed. Meanwhile, pins GP6 down to GP3 connect directly to Input pins 1 through 4 to establish the direction of rotation.

For the power layout, the motor driver is supplied by an external 12V source to properly drive the motors. Crucially, the ground from our 12V supply, the L298N module, our input switches, and the Raspberry Pi Pico 2 are all tied together to establish a stable common ground reference.

Now watch what happens when the code runs. Pressing the top button on pin 14 shifts our directional logic: IN1 and IN4 go HIGH, while IN2 and IN3 go LOW. Combined with our 80% duty-cycle PWM signal on the enable lines, both motors spin forward perfectly. Releasing it stops them instantly."

When we press the bottom button on pin 15, the states invert. IN1 and IN4 switch to LOW, and IN2 and IN4 shift to HIGH, immediately reversing the rotation of both motors. This clean, conflict-free layout is fully responsive and completely ready to be moved onto a physical breadboard or custom PCB for your next robotics project. 

Technical Circuit Analysis

  • Microcontroller: A Raspberry Pi Pico 2 serves as the central control unit.

  • Motor Driver: An L298N H-Bridge Dual Motor Driver module is utilized to handle the high current demands of the motors, protecting the Pico's GPIO pins.

  • Actuators: Two standard DC Motors representing a mobile robot's drive configuration (Left and Right).

  • Inputs: Two tactile push-buttons configured with internal pull-up resistors.

    • Button 1 (Forward): Connected between GP14 and GND.

    • Button 2 (Reverse): Connected between GP15 and GND.

  • Control Wiring (Pico to L298N):

    • GP7 $\rightarrow$ ENA (Enable A - Left Motor Speed PWM)

    • GP6 $\rightarrow$ IN1 (Left Motor Direction 1)

    • GP5 $\rightarrow$ IN2 (Left Motor Direction 2)

    • GP4 $\rightarrow$ IN3 (Right Motor Direction 1)

    • GP3 $\rightarrow$ IN4 (Right Motor Direction 2)

    • GP2 $\rightarrow$ ENB (Enable B - Right Motor Speed PWM)

  • Power Rails: * A 12V DC Source powers the L298N motor driver supply terminal to drive the motors.

    • A shared Common Ground (GND) explicitly links the Pico, the L298N module, the power source, and the buttons to ensure proper logic level references.

Python Code in micropython

The Python Code for this project is below:

from machine import Pin, PWM
from time import sleep

# --- Pin Definitions (Your Hardware Assignment) ---
ENA, IN1, IN2 = 7, 6, 5
IN3, IN4, ENB = 4, 3, 2
BTN_FWD, BTN_REV = 14, 15

# --- Setup Output Pins ---
IN1_pin = Pin(IN1, Pin.OUT)
IN2_pin = Pin(IN2, Pin.OUT)
IN3_pin = Pin(IN3, Pin.OUT)
IN4_pin = Pin(IN4, Pin.OUT)

# --- Setup Input Pins with Internal Pull-ups ---
BTN_FWD_pin = Pin(BTN_FWD, Pin.IN, Pin.PULL_UP)
BTN_REV_pin = Pin(BTN_REV, Pin.IN, Pin.PULL_UP)

# --- PWM Initialization ---
# Pins 7 (Slice 3B) and 2 (Slice 1B) are excellent choices for independent hardware PWM
pwm_a = PWM(Pin(ENA))
pwm_b = PWM(Pin(ENB))
pwm_a.freq(1000)
pwm_b.freq(1000)

# 16-bit duty cycles (0 to 65535)
DUTY_80_PERCENT = 52428
DUTY_0_PERCENT = 0

pwm_a.duty_u16(DUTY_0_PERCENT) 
pwm_b.duty_u16(DUTY_0_PERCENT)

# Track the state to prevent redundant commands in the loop
current_state = "STOP"

def move_forward():
    IN1_pin.value(1)
    IN2_pin.value(0)
    IN3_pin.value(0)
    IN4_pin.value(1)
    pwm_a.duty_u16(DUTY_80_PERCENT) 
    pwm_b.duty_u16(DUTY_80_PERCENT)
    print("Action: Moving Forward")

def move_reverse():
    IN1_pin.value(0)
    IN2_pin.value(1)
    IN3_pin.value(1)
    IN4_pin.value(0)
    pwm_a.duty_u16(DUTY_80_PERCENT)
    pwm_b.duty_u16(DUTY_80_PERCENT)
    print("Action: Moving Reverse")

def stop_motors():
    pwm_a.duty_u16(DUTY_0_PERCENT)
    pwm_b.duty_u16(DUTY_0_PERCENT)
    IN1_pin.value(0)
    IN2_pin.value(0)
    IN3_pin.value(0)
    IN4_pin.value(0)
    print("Action: Stopped")

# --- Main Loop ---
print(f"Simulation Ready. Pin {BTN_FWD}: FWD | Pin {BTN_REV}: REV")

try:
    while True:
        # Read button states (0 = Pressed because of PULL_UP resistors)
        fwd_pressed = (BTN_FWD_pin.value() == 0)
        rev_pressed = (BTN_REV_pin.value() == 0)

        # 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) # Kept at 50ms for smooth Proteus simulation execution

except KeyboardInterrupt:
    print("\nCleaning up PWM channels...")
    pwm_a.deinit()
    pwm_b.deinit()

This MicroPython code controls two robotic DC motors via an L298N H-Bridge driver using a Raspberry Pi Pico 2. It initializes Pins 7 and 2 as hardware PWM outputs running at 1000Hz to handle smooth speed adjustment, using MicroPython's 16-bit duty cycle scale where 52428 sets an 80% speed and 0 stops them. Pins 3 through 6 are set as digital outputs to manage the H-bridge directional logic, while Pins 14 and 15 are configured as inputs with internal pull-up resistors to read the forward and reverse button presses as logic LOW (0). To prevent redundant CPU overhead and optimize Proteus simulation performance, a state-tracking variable ensures motor functions are only called once upon a button state change, all running inside a continuous loop buffered by a stable 50ms sleep delay and wrapped in a safe interrupt cleanup structure.

Thanks for reading and see you in the next one!

Download Proteus Library/Model

Related Tutorials


Post a Comment

Previous Post Next Post