Read, decode and send BMW I-Bus and K-Bus messages from your own sketch.
A compact library for Arduino, ESP32, STM32 and Raspberry Pi Pico that handles framing, checksums and bus arbitration, so you can sniff bus traffic or control lights, windows, locks and radio on classic BMWs — E46, E39, E38, E53 and more.
Installation · Quick Start · API · Wiring · Examples · Where to Connect · FAQ · Firmware Project
Classic BMWs link their body and infotainment modules over a single 12 V wire: the I-Bus and K-Bus. Every key-fob click, steering-wheel button and light switch travels across it as a short serial message at 9600 baud, 8E1.
BMW IBus KBus is the library that puts your microcontroller on that bus. You register a callback, call run() in your loop, and the library does the rest.
flowchart LR
CAR["BMW I-Bus / K-Bus<br/>12 V · single wire"] <--> TRX["Bus transceiver<br/>TH3122.4 · ELMOS 10026B"]
TRX <-->|"UART · 9600 8E1"| LIB["BMW IBus KBus<br/>library"]
LIB -->|"packet handler"| APP["Your sketch"]
APP -->|"write()"| LIB
Looking for ready-to-flash firmware? The companion project BMW_IBus_KBus has complete sketches for the ESP32 and the Arduino Nano — a web interface to control the car from your phone, welcome lights and follow-me-home — together with an E46 message table.
- 📥 Reliable receiving — a state machine finds frame boundaries in the byte stream and verifies every checksum.
- 📤 Safe transmitting — messages are queued and sent only after the bus has been idle, so they never collide with the car's own traffic.
- 🧮 Automatic checksums — define a message without its checksum and the library appends it, or send a complete frame unchanged.
- 🎯 Source filter — receive everything, or only the modules you care about.
- 💤 Sleep support — powers the transceiver down after a period of bus silence, like the car's own modules.
- 🔎 Built-in debug output — every received and transmitted frame as hex on any
Stream. - 📍 Configurable pins and an optional header with more than 80 named module addresses.
| Chassis | Series | Years | I-Bus | K-Bus |
|---|---|---|---|---|
| E31 | 8 Series | 1989–1999 | ✅ | |
| E38 | 7 Series | 1994–2001 | ✅ | ✅ |
| E39 | 5 Series | 1995–2004 | ✅ | ✅ |
| E46 | 3 Series | 1997–2006 | ✅ | |
| E52 | Z8 | 2000–2003 | ✅ | |
| E53 | X5 | 1999–2006 | ✅ | ✅ |
| E83 | X3 | 2003–2010 | ✅ | |
| E85 | Z4 | 2002–2008 | ✅ | |
| E87 | 1 Series | 2004–2013 | ✅ |
Arduino IDE Library Manager — the library is on its way into the Library Manager. As soon as it is listed, open Sketch → Include Library → Manage Libraries…, search for BMW IBus KBus and click Install.
ZIP file — until then, download this repository as a ZIP (Code → Download ZIP) and choose Sketch → Include Library → Add .ZIP Library…
Git — or clone it into your libraries folder:
git clone https://github.com/muki01/BMW_IBus_KBus_Library.gitPlatformIO — add it to platformio.ini:
lib_deps = https://github.com/muki01/BMW_IBus_KBus_Library.git#include <BMW_IBus_KBus.h>
#include <SoftwareSerial.h>
SoftwareSerial debugSerial(7, 8); // RX, TX — to a USB-serial adapter
BMW_IBus_KBus ibus;
void startTimer() { // called on every SEN/STA level change
ibus.startTimer();
}
void setup() {
debugSerial.begin(9600);
ibus.setIbusSerial(Serial); // bus on the hardware UART (9600 8E1)
ibus.setIbusDebug(debugSerial); // print every frame as hex
ibus.setIbusPacketHandler(packetHandler);
attachInterrupt(digitalPinToInterrupt(3), startTimer, CHANGE);
}
void loop() {
ibus.run();
}
void packetHandler(byte *packet) {
byte source = packet[0]; // sender module
byte length = packet[1]; // bytes that follow (destination … checksum)
byte destination = packet[2]; // target module
byte command = packet[3]; // message type, followed by data bytes
if (source == 0x50 && destination == 0x68 && command == 0x32) {
// steering wheel → radio: volume button
}
}Example debug output:
Good Message -> 50 04 68 32 11 1F
Good Message -> 00 04 BF 72 22 EB
Good Message -> 80 04 BF 11 01 2B
Tip
On the Arduino Nano and Uno the bus uses the hardware UART (D0 / D1), which is shared with USB. Disconnect the transceiver from D0 / D1 while uploading.
Store the message in PROGMEM without its checksum. The library calculates and appends the checksum, then transmits as soon as the bus is idle.
// diagnostic interface → body module: switch the interior light on
const byte Interior_On[] PROGMEM = {0x3F, 0x05, 0x00, 0x0C, 0x01, 0x01};
ibus.write(Interior_On, sizeof(Interior_On));If a message already ends with its checksum, pass false as the third argument and it is sent unchanged:
const byte Interior_On_Full[] PROGMEM = {0x3F, 0x05, 0x00, 0x0C, 0x01, 0x01, 0x36};
ibus.write(Interior_On_Full, sizeof(Interior_On_Full), false);By default every valid message reaches your packet handler. To receive only certain modules, pass their addresses:
#include <BMW_IBus_KBus_Modules.h>
const byte sourceFilter[] = {M_GM5, M_MFL, M_RAD}; // body module, steering wheel, radio
ibus.setSourceFilter(sourceFilter, sizeof(sourceFilter));| Method | Description |
|---|---|
setIbusSerial(serial) |
Attach the bus to a hardware UART and configure it for 9600 8E1. |
setIbusPacketHandler(fn) |
Register the function called for every valid message: void fn(byte *packet). |
setIbusDebug(stream) |
Mirror all received and transmitted messages to any Stream as hex. |
setPins(senSta, enable, led) |
Select the SEN/STA, EN and activity-LED pins. Defaults: 3, 4, 13. |
setSourceFilter(sources, count) |
Pass only messages from the listed modules to the handler. count 0 disables the filter. |
write(message, size) |
Queue a PROGMEM message; the checksum is calculated and appended. |
write(message, size, false) |
Queue a PROGMEM message that already includes its checksum. |
sleepEnable(seconds) |
Pull EN low after this many seconds without a valid message. |
startTimer() |
Call from the SEN/STA pin-change interrupt to track bus-idle time. |
run() |
Call continuously from loop() — receives, parses and transmits. |
calculateChecksum(data, length) |
Return the XOR checksum of a buffer in RAM. |
The packet passed to the handler is the complete frame:
| Index | Field | Description |
|---|---|---|
packet[0] |
Source | Sender module address |
packet[1] |
Length | Number of bytes that follow |
packet[2] |
Destination | Target module address |
packet[3] |
Command | Message type |
packet[4…] |
Data | Payload, followed by the checksum |
The bus idles at battery voltage — never connect it directly to a microcontroller pin. The library is designed around the TH3122.4 / ELMOS 10026B bus transceiver:
| Transceiver pin | Arduino Nano / Uno | Purpose |
|---|---|---|
| TXD | D0 (RX) |
Bus → microcontroller |
| RXD | D1 (TX) |
Microcontroller → bus |
| SEN/STA | D3 |
Bus-idle detection before transmitting |
| EN | D4 |
Transceiver enable / sleep |
| — | D13 |
Bus activity LED |
Use setPins() when your board needs different pins. SEN/STA must be connected to an interrupt-capable pin.
Optocouplers (PC817) — built from common parts; well suited to sniffing.
MCP2025 LIN transceiver — compact, with a built-in voltage regulator.
These circuits have no SEN/STA output. Receiving works as shown; for transmitting, the library expects a bus-idle signal on the SEN/STA pin.
With the TH3122 / ELMOS transceiver powering the microcontroller from its 5 V regulator, sleepEnable(60) pulls EN low after 60 seconds of bus silence. The transceiver switches the regulator off, and bus activity wakes everything up again.
| Platform | Status |
|---|---|
| Arduino Nano / Uno / Mega (ATmega328P, ATmega2560) | ✅ Tested in a BMW E46 with an Arduino Nano |
| ESP32 | ✅ Tested in a BMW E46 with an ESP32-C6 (Arduino-ESP32 3.x); compiles on Arduino-ESP32 2.x and 3.x |
| Raspberry Pi Pico, STM32, UNO R4 | 🧪 Experimental — compiles, not yet verified in a vehicle |
On the ESP32 the examples use Serial1 for the bus and USB for the debug output. Open the port on your pins before the library uses it — the ESP32-C3 has no Serial2, and on the ESP32-C6 Serial2 is the low-power UART:
Serial1.begin(9600, SERIAL_8E1, BUS_RX_PIN, BUS_TX_PIN);
ibus.setIbusSerial(Serial1);The examples use RX 5, TX 4, SEN/STA 7 and EN 3 on the ESP32-C6, C3 and S3, and RX 16, TX 17, SEN/STA 4 and EN 5 on the classic ESP32, where GPIO 6–11 belong to the flash chip.
Mark your interrupt function with IBUS_ISR_ATTR so that it is placed in RAM:
void IBUS_ISR_ATTR startTimer() {
ibus.startTimer();
}If you run the library on one of the experimental platforms, please report how it went.
| Example | What it shows |
|---|---|
Bus_Reader |
Print every message on the bus — start here. |
Send_Command |
Send a message to the car, with and without a pre-calculated checksum. |
Key_Fob_Events |
Filter by module and react to the lock, unlock and trunk buttons of the remote key. |
Complete vehicle firmware — welcome lights, goodbye lights, follow-me-home and an ESP32 web interface — and a table of more than 100 E46 messages live in the companion project.
The I/K-Bus is not available on the OBD-II port. The easiest access point is the CD-changer connector in the trunk, which is pre-wired on most cars and provides power, ground and the bus in one plug.
![]() |
![]() |
![]() |
| 1. Open the trunk — driver's side | 2. Remove the trim to reach the bracket | 3. The 3-pin connector X18180 |
| Wire colour | Signal |
|---|---|
| ⚪🔴🟡 White / red with yellow dots | K-Bus |
| 🔴🟢 Red / green | +12 V |
| 🟤 Brown | Ground |
![]() |
![]() |
| 1. Locate the connector block above the fuse box | 2. Unclip it and pull it out |
![]() |
![]() |
| 3. Find the K-Bus junction block | 4. All white / red / yellow wires are K-Bus |
50 04 68 32 11 1F
│ │ │ │ │ └─ Checksum XOR of all previous bytes
│ │ │ │ └─────── Data one or more bytes
│ │ │ └───────────── Command message type
│ │ └─────────────────── Destination 0x68 = radio
│ └───────────────────────── Length number of bytes that follow
└─────────────────────────────── Source 0x50 = multifunction steering wheel
Include BMW_IBus_KBus_Modules.h to use names such as M_MFL, M_RAD, M_IKE and M_LCM instead of raw addresses.
What is the difference between the I-Bus and the K-Bus?
Electrically and logically they are the same protocol. The I-Bus (Instrument / Information Bus) connects infotainment devices such as the radio, navigation, telephone and steering-wheel buttons. The K-Bus (Karosserie — body bus) connects body electronics such as the general module, light module, climate control and rain sensor. Cars with both buses link them through the instrument cluster, which acts as a gateway. The E46 uses the K-Bus for everything.
Is the K-Bus the same as the K-Line or OBD-II?
No. The K-Line (ISO 9141 / KWP2000) is the diagnostic line on the OBD-II port. The K-Bus is the car's internal body network and is not present on the OBD-II connector. For K-Line diagnostics see the OBD2 K-Line Library.
Can I connect the bus directly to a microcontroller pin?
No. The bus idles at battery voltage and will destroy a 5 V or 3.3 V input. Always use a transceiver circuit — see Wiring.
Is it only for Arduino boards?
No. It is written for the Arduino framework, so it also builds for the ESP32, STM32, Raspberry Pi Pico and UNO R4. See Platform Support for what has been verified.
Where can I find messages for my car?
The firmware project contains a table of more than 100 messages for the BMW E46 — lights, windows, locks, wipers and more — together with complete sketches.
Contributions are welcome — especially test reports from the experimental platforms and from other chassis. Please read the Contributing Guide and the Code of Conduct, then open an issue or a pull request.
This library is part of a family of open-source automotive projects. They share the same hardware approach, so what you build for one carries over to the others.
| Firmware — flash it and use it | ||
|---|---|---|
| BMW I-Bus / K-Bus Firmware | Phone control and key-fob light functions for the BMW E46, on the ESP32 and Arduino. | |
| OBD2 K-Line Reader | Scan tool for K-Line cars (ISO 9141-2, KWP2000) with a web dashboard, for the ESP32, ESP8266 and Arduino. | |
| OBD2 CAN Bus Reader | Scan tool for CAN bus cars (ISO 15765-4) with the same web dashboard, for the ESP32. | |
| VAG KW1281 | KW1281 diagnostics for VW, Audi, Škoda and SEAT: ECU information, measuring groups and fault codes. | |
| Libraries — build your own firmware | ||
| BMW IBus KBus Library you are here |
Receives, checks and sends BMW I-Bus and K-Bus messages; the library behind the BMW firmware. | |
| OBD2 K-Line Library | K-Line diagnostics behind one API: ISO 9141-2, KWP2000, KW1281, DS2 and KW82. | |
| OBD2 CAN Bus Library | OBD-II diagnostics over ISO 15765-4 with the ESP32's built-in CAN controller. | |
| Interface | ||
| OBD2 Diagnostic UI | The web dashboard used by the two OBD2 readers. | |
I design automotive diagnostic tools, firmware and hardware professionally. Whether you need a complete product or only the communication layer, I can help.
| Service | Details |
|---|---|
| Protocol implementation | BMW I/K-Bus, K-Line (ISO 9141-2 / KWP2000), CAN / UDS, VAG KW1281 and other manufacturer-specific protocols |
| ECU communication & reverse engineering | Bus sniffing, packet decoding, module control, undocumented ECUs and buses |
| ECU security access | Seed-key algorithms and unlock routines for KWP2000 / UDS |
| Embedded firmware | Arduino, ESP32, ESP8266, STM32, Raspberry Pi Pico |
| Custom hardware | Diagnostic dongles, shields and PCBs designed to your requirements |
| Companion apps | Android, iOS and web apps to visualise, log and control your device |
Have a project in mind? Reach out through the Contact section below.
For custom development, collaboration, sponsorship or ready-made devices:
| Channel | Address |
|---|---|
| muksin.muksin04@gmail.com | |
| linkedin.com/in/muksin-muksin | |
| 🐙 GitHub | @muki01 |
If this project helped you, consider supporting its development:
Warning
This is a hobby and development project. Transmitting on a live vehicle bus can affect lighting, locking and other body functions. Test with the vehicle stationary and proceed at your own risk. The author accepts no responsibility for damage or malfunction.
BMW is a registered trademark of BMW AG. This project is independent and is not affiliated with, endorsed by or sponsored by BMW AG.
Released under the GNU General Public License v3.0.
- You are free to use, study, modify and share this library.
- If you distribute it — on its own or as part of a product or firmware — you must make the complete source available under the same license.
Closed-source or commercial product? A separate commercial license is available. Get in touch through the Contact section.
Copyright © 2025–2026 Muksin Muksin.
Created by Muki · If this project helped you, please give it a ⭐
BMW · I-Bus · K-Bus · IBus · KBus · E46 · E39 · E38 · E53 · Arduino · ESP32 · STM32 · Raspberry Pi Pico · TH3122 · car hacking









