Motor drivers

Every motor class implements the same Motor contract (Interfaces), so higher layers — and your code — swap motor types without changes. Speeds are output-shaft degrees per second, angles are degrees, dc() duty is -100..100.

from openbricks.drivers.st3032 import ST3032Motor

m = ST3032Motor(servo_id=1, uart_id=1, tx=14, rx=6)
m.run_speed(120)          # wheel mode: spin at 120 deg/s
print(m.angle())          # multi-turn accumulated degrees
m.coast()

from openbricks.drivers.jgb37_520 import JGB37Motor

e = JGB37Motor(in1=12, in2=14, pwm=27, encoder_a=18, encoder_b=19)
e.run_angle(360, 720)     # two turns at 360 deg/s, blocking

DC gear motors with encoders

JGB37-520

JGB37-520 geared DC motor with magnetic quadrature encoder.

This is a 6-wire motor: two power leads (into an H-bridge, typically L298N), Vcc and GND for the hall sensors, and two encoder channels A and B.

Typical spec sheet values (varies by gear ratio variant):
  • Encoder CPR at motor shaft: 11

  • Gear ratio (example): 1:30 -> output shaft CPR = 11 * 30 * 4 = 1320 edges (multiply by 4 because we count both edges on both channels)

This class is a thin Python wrapper over the C Servo type in _openbricks_native (see native/user_c_modules/openbricks/servo.c). The wrapper’s job is to build the hardware handles (Pin / PWM / encoder) from pin numbers and pass them to the native servo — the closed-loop control tick runs in C at the motor_process tick rate (1 kHz default).

class openbricks.drivers.jgb37_520.JGB37Motor(in1, in2, pwm, encoder_a, encoder_b, counts_per_output_rev=1320, invert=False, encoder_invert=False, kp=_DEFAULT_KP)[source]

Bases: Motor

JGB37-520 DC gear motor with quadrature encoder, closed-loop.

Runs the full Motor contract on the native 1 kHz servo core: run_speed(deg_per_s), run_angle(deg_per_s, target_angle), dc(duty), brake() / coast(), angle() / reset_angle(), speed(). Pair two of them in a DriveBase for the coupled 2-DOF chassis controller.

Parameters:
  • in1 – H-bridge pins (e.g. L298N IN1/IN2/EN).

  • in2 – H-bridge pins (e.g. L298N IN1/IN2/EN).

  • pwm – H-bridge pins (e.g. L298N IN1/IN2/EN).

  • encoder_a – quadrature encoder channel pins.

  • encoder_b – quadrature encoder channel pins.

  • counts_per_output_rev – encoder edges per output-shaft revolution (4 × PPR × gear ratio; 1320 for the common 11-PPR 1:30 variant).

  • invert – flip motor command AND encoder together — for a motor wired backwards end-to-end (typically the mirrored side of a drivebase).

  • encoder_invert – flip ONLY the encoder reading — for mirror-mounted encoders that count down on motor-forward.

  • kp – speed-loop proportional gain (native servo core).

Example:

from openbricks.drivers.jgb37_520 import JGB37Motor

m = JGB37Motor(in1=12, in2=14, pwm=27,
               encoder_a=18, encoder_b=19)
m.run_angle(360, 720)      # two turns at 360 deg/s, blocking
print(m.angle())
dc(duty)[source]

Run at a fixed raw duty cycle (-100..100), open loop — Pybricks Motor.dc(). Non-blocking. This is the pre-1.21.0 run().

speed()[source]

Measured shaft speed in degrees per second — Pybricks Motor.speed(). Closed-loop drivers implement it (encoder observer / servo present-speed register).

brake()[source]

Stop with active braking (both terminals shorted).

coast()[source]

Stop by cutting drive power (motor free-wheels).

angle()[source]

Return the current shaft angle in degrees.

reset_angle(angle=0)[source]

Set the current angle to angle degrees.

run_speed(deg_per_s)[source]

Enter closed-loop speed control with the given target (deg/s).

run_angle(deg_per_s, target_angle, wait=True, accel_dps2=1500.0)[source]

Rotate by target_angle degrees, following a trapezoidal profile at up to deg_per_s.

The native servo builds the profile once and the scheduler samples it at 1 kHz, so acceleration / cruise / deceleration phases are smooth and the move stops cleanly at the target without overshoot (apart from small integration error).

With wait=False the scheduler keeps running the profile; the caller can poll self._servo.is_done() or eventually call brake() / coast().

MG370

MG370 geared DC motor with GMR (giant magnetoresistance) quadrature encoder.

The GMR variant has a 500-PPR encoder on the motor shaft, giving massively more resolution than a standard Hall-effect encoder — at a 1:34 gearbox the output shaft sees 500 * 4 * 34.014 68028 CPR. That edge rate is too fast for the software QuadratureEncoder (Pin.irq() tops out around 5-10 kHz), so this driver uses the native PCNTEncoder — a C wrapper over the ESP32 PCNT peripheral — baked into the firmware image.

