ShiboSoftwareDev/f1c100s-linux-stepper-controller
A four-layer Linux motor-control carrier built around an Allwinner F1C100S SoC, USB‑C, SPI flash, multi-rail power regulation, DRV8825 stepper-driver stage with protection/current sensing, temperature monitoring, and external I/O connectors.
- Version
- 0.1.1
- License
- unset
- Stars
- 0
firmware/motor-control.py
#!/usr/bin/env python3
"""Conservative DRV8825 step/dir control through Linux libgpiod 2.x.
This is an integration and bring-up utility, not a real-time motion planner.
Linux userspace scheduling adds pulse jitter; use a timer/PWM-capable kernel
driver or a dedicated motion coprocessor when precise or high-rate motion is
required. The board is fixed at 1/16 microstepping and about 0.5 A peak/phase.
"""
import argparse
import pathlib
import time
STEP = "PE2"
DIR = "PE3"
ENABLE = "PE4" # high releases both nSLEEP and nRESET
FAULT = "PE5" # active low
TEMP_ALERT = "PE6" # active low
def find_tmp102_input():
for name_path in pathlib.Path("/sys/class/hwmon").glob("hwmon*/name"):
try:
if name_path.read_text().strip() == "tmp102":
candidate = name_path.parent / "temp1_input"
if candidate.exists():
return candidate
except OSError:
pass
return None
def read_temperature_c(path):
if path is None:
return None
return int(path.read_text().strip()) / 1000.0
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--chip", default="/dev/gpiochip0", help="PIO GPIO chip from gpioinfo")
parser.add_argument("--steps", type=int, default=3200, help="step pulses; 3200 is one revolution for a 200-step motor")
parser.add_argument("--hz", type=float, default=200.0, help="step frequency (userspace; keep conservative)")
parser.add_argument("--direction", choices=("forward", "reverse"), default="forward")
parser.add_argument("--hold-seconds", type=float, default=0.0, help="hold torque before disabling")
parser.add_argument("--max-temperature", type=float, default=80.0, help="abort at this TMP102 temperature in degC")
parser.add_argument("--temp-path", type=pathlib.Path, help="override TMP102 hwmon temp1_input path")
parser.add_argument("--dry-run", action="store_true", help="validate arguments and print the intended motion")
args = parser.parse_args()
if args.steps < 0:
parser.error("--steps must be nonnegative")
if not 0.5 <= args.hz <= 2000:
parser.error("--hz must be between 0.5 and 2000 for this userspace utility")
if args.hold_seconds < 0:
parser.error("--hold-seconds must be nonnegative")
temp_path = args.temp_path or find_tmp102_input()
if args.dry_run:
print(f"{args.steps} pulses at {args.hz:g} Hz, direction={args.direction}, "
f"estimated duration={args.steps / args.hz:.3f} s")
print(f"TMP102 path: {temp_path or 'not found (GPIO ALERT remains active)'}")
return
import gpiod
from gpiod.line import Direction, Value
low, high = Value.INACTIVE, Value.ACTIVE
output = gpiod.LineSettings(direction=Direction.OUTPUT, output_value=low)
input_line = gpiod.LineSettings(direction=Direction.INPUT)
config = {(STEP, DIR, ENABLE): output, (FAULT, TEMP_ALERT): input_line}
half_period = 0.5 / args.hz
with gpiod.request_lines(args.chip, consumer="f1c100s-drv8825", config=config) as request:
def shutdown():
request.set_value(STEP, low)
request.set_value(ENABLE, low)
def safety_check():
if request.get_value(FAULT) == low:
raise RuntimeError("DRV8825 nFAULT asserted; motor disabled")
temperature = read_temperature_c(temp_path)
if request.get_value(TEMP_ALERT) == low:
raise RuntimeError("TMP102 ALERT asserted; motor disabled")
if temperature is not None and temperature >= args.max_temperature:
raise RuntimeError(f"TMP102 temperature {temperature:.1f} degC exceeds limit")
try:
shutdown()
request.set_value(DIR, high if args.direction == "reverse" else low)
time.sleep(0.001)
safety_check()
request.set_value(ENABLE, high)
time.sleep(0.002)
for index in range(args.steps):
if index % 32 == 0:
safety_check()
request.set_value(STEP, high)
time.sleep(half_period)
request.set_value(STEP, low)
time.sleep(half_period)
if args.hold_seconds:
deadline = time.monotonic() + args.hold_seconds
while time.monotonic() < deadline:
safety_check()
time.sleep(min(0.1, max(0.0, deadline - time.monotonic())))
finally:
shutdown()
if __name__ == "__main__":
main()