Skip to main content

Learning CAN BUS Communication for OBD2 (and building the communication device)


I always wanted to know how CAN bus worked. I've heard about it in the context of automotive communication back to the early 2000s, when my friend who worked at Halfords, used to regale tails of accidents involving screws and CAN bus wiring, leading to all kinds of unrelated systems suddenly changing their behaviour. I had absolutely no idea how it worked, but understood that it was some kind of network allowing data to be fed around the car using fewer wires than a conventional toggle switch and relay type system.

Fast forward many years when I first ECU swapped my first Chimaera for the GEMS control unit, I was introduced to the concept of OBD2 and PIDs (Parameter IDs). I understood that the data was brought in using packets at a reasonably high refresh rate, and that the network was bi-directional; if an error code was sent to my OBD reader, I could send back messages to clear the error. Cool!

Moving to my current Chimaera project, I knew that I wanted to use the PIDs output by by engine ECU to feed in to both my custom instrument pod and to the power distribution module, that I intend to replace the fuse box. 


Before understanding how to communicate with CAN bus, I first needed some tools with which to practise, first on OBD2. Knowing that I would be using my Teensy Arduino boards (with on-board CAN bus functionality) to drive the system, I decided to purchase a couple of units from skpang.co.uk/.

I purchased an OBD simulator, to allow me to replicate some typical PIDs on my work bench, then a very simple reader board to read a couple of PIDs and display them on a screen:


I learn best by playing with a working system and modifying it, so for me this was a fantastic way to observe a working system and to play with the code for both the reader and the simulator to see what was really going on. I did this in conjunction with this super helpful wiki covering the OBD2 PID structure.


At first, I focused simply on the way the service worked; what is being sent to the ECU to ask it to respond, then how to read the response and translate the incoming messages into actual data that I could use in my instrument pod (things like speed, engine rpm, temperatures and pressures etc.). I found this to be a really good explanation of how OBD frames actually work

So a message such as "7E8 03 41 0D 32 AA AA AA AA" could be broken down as such:

Identifier: For OBD2 messages, the identifier is standard 11-bit and used to distinguish between "request messages" (ID 7DF) and "response messages" (ID 7E8 to 7EF). Note that 7E8 will typically be where the main engine or ECU responds at.

Length: This simply reflects the length in number of bytes of the remaining data (03 to 06). For the Vehicle Speed example, it is 02 for the request (since only 01 and 0D follow), while for the response it is 03 as both 41, 0D and 32 follow.

Mode: For requests, this will be between 01-0A. For responses the 0 is replaced by 4 (i.e. 41, 42, … , 4A). There are 10 modes as described in the SAE J1979 OBD2 standard. Mode 1 shows Current Data and is e.g. used for looking at real-time vehicle speed, RPM etc. Other modes are used to e.g. show or clear stored diagnostic trouble codes and show freeze frame data.

PID: For each mode, a list of standard OBD2 PIDs exist - e.g. in Mode 01, PID 0D is Vehicle Speed. For the full list, check out our OBD2 PID overview. Each PID has a description and some have a specified min/max and conversion formula.

The formula for speed is e.g. simply A, meaning that the A data byte (which is in HEX) is converted to decimal to get the km/h converted value (i.e. 32 becomes 50 km/h above). For e.g. RPM (PID 0C), the formula is (256*A + B) / 4.

A, B, C, D: These are the data bytes in HEX, which need to be converted to decimal form before they are used in the PID formula calculations. Note that the last data byte (after Dh) is not used.


This took some head scratching, but I eventually managed to understand it well enough, that I could modify both the simulator and the reader to display many more PIDs than skpang originally intended. The acid test was to plug the reader into a real car and see whether it worked... Which it did (after a couple of tweaks).

I thought the next bit would be a simple case of taking the code from the skpang example and dropping it into my instrument pod code that I now had working reasonably well. It turned out that this wasn't exactly the case, and the way it had been written was fine for the original use case, but didn't gel very well with the rest of the code and was a little clunky. I started by trying to adapt it, but I ended up pretty much completely re-writing it using elements of several other examples that I'd found. The skpang example relied heavily on a little library that came with it that didn't scale up very well to a full suite of codes, and required a huge amount of duplicated code for each new PID. My new solution was to retain use of the Collin80 fork of FlexCAN for Teensy (to handle the CAN mailbox legwork), then to operate FlexCAN entirely from within the main sketch on one of my Teensy 3.2s. I should probably add at this point that I added the following hardware to my PCB design to facilitate CAN bus communication between Teensy's Tx/Rx pins, and the CAN H/CAN L lines (a CAN transceiver is required).


My simplified solution was to create a single send function, and a receive function; each would be called after a set elapsed time (around 10ms if I recall correctly) with parameters to define which information to request. Originally I requested all PIDs and error codes then read all received PIDs, but I realised that I actually don't always need all PIDs and in some cars, not all PIDs are available for return. To make the solution more elegant, I created a struct array using pointers (referenced variables). This I learned with a lot of help from the Arduino.cc forum, with a great collection of very smart people!


Essentially what this means is that I can use a function within my code to roll sequentially through the array with all possible PIDs that I might want. The code then checks a field to see if the request is active (do we want this PID or not). If not then it will skip to the next, but if it does then it will fire out a request via the OBD request function and wait for a couple of milliseconds. If a response was not received then it will try again. On the third try, if nothing was received then I have currently set it to then deactivate this request for this session, since the ECU doesn't want to give it to us. It will then continue with the others. Once a message is received and it contains the right PID, then it will assign it to the array reference variable, which means it puts it in the array, but this is really a forwarding address on to a standalone variable; e.g "RPM". What this means is that the send/receive code can roll through my array on a loop to update this number when new data is received, but I can use this number at any time by just using the RPM variable. This was a bit of a revelation to me and works beautifully. I actually copied the practice for my OLED screen warning/error flag handling process too.


I then went to plug my new sketch into a car and found it to work pretty well, however I realised that while starting the car, the power goes off and CAN doesn't respond for long enough for all of my "non response" triggers to go off, so when it comes back, the instrument pod had given up asking for data.

The solution was to write a CAN sniffer function, that simply looks for a single common PID that should always be available on any car (RPM). It will keep pinging every few milliseconds until it received a response, and when it does it will declare the connection made, then start the communication process. If the connection is severed, it will switch back to sniffing mode before restarting the process. This works much better and provides a solid connection, but one where only the required data is requested - this reduces traffic on the bus, to hopefully maximise the speed of communication for the PIDs I do want; especially one which require a high refresh rate (e.g. RPM).


Pretty happy with my new OBD solution, I have made a few more refinements but now have a really solid interface which reliably connects and provides a good strong data stream for the PIDs I need. I have also built a little settings menu to allow me to switch individual sources from CAN to analogue for vehicle speed, RPM, Coolant Temperature and Air Temperature.

In case anyone wants to use my code as a starting point, I'll put a copy of it on GitHub:

Github Repository for my OBD2 Example Code






Comments