OBD-II Software
When I first owned a car with an "electronic brain", I was always curious about how this computer worked. The curiosity was borne out of the fact that my profession deals with embedded computers. Subsequently, I have the hardware and software tools at my disposal to act on my curiosity.
So, after cracking ALDL on my Lotus Esprit, the next stage was to investigate this mysterious OBD-II that everybody is talking about.
Flavours
OBD-II, although it's a standard, has three distinct interfaces:
- Variable Pulse Width - VPW
- Pulse Coded Modulation - PCM
- Standard Serial - ISO
VPW (usually found on American GM vehicles) and PCM (usually found on Fords) unfortunately need a dedicated piece of communications hardware to act as a gateway between the PC's communication port and the ECU. The reason for this is because a standard PC is just not equipped to handle these hardware formats.
You could probably use a serial port or a pin from the parallel port to emulate these interface formats but the PC will spend most of its time polling the port due to lack of dedicated hardware. This means that your brand-new 2GHz Pentium 4 PC will be sitting there wasting billions of instruction cycles dealing with the interface.
ISO, however, is a Godsend. This is because it uses a data-format that a standard PC UART can deal with, the only thing you need to do is to convert the RS232 voltage levels of the PC into the 12V OBD-II levels of the ECU interface.
There are a few interfaces available on the market, including mine OBD-II Hardware.
K and L Lines
Whenever I read specifications, I wonder what the people were smoking when they were writing them. A typical example is the naming of the ISO part of the OBD-II interface; the K and L lines.
Well, the line that you use to communicate with the ECU is the K line. Most newer ECUs allow you to wake-up the ECU with this line too (i.e. the 5 baud, 0x33 transmission).
Older ECUs need to be woken-up by transmitting 0x33 at 5 baud on the L line. To the best of my knowledge, the L line is then redundant and not used.
So, that's it. K line is the ISO data to and from the ECU and the L line is sometimes needed to wake-up the ECU.
Any suggestions to where K and L came from, please put your answers on a postcard to.....
Software
The OBD-II software is the bit where the trouble starts. Lots of people want it, a few people know how to initiate an ECU conversation.... and about 3 people know how to code it!
If you want to speak to an OBD-II device you have to do the following:
- Set your serial port to 5 baud
- Send out 0x33 to the ECU
- Set your serial port to 10400 baud
- Listen for the 0x33 echo because the interface is half-duplex
- Listen for 0x55 from the ECU.
- Send all sorts of commands to the ECU to get its data....
So what's the problem with that?
Unfortunately, if you want to be a good little programmer on 32-bit Windows platforms, Microsoft likes you to use its File I/O to communicate with the serial port. You can have no direct contact with the serial hardware.
Another problem you'll encounter is that Windows is a message based, multitasking operating system. You cannot hog the machine, you must co-operate with the other programs running on the system. You are almost forced to follow this programming paradigm because, if you don't, your user interface (GUI) will not function (due to the lack of messages being sent).
Allowing Windows To Multitask
This is easy...
void ISO_L0::PumpMessages()
{
MSG msg;
// if there is a message on the queue, then dispatch it
if(::PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ))
{
::GetMessage(&msg, NULL, NULL, NULL);
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
We just need to periodically call PumpMessages() to allow Windows to switch to other tasks and dispatch its own messages.
Accurate Waiting
You'll see later that we need an accurate waiting function. Windows supplies high-resolution timers within its API. To wait, we need to glue the functions together like this:
// Waits for specified time in milliseconds.
void ISO_L0::TimerWait(unsigned int mSeconds)
{
BOOL bResult=FALSE;
LARGE_INTEGER liFirst, liSecond, liElasped, liTarget;
if (m_liPerfFreq.QuadPart == 0)
{ //first time we're calling this
// current timer frequency in counts per second
bResult = QueryPerformanceFrequency(&m_liPerfFreq);
if (!bResult)
{
TRACE("TimerWait failed on call to QueryPerformanceFrequency\n");
AfxMessageBox("TimerWait failed on call to QueryPerformanceFrequency");
return;
}
CString csTemp;
csTemp.Format("Timer resolution %ld%ld counts per second\n", m_liPerfFreq.HighPart, m_liPerfFreq.LowPart);
TRACE(csTemp);
}
liTarget.QuadPart = m_liPerfFreq.QuadPart * (ULONGLONG) mSeconds;
liTarget.QuadPart = liTarget.QuadPart / (ULONGLONG) 1000; // convert counts per mSecond
// liTarget is now our target time
// Query the current counter for start point.
bResult = QueryPerformanceCounter(&liFirst);
if (!bResult)
{
TRACE("TimerWait failed on call to QueryPerformanceCounter\n");
return;
}
liElasped.QuadPart = (ULONGLONG) 0;
while(liElasped.QuadPart < liTarget.QuadPart)
{
// Greatly reduce the processing time while waiting,
// as we near 5 ms of our target time, use full CPU
// Thanks for Uwe Ross for this one.
if((liTarget.QuadPart - liElasped.QuadPart) < (ULONGLONG) 5)
{ // we're very close to target time
}
else
{ // we're a long way off target time
Sleep(1); // less processor intensive
}
bResult = QueryPerformanceCounter(&liSecond);
if (!bResult)
{
TRACE("TimerWait failed on call to QueryPerformanceCounter\n");
break;
}
liElasped.QuadPart = liSecond.QuadPart - liFirst.QuadPart;
}
}
This has been modified since I first published it on the recommendations of Uwe Ross. The routine uses Sleep(1) which greatly reduces the processing time while it's being called; as the delay loop sees the target time approaching i.e. within 5 miliseconds, it reverts back to Sleep(0).
I also only call the QueryPerformanceFrequency() function once and store its result in a member variable.
Serial Communications
What an absolute pig serial communications are in Win32! Just when you think you know what you're doing.... something doesn't work and you spend days scratching your head wondering why a seemingly simple procedure doesn't work.
You need a Device Control Structure (DCB) to set up the COM ports. This is what you require:
dcb.BaudRate = DWORD(eBaudrate);
dcb.ByteSize = BYTE(eDataBits);
dcb.Parity = BYTE(eParity);
dcb.StopBits = BYTE(eStopBits);
dcb.fBinary = TRUE;
dcb.fParity = FALSE;
dcb.fDsrSensitivity = FALSE;
dcb.fNull = FALSE;
dcb.fAbortOnError = FALSE;
dcb.fOutxCtsFlow = false; // Disable CTS monitoring
dcb.fOutxDsrFlow = false; // Disable DSR monitoring
dcb.fDtrControl = DTR_CONTROL_ENABLE; // Enable DTR control (for self-powered interfaces)
dcb.fOutX = false; // Disable XON/XOFF for transmission
dcb.fInX = false; // Disable XON/XOFF for receiving
dcb.fRtsControl = RTS_CONTROL_ENABLE; // Enable RTS to go high
You'll also need to set-up some overlapped structures.... don't even contemplate trying to understand and code from scratch. There are a few serial classes out there in the wild that can be downloaded. I used Ramon's class http://home.ict.nl/~ramklein/Projects/Serial.html which I found out that I needed to make a few modifications to.
Next, because we're message based, we need a message handler to react to any characters entering the serial port from the ECU. You can fire off a thread and use it to block when no characters are the receive buffer. Hmmmm, why do that when you can just react to a message sent by the port? The down-side to message programming is that messages are not guaranteed to be received.
The 5 Baud Problem - Part 1
Ok, we're armed with all the tools that we need. We set the baud rate to 5, send 0x33 and..... oh, dear we soon find out that it's impossible to know when a character is transmitted. Also, Windows starts to run very slowly once the serial port is set to 5 baud.
What's wrong? When we send out 0x33 to the serial port, it takes 2 seconds to transmit the 10 bits of data (1 start, 8 data, 1 stop at 5 bits per second). However, the ECU switches instantly to 10400 baud and transmits back 0x55 to you. You have to have the serial port set to 10400 after the 0x33 has transmitted and before the ECU sends its 0x55.
If you send 0x33 and then immediately set the port to 10400, the character is lost.
The solution.... it's quite easy really, you send out 0x33 at 5 baud and then use a timer to wait for 2000 milliseconds. Once this is done, you must set the baud rate to 10400.
You'll then receive the echo of 0x33 back from your interface, then 0x55 from the ECU followed by a number of data bytes depending what your ECU is.
The 5 Baud Problem - Part 2
Release 1.1 of the code adds a second initialisation function which manually calls Set_Break and Clr_Break functions to send the 5 baud init. The code basically emulates a UART and sends out the start and data bits programatically. The advantage of this is that I can also manipulate the RTS line, so therefore the L line gets moved as well.
Again, this idea is thanks to Uwe Ross. With this method, the baud rate can stay at 10400 and ECUs requiring L line initialisation can be supported. However, although the way I've done this works, it is not as reliable as the UART method above.
Downloads
PLEASE NOTE: This software only wakes-up the ECU. It is not a full version of OBD-II software by any stretch of the imagination!
I've designed a simple project using Microsoft's Visual Studio v6.0 (service pack 5) and coding in C++ using MFC. This code simply sends out 0x33 to the ECU at 5 baud and logs the response.
This new version wakes-up the ECU via the K line or via K and L lines using an experimental method.
The software was developed on Windows XP Professional, I have not had time to test it on any other Windows platforms as of yet. Please note that it probably won't work on Win95, you'll need Win98 onwards (which you should really upgrade to anyway).
V1.2 - 31st December 2001
Download the OBD-II project here
If you have trouble, try updating your Windows components:
Microsoft Common Controls Update
The next stage will be the Win32 implementation of FreeDiag at http://sourceforge.net/projects/freediag/ this will be released early 2002 but there's a lot of work to do before that.
PLEASE NOTE: This software only wakes-up the ECU. It is not a full version of OBD-II software by any stretch of the imagination!
What does the OBD-II Connector Look Like?
The connector that's mounted in the car looks like this:

All ISO compatible cars with have pins in all of these locations with, maybe, the exception of the L Line. The L Line is used to wake up the ECU but this can be done via the main data line (K Line) on most newer ECUs.
Links