i-k-bus-board/CLAUDE.md
Ryan Malloy 30ef51f26c 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.
2026-02-13 00:04:55 -07:00

15 KiB

BMW I-Bus / K-Bus Interface Board (ESP32, Optocoupler Isolated)

Attribution

Circuit design and protocol library based on muki01/I-K_Bus by @muki01, licensed under MIT. Optocoupler schematic, IbusSerial library, E46 command codes, and bus documentation are from that project.

Project Overview

Interface board for BMW I-Bus (Instrumentation Bus) and K-Bus (Body Bus) communication. Uses an ESP32 with PC817 optocouplers for galvanic isolation from the vehicle's electrical system.

This is NOT the same as OBD-II K-line. The Tucker project (~/claude/tucker/k-line-board/) handles ISO 9141/14230 OBD diagnostics using a transistor-based interface. This project targets BMW's proprietary body/instrumentation bus for module control (lights, windows, locks, multimedia).

Protocol Comparison: BMW I/K-Bus vs OBD-II K-line

Parameter BMW I/K-Bus (this project) OBD-II K-line (Tucker)
Baud rate 9600 10400
Framing 8E1 (even parity) 8N1 (no parity)
Bus topology Multi-master (any module can talk) Master/slave (tester initiates)
Checksum XOR of all bytes Additive sum mod 256
Init sequence None (always-on bus) 5-baud wake-up or fast init pulse
Message format Source + Length + Dest + Data + XOR Header + Source + Mode + PID + Sum
Idle detection ~1.5ms bus quiet before TX 5.5s quiet before init
Isolation needed Yes (multi-master, risk to bus) Nice to have (point-to-point)
Vehicles BMW E31/E38/E39/E46/E53/E83/E85/E87 Ford and other ISO 9141/14230

Why Different Hardware

The transistor circuit (Tucker) shares ground with the car and directly drives the K-line. Fine for OBD-II because it's a dedicated diagnostic port — the tester is the only external device, and the ECU expects it.

BMW I/K-Bus is a live multi-master network with dozens of modules (GM5, IKE, LCM, RAD, MFL, DSP, etc.) all communicating constantly. Injecting signals requires:

  1. Galvanic isolation — a ground fault from your board can't propagate to the entire bus network
  2. Bus contention awareness — you must listen for 1.5ms of silence before transmitting
  3. Proper framing — even parity, XOR checksum, valid source/destination addresses

The optocoupler design satisfies requirement #1. The IbusSerial library handles #2 and #3.

Circuit Design — Optocoupler Interface

Reference image: reference/muki01-optocoupler-schematic.png Alternative (non-isolated): reference/muki01-transistor-schematic.png

Schematic Components

  I/K-Bus Side (12V)                     MCU Side (3.3V/5V)
  ==================                     ====================

  12V ────── D1 (1N4007) ──────────────── 12V (to regulator)
             (reverse protection)

  I/K-Bus ─── R1 (2k) ──→ U1 pin 1      U1 pin 4 ←── 5V/3.3V
              (LED anode)  (PC817)        (collector)
              GND ←── U1 pin 2           U1 pin 3 ──→ RX_Pin
              (LED cathode)               (emitter)
                                          R4 (1k) pull-down to GND

                          ┊ photons ┊

  I/K-Bus ←── U2 pin 4    U2 pin 1 ←── R2 (470)
              (collector)  (LED anode)    │
              U2 pin 3     U2 pin 2 ←── Q1 collector (BC547)
              (emitter)    (LED cathode)
              │                          Q1 base ←── R3 (10k) ←── R5 (470) ←── TX_Pin
              GND                        Q1 emitter ──→ GND

RX Path (Bus -> MCU via U1)

  • Bus HIGH (12V idle): Current = (12V - 1.2V_LED) / 2k = 5.4mA through U1 LED
  • PC817 CTR (Current Transfer Ratio) >= 50% at 5mA -> phototransistor saturates
  • U1 emitter (pin 3) pulled toward collector voltage (VCC) -> RX = HIGH
  • Bus LOW (module transmitting): No LED current -> phototransistor OFF -> RX pulled LOW by R4

Signal inversion: Bus HIGH -> RX HIGH, Bus LOW -> RX LOW. Polarity is preserved because the emitter-follower config doesn't invert.

TX Path (MCU -> Bus via U2 + Q1)

  • TX HIGH (idle): Through R5 (470) + R3 (10k) -> Q1 base driven -> Q1 ON -> current through U2 LED via R2 (470) -> U2 phototransistor ON -> pulls bus node LOW
  • TX LOW (transmitting): Q1 OFF -> U2 LED OFF -> phototransistor OFF -> bus released (goes HIGH via bus pull-up)

