A direct current motor (DC motor) produces continuous rotary motion at variable speed and direction. Unlike the servo, which holds a position, the DC motor turns continuously: its speed is set by PWM, its direction by reversing the polarity across its terminals. Doing that reversal from a microcontroller calls for an H-bridge (DRV8833, TB6612FNG, L298N): it is on that circuit that this page concentrates. In MicroPython, two Pin give the direction and one PWM the speed.

What is it for?

Typical cases: a two-wheeled mobile robot (2 DC motors, independent speed and direction), a conveyor, winch or rotating arm, a pump or fan set to a speed.

ActuatorPositionSpeedAngular accuracy
Standard servo✅ 0-180°poormedium
DC motor + H-bridge❌✅ continuousnone without an encoder
Stepper motor✅ step by step✅ controlledexcellent

Step by step

Four steps: choose the driver, wire it, power it, write the code.

1. Choose the driver — mind the 3.3 V logic

DriverMotor voltageCurrent3.3 V logicNotes
DRV88332.7-10.8 V1.2 A/channel✅ yescompact, ideal for the Pico
TB6612FNG4.5-13.5 V1.2 A/channel✅ yesMOSFET, good efficiency
L298N5-35 V2 A/channel⚠️ inputs meant for 5 Vwidespread, but 3.3 V sometimes marginal + a large voltage drop

On the Pico (3.3 V outputs), prefer the DRV8833 or the TB6612FNG, whose logic inputs accept 3.3 V. The L298N, designed for 5 V inputs, may read 3.3 V badly, and loses ~2 V in the bridge. We take the DRV8833 for what follows.

2. Wire it (DRV8833, one motor)

DRV8833Pico
AIN1GP12 (direction 1)
AIN2GP11 (direction 2)
AO1, AO2motor terminals
VCC (logic)3.3 V
VM (power)motor supply (depending on the motor)
GNDsupply GND plus Pico GND (common)

The DRV8833 expects two signals per motor: you vary the speed by sending a PWM onto AIN1 (forward) or AIN2 (reverse), the other being at 0.

Wiring a DRV8833 H-bridge on the Pico: AIN1 on GP12 (forward PWM), AIN2 on GP11 (reverse PWM), logic VCC on 3.3 V, VM on a separate motor supply, motor on AO1/AO2 with a 100 µF capacitor, common ground.|640

3. Power it

A separate motor supply as soon as the motor draws more than ~50 mA: a battery or pack suited to the motor, a LiPo, or a bench supply. Common GND between the Pico and the motor supply. A 100 µF capacitor near the motor terminals absorbs the peaks.

4. Write the code

from machine import Pin, PWM
from time import sleep
 
av = PWM(Pin(12)); av.freq(1000)     # AIN1: forward
ar = PWM(Pin(11)); ar.freq(1000)     # AIN2: reverse
 
def avancer(vitesse):                # speed 0..65535
    ar.duty_u16(0)
    av.duty_u16(vitesse)
 
def reculer(vitesse):
    av.duty_u16(0)
    ar.duty_u16(vitesse)
 
def arret():
    av.duty_u16(0)
    ar.duty_u16(0)
 
while True:
    avancer(30000); sleep(2)
    arret();        sleep(0.5)
    reculer(50000); sleep(2)
    arret();        sleep(0.5)

The motor turns one way, stops, sets off the other way. (On an L298N you would keep 2 direction Pin plus 1 PWM on ENA. The principle is the same.)

Example — Speed on a potentiometer plus a direction button

A MicroPython elegance: read_u16() and duty_u16() are both 16 bits → the potentiometer’s speed drives the PWM with no scaling.

Wiring: DRV8833 as in step 2; potentiometer on GP26 (→ micropython-capteur-analogique-en); button between GP14 and GND (PULL_UP, no external resistor).

from machine import Pin, PWM, ADC
from time import ticks_ms, ticks_diff
 
av = PWM(Pin(12)); av.freq(1000)
ar = PWM(Pin(11)); ar.freq(1000)
pot = ADC(Pin(26))
bouton = Pin(14, Pin.IN, Pin.PULL_UP)
 
sens_avant = True
etat_stable, dernier_btn = 1, 1
dernier_chg = ticks_ms()
 
while True:
    vitesse = pot.read_u16()              # 0..65535, usable as is
 
    lect = bouton.value()                 # debouncing + edge
    if lect != dernier_btn:
        dernier_chg = ticks_ms(); dernier_btn = lect
    if ticks_diff(ticks_ms(), dernier_chg) > 30 and lect != etat_stable:
        etat_stable = lect
        if etat_stable == 0:
            sens_avant = not sens_avant
 
    if sens_avant:
        ar.duty_u16(0); av.duty_u16(vitesse)
    else:
        av.duty_u16(0); ar.duty_u16(vitesse)

How to read this code

The non-blocking debouncing rests on two variables: dernier_btn holds the last raw reading (to restart the stopwatch as soon as the signal moves), etat_stable holds the confirmed state (kept once the signal has been stable for 30 ms). The direction only flips on a falling edge (etat_stable == 0): with PULL_UP, the pin sits at 1 at rest and drops to 0 when pressed — one flip per press. Everything is non-blocking: no sleep(), the motor stays driven on every pass.

Pitfalls

3.3 V logic on an L298N. The L298N’s inputs are meant for 5 V: 3.3 V may be read badly. On the Pico, prefer the DRV8833 or TB6612 (3.3 V inputs). A pitfall specific to 3.3 V boards.

The Pico reboots when the motor starts. A current peak causes a voltage drop. A separate supply for the motor, a 100 µF capacitor across its terminals.

No common GND. Direction signals with no reference → the bridge flips at random. Always a common GND.

Instant reversal of direction. Switching brutally from one direction to the other (with no arret() in between) causes a violent reverse current. Good practice: arret() plus a short pause before the reversal.

Two PWM high at the same time. Setting AIN1 and AIN2 above 0 = braking or an internal short circuit. Drive only one at a time (the other at 0).

Motor stalled (rotor blocked). The motor draws full current (5-10× the nominal) without turning, and heats up. Detect it (current measurement, rotation sensor) and cut it.

PWM too low, motor audible. A PWM frequency in the audible band makes the motor whine. Set freq() (often 1-20 kHz) to suit the motor.

Special case — Closed loop with an encoder

A bare DC motor has no feedback: the real speed depends on the supply, the load, the wear. For accurate control: an encoder on the shaft (read through interrupts) plus PID control on the measured speed (see PID control). That is the natural evolution of a robot that has to follow a repeatable path.

Where it fits in the project

  • Step 2 of the proof of concept phase — motor plus driver validated on an isolated bench (rotation, direction, speed range).
  • Step 3 of the proof of concept phase — integration into the measure → decide → move chain.
  • Step 4 of the concept phase — choosing between DC motor, stepper and servo according to the need.

A 2-wheeled robot plus a DRV8833 plus 2 DC motors is one of the most instructive school proofs of concept: GPIO, PWM, separate supply and debouncing in a single project.

See also