rm3100/diagnose.py
2026-08-19 23:00:47 -04:00

121 lines
4.8 KiB
Python

#!/usr/bin/env python3
"""Hardware diagnostics for the RM3100 / CH347 link.
Walks the signal chain from USB inwards and stops at the first thing that is
broken, so a failure points at a specific wire rather than "it doesn't work":
adapter -> I2C bus -> sensor identity -> registers -> analog section
Usage: ./.venv/bin/python diagnose.py
"""
import sys
import time
import ch347
import rm3100
# BIST with STE=1, BW=11 (4 sleep oscillation cycles), BP=11 (4 LR periods):
# the most forgiving self-test settings (manual Tables 5-6, 5-7).
BIST_RUN = 0x8F
def check(label, ok, detail=""):
print(f" [{'OK ' if ok else 'FAIL'}] {label}" + (f" -- {detail}" if detail else ""))
return ok
def main():
print("1. USB adapter")
try:
bus = ch347.CH347I2C()
except IOError as exc:
check("open CH347", False, str(exc))
print("\n-> Run ./setup.sh, or check the adapter is plugged in and in Mode 1.")
return 1
check("open CH347 and set I2C speed", True, "interface 2 claimed, 100 kHz")
with bus:
print("\n2. I2C bus")
found = bus.scan()
if not check("devices respond", bool(found),
", ".join(f"0x{a:02x}" for a in found) if found else "bus is silent"):
print("\n-> Nothing ACKs. In order of likelihood:")
print(" - I2CEN not tied HIGH, so the chip is in SPI mode (most common)")
print(" - SDA/SCL swapped")
print(" - missing pull-up resistors on SDA and SCL")
print(" - module not powered")
return 1
candidates = [a for a in found if a in rm3100.RM3100.ADDRESSES]
if not check("an address in the RM3100 range 0x20-0x23", bool(candidates),
", ".join(f"0x{a:02x}" for a in candidates) if candidates
else "responders are not RM3100 addresses"):
return 1
sensor = rm3100.RM3100(bus, candidates[0])
print(f"\n3. Sensor identity (at 0x{sensor.address:02x})")
revid = sensor.revid()
check("REVID", revid == rm3100.EXPECTED_REVID,
f"0x{revid:02x}" + ("" if revid == rm3100.EXPECTED_REVID
else f", expected 0x{rm3100.EXPECTED_REVID:02x}"))
print("\n4. Register read/write")
sensor.stop_cmm()
time.sleep(0.05)
original = sensor.get_cycle_counts()
sensor.set_cycle_counts(100)
readback = sensor.get_cycle_counts()
ok = readback == (100, 100, 100)
check("cycle counts write then read back", ok, f"wrote 100, read {readback}")
sensor.set_cycle_counts(original[0] or rm3100.DEFAULT_CYCLE_COUNT)
if not ok:
print("\n-> Registers are unreliable. Try a lower bus speed or shorter wires.")
return 1
print("\n5. Analog section (BIST -- drives the sensor coils)")
sensor.write_reg(rm3100.REG_BIST, [BIST_RUN])
sensor.write_reg(rm3100.REG_POLL, [rm3100.POLL_ALL_AXES])
time.sleep(0.5)
bist = sensor.read_reg(rm3100.REG_BIST)[0]
sensor.write_reg(rm3100.REG_BIST, [0x00])
axes = {"X": (bist >> 4) & 1, "Y": (bist >> 5) & 1, "Z": (bist >> 6) & 1}
all_ok = all(axes.values())
check("LR oscillators", all_ok,
f"BIST=0x{bist:02x} " + " ".join(f"{k}OK={v}" for k, v in axes.items()))
if not all_ok:
print("\n-> The coils are not oscillating, though I2C is fine. Check:")
if not any(axes.values()):
print(" - AVDD/AVSS not connected (most common when ALL axes fail).")
print(" Registers run on DVDD alone, so I2C works while the analog")
print(" section is dead. Tie AVDD to the same 3V3 rail as DVDD")
print(" (they must stay within 0.1 V), and AVSS to ground.")
print(" - REXT timing resistor (33k) missing")
else:
print(f" - coil wiring for {', '.join(k for k, v in axes.items() if not v)}")
return 1
print("\n6. Live measurement")
sensor.configure()
sensor.set_rate(0x96)
sensor.start_cmm()
try:
ready = sensor.wait_for_data(timeout=2.0)
if not check("DRDY asserts", ready, "" if ready else "no data within 2 s"):
return 1
counts, ut = sensor.read_measurements()
magnitude = sum(v * v for v in ut) ** 0.5
check("field magnitude is plausible", 25 <= magnitude <= 65,
f"{magnitude:.1f} uT (Earth's field is 25-65)")
print(f" raw {counts}")
print(f" X {ut[0]:+.3f} Y {ut[1]:+.3f} Z {ut[2]:+.3f} uT")
finally:
sensor.stop_cmm()
print("\nAll checks passed -- the link is fully working.")
return 0
if __name__ == "__main__":
sys.exit(main())