From 30ef51f26c72326ff115ab50f6f9a3fa19c615d1 Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Fri, 13 Feb 2026 00:04:55 -0700 Subject: [PATCH] SPICE simulation of PC817 optocoupler RX/TX paths (9600 baud) Both signal paths validated with LTspice using built-in PC817 subcircuit model (Igain=1m). RX gives clean 0-3.13V at 3.3V VCC with 5.8us rise time. TX confirms signal inversion and 0.17V bus LOW with 4.66mA LED drive. 3.3V design works but is marginal vs the original 5V Arduino circuit at worst-case CTR. --- CLAUDE.md | 52 +++++++++++++++---- reference/ibus_rx_path.cir | 65 ++++++++++++++++++++++++ reference/ibus_tx_path.cir | 80 ++++++++++++++++++++++++++++++ reference/rx_path_vibus.svg | 20 ++++++++ reference/rx_path_vrx.svg | 20 ++++++++ reference/tx_path_vibus.svg | 20 ++++++++ reference/tx_path_vrx_loopback.svg | 20 ++++++++ 7 files changed, 266 insertions(+), 11 deletions(-) create mode 100644 reference/ibus_rx_path.cir create mode 100644 reference/ibus_tx_path.cir create mode 100644 reference/rx_path_vibus.svg create mode 100644 reference/rx_path_vrx.svg create mode 100644 reference/tx_path_vibus.svg create mode 100644 reference/tx_path_vrx_loopback.svg diff --git a/CLAUDE.md b/CLAUDE.md index 6e775f8..0ca193d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -244,12 +244,42 @@ mcp__mcp-ltspice__get_waveform(raw_file_path, signal_names, max_points) mcp__mcp-ltspice__analyze_waveform(raw_file_path, signal_name, analyses) ``` -**Not yet simulated.** The optocoupler circuit needs a PC817 SPICE model with CTR curve and switching time parameters. A generic model: -```spice -* PC817 optocoupler (simplified) -* LED: forward voltage ~1.2V at 5mA -* Phototransistor: CTR ~100% at 5mA, rise ~4us, fall ~3us -``` +**Simulated and validated.** Uses LTspice's built-in PC817 subcircuit (from `lib/sub/PC817.sub`) with `Igain=1m` (PC817A, ~100% CTR). Two netlists cover both signal paths: + +- `reference/ibus_rx_path.cir` — RX path: bus byte 0x50 at 9600 baud → U1 PC817 → ESP32 RX +- `reference/ibus_tx_path.cir` — TX path: ESP32 TX → Q1 BC547B → U2 PC817 → bus (with RX loopback) + +**PC817 SPICE pin order:** `1=Anode, 2=Cathode, 3=Collector, 4=Emitter` — subcircuit uses a VCCS (G1) with `{Igain}` parameter to model optical coupling. + +### Simulation Results + +**RX Path (Bus → ESP32, 3.3V VCC):** + +| Measurement | Value | Notes | +|-------------|-------|-------| +| V(RX) HIGH | 3.128V | > 2.475V ESP32 VIH threshold | +| V(RX) LOW | ~0V | < 0.825V ESP32 VIL threshold | +| LED current | 5.21mA | Through R1 (2k) when bus at 12V | +| Rise time (10-90%) | 5.8μs | 5.6% of 104.17μs bit time | +| Signal polarity | Preserved | Bus HIGH → RX HIGH (emitter-follower) | + +**TX Path (ESP32 → Bus, 3.3V VCC, 4.7kΩ bus pull-up):** + +| Measurement | Value | Notes | +|-------------|-------|-------| +| V(IBUS) LOW | 0.167V | U2 phototransistor pulls bus down | +| U2 LED current | 4.66mA | Through R2 (470Ω) when Q1 ON | +| Q1 Vce(sat) | 0.048V | Fully saturated | +| Bus rise time (10-90%) | 19.0μs | Passive pull-up limited; real bus is faster | +| Signal inversion | Confirmed | TX HIGH → bus LOW (software must invert) | + +### Design Notes from Simulation + +1. **3.3V works but is marginal for TX.** At worst-case CTR (50%, PC817A grade), the phototransistor may not fully pull the bus LOW against pull-up impedances below ~2kΩ. The original 5V Arduino design has 70% more LED headroom. Consider using PC817C/D (higher CTR) or reducing R2 to 330Ω for more LED current. + +2. **Bus rise time depends on external impedance.** The 19μs rise time (with 4.7kΩ pull-up) is 18% of bit time — acceptable for 9600 baud. Real BMW bus has TH3122 transceivers providing low-impedance drive, so actual rise will be faster. + +3. **Bench testing needs a low-impedance 12V source.** U1's RX LED (through R1=2kΩ) loads the bus continuously. With only a passive pull-up, IBUS sags to ~4.3V. Use a 12V supply through 100-510Ω to simulate a real bus during development. The Tucker project's validated transistor netlist is at `~/claude/tucker/k-line-board/reference/kline_esp32_validated.cir` for comparison. @@ -266,14 +296,14 @@ The repo includes an extensive documentation collection (`Docs/`): 2. Protocol differences documented (vs OBD-II K-line for Tucker project) 3. Software reference identified (IbusSerial library, E46 command codes) 4. Module address map and message format documented +5. **SPICE simulation validated** — both RX and TX paths simulated with LTspice PC817 model at 9600 baud. RX path gives clean 0-3.13V logic at 3.3V VCC. TX path confirms signal inversion and 0.17V bus LOW. Rise/fall times within 9600 baud budget. ## What's Next -1. **Simulate** optocoupler circuit with mcp-ltspice (need PC817 model) -2. **Port IbusSerial** library to ESP32 (replace AVR Timer2, adapt pin config) -3. **Breadboard** prototype with PC817 + BC547 + ESP32 -4. **Test** on BMW E46 K-Bus (CD changer connector in trunk) -5. **Build** command library for target features (lights, locks, windows) +1. **Port IbusSerial** library to ESP32 (replace AVR Timer2, adapt pin config) +2. **Breadboard** prototype with PC817 + BC547 + ESP32 +3. **Test** on BMW E46 K-Bus (CD changer connector in trunk) +4. **Build** command library for target features (lights, locks, windows) ## Safety Notes diff --git a/reference/ibus_rx_path.cir b/reference/ibus_rx_path.cir new file mode 100644 index 0000000..4447417 --- /dev/null +++ b/reference/ibus_rx_path.cir @@ -0,0 +1,65 @@ +* BMW I/K-Bus Interface - RX Path (Bus to ESP32) - PC817 Optocoupler +* Bus drives byte 0x50 (MFL addr) at 9600 baud 8E1 +* 0x50 = 01010000, LSB first = 00001010, even parity = 0 +* Frame: START(0) d0(0) d1(0) d2(0) d3(0) d4(1) d5(0) d6(1) d7(0) P(0) STOP(1) +* Bus: 12V=idle/HIGH, 0V=active/LOW, bit time=104.17us +* ESP32 GPIO thresholds: LOW < 0.825V, HIGH > 2.475V +* +* PC817 pin order: 1=Anode, 2=Cathode, 3=Collector, 4=Emitter +* U1 config: emitter-follower (collector to VCC, emitter to RX output) + +* === Power Supplies === +V_BAT V12 0 12 +V_MCU VCC 0 3.3 + +* === Bus Model === +* External module driving byte 0x50 through 100R source impedance +V_BUS V_BUS_SRC 0 PWL( ++ 0u 12 ++ 199.5u 12 ++ 200u 0 ++ 720.33u 0 ++ 720.83u 12 ++ 824.5u 12 ++ 825u 0 ++ 928.67u 0 ++ 929.17u 12 ++ 1032.83u 12 ++ 1033.33u 0 ++ 1241.17u 0 ++ 1241.67u 12 ++ 2000u 12) +R_BUS V_BUS_SRC IBUS 100 +C_BUS IBUS 0 100p + +* === RX Optocoupler (U1) === +* Bus HIGH (12V): LED on -> phototransistor ON -> RX HIGH +* Bus LOW (0V): LED off -> phototransistor OFF -> RX LOW (via R4) +R1 IBUS U1_A 2k +XU1 U1_A 0 VCC RX PC817 Igain=1m +R4 RX 0 1k + +* === PC817 Subcircuit (inlined from LTspice library) === +.subckt PC817 1 2 3 4 +R1 N003 2 2 +D1 1 N003 LD +G1 3 N004 N003 2 {Igain} +C1 1 2 18p +Q1 3 N004 4 [4] NP +.model LD D(Is=1e-20 Cjo=18p) +.model NP NPN(Bf=1200 Vaf=140 Ikf=100m Rc=1 Cjc=19p Cje=7p Cjs=7p C2=3e-15) +.ends PC817 + +* === Simulation === +.tran 0 2000u 0 0.1u + +* === Measurements === +.meas tran RX_HIGH MAX V(RX) +.meas tran RX_LOW MIN V(RX) +.meas tran IBUS_HIGH MAX V(IBUS) +.meas tran IBUS_LOW MIN V(IBUS) +.meas tran LED_CURRENT_MAX MAX I(R1) +.meas tran RX_RISE TRIG V(RX) VAL=0.825 RISE=1 TARG V(RX) VAL=2.475 RISE=1 +.meas tran RX_FALL TRIG V(RX) VAL=2.475 FALL=1 TARG V(RX) VAL=0.825 FALL=1 +.backanno +.end diff --git a/reference/ibus_tx_path.cir b/reference/ibus_tx_path.cir new file mode 100644 index 0000000..02b9397 --- /dev/null +++ b/reference/ibus_tx_path.cir @@ -0,0 +1,80 @@ +* BMW I/K-Bus Interface - TX Path (ESP32 to Bus) - PC817 + BC547 +* MCU TX drives Q1 (BC547B) which drives U2 (PC817) LED +* U2 phototransistor pulls bus LOW against 4.7k pull-up to 12V +* Test pattern: alternating 3-bit-time (312us) pulses +* TX 0V = bus idle (HIGH), TX 3.3V = bus active (LOW via U2) +* Includes U1 RX path for loopback observation +* Signal inversion: TX HIGH -> bus LOW (documented in design) +* +* PC817 pin order: 1=Anode, 2=Cathode, 3=Collector, 4=Emitter + +* === Power Supplies === +V_BAT V12 0 12 +V_MCU VCC 0 3.3 + +* === TX Test Signal === +* 0V=idle (U2 OFF, bus HIGH), 3.3V=active (U2 ON, bus LOW) +* Alternating ~3 bit times (312us) to show inversion +V_TX TX 0 PWL( ++ 0u 0 ++ 199.5u 0 ++ 200u 3.3 ++ 512u 3.3 ++ 512.5u 0 ++ 825u 0 ++ 825.5u 3.3 ++ 1137u 3.3 ++ 1137.5u 0 ++ 2000u 0) + +* === Bus Model === +* Idle pull-up: represents combined impedance of other modules' inputs +* No active driver — only U2 phototransistor drives the bus here +R_PULL V12 IBUS 4.7k +C_BUS IBUS 0 100p + +* === TX Driver: Q1 (BC547B) === +R5 TX R5_R3 470 +R3 R5_R3 Q1B 10k +Q1 Q1C Q1B 0 BC547B + +* === TX Optocoupler (U2) === +* LED path: VCC -> R2 -> U2 anode(1) -> U2 cathode(2) -> Q1 collector +* Phototransistor: collector(3) = IBUS, emitter(4) = GND +R2 VCC U2_A 470 +XU2 U2_A Q1C IBUS 0 PC817 Igain=1m + +* === RX Optocoupler (U1) for Loopback === +* Shows what the MCU sees on RX when it transmits +R1_RX IBUS U1_A 2k +XU1 U1_A 0 VCC RX PC817 Igain=1m +R4_RX RX 0 1k + +* === PC817 Subcircuit (inlined from LTspice library) === +.subckt PC817 1 2 3 4 +R1 N003 2 2 +D1 1 N003 LD +G1 3 N004 N003 2 {Igain} +C1 1 2 18p +Q1 3 N004 4 [4] NP +.model LD D(Is=1e-20 Cjo=18p) +.model NP NPN(Bf=1200 Vaf=140 Ikf=100m Rc=1 Cjc=19p Cje=7p Cjs=7p C2=3e-15) +.ends PC817 + +* === Transistor Model === +.model BC547B NPN(IS=2.39E-14 NF=1.008 ISE=3.545E-15 NE=1.541 BF=294.3 IKF=0.1357 VAF=63.2 NR=1.004 ISC=6.272E-14 NC=1.243 BR=7.946 IKR=0.1144 VAR=25.9 RB=1 IRB=1.00E-06 RBM=1 RE=0.4683 RC=0.85 XTB=0 EG=1.11 XTI=3 CJE=1.358E-11 VJE=0.65 MJE=0.3279 TF=4.391E-10 XTF=120 VTF=2.643 ITF=0.7495 PTF=0 CJC=3.728E-12 VJC=0.3997 MJC=0.2955 XCJC=0.6193 TR=1.00E-32 CJS=0 VJS=0.75 MJS=0.333 FC=0.9579 Vceo=45 Icrating=100m mfg=NXP) + +* === Simulation === +.tran 0 2000u 0 0.1u + +* === Measurements === +.meas tran IBUS_HIGH MAX V(IBUS) +.meas tran IBUS_LOW MIN V(IBUS) +.meas tran IBUS_SWING PP V(IBUS) +.meas tran TX_TO_BUS_DELAY TRIG V(TX) VAL=1.65 RISE=1 TARG V(IBUS) VAL=6 FALL=1 +.meas tran U2_LED_CURRENT MAX I(R2) +.meas tran RX_LOOPBACK_HIGH MAX V(RX) +.meas tran RX_LOOPBACK_LOW MIN V(RX) +.meas tran Q1_BASE_CURRENT MAX I(R3) +.backanno +.end diff --git a/reference/rx_path_vibus.svg b/reference/rx_path_vibus.svg new file mode 100644 index 0000000..d1cf098 --- /dev/null +++ b/reference/rx_path_vibus.svg @@ -0,0 +1,20 @@ + + + + +0 + +5 + +10 + +0 + +5 + +10 + +Time Domain — V(ibus) +V(ibus) +Time (s) + \ No newline at end of file diff --git a/reference/rx_path_vrx.svg b/reference/rx_path_vrx.svg new file mode 100644 index 0000000..d24be95 --- /dev/null +++ b/reference/rx_path_vrx.svg @@ -0,0 +1,20 @@ + + + + +0 + +5 + +10 + +0 + +5 + +10 + +Time Domain — V(rx) +V(rx) +Time (s) + \ No newline at end of file diff --git a/reference/tx_path_vibus.svg b/reference/tx_path_vibus.svg new file mode 100644 index 0000000..74a9063 --- /dev/null +++ b/reference/tx_path_vibus.svg @@ -0,0 +1,20 @@ + + + + +0 + +5 + +10 + +0 + +5 + +10 + +Time Domain — V(ibus) +V(ibus) +Time (s) + \ No newline at end of file diff --git a/reference/tx_path_vrx_loopback.svg b/reference/tx_path_vrx_loopback.svg new file mode 100644 index 0000000..16fa89f --- /dev/null +++ b/reference/tx_path_vrx_loopback.svg @@ -0,0 +1,20 @@ + + + + +0 + +5 + +10 + +0 + +5 + +10 + +Time Domain — V(rx) +V(rx) +Time (s) + \ No newline at end of file