DRV8833 Motor Module: Coast, Brake and Current Regulation Are Different

A motor driver can receive the same average command and still stop very differently. Build a small two-channel driver module, distinguish its electrical states, and use a controller that pauses before reversing direction. The experiment keeps commanded duty, winding-current regulation and observed shaft motion as three separate quantities.

This is a design candidate for a low-voltage bench prototype. The supplied software has host checks; the board has not been assembled, thermally qualified or run with a motor, and no target firmware binary is claimed. Start with a regulated 5 V supply and a 0.5 A supply-current limit. The prototype targets a nominal 0.2 A current trip per bridge; it does not have a qualified continuous-current rating.

What this module does

The Texas Instruments DRV8833 is an integrated circuit (IC): a semiconductor device containing both control circuits and power switches. Its two H-bridges each connect a motor winding to the supply in either polarity. Reversing that polarity can reverse a brushed direct-current motor. A direct-current (DC) supply maintains one polarity; the bridge changes which motor terminal receives its positive side.

Here we use the exact DRV8833PWPT, in the PWP 16-pin thermally enhanced small-outline package. The final T specifies tape packaging. PWP has an exposed underside thermal connection, unlike the similarly named PW package. Package choice changes both assembly and the conditions behind the data-sheet current figures. A library symbol labelled only “DRV8833” is insufficient unless its pins and footprint match this exact package.

Pulse-width modulation (PWM) controls how much of each repeated interval an input remains HIGH. Duty is that HIGH fraction, not a speed measurement. Motor resistance, friction, load, supply voltage and internal current regulation all affect the resulting speed. The complete example runs on an original non-wireless Raspberry Pi Pico with the RP2040 controller, using MicroPython, a Python implementation intended for microcontrollers.

Circuit, components and named connections

DRV8833PWPT module schematic with physical pins, separate A and B motor connectors, one-ohm sense resistors, sleep pull-down, fault pull-up, VM and VINT bypass capacitors VCP capacitor to VM and a unidirectional TVS from VM to GND.
Original complete module connection drawing. Identically named nets are connected electrically; GND is their shared zero-volt reference. Editable SVG · Full-size PNG.

A net is a set of terminals joined by conductive wiring. The repeated labels in this drawing keep the two bridges and their support parts readable without overlapping wire bundles. They do not imply isolation between repeated labels. Ground (GND) is the common reference conductor, including the Pico, motor supply and driver module.

PartExact choice or valueJob
U1TI DRV8833PWPT, PWP0016CTwo bridges and winding-current regulation
R1, R2Vishay CRCW12061R00FKEA; 1 Ω, ±1%, 0.25 W, 1206Convert each bridge current into a sense voltage
C1TDK C3225X7R1E226M250AB; 22 µF, ±20%, 25 V, 1210Local motor-supply ceramic bypass; qualify at least 10 µF effective
C2TDK C2012X7R1C225K125AB; 2.2 µF, 16 V, 0805Bypass the internal regulator; do not power another circuit from VINT
C3TDK C1608X7R1H103K080AA; 10 nF, 50 V, 0603Connect the charge-pump node VCP to VM
D1Vishay SMBJ6.0A-E3/52, unidirectional transient suppressorBanded cathode to VM, anode to GND; finite-pulse rail clamp, not a continuous shunt supply
R3, R410 kΩ, ±1%, 0805Pull nFAULT to host 3.3 V and hold nSLEEP LOW by default
J1–J3, J4Samtec TSW-102-07-G-S and TSW-108-07-G-SPower, two motors and eight-pin logic interface

The leading n in nSLEEP and nFAULT identifies signals that are active LOW: LOW requests sleep or reports a fault. nFAULT is an open-drain output, a switch that can pull its signal toward ground but requires an external resistor for HIGH. R3 uses the Pico’s 3.3 V supply reference, not the motor supply. HIGH on that wire alone does not prove that the motor rail is present, correctly regulated or healthy.

U1 physical pinConnection
1; 16; 15nSLEEP to SLEEP; AIN1 and AIN2 to the A command nets
2; 4; 3AOUT1 and AOUT2 to J2; AISEN through R1 to GND
7; 5; 6BOUT1 and BOUT2 to J3; BISEN through R2 to GND
9; 10; 8BIN1, BIN2 and nFAULT to their named logic nets
12; 11; 14VM supply, VCP with C3 to VM, VINT with C2 to GND
13; exposed pad 17Both to GND copper; the thermal pad is not optional floating metal

