1991 BMW 850i
The scope of this project changed significantly as I became more comfortable programming in Python and understood the limitations of my initial hardware choices.
I started out with the setup below... a very simple FM tuner module (no AM and only very basic features). I replaced it with a DMHD-1000 HD Radio tuner (found on ebay for about $30)!
The HDMI audio extractor proved to be finicky and was replaced with a HiFi sound card specifically designed to work with the Raspberry.
The GPS receiver works quite well and the Navit navigation software is OK but these are no comparison to my I-phone navigation and so this feature has been temporarily disabled as I simply don't see myself using it.
Below you can see my initial setup which has now been cleaned up (see later).
The black enclosure is the HD receiver module (modified to use a mini-usb interface). It is presently controlled by an external USB to serial converter in the small clear plastic case which will no longer be required.
The Raspberry pi and HiFi sound card are mounted to the top of the HD receiver module and in the upper corner is a 4 ch ADC and scaling resistors used to measure battery voltage and fuel pressure sensors. The small circuit hanging on the left is the I-Bus interface. The HiFi card is modified with a small relay to select between audio from itself and from the HD receiver.
Not shown here is an Arduino based diagnostics interface.
Here's what it looks like installed in the car (sorry for the poor lighting). It is all fully functional:
This is the main screen after boot up. Outside temperature is pulled from the I-bus.
This is the MID (Computer). Everything works 100%.
Warnings and touching any of the reset-able or configurable items brings up a dialog...
Last Oil service is a reset-able parameter and is calculated using vehicle mileage extracted from the I-Bus.
Some useful info incase I forget it (this is user editable)...
The MP3 music player has a few different visualization options... I like this one.
Radio and MP3 player info and controls are also on the main screen when playing...
On-board diagnostics to monitor Fuel pressure and battery volts initially but this has now been expanded to include DME and EML monitoring as well as intake vacuum.
Adding Engine performance monitoring...
Real time monitoring of fuel pressure and battery volts (very accurately I might add) is nice but I wanted to complement this with some basic engine dignostics…..
This car pre-dates OBDII and instead has a multipin connecter under the hood that provides access to the car’s TxD/RxD bus (sometimes referred to as K/L-lines). BMW service tools ‘INPA’ and ‘DIS’ connect to this bus with an ADS interface. There are a few designs on the web for ADS interfaces that will allow you to connect a standard RS232 PC com port (you can see details of the one I built somewhere on this site) and software can be downloaded if you search for it. But this software is designed for a Windows PC and I'm using a Raspberry pi and Linux.
I found little to no technical information on the TxD/RxD bus on the web. Most of the information presented here was reverse engineered... Initially by connecting INPA to my car and capturing the serial messages sent back and forth. Looking for patterns in the data I was able to separate the message boundaries. At first I had hoped the protocol would be similar to the I-Bus for which I was familiar but it turned out to be quite different. After a lot of searching I stumbled across some information that began to explain things and the data I captured began to make sense.... Early BMW’s (Bosch Motronic) use a protocol called KW-71 for diagnostics to the DME and EML (other modules in the car use a different protocol).
Once the basic protocol was understood (see the diagram below) I built an emulator (software tool running on a PC) that mimics the messages sent by the DME (and later the EML) to INPA. INPA connects to this tool just as it would the car and this allowed me to manipulate the response messages and so learn how these are mapped (which bytes carry which information). Once I had this emulator working nicely with INPA I had all the information I needed to design my own diagnostics program (coded in Python to run on the Raspberry).
Below are the details of the KW-71 Protocol when addressing one of the DME’s in my 850i (there are some slight differences when connecting to the EML).
All modules sit on a shared TxD/RxD bus (2 wires) and are in listen only mode until awakened. The awakening or addressing process for the DME's takes place at the blistering speed of 5bps (8N1) on the RxD line (or L-Line). A single byte 0x10 will awaken DME #1 and 0x14 will awaken DME #2 (850i has 2 x DME’s). Since further communications takes place on the single wire TxD line (at 9600bd 8N1), you can only talk to one module at a time and you must each take turns to talk (see protocol below).
After sending a wake up command (INPA will try 3 x times before giving up) the DME responds by sending 0x55, 0x00, 0x81 on the TxD line to which diagnostics responds with 0x7E (acknowledge). At this point the communications channel has been established and both can now exchange information on the TXD line (RXD is no longer needed). The timing of these messages is critical which later led me to implement this using an Arduino.
The first byte of every message indicates it’s length. It is the number of additional bytes in the message or you could say it's total length minus the first byte.
The second byte is a message counter that increments by one for every message sent. It starts at 0x00 and wraps around at 0xFF.
The third byte signifies the type of message; for example 0xF6 indicates asci text and 0x09 is a NOP (no operation) or 'keep alive' message. The latter does nothing but keep the communication channel alive (without it the DME wall go back to sleep mode).
The last byte of every message is always 0x03.
DME and Diagnostics (INPA) take turns to transmit 1 x message at a time and this process is maintained to keep the exchange alive. If neither have anything useful to transmit they send a NOP message.
No CRC or checksum is sent…. Instead, every byte sent (except the end of message identifier) is inverted and echoed back to the sender. This is one aspect of this protocol that makes it very inefficient and slow!
Below are the messages sent between INPA and one of my DME’s upon wakeup;
The wakeup sequence initially conveys basic information about the DME being accessed (it’s part number, revision, etc). If you look at a DME you will see these same numbers on its labels. Once the basic information is sent NOP messages are exchanged to keep the link alive and diagnostics is free to request information...
car orig 55 00 81 (I’m awake
inp ack 7E
car orig 0D 01 F6 32 35 33 30 30 32 31 36 32 30 03 ascii 2530021620 reverse= 0261200353
inp ack F2 FE 09 CD CA CC CF CF CD CE C9 CD CF
inp orig 03 02 09 03
car ack FC FD F6
car orig 0D 03 F6 37 33 35 36 35 33 37 36 32 31 03 ascii 7356537621 reverse= 1267356537
inpa ack F2 FC 09 C8 CC CA C9 CA CC C8 C9 CD CE
inp orig 03 04 09 03 NOP
car ack FC FB F6
car orig 0A 05 F6 30 37 33 36 33 37 31 03 ascii 0736371 reverse= 1736370
inp ack F5 FA 09 CF C8 CC C9 CC C8 CE
inp orig 03 06 09 03
car ack FC F9 F6
car orig 06 07 F6 31 30 30 03 ascii 100 reverse = 001
inp ack F9 F8 09 CE CF CF
inp orig 03 08 09 03 NOP
car ack FC F7 F6
car orig 06 09 F6 30 37 30 03 ascii 070 reverse = 070
inp ack F9 F6 09 CF C8 CF
inp orig 03 0A 09 03 NOP
car ack FC F5 F6
car orig 03 0B 09 03 NOP
inp ack FC F4 F6
inp orig 03 0C 09 03 NOP
car ack FC F3 F6
car orig 03 0D 09 03 NOP
inp ack FC F2 F6
I wanted to read and display the analog and exhaust values along with some of the digital values and decoded error messages.
Here’s what the analog read looks like. INPA sends 2 x NOP messages between each information request. This slows down the INPA update speed so I tried removing these to speed things up but the values did not update correctly so it appears this protocol must be followed.
Retrieve Analog values
There are 7 register reads 0x 00 37, 00 38, 00 55, 00 40,
00 36, 00 3C, 00 8B that repeat when on this page in INPA (first 3 are shown
Inpa req 03 D0 09 03 NOP
DME ack FC 2F F6
DME req 03 D1 09 03 NOP
Inp ack FC 2E F6
Inpa req 06 D2 01 02 00 37 03 Request 1
DME ack F9 2D FE FD FF C8
DME reply 05 D3 FE 00 00 03 Value 1
Inpa ack FA 2C 01 FF FF ()
Inpa req 03 D4 09 03 NOP
DME ack FC 2B F6
DME req 03 D5 09 03 NOP
Inpa ack FC 2A F6
Inpa req 03 D6 09 03 NOP
DME ack FC 29 F6
DME req 03 D7 09
Inpa ack FC 28 F6
Inpa req 06 D8 01 02 00 38 03
DME ack F9 27 FE FD FF C7
DME sends 05 D9 FE 00 00 03 Value 2
Inpa ack FA 26 01 FF FF
Inpa sends 03 DA 09 03 NOP
DME ack FC 25 F6
DME sends 03 DB 09 03 NOP
Inpa ack FC 24 F6
Inpa sends 03 DC 09 03 NOP
DME ack FC 23 F6
DME sends 03 DD 09 03 NOP
Inpa ack FC 22 F6
Inpa req 06 DE 01 02 00 55 03 Request 3
There are 5 register reads when monitoring exhaust 0x
00 9D, 02 11, 02 07, 02 01, 02 62
Inpa req 06 96 01 02 00 9D 03 Retrieve 1
DME ack F9 69 FE FD FF 62
DME sends 05 97 FE 00 00 03 Value 1
Inpa ack FA 68 01 FF FF
Inpa sends 03 98 09 03 NOP
DME ack FC 67 F6
DME sends 03 99 09 03 NOP
Inpa ack FC 66 F6
Inpa sends 03 9A 09 03 NOP
DME ack FC 65 F6
DME sends 03 9B 09 03 NOP
Inpa ack FC 64 F6
Inpa sends 04 9C 08 05 03 Retrieve 2
DME ack FB 63 F7 FA
DME sends 9D FB 00 05 03 Value 2
Inpa ack FA 62 04 FF FA
Inpa sends 06 A2 01 02 02 11 03 Retrieve 3
DME ack F9 5D FE FD FD FF
DME sends 05 A3 FE 82 02 03 Value 3
Inpa ack FA 5C 01 7D FD
There is only 1 message that conveys all digital
Inpa sends 03 24 09 03 NOP
DME ack FC DB F6
DME sends 03 25 09 03
Inpa ack FC DA F6
Inpa sends 03 26 09 03 NOP
DME ack FC D9 F6
DME sends 03 27 09 03
Inpa ack FC D8 F6
Inpa req 06 28 01 0A 00 20 03 Request for digital values
DME ack F9 D7 FE F5 FF DF
DME sends 0D 29 FE 06 DC FD 00 00 84 00 00 00 04 03 DME sends values
Inpa ack F2 D6 01 F9 23 02 FF FF 7B FF FF FF FB
Inpa sends 03 2A 09 03 NOP
DME ack FC D5 F6
DME sends 03 2B 09 03 NOP
DME ack FC D4 F6
The error code retrieval message is below. If there are any error codes present the DME will follow the error count message with separate messages for each error present (providing the error code and conditions under which it occurred).
Sends 03 D2 09 03
Ack FC 2D F6
Sends 03 D3 09 03 NOP
Ack FC 2C F6
Inpa sends 03 D4 07 03 Inpa req error count
DME ack FC 2B F8
DME sends 04 D5 FC 00 03 DME returns error count
Inpa ack FB 2A 03 FF
Inpa sends 03 D6 09 03 NOP
DME ack FC 29 F6
DME sends 03 D7 09 03 NOP
Inpa ack FC 28 F6
Inpa sends 03 D8 09 03 NOP
As indicated earlier, I wrote a
Python program to emulate the DME (fool INPA into thinking it was talking to a
DME). I used this to manipulate response messages to determine what it was and how it was
mapped into the INPA.
This excerpt from my code explains the message protocol at least as it relates to what is displayed in INPA. There are additional fields in many of these messages that INPA does not appear to use.
# ******* DME canned address and
# ana[1:7] address
ana1=[0x05, 0xd3, 0xfe, 0xf0, 0x00, 0x03] # Battery volts = .0681 * Byte3, Byte4 not used
ana2=[0x05, 0xd3, 0xfe, 0xff, 0x00, 0x03] # RPM = 10 * Byte3, Byte 4 not used
ana3=[0x05, 0xd3, 0xfe, 0x71, 0x00, 0x03] # Speed (Km/hr) = 1.102 * Byte3, Byte4 not used
ana4=[0x05, 0xd3, 0xfe, 0x40, 0x00, 0x03] # air intake temp (deg.C) -33.5 + (0.65 * Byte3), Byte4 not used
ana5=[0x05, 0xd3, 0xfe, 0xa0, 0x00, 0x03] # coolant temp (deg.C) -32.5 + (0.65 * Byte3), Byte4 not used
ana6=[0x05, 0xd3, 0xfe, 0x60, 0x00, 0x03] # ign angle = 0.75 * (96 - Byte3), Byte4 not used
ana7=[0x05, 0xd3, 0xfe, 0x70, 0x00, 0x03] # load (mS) = .05 x Byte3, Byte4 not used
# exhaust 1:6 address (no 2)
#digital values address
exm1=[0x05, 0xd3, 0xfe, 0x40, 0x00, 0x03] # air consumption Kg/h = 0.2 * Byte3, Byte 4 not used
exm2=[0x05, 0xd3, 0xfb, 0x00, 0x40, 0x03] # Lambda sensor (volts) = .00484 * Byte4
exm3=[0x05, 0xd3, 0xfe, 0xa0, 0x02, 0x03] # Not sure what Byte4 is. Lambdaint = Byte3 - 128
exm4=[0x05, 0xd3, 0xfe, 0x85, 0x00, 0x03] # Adaption additive = Byte3 - 128, Byte4 ??
exm5=[0x05, 0xd3, 0xfe, 0x85, 0x00, 0x03] # Adaption multiplcativ = Byte3 - 128, Byte4 ??
exm6=[0x05, 0xd3, 0xfe, 0x85, 0x00, 0x03] # Adaption TEV = Byte3 - 128, Byte4 ??
# Digital values Byte12 xx1x xxxx (1= lambda ctr)
# Byte11 xxx1 xxxx Status idle speed
#tc1=[0x04, 0xd5, 0xfc, 0x00, 0x03] # trouble code message Byte = number of errors
tc1=[0x08, 0xd5, 0xfc, 0x64, 0x01, 0x80, 0x80, 0x03, 0x03] # individual trouble code message
#Byte=TC#, additional info, x40=rpm, bat volts .0681x=volts, error frequency
tc2=[0x08, 0xd5, 0xfc, 0xc9, 0x02,
0x80, 0x40, 0x03, 0x03] #  temp x80=48C x40=0c xa0=72c x60=24C
#  load signal x60=4.8mS xa0=8mS x80=6.4mS x40=3.2mS
# Byte 4 (can be more than one)
# 00 open circuit/error not present/static error
# 01=short to B+
# 02=short to ground
# 04=not allowed
# 08=invalid working area
# 10=exhaust gas relavant fault
# 20=fault stored after re-bouncing delay
# 40=error present
# 80=sporadic error
#These are the identification messages (ascii) sent by the DME and their meaning.
msg1=[0x0d, 0x01, 0xf6, 0x32, 0x35, 0x33, 0x30, 0x30, 0x32, 0x31, 0x36, 0x32, 0x30, 0x03] # H/W# rev
msg2=[0x0d, 0x03, 0xf6, 0x37, 0x33, 0x35, 0x36, 0x35, 0x33, 0x37, 0x36, 0x32, 0x31, 0x03] # S/W#rev
msg3=[0x0a, 0x05, 0xf6, 0x30, 0x37, 0x33, 0x36, 0x33, 0x37, 0x31, 0x03] # BMW part# rev
msg4=[0x06, 0x07, 0xf6, 0x31, 0x30, 0x30, 0x03] # production number? reverse
msg5=[0x06, 0x09, 0xf6, 0x30, 0x37, 0x30, 0x03] # DOM reverse
msg6=[0x03, 0x09, 0x09, 0x03] # NOP
And from my Raspberry code…. the error code mapping for the DME;
tclook = '#0 Undefined Fault'
tclook = '#1 Fuel Pump Relay'
tclook = '#2 Idle-speed controller'
tclook = '#3 Injectors (2,4,6 or 8,10,12)'
tclook = '#8 CHECK ENGINE light fail'
tclook = '#16 Ignition system or CPS'
tclook = '#18 Final Stage Pin 18'
tclook = '#32 Injectors (1,3,5 or 7,9,11)'
tclook = '#36 EVAP Canister Valve'
tclook = '#37 Lambda Heater Relay'
tclook = '#41 Air Mass Sensor (MAF)'
tclook = '#48 A/C Compressor Shut Off'
tclook = '#54 Voltage Supply'
tclook = '#63 Torque Cnvtr Lockup Clutch'
tclook = '#64 EGS/DME Connection Error'
tclook = '#70 Oxygen Sensor'
tclook = '#73 Road Speed Signal'
tclook = '#76 Idle Speed - CO adjust'
tclook = '#77 Intake Air Temp Sensor'
tclook = '#78 Coolant Temp Sensor'
tclook = '#82 Engine Drag Torque Control'
tclook = '#83 ASC (EML)'
tclook = '#100 DME Output Stage'
tclook = '#200 DME Control Unit'
tclook = '#201 Oxygen Control'
The messages I have chosen to implement and information I can display ….
# request info messages
ana1=[0x06, 0xea, 0x01, 0x02, 0x00,
0x36, 0x03] # Battery volts
ana2=[0x06, 0xf0, 0x01, 0x02, 0x00, 0x3C, 0x03] # RPM
ana3=[0x06, 0xf6, 0x01, 0x02, 0x00, 0x8B, 0x03] # Speed Km/h
ana4=[0x06, 0xd2, 0x01, 0x02, 0x00, 0x37, 0x03] # air intake temp
ana5=[0x06, 0xd8, 0x01, 0x02, 0x00, 0x38, 0x03] # coolant temp
ana6=[0x06, 0xde, 0x01, 0x02, 0x00, 0x55, 0x03] # ign angle
ana7=[0x06, 0xe4, 0x01, 0x02, 0x00, 0x40, 0x03] # load
exh1=[0x06, 0x96, 0x01, 0x02, 0x00,
0x9d, 0x03] # air consumption
exh2=[0x04, 0x9c, 0x08, 0x05, 0x03] # Labda sensor
exh3=[0x06, 0xa2, 0x01, 0x02, 0x02, 0x11, 0x03] # lambda int
exh4=[0x06, 0xa8, 0x01, 0x02, 0x02, 0x07, 0x03] # Adaption additiv
exh5=[0x06, 0xae, 0x01, 0x02, 0x02, 0x01, 0x03] # adaption mult
exh6=[0x06, 0xb4, 0x01, 0x02, 0x02, 0x62, 0x03] # adaption TEV
eml1=[0x04, 0x6c, 0x08, 0x03, 0x03] # EML coolant temp
eml2=[0x04, 0x72, 0x08, 0x05, 0x03] # pedal position
eml3=[0x06, 0xe6, 0x01, 0x01, 0x00, 0x2d, 0x03] # EML dig values
dig1=[0x06, 0x28, 0x01, 0x0A, 0x00, 0x20, 0x03] # idle status
clerrors=[0x03, 0xd4, 0x05, 0x03] #clear error count
rderrors=[0x03, 0xd4, 0x07, 0x03] #request error count
nop=[0x03, 0x09, 0x09, 0x03] # NOP
endcon=[0x03, 0x09, 0x06, 0x03] # end connection
Here’s what the next version of my diagnostics
interface looked like….
Selected DME 1 and it is connecting...
DME 1 connected and presents its info (there is a moving string of dots on the bottom that lets you know the connection is active)
DME 1 Analog (simulated data)
DME 1 exhaust
And for the EML
EML shares the same error screen as the DME but the Analog is different
When the system was first installed in the car INPA functionality appeared to work reasonably well but would often disconnect. Later I found that when playing music at the same time it was barely usable. It was then that I realized the KW-71 protocol is very sensitive to timing and the Raspberry is not good at this sort of thing.
The Raspberry runs an Operating System (Linux) and as such is not well suited for timing sensitive tasks. Given the infrequent use this feature might see, this would be an acceptable compromise for some.... but in the quest for perfection I moved the timing critical elements to an Arduino!
Below a $7 Arduino Uno connected to my TxD/RxD breadboard. A single USB cable is the only connection to the Raspberry.
This has now been migrated to an Arduino nano (Chinese clone) and a real Printed circuit board:
The I-Bus interface was also migrated to a PCB along with 2 x 4ch ADC converters.
The Arduino is a microcontroller and does not use an operating system.... it's timing is very predictable. The Arduino is programmed in 'C' a new language for me but was very easy to pick up, in fact I had the whole thing together and working in a little over a week. And now it works very solid... this really is the best combination; the low level timing sensitive protocol handled by the Arduino and the Raspberry Pi computing and presenting the collected data. I spent quite a bit of time optimizing the KW-71 protocol timing to yield the fastest but reliable response.
In this space (behind the glove box) I will mount the HD receiver, raspberry pi and interfaces.
I was able to tap into the TxD/RxD diagnostic lines at the air bag controller connector (also behind the glove box). There is a nice slot under the airbag controller where I could mount some of the hardware but it would not be possible to remove it without removing the lower glove box. It is also just slightly too narrow for the HD receiver.
To facilitate comparing the results from each DME (each side of the engine), I added a the feature to store and view on screen values.
There is just one outstanding issue that I'm unsure about.... I'm not able too connect to the EML when the engine is running. My stand alone PC version of INPA does exactly the same thing so it is not a fault of my hardware. I'm not sure if this is normal or not. I can connect to it just fine when the engine is not running. I've asked the question on a forum but no one has been able to answer.
In my effort to clean up the wiring, the DMHD-1000 HD receiver was modified to interface directly to mini-USB.
The 2 pin connector under the mini-USB is for the remote turn on-off. It connects directly to a I/O pin on the raspberry and works more predictably than trying to use USB RTS.
Here you can see the HD receiver, raspberry pi + sound card and the Arduino diagnostic interface mounted in the space behind the glove box. They are bolted to an aluminum plate that attaches to the cradle below the airbag controller (orange) with 2 x 10mm bolts. The small switch visible on the lower right is mounted to the side of the lower glove box and accessible from inside; it disconnects the diagnostics interface so that the standard diagnostics interface under the hood can be used.
Link to Page 12