1991 BMW 850i

page 11

  1. CarPC cont.

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.



HD Radio...



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 message)
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                                             NOP
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 below);

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 03                                           NOP
Inpa ack     FC 28 F6                                

Inpa req     06 D8 01 02 00 38 03                                  Request 2
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 values;

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                                          NOP
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 messages   ********

# 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[3] = number of errors
tc1=[0x08, 0xd5, 0xfc, 0x64, 0x01, 0x80, 0x80, 0x03, 0x03] # individual trouble code message
#Byte[3]=TC#, [4]additional info, [5]x40=rpm, [6]bat volts .0681x=volts, [7]error frequency

tc2=[0x08, 0xd5, 0xfc, 0xc9, 0x02, 0x80, 0x40, 0x03, 0x03] #     [6] temp x80=48C x40=0c xa0=72c x60=24C
                                                           #     [6] 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] = '#0 Undefined Fault'
tclook[1] = '#1 Fuel Pump Relay'
tclook[2] = '#2 Idle-speed controller'
tclook[3] = '#3 Injectors (2,4,6 or 8,10,12)'
tclook[8] = '#8 CHECK ENGINE light fail'
tclook[16] = '#16 Ignition system or CPS'
tclook[18] = '#18 Final Stage Pin 18'
tclook[32] = '#32 Injectors (1,3,5 or 7,9,11)'
tclook[36] = '#36 EVAP Canister Valve'
tclook[37] = '#37 Lambda Heater Relay'
tclook[41] = '#41 Air Mass Sensor (MAF)'
tclook[48] = '#48 A/C Compressor Shut Off'
tclook[54] = '#54 Voltage Supply'
tclook[63] = '#63 Torque Cnvtr Lockup Clutch'
tclook[64] = '#64 EGS/DME Connection Error'
tclook[70] = '#70 Oxygen Sensor'
tclook[73] = '#73 Road Speed Signal'
tclook[76] = '#76 Idle Speed - CO adjust'
tclook[77] = '#77 Intake Air Temp Sensor'
tclook[78] = '#78 Coolant Temp Sensor'
tclook[82] = '#82 Engine Drag Torque Control'
tclook[83] = '#83 ASC (EML)'
tclook[100] = '#100 DME Output Stage'
tclook[200] = '#200 DME Control Unit'
tclook[201] = '#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


Error codes


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