Initial complete commit
This commit is contained in:
187
gauge_vid6008.py
Normal file
187
gauge_vid6008.py
Normal file
@@ -0,0 +1,187 @@
|
||||
"""
|
||||
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.")
|
||||
Reference in New Issue
Block a user