For the supplied firmware, connect J4 pins 1–8 to Pico GND, 3V3 OUT, GP2, GP3, GP4, GP5, GP6 and GP7 respectively. On the original Pico, GP2/3/4/5 are physical pins 4/5/6/7, GP6/7 are pins 9/10, 3V3 OUT is pin 36 and GND can be pin 38. The Pico’s 3V3 OUT connection only supplies the logic pull-up reference; it must not supply the motor. Check the exact board label before wiring another Pico variant.

The charge pump is an internal switching circuit that creates the gate-drive voltage for the bridge switches. C3 is its specified support capacitor; it must return to VM exactly as shown, rather than to a convenient ground pad.

Current regulation is a threshold, not an average promise

The nominal sense threshold is 0.2 V. With 1 Ω resistance, the nominal trip is Itrip = Vtrip / Rsense = 0.2 V / 1 Ω = 0.2 A. Using the data-sheet 160–240 mV threshold range and ±1% resistor tolerance gives a static threshold calculation from 0.16/1.01 = 0.1584 A to 0.24/0.99 = 0.2424 A under the stated data-sheet conditions. This is not a bound on every instantaneous peak: blanking time, winding inductance and switching transients matter.

At the nominal trip, resistor dissipation is I²R = 0.04 W. Even the high static threshold calculation gives approximately 0.0582 W, below the selected resistor’s nominal power rating. Check its temperature derating and actual layout. The chip’s illustrative conduction loss at 0.2 A and its typical 0.36 Ω combined switch resistance is 0.0144 W per bridge; this excludes hot resistance, switching and other losses. It does not establish the temperature of this board.

The DRV8833 current regulator can interrupt a bridge even while the external command remains HIGH. Its short-circuit protection is a separate mechanism with different thresholds. Do not deliberately short a motor connector to “test the 0.2 A limit.” A stall can also heat a motor at a current the driver can tolerate. Choose a motor whose documented voltage, winding current, starting demand and mechanical limits suit this deliberately modest experiment.

Four input combinations, four useful observations

IN1IN2Bridge condition
LOWLOWCoast: both outputs high impedance
HIGHLOWForward polarity
LOWHIGHReverse polarity
HIGHHIGHBrake: both outputs LOW

High impedance means the switches are not actively fixing the terminal voltage. An inductive winding can still carry recirculating current through available semiconductor paths. In brake mode the winding is shorted through the bridge’s LOW-side paths; it is not disconnected. Neither state gives an unconditional mechanical stopping distance.

The delivered hardware demonstration defaults to forward and coast only. Hard braking and reversal are explicit function options, to be enabled only after load-energy and supply-transient qualification. The supplied controller implements forward PWM by toggling IN1 while holding IN2 LOW, giving the fast-decay command pattern described in the data sheet. Reversal requests pass through a chosen 100 ms coast interval. This pause makes the experiment repeatable but does not prove that a loaded rotor has stopped. Actual motor reversal and rail excursions still require measurement.

Software command illustration showing AIN1 forward HIGH, a reverse request at 200 milliseconds, both inputs LOW for a 100 millisecond coast pause, then AIN2 HIGH while nSLEEP stays HIGH.
Calculated command-policy example, not a measured electrical waveform. Editable SVG · Full-size PNG.

Complete controller and actual test boundary

The pure policy classes return desired PWM values and a sleep state. The hardware entry point writes those values to the four actual Pico outputs. It latches a LOW fault report, clears requests and requires explicit rearming; a later HIGH does not restart the motor automatically. Cleanup drives sleep LOW and releases PWM resources even if an exception interrupts the demonstration. A stalled Python program is a separate failure: this is not a safety-rated motor controller or a complete watchdog system.

"""Pico MicroPython two-bridge bench controller; host-testable policy.
No motor speed, motor-current or power-rail feedback is inferred from commands.
"""
MODULUS = 1 << 30