Every MG370Motor instance needs its own PCNT unit — ESP32 has 8, ESP32-S3 has 4. Pick unit=0 for the first motor, unit=1 for the second, etc.

Apart from the encoder layer, MG370Motor is identical to JGB37Motor: same native Servo underneath, same control tick, same closed-loop API.

class openbricks.drivers.mg370.MG370Motor(in1, in2, pwm, encoder_a, encoder_b, pcnt_unit=0, pcnt_filter=1023, counts_per_output_rev=_DEFAULT_CPR, invert=False, encoder_invert=False, kp=_DEFAULT_KP)[source]

Bases: Motor

MG370 DC gear motor with high-resolution GMR encoder, closed-loop.

Same Motor contract and native 1 kHz servo core as JGB37Motor; the difference is the encoder path — the 500-PPR GMR encoder is counted by the ESP32 PCNT hardware peripheral (pcnt_unit), not GPIO interrupts, because its edge rate exceeds what Pin.irq can service.

Parameters:
  • in1 – H-bridge pins.

  • in2 – H-bridge pins.

  • pwm – H-bridge pins.

  • encoder_a – encoder channel pins (into PCNT).

  • encoder_b – encoder channel pins (into PCNT).

  • pcnt_unit – PCNT peripheral unit, unique per motor (ESP32 has 8, ESP32-S3 has 4 — first motor 0, second 1, …).

  • pcnt_filter – PCNT glitch filter in APB cycles (default 1023 ≈ 12.8 µs; lower it only for encoders faster than ~35 kHz edge rate).

  • counts_per_output_rev – encoder edges per output-shaft revolution (default matches the 1:34 GMR variant).

  • kp (invert / encoder_invert /) – as in JGB37Motor.

dc(duty)[source]

Run at a fixed raw duty cycle (-100..100), open loop — Pybricks Motor.dc(). Non-blocking. This is the pre-1.21.0 run().

speed()[source]

Measured shaft speed in degrees per second — Pybricks Motor.speed(). Closed-loop drivers implement it (encoder observer / servo present-speed register).

brake()[source]

Stop with active braking (both terminals shorted).

coast()[source]

Stop by cutting drive power (motor free-wheels).

angle()[source]

Return the current shaft angle in degrees.

reset_angle(angle=0)[source]

Set the current angle to angle degrees.

run_speed(deg_per_s)[source]

Hold a target speed (closed loop).

run_angle(deg_per_s, target_angle, wait=True, accel_dps2=1500.0)[source]

Rotate by target_angle degrees at deg_per_s. Blocks if wait; otherwise returns immediately and the caller polls done() to advance the move and detect completion.

H-bridge drivers (open loop)

L298N

L298N H-bridge motor driver.

The L298N drives a brushed DC motor via two direction pins (IN1/IN2) and one PWM pin (EN-A or EN-B). It’s open-loop: the class knows nothing about the physical motor speed or position. For closed-loop use, wrap one of these in jgb37_520.JGB37Motor (which adds an encoder).

Pinout recap for one channel:

IN1  = direction bit A
IN2  = direction bit B
PWM  = enable / speed (tie to VCC for 100%, PWM for speed control)

IN1  IN2  result
---  ---  ----------
 0    0   coast
 0    1   reverse
 1    0   forward
 1    1   brake
class openbricks.drivers.l298n.L298NMotor(in1, in2, pwm, invert=False, pwm_freq=_PWM_FREQ_HZ)[source]

Bases: Motor

Open-loop brushed DC motor on an L298N H-bridge channel.

No encoder, so only the open-loop subset of the Motor contract works: dc(duty), brake(), coast(). Closed-loop methods (run_speed, run_angle, angle, hold) raise NotImplementedError — wrap the same H-bridge channel in JGB37Motor when the motor has an encoder.

dc(duty)[source]

Duty is -100..100 — Pybricks Motor.dc(). (Open-loop H-bridge: there is no closed-loop run(speed) here; the inherited run surfaces run_speed’s NotImplementedError.)

brake()[source]

Short both terminals — active brake.

coast()[source]

Cut drive entirely — motor spins freely.

TB6612FNG

TB6612FNG dual MOSFET H-bridge — re-exposes L298NMotor under the TB6612 name.

Both chips drive each motor channel with the same three signals:

IN1 — direction bit 1 IN2 — direction bit 2 PWM — speed (PWM duty cycle)

…so the openbricks driver is identical. TB6612 is generally the better commodity choice — MOSFETs instead of Darlingtons, ~0.3 V drop instead of ~1.8 V, 3.3 V-logic compatible directly from an ESP32 GPIO, and 3.2 A peak per channel vs L298N’s 2 A.

Wiring difference to be aware of: TB6612 has a chip-level STBY pin that must be held high for either channel to operate. Tie it to 3.3 V on your breakout, or drive a spare GPIO high at boot — openbricks doesn’t model STBY because most breakout modules already pull it up.