Signal inversion: TX HIGH -> bus LOW. This inverts the UART signal. The software must account for this, or use SoftwareSerial with inverted logic, or the bus idle state interpretation handles it. The muki01 library is written for this hardware — it works as-is.

D1 (1N4007) Purpose

Bridges the bus-side 12V to the MCU-side 12V rail. This powers the MCU (via a 3.3V regulator) from the car battery while maintaining signal isolation through the optocouplers. The diode blocks reverse current if the MCU has its own power source (USB during development).

Bill of Materials

Ref Value Package Qty Notes
U1, U2 PC817 DIP-4 2 Optocoupler, CTR >= 50% at 5mA
Q1 BC547 TO-92 1 Or 2N3904 — TX LED driver
R1 2k 0805/TH 1 RX LED current limiter
R2 470 0805/TH 1 TX LED current limiter
R3 10k 0805/TH 1 Q1 base resistor
R4 1k 0805/TH 1 RX pull-down (defines LOW when opto OFF)
R5 470 0805/TH 1 TX input series resistor
D1 1N4007 DO-41 1 Reverse polarity protection, 12V bridge
U3 ESP32-C3 Module 1 Any ESP32 with hardware UART
J1 OBD-II / roundel 1 Or direct wire to CD changer connector

Optional for permanent install:

  • AMS1117-3.3 or MP1584 buck: 12V -> 3.3V for ESP32
  • 10uF + 100nF ceramic caps on 3.3V rail
  • TVS diode (SMBJ16A) on bus line
  • Status LED on spare GPIO (NOT in signal path)

ESP32 Pin Assignments

Function GPIO UART Notes
I/K-Bus TX GPIO 17 UART1 TX Through R5 -> R3 -> Q1 -> U2
I/K-Bus RX GPIO 16 UART1 RX From U1 emitter (pin 3)
Debug TX GPIO 1 UART0 TX USB serial monitor
Debug RX GPIO 3 UART0 RX USB serial monitor
Status LED GPIO 2 Activity indicator

UART config: Serial1.begin(9600, SERIAL_8E1, RX_PIN, TX_PIN);

BMW I-Bus / K-Bus Protocol

Message Format

Byte 0:  Source address        (which module sent this)
Byte 1:  Length                (count of remaining bytes: dest + data + checksum)
Byte 2:  Destination address   (target module, 0xBF = broadcast)
Byte 3:  Command type
Byte 4+: Data field(s)
Last:    Checksum              (XOR of all preceding bytes)

Example: 50 04 68 32 11 1F (steering wheel volume up)

  • 0x50 = MFL (Multi-Function steering wheel)
  • 0x04 = 4 bytes follow
  • 0x68 = RAD (radio)
  • 0x32 = volume control command
  • 0x11 = volume up
  • 0x1F = XOR checksum (50 ^ 04 ^ 68 ^ 32 ^ 11 = 1F)

Module Address Map

Addr ID Module
0x00 GM5 Body control module
0x18 CDC CD Changer
0x3F DIA Diagnostic computer
0x44 EWS Immobilizer
0x50 MFL Steering wheel controls
0x5B IHKA Climate control
0x68 RAD Radio
0x6A DSP Digital Sound Processor
0x80 IKE Instrument cluster
0xB0 SES Speed-dependent volume
0xBF ALL Broadcast
0xC8 TEL Telephone
0xD0 LCM Light Control Module
0xE7 ANZV Display (phone status)
0xE8 RLS Rain/Light Sensor
0xFF LOC Local (post-reset broadcast)

Bus Contention Protocol

BMW I/K-Bus is multi-master. Before transmitting:

  1. Monitor bus for activity (SEN/STA pin on TH3122, or RX line with optocoupler)
  2. Wait for 1.5ms of bus silence (no transitions)
  3. Only then begin transmitting
  4. Maintain 10ms minimum gap between your own packets
  5. If bus activity detected during your transmission, back off and retry

The muki01 IbusSerial library uses AVR Timer2 in CTC mode (OCR2A=94, prescaler=256) to time the 1.5ms idle window. For ESP32, this needs porting to a hardware timer or esp_timer.

Supported BMW Models