def elapsed(now, then):
    """Signed ticks difference, valid for intervals shorter than half a wrap."""
    import time
    if hasattr(time, 'ticks_diff'):
        return time.ticks_diff(now, then)
    return ((now - then + MODULUS // 2) % MODULUS) - MODULUS // 2


def drive_pair(mode, duty):
    if mode not in ('coast', 'brake', 'forward', 'reverse'):
        raise ValueError('Unknown bridge mode')
    if not isinstance(duty, int) or isinstance(duty, bool) or not 0 <= duty <= 65535:
        raise ValueError('duty must be an integer from 0 through 65535')
    if mode == 'coast':
        return (0, 0)
    if mode == 'brake':
        return (65535, 65535)
    return (duty, 0) if mode == 'forward' else (0, duty)


def trip_current(resistance_ohm, trip_v=0.2):
    if not 0 < resistance_ohm or not 0 < trip_v:
        raise ValueError('Positive sense resistance and threshold required')
    return trip_v / resistance_ohm


class Bridge:
    """Direction reversal passes through a 100ms COAST interval.
    This is a chosen bench pause, not a proof that the shaft has stopped.
    """
    def __init__(self):
        self.requested = ('coast', 0)
        self.applied = ('coast', 0)
        self.last_direction = None
        self.coast_since = None

    def request(self, mode, duty=0):
        drive_pair(mode, duty)  # validate before changing the previous request
        self.requested = (mode, duty)

    def clear(self):
        self.__init__()

    def step(self, now):
        mode, duty = self.requested
        direction = mode if mode in ('forward', 'reverse') and duty else None
        if direction and self.last_direction and direction != self.last_direction:
            if self.coast_since is None:
                self.coast_since = now
            if elapsed(now, self.coast_since) < 100:
                self.applied = ('coast', 0)
                return (0, 0)
            self.last_direction = direction
            self.coast_since = None
        elif direction:
            self.last_direction = direction
            self.coast_since = None
        else:
            # Explicit COAST or BRAKE cancels the reversal wait. Retain the
            # previous direction so a later opposite command still waits.
            self.coast_since = None
        self.applied = (mode, duty)
        return drive_pair(mode, duty)


class Controller:
    """Caller writes the returned input pairs and nSLEEP state every cycle."""
    def __init__(self):
        self.bridges = [Bridge(), Bridge()]
        self.enabled_at = None
        self.fault_latched = False

    def arm(self, now, fault_ok):
        if not fault_ok:
            self.stop(True)
            return False
        self.stop(False)
        self.enabled_at = now
        return True

    def stop(self, fault=False):
        for bridge in self.bridges:
            bridge.clear()
        self.enabled_at = None
        self.fault_latched = bool(fault)

    def request(self, bridge, mode, duty=0):
        if bridge not in (0, 1):
            raise ValueError('Bridge must be 0 or 1')
        if self.enabled_at is None or self.fault_latched:
            raise RuntimeError('Explicit arm required')
        self.bridges[bridge].request(mode, duty)

    def step(self, now, fault_ok):
        if not fault_ok:
            self.stop(True)
        if self.enabled_at is None or self.fault_latched:
            return False, ((0, 0), (0, 0))
        # Datasheet lists 1ms wake time; a 2ms software guard is chosen here.
        # This loop is not a verified timing instrument or a rail monitor.
        if elapsed(now, self.enabled_at) < 2:
            return True, ((0, 0), (0, 0))
        return True, tuple(b.step(now) for b in self.bridges)


def demo_sequence(allow_brake=False, allow_reverse=False):
    return [(0, 'forward', 16000), (3000, 'coast', 0),
            (5000, 'forward', 16000),
            (8000, 'brake' if allow_brake else 'coast', 0),
            (10000, 'coast', 0),
            (12000, 'reverse' if allow_reverse else 'coast',
             16000 if allow_reverse else 0), (15000, 'coast', 0)]


def run_demo(allow_brake=False, allow_reverse=False):
    from machine import Pin, PWM
    import time
    sleep = Pin(6, Pin.OUT, value=0)
    outputs = [PWM(Pin(n)) for n in (2, 3, 4, 5)]
    for out in outputs:
        out.freq(1000)
        out.duty_u16(0)
    fault = Pin(7, Pin.IN)  # module R3 pulls to the host's 3.3V reference
    controller = Controller()
    started = time.ticks_ms()
    # Hard braking/reversal require a separately qualified energy setup.
    sequence = demo_sequence(allow_brake, allow_reverse)
    index = 0
    try:
        if not controller.arm(started, bool(fault.value())):
            raise RuntimeError('nFAULT LOW before arm')
        while time.ticks_diff(time.ticks_ms(), started) < 17000:
            now = time.ticks_ms()
            age = time.ticks_diff(now, started)
            if index < len(sequence) and age >= sequence[index][0]:
                _, mode, duty = sequence[index]
                controller.request(0, mode, duty)
                print(age, mode, duty, 'command only; no RPM measurement')
                index += 1
            enabled, pairs = controller.step(now, bool(fault.value()))
            # Sleep LOW takes precedence over the input commands on any fault.
            if not enabled:
                sleep.value(0)
            for out, value in zip(outputs, pairs[0] + pairs[1]):
                out.duty_u16(value)
            if enabled:
                sleep.value(1)
            if controller.fault_latched:
                print('FAULT latched; no automatic restart')
                break
            time.sleep_ms(5)
    finally:
        sleep.value(0)
        for out in outputs:
            out.duty_u16(0)
            out.deinit()


if __name__ == '__main__':
    run_demo()

Run python3 test_drv8833_control.py on a computer to exercise the supplied command classes, invalid requests, independent bridges, fault latching, reversal timing, wrap arithmetic and sense-threshold calculations. These tests do not import the target’s machine module. They are not a MicroPython board run, an electrical driver simulation or a firmware-binary build. Copy the full file to the selected Pico only after the wiring review and bench checks below.

Bench procedure and useful failures

  1. With every supply OFF, inspect the package mark and pin-one orientation. Check both motor connectors for shorts to the supply and ground. Confirm that the exposed pad, pin 13 and supply negative are joined, and that C3 goes to VM rather than GND.
  2. Leave both motor connectors empty. Hold nSLEEP LOW, connect the Pico and the regulated 5 V input, then enable the bench supply with its current limit set. Measure VM, VINT and the logic reference using GND as the reference. Record values; do not assume an instrument reading from the component labels.
  3. Verify default sleep and the requested input combinations with an appropriate logic instrument. Do not attach an earth-referenced ground clip to a motor terminal; both terminals can be actively driven.
  4. Power OFF before attaching one documented low-power motor with its shaft free and secured. Keep hands, loose wires and mechanical loads clear. Begin at the example low duty and stop if it does not start normally. Repeatedly stalled starts are not a successful low-speed control method.
  5. Keep the delivered forward/coast-only demonstration first. Compare coast and brake from the same initial condition only after the actual load energy and rail transient setup have been qualified. Record supply voltage and current, observed stopping time and whether nFAULT asserted. Use the blank worksheet instead of treating the command printout as a measurement.
  6. Test the software’s fault path by substituting a controlled LOW at the host input while the module’s open-drain fault connection is isolated appropriately, or by host tests. Do not force the chip into thermal shutdown or a short circuit to obtain a photograph. Confirm that recovery requires rearming.

Local bypass is only part of the power design. A transient-voltage suppressor (TVS) is a diode intended to divert a specified short overvoltage pulse. The module adds Vishay SMBJ6.0A-E3/52 across VM and GND, with its banded cathode at VM. Its 6 V stand-off is above the prototype input range; the data sheet gives a 10.3 V maximum clamp at a specified 58.3 A, 10/1000 µs pulse and temperature condition. This is not a guarantee for every front edge or layout. The 600 W pulse figure is not a continuous power rating or an unlimited motor-energy allowance. Motor braking can return energy to the supply; many bench supplies do not absorb it. At least 100 µF of external bulk capacitance rated 16 V or higher must be fitted very close to J1, and the complete energy-handling arrangement must be qualified for the actual motor, lead inductance and braking method. The IC’s 10.8 V operating maximum and 11.8 V absolute maximum are different limits, and neither grants permission for uncontrolled rail spikes on a nominal 5 V board.

For the printed circuit board (PCB), use short output loops, keep both sense resistors near the IC and provide their low-impedance ground returns. Solder the exposed pad to appropriate copper. Inspect actual thermal-via and paste geometry with the chosen assembly process. A clean design-rule report only checks the configured geometry; it does not prove electrical current capability, thermal performance or motor compatibility.

Challenge

The serial log says “brake,” but the measured stopping time changes when a heavier wheel is fitted. Has the truth table failed?

Show the reasoning

No. The table specifies an electrical connection, not a mechanical stop time. The larger inertia changes stored energy and the current/torque history. Check actual input states, winding current, supply behavior and the mechanical setup; do not redefine a command as an RPM sensor.

Files and primary reading

Download the complete source and experiment files · Blank observations CSV. CSV means comma-separated values, a plain-text table format. For another example separating a timer command from a physical load, read the published ATtiny85 passive-piezo experiment.