CAN bus reverse-engineering with Arduino and iOS
I always wanted a backup camera on my car. Unfortunately, my Peugeot 207 is more than 10 years old, and backup cameras weren’t common back then. Instead of buying a new car, I decided to build one myself!
You can find aftermarket backup cameras online that you can put above your license plate, and they make them for many car models. However, you need to find a way to display it somewhere on the dashboard.
I wanted to have the most OEM-like solution, so I decided to replace the stock monochrome display by a color LCD screen.
But this meant that I would lose all the displayed information. The time, current radio station, outside temperature, etc…
To summarize, I needed to:
- Install a camera on the back of my car
- Replace the current display by a color LCD screen
- Find a way to get back the car’s informations
- Display the informations on the new screen, and the backup camera when I switch to reverse gear
Installing the camera hardware
I found a camera online made exactly for my car. It replaces the cover of one of my plate lights. It’s powered by 12V, so I just had to hook it to the reversing light, so it would only turn on when I’m actually reversing. The outputted analog video was already mirrored and even had an overlay to guide me when parking.
The hardest part was to bring the cable from the rear of the car to the dashboard. I had to remove a dozen plastic covers to cleanly route it along existing cables. But after a few hours it was cleanly installed, with a single video cable coming from the dashboard!
Replacing the display
There is no way I could fit a big enough screen instead of the stock display. Fortunately, the Peugeot 207 is also available with a GPS navigation system which has a 7" color LCD screen instead of the one I have.
While Peugeot sells the navigation system screen in spare parts, it costs more than 300€, and it seemed to have some kind of proprietary interface, so I had to find another way. I found a website which sells the screen enclosure without the screen for much cheaper. Then I could buy a 7" LCD screen off Amazon to put inside:
The screen has multiple inputs: HDMI, VGA and analog video. There is even a specific pin on the board to automatically switch to the analog video input, which is perfect for a backup camera. This will allow me to display the car’s informations via HDMI or VGA, and switch to the backup camera video feed when I switch to reverse gear. Next step: retrieving the car’s informations.
Interfacing with the car
This is where the Arduino comes in. But how can I connect an Arduino to my car? The obvious candidate would be to use the OBD-II port. In Europe, car manufactured after 2004 are required to include it. It’s a standard port used to perform diagnostics and retrieve various informations about the car (like the battery voltage, oil temperature, …).
Sadly, it doesn’t return data about the current tuned FM station, or the trip computer’s informations (distance travelled, fuel estimate), at least not on my car.
After some digging, it turns out that every piece of electronic of my car is connected to a CAN bus. The CAN bus is present in most car nowadays because it allows to have much less cables than before. It requires only two cables and every node connected on it can talk to each other. Nodes send and receives frames with a numeric ID and up to 8 bytes of data.
As I expected, there is not much information online about my car’s CAN bus. The content of each frame is not publicly documented and probably unique to each brand of car, since this is not standardized like the OBD-II protocol. Luckily, I found the website of a university teacher who taught a course on CAN buses, and the students did practical work on… a Peugeot 207! This is perfect, as I could find valuable information in this PDF (in French).
It did not contain information on the frames I needed, but I learned that my car actually has 3 different CAN buses:
- The “confort” bus, which controls air conditioning, the radio, speedometer, fuel gauge…
- The “body” bus, which controls stuff like headlights, turn signals, wipers…
- The “inter-systems” bus, which connects the different control modules for the engine, the brakes and so on…
The bus I’m interested in is the first one, where the radio and the old display are connected.
I found an unused connector on the back of my radio unit. Apparently, its purpose is to connect an optional CD changer. But a quick search revealed that the connector has 2 pins exposing the “confort” bus. So I wired the Arduino to the radio using a CAN bus shield. And to my delight, I started receiving CAN frames!
Decoding CAN frames
At first, I tried printing every frame I could read on the bus. But I couldn’t make sense of anything because there was too much data being sent at the same time. It turns out, each node broadcast its status a few times every second, even if it didn’t change since the last time. So I made a Python script to have a better visualization of the frames:
The frames are ordered by ID. Being able to see bytes changing in real-time made it much easier to decode the frames:
- By turning the volume of the radio up and down, I immediately found that the volume level was in frame 421
- Shifting to reverse gear flips a bit in frame 246. This is perfect as I could connect the Arduino to the LCD controller board to switch to the backup camera video automatically. I was lucky this information was on the confort bus, as far as I know there is nothing which displays the current gear on the dashboard
- Current radio station: the name of the tuned radio station is encoded in ASCII in frame 677
- Opening and closing doors revealed that frame 417 contained information about which door was currently open
However, some data was more difficult to find:
- The outside temperature can be negative, which means they had to find a way to represent negative numbers. I put my finger on the temperature sensor and tried to see which value was slowly going up. Turns out the temperature value is offset by
40is 0ºC, and so on… This means that the car can’t display temperatures below -40ºC (but by then the car would probably have died already…)
- FM frequency was offset by 500 and multiplied by 10. For example,
577represents the 107.70MHz frequency (
(577 + 500) / 10 = 107.7)
- Information messages. Sometimes the car displays messages such as “low exterior temperature”, “passenger door open”, etc… I tried to reproduce each of those cases to see which frame was causing the messages to appear on the screen. However, some were difficult to reproduce (like the low gas warning). So instead of trying to map every message manually, I tried to send manually the frame which made the message appear on the display. The frame would contain a number corresponding to a specific message to display on the screen. So I sent frames with every numbers from 0 to 255 and I successfully mapped all the messages the display could show (even those who did not apply to my car such as messages about the sunroof being stuck!)
- Radio text. Many radio stations send some text over RDS containing a slogan or the name of the song currently playing. This text can contain up to 64 characters, way more than can be sent using a 8-bytes CAN frame. To work around this limitation, the radio cuts the text and send it in separate frames. Putting the frames together would reconstruct the original text. This was all working fine until I unplugged the screen. I would then receive only the first frame, then nothing. It turns out, the screen actually has some built-in logic and sends an acknowledgment frame when it receives the first part of the text. Once I made the Arduino send this special frame, I managed to reconstruct the full text even when the screen was unplugged!
There is some data I still miss, such as the oil temperature (I have no gauge displaying it, so it’s almost impossible to reverse-engineer!)
Displaying the data on screen
Once I gathered all the informations I needed to replace my old display, I had to find a way to display it on the color LCD screen. The Arduino is not powerful enough to drive a 7" color screen, so I needed to find another way.
I’m an iOS developer, so I figured: why not use an iOS device? This is probably less cost-efficient than using a Raspberry Pi but it had a few advantages over it. Being a mobile device, it’s optimized for low power consumption, which will avoid draining my car’s battery too fast. It has Wi-Fi and Bluetooth built-in (the Pi 3 was not released yet when I began working on this project). It can also temporarily run on battery when the car is starting (the 12V power to all nonessential elements during ignition).
These problems could still have been solved with a Raspberry Pi, by adding dongles and a battery. But iOS devices already contains everything needed, in a small form factor. I was also more familiar with iOS development, and UIKit is notoriously great to build nice user interfaces!
So I went on Leboncoin (basically France’s Craigslist) and bought an old iPod Touch with a broken screen for around 20€. I did not care about the screen since I’d use it exclusively through the HDMI output.
But how could I make an iPod Touch and an Arduino talk to each other? Little known fact: the Dock connector on iOS devices has a serial interface! However, it wasn’t easy to make my own serial cable. I did not have a male connector with all the necessary pins. So I decided to solder wires directly on the iPod’s logic board:
This actually worked pretty well, I could connect it to the Arduino and display data from the car.
In this quick clip you can see the iPod displaying the radio’s volume, updating in real time as I change it.
All was perfect until I hooked up the iPod to the screen using Apple’s HDMI adapter. When it was plugged in, the serial communication didn’t work anymore… It seemed that the adapter conflicted with the serial port. Unfortunately, there was no information about that kind of problem online, there was already very few people using iOS’s serial device, I was probably the first one to use it with an HDMI adapter plugged in! I tried playing with different resistors value on the connector’s pin 21 but I never managed to make it work. So I needed to find another way to communicate with the Arduino…
This is when I stumbled upon the HM-10. It’s a tiny Bluetooth module capable of many things, but is most commonly used to enable serial communication with any device (such as an Arduino board) over Bluetooth. It’s also very cheap: around $5! And there are libraries available to communicate with it from an iOS app. This was perfect because I didn’t have to make a special cable or solder anything! The only caveat is that it requires an iOS device supporting Bluetooth Low Energy, which means an iPhone 4S or newer. Unfortunately, my iPod Touch too old. I also was worried about the latency compared to a regular wired connection.
Before searching for a cheap iPhone 4S, I made a simple prototype on my iPad Air 2, which supports Bluetooth LE. It just displayed the outside temperature, radio name, and radio frequency.
I wrote the app in Swift. Thanks to Swift’s type-safety, it’s much easier to write robust and bug-free code, which is especially important in my case as I didn’t want to have to restart the app regularly because of crashes.
My first approach was to make the Arduino a simple interface that would send all the CAN frames it received over Bluetooth to the iOS app, which would then handle the processing.
The main benefit was to be able to support new CAN frames by simply editing the iOS app, instead of uploading a new sketch to the Arduino. This would put all the logic in one place. However, due to the huge amount of data coming from the CAN bus, the Bluetooth module didn’t keep up. The latency was way too high and I would even lose some frames.
So, instead of blindly sending every frame to the app, I decided to preprocess them on the Arduino. It stores the information I need in memory (volume, temperature, etc…) so it can send the data over Bluetooth only when the value has actually changed, using a simple binary protocol. This dramatically reduced the amount of data being sent over Bluetooth and brought the latency to almost zero. I couldn’t even see any difference compared to my previous wired tests!
Putting it all together
I was almost done. I had the backup camera, a 7" LCD screen replacement for my old display, an Arduino connected to the CAN bus which could be connected to an iOS app over Bluetooth.
I bought an old iPhone 4S from a coworker and started installing everything in the car. I put the iPhone behind a plastic cover under the steering wheel. It’s not visible from the outside (I didn’t want someone smashing my window to steal it) but still easily accessible if I needed to tweak something on it.
Then, I replaced the breadboard and jumper wires by a stripboard. I made it to be the same size as an Arduino shield so I could connect it over the CAN shield. I soldered the components upside-down so I could fit it nicely in a little plastic case. It fits nicely inside my dashboard, in a mess of wires under the radio.
The circuit is pretty simple, it consists of a level shifter because the Arduino uses 5V logic levels whereas the HM-10 Bluetooth modules uses 3.3V. There is also some relays and transistors to switch the LCD screen on when the ignition is turned on, and switch to the camera when shifting into reverse. All of these informations are retrieved on the CAN bus by the board.
Next, I removed the stock screen, routed all the cables, and put the new screen in place. Everything was working: HDMI output from the iPhone and automatic switching to the backup camera when shifting to reverse gear with the Arduino!
Everything is directly connected to the battery. So the iPhone and Arduino are powered on all the time. This is to prevent the iPhone battery from dying when the car is powered off and having to restart the app when it happens.
Finally, I polished the interface and added the remaining informations the old screen used to display (such as trip computer data, fuel usage, information messages, …). I’m not a graphic designer but I made the interface myself with some inspiration from the car entertainment system a rental car I had at the time. I also added the logos of the different radio stations in my area and display them based on the current station name. I’m pretty satisfied with how it ended up looking!
The blank space under “Consommation” is actually a real time fuel usage graph. It was a fun feature to make: during development, I had a graphical bug which would only happen when I was driving, and I couldn’t reproduce it at home when using some dummy data. So I found an empty parking lot, put my Macbook on the passenger seat with Xcode running and drove in circles, triggering breakpoints, stopping to change the code, until I fixed the bug!
Also, there is a really cool benefit of having an iPhone always in my car: the ability to track it at all times using the built-in GPS. My ISP provides me a tiny phone plan with 50MB of data, for free! This is more than enough to use Apple’s Find My Friends app. I can now remotely locate my car in case it gets stolen (or more likely if I can’t find it on a big parking lot).
See it in action
In this video you can see the final setup. I’m really satisfied with it! Most people don’t even realise it’s custom made, they suppose the car actually came like that. And it has been working almost non-stop for more than a year now!
I’ve been working on this project for quite some time now. I began in May 2015, and had the first working implementation in September 2015. At first it would display only some basic information but I made constant improvements since then. Of course, I didn’t work on it full time, sometimes I wouldn’t touch it for weeks or months, or I would wait for some components to ship.
In the end, this is all the hardware I bought (not counting various wires and electrical components I already had):
- Backup camera: 50€
- Dashboard screen enclosure: 60€
- 7" LCD screen: 40€ (yes, the enclosure cost more than the screen)
- Arduino: 20€
- CAN bus shield: 20€
- HM-10 Bluetooth module: 5€
- iPhone 4S: 80€
Total: 275€. It’s a bit pricey but it’s still cheaper than a new car!
Thank you if you made it this far! Feel free to ask me any questions. I intentionally omitted some parts to keep this article short enough. But I may write other posts with more details if some people are interested!
I also put all the code on GitHub: