Tooling and findings for running an E8x/E9x electric power steering unit
outside its donor car, e.g. in an EV conversion.
Headline result: the EPS needs only two CAN messages plus a 12V enable
wire, not the 69-message set the car puts on the bus:
0x130 CAS terminal status (100ms) brings the unit up
0x1A0 DSC road speed (20ms) sets the assist level
Total required rate is 60 frames/s. Protocol write-up, including what is
proven vs. inferred and the open questions, is in eps-comms/.
Contents:
adapters/ CANdapter (its SLCAN dialect differs) and generic SLCAN
gateway/ car<->EPS relay, replay, message bench, EPS controller,
4-tab Streamlit UI
decoder/ PT-CAN frame decoding and live/replay sources
can-io/ XIAO ESP32-S3 firmware: CAN IO board + USB-CAN bridge with
a CAN-independent digital IO channel
tools/ capture, bitrate scan, startup-order and session analysis,
checksum solver
captures/ reference working session + the replay set eps_control reads
87 lines
3.3 KiB
Python
87 lines
3.3 KiB
Python
"""Scan common automotive CAN bitrates to find one that yields valid frames.
|
|
|
|
Usage:
|
|
python scan_bitrate.py [--port /dev/cu.usbserial-DNBJV4F5] [--seconds 2]
|
|
|
|
Tries each candidate bitrate in turn, listens briefly, and reports how many
|
|
frames came back and whether they look well-formed (DLC 0-8, plausible IDs).
|
|
The bitrate with the most valid frames is your best bet.
|
|
|
|
Note on wiring: CAN H and L being swapped is NOT usually something a bitrate
|
|
scan can "see through" - CAN transceivers work on the differential voltage
|
|
(H minus L), so swapping the two wires inverts dominant/recessive and
|
|
corrupts every bit, at every bitrate. If this scan finds zero valid frames
|
|
at all common bitrates, that's a strong signal to double check H/L wiring
|
|
(and termination - a CAN bus needs ~120 ohm resistors at each end) rather
|
|
than trying more bitrates.
|
|
"""
|
|
import argparse
|
|
import sys
|
|
import time
|
|
from collections import Counter
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
from adapters.candapter import Candapter # noqa: E402
|
|
|
|
DEFAULT_PORT = "/dev/cu.usbserial-DNBJV4F5"
|
|
|
|
# Common automotive bitrates, most-likely-first for a steering/body bus.
|
|
CANDIDATE_BITRATES = [500_000, 125_000, 250_000, 1_000_000, 100_000, 50_000, 20_000, 10_000]
|
|
|
|
|
|
def scan_one(port: str, bitrate: int, seconds: float) -> tuple[int, int, Counter]:
|
|
total = 0
|
|
valid = 0
|
|
ids = Counter()
|
|
with Candapter(port, bitrate) as adapter:
|
|
deadline = time.monotonic() + seconds
|
|
while time.monotonic() < deadline:
|
|
frame = adapter.read_frame(timeout=0.2)
|
|
if frame is None:
|
|
continue
|
|
total += 1
|
|
if 0 <= len(frame.data) <= 8:
|
|
valid += 1
|
|
ids[frame.arbitration_id] += 1
|
|
return total, valid, ids
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--port", default=DEFAULT_PORT, help="Serial device path")
|
|
parser.add_argument("--seconds", type=float, default=2.0, help="Listen time per bitrate")
|
|
args = parser.parse_args()
|
|
|
|
print(f"Scanning {args.port} across {len(CANDIDATE_BITRATES)} bitrates ({args.seconds}s each)...\n")
|
|
print(f"{'bitrate':>10} {'frames':>7} {'valid':>6} {'unique ids':>10}")
|
|
|
|
results = []
|
|
for bitrate in CANDIDATE_BITRATES:
|
|
try:
|
|
total, valid, ids = scan_one(args.port, bitrate, args.seconds)
|
|
except OSError as exc:
|
|
print(f"{bitrate:>10} error opening port: {exc}")
|
|
continue
|
|
print(f"{bitrate:>10} {total:>7} {valid:>6} {len(ids):>10}")
|
|
results.append((bitrate, valid, ids))
|
|
|
|
results.sort(key=lambda r: r[1], reverse=True)
|
|
best = results[0] if results else None
|
|
|
|
if best and best[1] > 0:
|
|
bitrate, valid, ids = best
|
|
print(f"\nBest match: {bitrate} bps ({valid} valid frames, {len(ids)} unique IDs).")
|
|
print("Top IDs seen:", ", ".join(f"0x{i:X}x{c}" for i, c in ids.most_common(10)))
|
|
else:
|
|
print(
|
|
"\nNo valid frames at any bitrate. Since this affects every bitrate equally, "
|
|
"check the physical connection first: CAN H/L polarity and bus termination "
|
|
"(~120 ohm at each end) before trying more bitrates."
|
|
)
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|