Arduino based BMW E46 steering wheel interface/IBUS receiver
In most cars the OEM head unit/infotainment system is integrated with all sorts of monitoring and control systems. The radio in the BMW E46 for example receives information like road speed, cabin brightness and steering wheel control commands over a unified network that ties a dozen or so systems together (the IBUS).
Aftermarket head unit are the opposite of that: they are designed to be as versatile and universal as possible. In order to somewhat integrate an aftermarket head unit with the car systems you need an interface that translates relevant data from the car into something that the head unit can understand.
Why this project? I wasn't satisfied with the steering wheel control interface that came with my dash kit/wiring harness (a little black box made by Connects2). As with most "jack of all trades" universal interfaces it kind of half-works for the BMW E46. It fails to register two out of the six buttons on the steering wheel, it has no support for long press actions, and worse of all: every once in a while it just randomly stopped working for no good reason at all...
As I got more and more frustrated by this I did some research and came up with this super basic DIY interface. It's not perfect, it's not pretty, but it damn well works. I've had it in my car for just over two months now and it hasn't failed on me yet.
DIY BMW E46 Arduino IBUS steering wheel interface
2. BMW IBUS interfacing
IBUS, or infotainment bus, is a BMW-specific controller network that allows all infotainment and convenience systems to exchange data with each other.
The IBUS is a one wire, low speed serial data network that runs throughout the car. I'm not going to dive into the specifics of what an IBUS packet looks like or how it all works electrically, there's plenty of documentation available online if you want to read more about it.
I cobbled together a super basic "listen only" IBUS interface, electrically it's nothing more than a resistive divider that takes the 12v IBUS voltage down to a safe level for the Arduino. There is no controller chip or magic converter box involved.
Wiring into the IBUS is easy, there is a large terminal block clipped to the top of the fuse box that contains all the IBUS wires from all the different modules. All IBUS wires follow a very distinct color scheme of white/red striping with yellow dots.
Main IBUS terminal block (connector X10116), located above the interior fuse box. Source: BMW WDS.
For this project I'm primarily interested in grabbing data from the MFL (multifunction steering wheel), I captured all traffic on the IBUS while playing with the steering wheel buttons in order to figure out what's what.
Not all buttons on the steering wheel behave the same way. The track up/down and talk buttons generate three separate messages for press, release and long press actions; the volume controls generate only one type of message that gets repeated for as long as the button is held down.
The MFL sends the following messages over the IBUS (hexadecimal representation):
|Volume UP||50 04 68 32 11 1F||repeats||-|
|Volume DOWN||50 04 68 32 10 1E||repeats||-|
|Seek UP||50 04 68 3B 01 06||50 04 68 3B 11 16||50 04 68 3B 21 26|
|Seek DOWN||50 04 68 3B 08 0F||50 04 68 3B 18 1F||50 04 68 3B 28 2F|
|Talk||50 04 C8 3B 80 27||50 04 C8 3B 90 37||50 04 C8 3B A0 07|
|R/T*||50 03 C8 01 9A||-||-|
* If the car is not equipped with a factory telephone system the R/T button will only generate a single "status request" message. On startup the MFL sends this message a bunch of times to detect the presence of a phone system, to prevent these messages from being interpreted as button presses I included a lockout delay for this button. Since I don't have a BMW telephone system I don't know what messages are normally generated by this button.
3. Controlling the head unit
Car head units often have a special input for a steering wheel control interface, this input enables third party systems to control basic functions of the head unit.
My Pioneer DEH-X8700 uses the Pioneer/Sony style control scheme, which is based around a simple resistor ladder between a control pin and ground. Different resistances activate different functions, for example: pulling the control pin to ground through a 2.2K resistor simulates a press of the "source" button.
Switching different resistance values with an Arduino can be done using a string of discrete resistors, or by using a digital potentiometer and some form of a switching device (like a transistor) to switch the output. I went with the resistor network to keep things simple.
Warning: before you fry your Arduino make sure that the control voltage coming out of your head unit does not exceed the operating voltage of your Arduino. Mine puts out 3.2v, which is fine for the 3.3v Arduino Pro Mini that I chose for this project. If your head unit puts out a higher voltage than what your Arduino can handle you may have to use external switching transistors or a digital potentiometer.
Testing with the head unit on the bench
4. Bonus feature: auto dimming based on ambient brightness
Almost all head units have a dimmer/illumination input for dimming the display, by default this line is tied to the interior illumination bus of the vehicle.
In my case syncing the dimmer to the interior lights is inconvenient for a couple of reasons:
- The interior illumination bus of the BMW E46 is PWM driven (makes sense with all LED lighting), if the duty cycle (brightness) drops below a certain level the head unit starts flickering between "day" and "night" mode.
- My headlights are always on when I'm driving...
Fortunately there is a really easy solution for this!
All BMW E46's have a little ambient light sensor by the headlight switch (pretty neat for a 16 year old car), the value of this sensor is transmitted digitally over the IBUS (along with the position of the user adjustable brightness dial). With this value and a little extra switching circuitry I'm able to switch between "day" and "night" mode based on the actual brightness of the cabin.
Note: cars equipped with rain sensing automatic wipers have an additional brightness sensor near the top of the windshield, values from this sensor are also transmitted over the IBUS.
LCM Ambient brightness sensor
There is no overvoltage protection (or any form of isolation) in this circuit, if you're worried about frying your Arduino you can always use optocouplers and/or switching transistors. If you're like me though and all you have in your electronics drawer are a resistor kit and a few salvaged components: don't worry too much about it, this works just fine.
Besides the Arduino there aren't many other things that you can fry: the IBUS modules are all designed to handle the harsh electrical environment of the vehicle.
All connections for this project can be made behind the radio, no additional wiring/splicing is required.
Note: If your aftermarket wiring harness does not include the IBUS signal you can always borrow (cut) the illumination wire and move that pin over to the IBUS position (or add a new wire to the main IBUS terminal block located above the fuse box inside the glove compartment).
For power I used a simple buck regulator module (with a 5v output), the on-board regulator of the Arduino takes that down to 3.3v to supply the micro.
6. Arduino codeDownload: e46-ibus-swc-v1.1.ino
The code is split up into three portions: IBUS message processing, head unit control and decoding/translating steering wheel button presses into control actions for the head unit.
Yes, using the delay() function is bad practice when working with incoming serial data, that's something to address in a future version...
7. Demo video
What other type of data can I grab from the IBUS?
With this receive only interface you can grab the speed, engine rpm, time/date, door/lock status, some on board computer values, odometer reading, A/C status, key id, ignition status, outside temperature, coolant temperature, parking brake status, status of all exterior lights, parking sensor distance values, and a few more things depending on options/trim level.
With a bidirectional interface you could (in theory) control and monitor pretty much every convenience feature in the car (if you know the right diagnostic commands).
The R/T button isn't being picked up, what's going on?
The R/T button transmits a different set of commands if a factory telephone system or voice control module is present, the easiest fix for this is to just disconnect the factory telephone (and voice control) modules.
I don't have a compatible Pioneer or Sony head unit; can I still use this interface?
Yes, partly. The IBUS side can still be used, you'll just have to figure out how to control your particular head unit with an Arduino.