Chassis Series Years I-Bus K-Bus
E31 8 Series 1989-1999 Yes
E38 7 Series 1999-2001 Yes Yes
E39 5 Series 1995-2004 Yes Yes
E46 3 Series 1997-2006 Yes
E52 Z8 2000-2003 Yes
E53 X5 1999-2006 Yes Yes
E83 X3 2003-2010 Yes
E85 Z4 2002-2008 Yes
E87 1 Series 2004-2013 Yes

K-Bus Connection Points (E46)

  1. CD Changer Connector (trunk, driver's side) — Connector X18180
    • K-Bus wire: White/Red with Yellow dots
    • Ground: Brown
    • 12V: Red/Green
  2. K-Bus junction block (above fuse box)
    • K-Bus wire: White/Red with Yellow dots
    • 12V and GND from a separate source

Software Reference

Primary: muki01/I-K_Bus

Repository: https://github.com/muki01/I-K_Bus

Library files (I-K Bus Library/):

  • IbusSerial.h / IbusSerial.cpp — State machine protocol handler (FIND_SOURCE -> FIND_LENGTH -> FIND_MESSAGE -> checksum validation), ring buffers for RX (128 bytes) and TX (64 bytes), bus contention timer, sleep management
  • RingBuffer.h / RingBuffer.cpp — Circular buffer implementation for async message handling

Example code (Codes/):

  • Basic_Code/Basic_Code.ino — Minimal sniffer: prints all bus traffic via debug serial
  • E46_KBus_Code/E46_KBus_Code.ino — E46-specific: goodbye lights on lock, follow-me-home on double-lock, park lights on unlock
  • E46_KBus_Code/E46_Codes.h — Complete command table: lighting (22 commands), windows (8), door locks (6), trunk (3), wipers (2), interior lighting (4), instrument cluster (4), remote (3), ignition (6), MFL (7), CD control (7), DSP (12), telephone (4), and more

Porting to ESP32

The IbusSerial library targets AVR (Arduino Nano) with:

  • HardwareSerial at 9600 8E1 for bus communication
  • SoftwareSerial on pins 7/8 for debug
  • AVR Timer2 CTC for 1.5ms bus idle detection
  • digitalPinToInterrupt(3) for SEN/STA pin change

For ESP32 port, replace:

  • Timer2 -> esp_timer_create() or hw_timer_t
  • SoftwareSerial -> hardware UART0 (USB) for debug
  • PROGMEM -> not needed (ESP32 flash is memory-mapped)
  • Pin interrupts -> attachInterrupt() works directly on any GPIO
  • Bus idle detection via RX pin interrupt + timer (no SEN/STA without TH3122)

Alternative Hardware: Dedicated Transceiver ICs

The repo also documents three IC-based alternatives (schematics in Schematics/):

  • TH3122.4 / ELMOS 10026B — BMW's own bus transceiver, has SEN/STA pin for hardware contention detection, EN pin for sleep. The IbusSerial library was designed for this IC.
  • MCP2025 — Microchip LIN bus transceiver, repurposed for I/K-Bus
  • SN65HVDA195 — TI automotive K-line transceiver

SPICE Simulation

The mcp-ltspice MCP server is available for circuit simulation. To validate the optocoupler design:

# Key tools:
mcp__mcp-ltspice__create_netlist(title, components, directives, output_path)
mcp__mcp-ltspice__simulate_netlist(netlist_path) -> returns raw_file path
mcp__mcp-ltspice__get_waveform(raw_file_path, signal_names, max_points)
mcp__mcp-ltspice__analyze_waveform(raw_file_path, signal_name, analyses)

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.

Docs Archive

The repo includes an extensive documentation collection (Docs/):

  • BMW BUS Information/ — 5 PDFs on BMW bus architecture
  • BMW Communication Codes/ — 12 files with I-Bus message codes for E39/E46
  • HackTheIBus/ — 21 PDFs covering individual module protocols (IKE, LCM, MFL, EWS, DSP, etc.)

What's Been Done

  1. Optocoupler schematic analyzed (RX path, TX path, isolation topology)
  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. 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

  • BMW I/K-Bus is a live vehicle control network. Malformed messages can trigger unintended behavior (lights, locks, wipers).
  • Always test with ignition OFF or key position 1 (accessories) first.
  • The optocoupler isolation protects the ESP32 from the car AND the car from the ESP32.
  • Sniff bus traffic (RX only) before attempting any TX operations.
  • The bus has no authentication — any properly formatted message will be accepted by target modules. Use responsibly.
  • Parasitic drain: the interface draws ~6mA from the bus 12V through R1 and U2. Disconnect or add a sleep circuit for long-term parking.