188 lines
5.1 KiB
Python
188 lines
5.1 KiB
Python
"""
|
|
gauge_vid6008.py — Automotive stepper gauge driver for ESP32 / MicroPython
|
|
|
|
Supports two driver modes:
|
|
|
|
1. STEP/DIR mode (e.g. VID-6008): 2 pins — DIR and STEP
|
|
Each step requires a pulse on STEP; DIR sets direction.
|
|
|
|
2. 4-PHASE mode (e.g. VID28/BKA30D/Switec X25): 4 pins — IN1..IN4
|
|
Driven via two H-bridge pairs (ULN2003 or direct GPIO).
|
|
"""
|
|
|
|
from machine import Pin
|
|
import utime
|
|
|
|
|
|
_TOTAL_STEPS = 3780
|
|
_OVERRUN_STEPS = 200
|
|
|
|
_FULL_SEQUENCE = [
|
|
(1, 0, 1, 0),
|
|
(0, 1, 1, 0),
|
|
(0, 1, 0, 1),
|
|
(1, 0, 0, 1),
|
|
]
|
|
|
|
|
|
class Gauge:
|
|
"""
|
|
Analog-style stepper gauge driver.
|
|
|
|
Parameters
|
|
----------
|
|
pins : tuple[int, ...]
|
|
2 pins for STEP/DIR mode: (DIR, STEP)
|
|
4 pins for 4-phase mode: (IN1, IN2, IN3, IN4)
|
|
|
|
mode : str
|
|
"stepdir" for VID-6008 style (2 pins), "4phase" for VID28/X25 (4 pins).
|
|
|
|
min_val : float
|
|
Value that corresponds to the physical lower stop.
|
|
|
|
max_val : float
|
|
Value that corresponds to the physical upper stop.
|
|
|
|
step_us : int
|
|
Delay between steps in microseconds (default 200).
|
|
"""
|
|
|
|
def __init__(self, pins, mode="4phase", min_val=0, max_val=100, step_us=200):
|
|
self._mode = mode
|
|
self._step_us = step_us
|
|
self._min_val = min_val
|
|
self._max_val = max_val
|
|
|
|
if mode == "stepdir":
|
|
if len(pins) != 2:
|
|
raise ValueError("stepdir mode requires 2 pins: (DIR, STEP)")
|
|
self._pin_dir = Pin(pins[0], Pin.OUT)
|
|
self._pin_step = Pin(pins[1], Pin.OUT)
|
|
self._total_steps = _TOTAL_STEPS
|
|
self._phase = 0
|
|
|
|
elif mode == "4phase":
|
|
if len(pins) != 4:
|
|
raise ValueError("4phase mode requires 4 pins: (IN1, IN2, IN3, IN4)")
|
|
self._pins = [Pin(p, Pin.OUT) for p in pins]
|
|
self._total_steps = _TOTAL_STEPS
|
|
self._sequence = _FULL_SEQUENCE
|
|
self._phase = 0
|
|
else:
|
|
raise ValueError("mode must be 'stepdir' or '4phase'")
|
|
|
|
self._current_step = 0
|
|
self._zeroed = False
|
|
|
|
def zero(self):
|
|
overrun = _OVERRUN_STEPS
|
|
if self._mode == "stepdir":
|
|
self._pin_dir.value(0)
|
|
utime.sleep_us(10)
|
|
for _ in range(_TOTAL_STEPS + overrun):
|
|
self._pulse_step(500)
|
|
else:
|
|
for _ in range(_TOTAL_STEPS + overrun):
|
|
self._step(-1)
|
|
utime.sleep_ms(2)
|
|
|
|
self._current_step = 0
|
|
self._zeroed = True
|
|
self._release()
|
|
|
|
def set(self, value, release=True):
|
|
if not self._zeroed:
|
|
raise RuntimeError("Call zero() before set()")
|
|
|
|
value = max(self._min_val, min(self._max_val, value))
|
|
target_step = self._val_to_step(value)
|
|
delta = target_step - self._current_step
|
|
|
|
if delta == 0:
|
|
return
|
|
|
|
if self._mode == "stepdir":
|
|
self._move_steps(delta)
|
|
else:
|
|
for _ in range(abs(delta)):
|
|
self._step(1 if delta > 0 else -1)
|
|
utime.sleep_ms(self._step_us)
|
|
|
|
self._current_step = target_step
|
|
|
|
if release:
|
|
self._release()
|
|
|
|
def _move_steps(self, delta):
|
|
direction = 1 if delta > 0 else 0
|
|
self._pin_dir.value(direction)
|
|
utime.sleep_us(10)
|
|
for _ in range(abs(delta)):
|
|
self._pulse_step(self._step_us)
|
|
|
|
def _pulse_step(self, delay_us):
|
|
self._pin_step.value(1)
|
|
utime.sleep_us(2)
|
|
self._pin_step.value(0)
|
|
utime.sleep_us(delay_us)
|
|
|
|
def get(self):
|
|
return self._step_to_val(self._current_step)
|
|
|
|
def release(self):
|
|
self._release()
|
|
|
|
def step(self, direction):
|
|
if self._mode == "stepdir":
|
|
self._pin_dir.value(1 if direction > 0 else 0)
|
|
utime.sleep_us(10)
|
|
self._pulse_step(self._step_us)
|
|
self._current_step += direction
|
|
else:
|
|
self._step(direction)
|
|
|
|
def _step(self, direction):
|
|
steps_in_seq = len(self._sequence)
|
|
self._phase = (self._phase + direction) % steps_in_seq
|
|
seq = self._sequence[self._phase]
|
|
for pin, state in zip(self._pins, seq):
|
|
pin.value(state)
|
|
self._current_step += direction
|
|
|
|
def _release(self):
|
|
if self._mode == "stepdir":
|
|
self._pin_step.value(0)
|
|
else:
|
|
for pin in self._pins:
|
|
pin.value(0)
|
|
|
|
def _val_to_step(self, value):
|
|
frac = (value - self._min_val) / (self._max_val - self._min_val)
|
|
return int(round(frac * self._total_steps))
|
|
|
|
def _step_to_val(self, step):
|
|
frac = step / self._total_steps
|
|
return self._min_val + frac * (self._max_val - self._min_val)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
g = Gauge(
|
|
pins=(12, 13),
|
|
mode="stepdir",
|
|
min_val=0,
|
|
max_val=100,
|
|
step_us=200,
|
|
)
|
|
|
|
print("Zeroing gauge...")
|
|
g.zero()
|
|
print("Zero complete.")
|
|
|
|
for target in [25, 50, 75, 100, 50, 0]:
|
|
print("Moving to {} (currently at {:.1f})".format(target, g.get()))
|
|
g.set(target)
|
|
utime.sleep_ms(500)
|
|
|
|
print("Done.")
|