Source code for openbricks.drivers.jgb37_520

# SPDX-License-Identifier: MIT
"""
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).
"""

import time

from machine import Pin, PWM

from openbricks._native import QuadratureEncoder, Servo
from openbricks.interfaces import Motor

_PWM_FREQ_HZ = 20_000
_DEFAULT_KP = 0.3


[docs] class JGB37Motor(Motor): def __init__( self, in1, in2, pwm, encoder_a, encoder_b, counts_per_output_rev=1320, invert=False, encoder_invert=False, kp=_DEFAULT_KP, ): # See ``MG370Motor`` for ``encoder_invert`` semantics: it flips # ONLY the encoder reading (mirror-mounted motor pairs where # the encoder counts down on motor-forward), unlike ``invert`` # which flips both motor command and encoder together (motors # wired backwards end-to-end). from openbricks.drivers.mg370 import _InvertedEncoder self._in1 = Pin(in1, Pin.OUT, value=0) self._in2 = Pin(in2, Pin.OUT, value=0) self._pwm = PWM(Pin(pwm), freq=_PWM_FREQ_HZ, duty=0) self._enc = QuadratureEncoder(pin_a=encoder_a, pin_b=encoder_b) encoder_for_servo = _InvertedEncoder(self._enc) if encoder_invert else self._enc self._servo = Servo( in1=self._in1, in2=self._in2, pwm=self._pwm, encoder=encoder_for_servo, counts_per_rev=counts_per_output_rev, invert=invert, kp=kp, ) # --- Open-loop passthroughs (native Servo detaches from scheduler) ---
[docs] def run(self, power): self._servo.run(float(power))
[docs] def brake(self): self._servo.brake()
[docs] def coast(self): self._servo.coast()
# --- Closed-loop state ---
[docs] def angle(self): return self._servo.angle()
[docs] def reset_angle(self, angle=0): self._servo.reset_angle(float(angle))
[docs] def run_speed(self, deg_per_s): """Enter closed-loop speed control with the given target (deg/s).""" self._servo.run_speed(float(deg_per_s))
[docs] def run_angle(self, deg_per_s, target_angle, wait=True, accel_dps2=720.0): """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()``. """ self._servo.run_target(float(target_angle), abs(float(deg_per_s)), float(accel_dps2)) if not wait: return while not self._servo.is_done(): time.sleep_ms(10) self.brake()