Thursday, March 4, 2010

Reading CAN data (Code Snippets)

I mentioned in a post a while back that I would post some source code from my the CANReader application I wrote.

There are a few areas of the application that were most difficult so I think they are probably the best bits to share as the rest of the program is relatively simple.

CAN Frames:
In a vehicle running a CAN bus there are a number of nodes that send and receive data to one another. These nodes are usually things like the engine computer, instrument cluster, abs computer, body control computer etc... They are much like computers on a ethernet network sending packets of data to one another via TCP. The CAN bus uses a much simpler can 11 or 29 bit data format which Wikipedia describes in detail.

The RX8 electronics and all other cars I've tested with only use the earlier 11 bit format. This format consists of an 11 bit header, a 1 bit RTR flag to identify if the message is a data request and a further 4 bits to describe the length of the message data (which can be up to 8 bytes long).

The OBDLink device that I use (which uses an ELM 327 chipset from ELM electronics) does a lot of work for me. It parses the headers for can frames and returns as an integer (i.e. base 10 not hex) along with the data bytes in hex seperated by spaces.

For example a frame might look like this: 201 1F 00 0F 00 FF EF A1 7F. 201 obviously being the header identifier and the following 8 bytes the data. Headers need to be turned on to get '201' at the beginning of the data line (use ATH1 for headers on, ATCAF0 to turn off CAN auto formatting and ATMA to get the CAN frames).

I've written a CANFrame VB.Net class that can take this data as a string and parse it into something a little more friendly. This class has two main methods NewELMCANFrame() which will take data from an ELM device and another methods NewRawCANFrame() which will take a raw CAN frame string (including the header) with each byte seperated by a string. This second method is not fully tested as I dont have anything to really test it with, but it should work with lower level devices that return full CAN frames (you might need to make it handle strings without spaces though depending on your application).

USB Serial Ports:
While developing this application I came across a nasty .Net framework bug. It seems that if you have a class uses the SerialPort class connected to a virtual com port (i.e. usb serial port) and the device connected to that port is disconnected the next time you try to read from the SerialPort it will obviously fail. This is easy to trap in a Try/Catch. However when you try to Dispose() the object and possibly open a new connection to the port the .Net framework will raise an exception that cannot be trapped by user code :-(

This is because the closing underlying stream is not handled property. This is a know issue but Microsoft seems unwilling to fix it. There is a couple of work arounds though, albeit an ugly ones. Firstly placing the following code in your app.config will fix the problem but may cause issues for you elsewhere if you have unhandled exceptions...

     <legacyUnhandledExceptionPolicy enabled="1"/>

I've also got a wrapper class for the SerialPort named FTDISerialPort that stops the Garbage Collector to calling Finalize() on the BaseStream of the SerialPort (which is what causes the exception after the usb device is disconnected).

Dynamic Scripting Code:
One of the most useful things with the CANReader application I wrote is to in real time run a algorithm over the bits in a CAN frames data. With this I can take bit X to bit Y, cast this to a number of numerical types and then perform some math on top of that to get a required value. For instance, if byte 1 to byte 2 is the engine rpm multiplied by 4 I can take bit 0 to bit 15, convert that to an Unsigned Integer and then divide that by 4 to get the engine rpm. This can all be done while the application is running without the need to recompile.

To get the bits for all of the bytes in a CANFrame class you must iterate the DateBytes() array and use the line Dim binaryValue As String = Convert.ToString(currentFrame.DataBytes(I), 2) to convert a byte to a binary number.

I've created a separate class based on code in my CANReader application and called it ParseBits, this has a shared method called ParseBits() that you can call with all of the pertinent information to run an algorithm (in VB.Net code) on your binary number. Quite useful if you need to pull flags and other such things out of bytes.

I hope this code is useful to you guys it certainly has been to me, if it is then let me know about it!


No comments:

Post a Comment